/** * 거래처 그룹(ClientGroup) API 훅 * * 백엔드 API: /api/v1/client-groups * - GET /client-groups - 목록 조회 * - GET /client-groups/{id} - 단건 조회 * - POST /client-groups - 생성 * - PUT /client-groups/{id} - 수정 * - DELETE /client-groups/{id} - 삭제 * - PATCH /client-groups/{id}/toggle - 활성/비활성 토글 */ import { useState, useCallback } from 'react'; // 백엔드 API 응답 타입 export interface ClientGroupApiResponse { id: number; tenant_id: number; group_code: string; group_name: string; price_rate: string | number; // decimal(10,4) is_active: boolean | number; // 0 or 1 created_at: string; updated_at: string; created_by: number | null; updated_by: number | null; } // 프론트엔드 타입 export interface ClientGroup { id: string; code: string; name: string; priceRate: number; status: '활성' | '비활성'; createdAt: string; updatedAt: string; } // 폼 데이터 타입 export interface ClientGroupFormData { groupCode: string; groupName: string; priceRate: number; isActive?: boolean; } // 페이지네이션 정보 export interface PaginationInfo { currentPage: number; lastPage: number; perPage: number; total: number; from: number; to: number; } // 훅 반환 타입 export interface UseClientGroupListReturn { groups: ClientGroup[]; pagination: PaginationInfo | null; isLoading: boolean; error: string | null; fetchGroups: (params?: FetchGroupsParams) => Promise; fetchGroup: (id: string) => Promise; createGroup: (data: ClientGroupFormData) => Promise; updateGroup: (id: string, data: ClientGroupFormData) => Promise; deleteGroup: (id: string) => Promise; toggleGroupStatus: (id: string) => Promise; } // API 요청 파라미터 interface FetchGroupsParams { page?: number; size?: number; q?: string; onlyActive?: boolean; } // API 응답 → 프론트엔드 타입 변환 function transformGroupFromApi(apiGroup: ClientGroupApiResponse): ClientGroup { // is_active가 boolean 또는 number(0/1)일 수 있음 const isActive = apiGroup.is_active === true || apiGroup.is_active === 1; return { id: String(apiGroup.id), code: apiGroup.group_code || '', name: apiGroup.group_name || '', priceRate: Number(apiGroup.price_rate) || 0, status: isActive ? '활성' : '비활성', createdAt: apiGroup.created_at || '', updatedAt: apiGroup.updated_at || '', }; } // 프론트엔드 타입 → API 요청 변환 (생성용) function transformGroupToApiCreate(data: ClientGroupFormData): Record { return { group_code: data.groupCode, group_name: data.groupName, price_rate: data.priceRate, is_active: data.isActive !== false, // 기본값 true }; } // 프론트엔드 타입 → API 요청 변환 (수정용) function transformGroupToApiUpdate(data: ClientGroupFormData): Record { return { group_code: data.groupCode, group_name: data.groupName, price_rate: data.priceRate, is_active: data.isActive, }; } /** * 거래처 그룹 관리 훅 */ export function useClientGroupList(): UseClientGroupListReturn { const [groups, setGroups] = useState([]); const [pagination, setPagination] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); /** * 거래처 그룹 목록 조회 */ const fetchGroups = useCallback(async (params?: FetchGroupsParams) => { setIsLoading(true); setError(null); try { const searchParams = new URLSearchParams(); if (params?.page) searchParams.set('page', String(params.page)); if (params?.size) searchParams.set('size', String(params.size)); if (params?.q) searchParams.set('q', params.q); if (params?.onlyActive !== undefined) { searchParams.set('only_active', String(params.onlyActive)); } const queryString = searchParams.toString(); const url = `/api/proxy/client-groups${queryString ? `?${queryString}` : ''}`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', }, credentials: 'include', }); if (!response.ok) { throw new Error(`거래처 그룹 목록 조회 실패: ${response.status}`); } const result = await response.json(); // Laravel paginate 응답 구조: { success, data: { current_page, data: [...], last_page, ... } } if (result.success && result.data) { const paginatedData = result.data; const items: ClientGroupApiResponse[] = paginatedData.data || []; const transformedGroups = items.map(transformGroupFromApi); setGroups(transformedGroups); setPagination({ currentPage: paginatedData.current_page || 1, lastPage: paginatedData.last_page || 1, perPage: paginatedData.per_page || 20, total: paginatedData.total || 0, from: paginatedData.from || 0, to: paginatedData.to || 0, }); } else if (Array.isArray(result)) { // 단순 배열 응답 (페이지네이션 없음) const transformedGroups = result.map(transformGroupFromApi); setGroups(transformedGroups); setPagination(null); } } catch (err) { const errorMessage = err instanceof Error ? err.message : '목록 조회 중 오류가 발생했습니다'; setError(errorMessage); console.error('fetchGroups error:', err); } finally { setIsLoading(false); } }, []); /** * 거래처 그룹 단건 조회 */ const fetchGroup = useCallback(async (id: string): Promise => { setIsLoading(true); setError(null); try { const response = await fetch(`/api/proxy/client-groups/${id}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, credentials: 'include', }); if (!response.ok) { throw new Error(`거래처 그룹 조회 실패: ${response.status}`); } const result = await response.json(); const data = result.data || result; return transformGroupFromApi(data); } catch (err) { const errorMessage = err instanceof Error ? err.message : '조회 중 오류가 발생했습니다'; setError(errorMessage); console.error('fetchGroup error:', err); return null; } finally { setIsLoading(false); } }, []); /** * 거래처 그룹 생성 */ const createGroup = useCallback(async (data: ClientGroupFormData): Promise => { setIsLoading(true); setError(null); try { const apiData = transformGroupToApiCreate(data); const response = await fetch('/api/proxy/client-groups', { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(apiData), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `거래처 그룹 생성 실패: ${response.status}`); } const result = await response.json(); const resultData = result.data || result; return transformGroupFromApi(resultData); } catch (err) { const errorMessage = err instanceof Error ? err.message : '생성 중 오류가 발생했습니다'; setError(errorMessage); throw err; } finally { setIsLoading(false); } }, []); /** * 거래처 그룹 수정 */ const updateGroup = useCallback(async (id: string, data: ClientGroupFormData): Promise => { setIsLoading(true); setError(null); try { const apiData = transformGroupToApiUpdate(data); const response = await fetch(`/api/proxy/client-groups/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify(apiData), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `거래처 그룹 수정 실패: ${response.status}`); } const result = await response.json(); const resultData = result.data || result; return transformGroupFromApi(resultData); } catch (err) { const errorMessage = err instanceof Error ? err.message : '수정 중 오류가 발생했습니다'; setError(errorMessage); throw err; } finally { setIsLoading(false); } }, []); /** * 거래처 그룹 삭제 */ const deleteGroup = useCallback(async (id: string): Promise => { setIsLoading(true); setError(null); try { const response = await fetch(`/api/proxy/client-groups/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', }, credentials: 'include', }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `거래처 그룹 삭제 실패: ${response.status}`); } } catch (err) { const errorMessage = err instanceof Error ? err.message : '삭제 중 오류가 발생했습니다'; setError(errorMessage); throw err; } finally { setIsLoading(false); } }, []); /** * 거래처 그룹 활성/비활성 토글 */ const toggleGroupStatus = useCallback(async (id: string): Promise => { setIsLoading(true); setError(null); try { const response = await fetch(`/api/proxy/client-groups/${id}/toggle`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, credentials: 'include', }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.message || `상태 변경 실패: ${response.status}`); } } catch (err) { const errorMessage = err instanceof Error ? err.message : '상태 변경 중 오류가 발생했습니다'; setError(errorMessage); throw err; } finally { setIsLoading(false); } }, []); return { groups, pagination, isLoading, error, fetchGroups, fetchGroup, createGroup, updateGroup, deleteGroup, toggleGroupStatus, }; }