From 45abc98524c72290ce58260c6055a4583e19ad4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=98=81=EB=B3=B4?= Date: Mon, 23 Mar 2026 10:32:55 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[=ED=8C=90=EB=A7=A4=EA=B4=80=EB=A6=AC]?= =?UTF-8?q?=20=EA=B1=B0=EB=9E=98=EC=B2=98=EA=B4=80=EB=A6=AC=20=EC=9D=BD?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=84=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 판매관리>거래처관리와 회계관리>거래처관리가 같은 API를 사용하지만 데이터 변환 로직이 달라 거래처 유형/상태값 등이 덮어써지는 문제 해결. - 판매관리 거래처: 등록/수정/삭제 버튼 제거, view 모드만 허용 - mode=new, mode=edit 접근 시 안내 메시지 후 목록으로 리다이렉트 - 입수정은 회계관리>거래처관리로 통일 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../client-management-sales-admin/page.tsx | 52 +---- .../clients/ClientDetailClientV2.tsx | 195 ++---------------- src/components/clients/clientDetailConfig.ts | 8 +- 3 files changed, 28 insertions(+), 227 deletions(-) diff --git a/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx b/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx index c605c683..07fbbe00 100644 --- a/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx +++ b/src/app/[locale]/(protected)/sales/client-management-sales-admin/page.tsx @@ -17,11 +17,9 @@ import { useState, useRef, useEffect, useCallback, useTransition, useMemo } from "react"; import { useRouter, useSearchParams } from "next/navigation"; -import { ClientDetailClientV2 } from '@/components/clients/ClientDetailClientV2'; import { useClientList, Client } from "@/hooks/useClientList"; import { Building2, - Plus, Users, CheckCircle, Loader2, @@ -43,7 +41,7 @@ import { } from "@/components/ui/table"; import { Checkbox } from "@/components/ui/checkbox"; import { ListMobileCard, InfoField } from "@/components/organisms/MobileCard"; -import { DeleteConfirmDialog } from "@/components/ui/confirm-dialog"; +// DeleteConfirmDialog 제거 — 판매관리 거래처는 읽기 전용 import { sendNewClientNotification } from "@/lib/actions/fcm"; import { isNextRedirectError } from "@/lib/utils/redirect-error"; @@ -359,9 +357,11 @@ export default function CustomerAccountManagementPage() { }); }, []); - // mode=new 처리 (모든 훅 호출 후에 조건부 return - React 훅 규칙 준수) + // mode=new 차단 — 등록은 회계관리>거래처관리에서만 가능 if (mode === 'new') { - return ; + toast.error('거래처 등록은 회계관리 > 거래처관리에서 가능합니다.'); + router.push('/sales/client-management-sales-admin'); + return null; } // 상태 뱃지 @@ -588,53 +588,13 @@ export default function CustomerAccountManagementPage() { 신규업체 - ), renderTableRow, renderMobileCard, - renderDialogs: () => ( - <> - {/* 삭제 확인 다이얼로그 */} - - {deleteTargetId - ? `거래처: ${clients.find((c) => c.id === deleteTargetId)?.name || deleteTargetId}` - : ""} -
- 이 거래처를 삭제하시겠습니까? 삭제된 데이터는 복구할 수 없습니다. - - } - /> - - {/* 일괄 삭제 확인 다이얼로그 */} - - 선택한 {selectedItems.size}개의 거래처를 삭제하시겠습니까? -
- - 삭제된 데이터는 복구할 수 없습니다. - - - } - /> - - ), + // 판매관리 거래처는 읽기 전용 — 삭제 다이얼로그 불필요 }; return ( diff --git a/src/components/clients/ClientDetailClientV2.tsx b/src/components/clients/ClientDetailClientV2.tsx index c333dd1f..a17d7703 100644 --- a/src/components/clients/ClientDetailClientV2.tsx +++ b/src/components/clients/ClientDetailClientV2.tsx @@ -7,62 +7,39 @@ * 클라이언트 사이드 데이터 페칭 (useClientList 훅 활용) */ -import { useState, useEffect, useCallback, useRef } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useState, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; -import type { DetailMode, IntegratedDetailTemplateRef } from '@/components/templates/IntegratedDetailTemplate/types'; -import type { Client, ClientFormData } from '@/hooks/useClientList'; +import type { DetailMode } from '@/components/templates/IntegratedDetailTemplate/types'; +import type { Client } from '@/hooks/useClientList'; import { useClientList } from '@/hooks/useClientList'; import { clientDetailConfig } from './clientDetailConfig'; import { toast } from 'sonner'; -import { useDevFillContext } from '@/components/dev/DevFillContext'; -import { generateClientData } from '@/components/dev/generators/clientData'; interface ClientDetailClientV2Props { clientId?: string; initialMode?: DetailMode; } -// 8자리 영문+숫자 코드 생성 -function generateCode(): string { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let result = ''; - for (let i = 0; i < 8; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; -} - -export function ClientDetailClientV2({ clientId, initialMode }: ClientDetailClientV2Props) { +export function ClientDetailClientV2({ clientId }: ClientDetailClientV2Props) { const router = useRouter(); - const searchParams = useSearchParams(); - const { fetchClient, createClient, updateClient, deleteClient } = useClientList(); - const templateRef = useRef(null); - const { isEnabled, registerFillForm, unregisterFillForm } = useDevFillContext(); + const { fetchClient } = useClientList(); - // URL 쿼리에서 모드 결정 - const modeFromQuery = searchParams.get('mode') as DetailMode | null; const isNewMode = !clientId || clientId === 'new'; - const [mode, setMode] = useState(() => { - if (isNewMode) return 'create'; - if (initialMode) return initialMode; - if (modeFromQuery === 'edit') return 'edit'; - return 'view'; - }); + // 판매관리 거래처는 읽기 전용 — 수정/등록은 회계관리>거래처관리에서만 가능 + const [mode] = useState('view'); const [clientData, setClientData] = useState(null); const [isLoading, setIsLoading] = useState(!isNewMode); - const [generatedCode, setGeneratedCode] = useState(''); // 데이터 로드 useEffect(() => { const loadData = async () => { + // 신규 등록 차단 — 회계관리>거래처관리에서만 가능 if (isNewMode) { - // 신규 등록 시 코드 생성 - const code = generateCode(); - setGeneratedCode(code); - setIsLoading(false); + toast.error('거래처 등록은 회계관리 > 거래처관리에서 가능합니다.'); + router.push(clientDetailConfig.basePath); return; } @@ -87,164 +64,32 @@ export function ClientDetailClientV2({ clientId, initialMode }: ClientDetailClie loadData(); }, [clientId, isNewMode, router, fetchClient]); - // URL 쿼리 변경 감지 - useEffect(() => { - if (!isNewMode && modeFromQuery === 'edit') { - setMode('edit'); - } else if (!isNewMode && !modeFromQuery) { - setMode('view'); - } - }, [modeFromQuery, isNewMode]); - - // DevFill 등록 (신규 등록 모드일 때만) - useEffect(() => { - if (!isEnabled || !isNewMode) return; - - const handleDevFill = () => { - const data = generateClientData(); - if (templateRef.current) { - templateRef.current.setFormData(data as unknown as Record); - } - }; - - registerFillForm('client', handleDevFill); - - return () => { - unregisterFillForm('client'); - }; - }, [isEnabled, isNewMode, registerFillForm, unregisterFillForm]); - - // 모드 변경 핸들러 + // 모드 변경 차단 — 읽기 전용 const handleModeChange = useCallback( - (newMode: DetailMode) => { - setMode(newMode); - if (newMode === 'edit' && clientId) { - router.push(`${clientDetailConfig.basePath}/${clientId}?mode=edit`); - } else if (newMode === 'view' && clientId) { - router.push(`${clientDetailConfig.basePath}/${clientId}?mode=view`); - } + (_newMode: DetailMode) => { + toast.info('거래처 수정은 회계관리 > 거래처관리에서 가능합니다.'); }, - [router, clientId] + [] ); - // 저장 핸들러 - const handleSubmit = useCallback( - async (formData: Record) => { - try { - // formData를 ClientFormData로 변환 - const clientFormData: ClientFormData = { - clientCode: (formData.clientCode as string) || generatedCode, - name: formData.name as string, - businessNo: formData.businessNo as string, - representative: formData.representative as string, - phone: formData.phone as string, - address: formData.address as string, - email: formData.email as string, - businessType: formData.businessType as string, - businessItem: formData.businessItem as string, - isActive: formData.isActive === 'true', - clientType: (formData.clientType as ClientFormData['clientType']) || '매입', - mobile: formData.mobile as string, - fax: formData.fax as string, - managerName: formData.managerName as string, - managerTel: formData.managerTel as string, - systemManager: formData.systemManager as string, - accountId: formData.accountId as string || '', - accountPassword: formData.accountPassword as string || '', - purchasePaymentDay: '말일', - salesPaymentDay: '말일', - taxAgreement: false, - taxAmount: '', - taxStartDate: '', - taxEndDate: '', - badDebt: false, - badDebtAmount: '', - badDebtReceiveDate: '', - badDebtEndDate: '', - badDebtProgress: '', - memo: formData.memo as string || '', - }; - - if (isNewMode) { - const result = await createClient(clientFormData); - if (result) { - toast.success('거래처가 등록되었습니다.'); - router.push(clientDetailConfig.basePath); - return { success: true }; - } - return { success: false, error: '거래처 등록에 실패했습니다.' }; - } else { - const result = await updateClient(clientId!, clientFormData); - if (result) { - toast.success('거래처가 수정되었습니다.'); - router.push(`${clientDetailConfig.basePath}/${clientId}?mode=view`); - return { success: true }; - } - return { success: false, error: '거래처 수정에 실패했습니다.' }; - } - } catch (error) { - console.error('저장 실패:', error); - return { success: false, error: error instanceof Error ? error.message : '저장 중 오류가 발생했습니다.' }; - } - }, - [isNewMode, clientId, generatedCode, router, createClient, updateClient] - ); - - // 삭제 핸들러 - const handleDelete = useCallback( - async (id: string | number) => { - try { - const result = await deleteClient(String(id)); - if (result) { - toast.success('거래처가 삭제되었습니다.'); - router.push(clientDetailConfig.basePath); - return { success: true }; - } - return { success: false, error: '거래처 삭제에 실패했습니다.' }; - } catch (error) { - console.error('삭제 실패:', error); - return { success: false, error: error instanceof Error ? error.message : '삭제 중 오류가 발생했습니다.' }; - } - }, - [router, deleteClient] - ); - - // 취소 핸들러 + // 취소(목록으로) 핸들러 const handleCancel = useCallback(() => { - if (isNewMode) { - router.push(clientDetailConfig.basePath); - } else { - setMode('view'); - router.push(`${clientDetailConfig.basePath}/${clientId}?mode=view`); - } - }, [router, clientId, isNewMode]); - - // 초기 데이터 (신규 등록 시 코드 포함) - const initialData = isNewMode - ? ({ code: generatedCode } as Client) - : clientData || undefined; + router.push(clientDetailConfig.basePath); + }, [router]); // 타이틀 동적 설정 const dynamicConfig = { ...clientDetailConfig, - title: - mode === 'create' - ? '거래처' - : mode === 'edit' - ? clientData?.name || '거래처' - : clientData?.name || '거래처 상세', + title: clientData?.name || '거래처 상세', }; return ( | undefined} + initialData={clientData as unknown as Record | undefined} itemId={clientId} isLoading={isLoading} - onSubmit={handleSubmit} - onDelete={handleDelete} onCancel={handleCancel} onModeChange={handleModeChange} /> diff --git a/src/components/clients/clientDetailConfig.ts b/src/components/clients/clientDetailConfig.ts index ffcb080d..a43fe825 100644 --- a/src/components/clients/clientDetailConfig.ts +++ b/src/components/clients/clientDetailConfig.ts @@ -202,16 +202,12 @@ export const clientDetailConfig: DetailConfig = { actions: { submitLabel: '저장', cancelLabel: '취소', - showDelete: true, + showDelete: false, deleteLabel: '삭제', - showEdit: true, + showEdit: false, editLabel: '수정', showBack: true, backLabel: '목록', - deleteConfirmMessage: { - title: '거래처 삭제', - description: '이 거래처를 삭제하시겠습니까? 삭제된 데이터는 복구할 수 없습니다.', - }, }, transformInitialData: (data: Client) => ({ businessNo: data.businessNo || '',