'use client'; /** * 단가표 등록/상세(수정) 통합 폼 * * 기획서 기준: * - edit 모드(=상세): 기본정보 readonly, 상태/단가정보 editable, 하단 삭제+수정 * - create 모드(=등록): 기본정보 전부 editable, 하단 등록 */ import { useState, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { ArrowLeft, Save, Trash2, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import { useMenuStore } from '@/store/menuStore'; import { usePermission } from '@/hooks/usePermission'; import { toast } from 'sonner'; import { createPricingTable, updatePricingTable, deletePricingTable } from './actions'; import { calculateSellingPrice } from './types'; import type { PricingTable, PricingTableFormData, GradePricing, TradeGrade, PricingTableStatus } from './types'; interface PricingTableFormProps { mode: 'create' | 'edit'; initialData?: PricingTable; } const GRADE_OPTIONS: TradeGrade[] = ['A등급', 'B등급', 'C등급', 'D등급']; export function PricingTableForm({ mode, initialData }: PricingTableFormProps) { const router = useRouter(); const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); const { canCreate, canUpdate, canDelete } = usePermission(); const [isSaving, setIsSaving] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const isEdit = mode === 'edit'; // ===== 폼 상태 ===== const [itemCode, setItemCode] = useState(initialData?.itemCode ?? ''); const [itemType, setItemType] = useState(initialData?.itemType ?? ''); const [itemName, setItemName] = useState(initialData?.itemName ?? ''); const [specification, setSpecification] = useState(initialData?.specification ?? ''); const [unit, setUnit] = useState(initialData?.unit ?? ''); const [status, setStatus] = useState(initialData?.status ?? '사용'); const [purchasePrice, setPurchasePrice] = useState(initialData?.purchasePrice ?? 0); const [processingCost, setProcessingCost] = useState(initialData?.processingCost ?? 0); const [gradePricings, setGradePricings] = useState( initialData?.gradePricings ?? [ { id: `gp-new-1`, grade: 'A등급', marginRate: 50.0, sellingPrice: 0, note: '' }, ] ); // ===== 판매단가 계산 ===== const recalcSellingPrices = useCallback( (newPurchasePrice: number, newProcessingCost: number, pricings: GradePricing[]) => { return pricings.map((gp) => ({ ...gp, sellingPrice: calculateSellingPrice(newPurchasePrice, gp.marginRate, newProcessingCost), })); }, [] ); const handlePurchasePriceChange = (value: string) => { const num = parseInt(value, 10) || 0; setPurchasePrice(num); setGradePricings((prev) => recalcSellingPrices(num, processingCost, prev)); }; const handleProcessingCostChange = (value: string) => { const num = parseInt(value, 10) || 0; setProcessingCost(num); setGradePricings((prev) => recalcSellingPrices(purchasePrice, num, prev)); }; const handleGradeChange = (index: number, grade: TradeGrade) => { setGradePricings((prev) => { const updated = [...prev]; updated[index] = { ...updated[index], grade }; return updated; }); }; const handleMarginRateChange = (index: number, value: string) => { const rate = parseFloat(value) || 0; setGradePricings((prev) => { const updated = [...prev]; updated[index] = { ...updated[index], marginRate: rate, sellingPrice: calculateSellingPrice(purchasePrice, rate, processingCost), }; return updated; }); }; const handleNoteChange = (index: number, note: string) => { setGradePricings((prev) => { const updated = [...prev]; updated[index] = { ...updated[index], note }; return updated; }); }; const handleAddRow = () => { const usedGrades = gradePricings.map((gp) => gp.grade); const nextGrade = GRADE_OPTIONS.find((g) => !usedGrades.includes(g)) ?? 'A등급'; setGradePricings((prev) => [ ...prev, { id: `gp-new-${Date.now()}`, grade: nextGrade, marginRate: 50.0, sellingPrice: calculateSellingPrice(purchasePrice, 50.0, processingCost), note: '', }, ]); }; const handleRemoveRow = (index: number) => { setGradePricings((prev) => prev.filter((_, i) => i !== index)); }; // ===== 저장 ===== const handleSave = async () => { if (!isEdit && !itemCode.trim()) { toast.error('품목코드를 입력해주세요.'); return; } if (!isEdit && !itemName.trim()) { toast.error('품목명을 입력해주세요.'); return; } if (gradePricings.length === 0) { toast.error('거래등급별 판매단가를 최소 1개 이상 등록해주세요.'); return; } setIsSaving(true); try { const formData: PricingTableFormData = { itemCode: isEdit ? initialData!.itemCode : itemCode, itemType: isEdit ? initialData!.itemType : itemType, itemName: isEdit ? initialData!.itemName : itemName, specification: isEdit ? initialData!.specification : specification, unit: isEdit ? initialData!.unit : unit, purchasePrice, processingCost, status, gradePricings, }; const result = isEdit ? await updatePricingTable(initialData!.id, formData) : await createPricingTable(formData); if (result.success) { toast.success(isEdit ? '단가표가 수정되었습니다.' : '단가표가 등록되었습니다.'); router.push('/ko/master-data/pricing-table-management'); } else { toast.error(result.error || '저장에 실패했습니다.'); } } catch { toast.error('저장 중 오류가 발생했습니다.'); } finally { setIsSaving(false); } }; // ===== 삭제 ===== const handleDeleteConfirm = useCallback(async () => { if (!initialData) return; setIsDeleting(true); try { const result = await deletePricingTable(initialData.id); if (result.success) { toast.success('단가표가 삭제되었습니다.'); router.push('/ko/master-data/pricing-table-management'); } else { toast.error(result.error || '삭제에 실패했습니다.'); } } catch { toast.error('삭제 중 오류가 발생했습니다.'); } finally { setIsDeleting(false); setDeleteDialogOpen(false); } }, [initialData, router]); const handleList = () => { router.push('/ko/master-data/pricing-table-management'); }; const formatNumber = (num: number) => num.toLocaleString('ko-KR'); return (
{/* 기본 정보 */} 기본 정보 {/* Row 1: 단가번호, 품목코드, 품목유형, 품목명 */}
setItemCode(e.target.value)} disabled={isEdit} placeholder={isEdit ? undefined : '품목코드 입력'} />
setItemType(e.target.value)} disabled={isEdit} placeholder={isEdit ? undefined : '품목유형 입력'} />
setItemName(e.target.value)} disabled={isEdit} placeholder={isEdit ? undefined : '품목명 입력'} />
{/* Row 2: 규격, 단위, 상태, 작성자 */}
setSpecification(e.target.value)} disabled={isEdit} placeholder={isEdit ? undefined : '규격 입력'} />
setUnit(e.target.value)} disabled={isEdit} placeholder={isEdit ? undefined : '단위 입력'} />
{/* Row 3: 변경일 */}
{/* 단가 정보 */} 단가 정보 {/* 매입단가 / 가공비 */}
handlePurchasePriceChange(e.target.value)} placeholder="숫자 입력" />
handleProcessingCostChange(e.target.value)} placeholder="숫자 입력" />
{/* 거래등급별 판매단가 */}
{gradePricings.map((gp, index) => ( ))}
거래등급 마진율 판매단가 비고
handleMarginRateChange(index, e.target.value)} className="h-9 text-right" /> %
{formatNumber(gp.sellingPrice)} handleNoteChange(index, e.target.value)} placeholder="비고" className="h-9" />

판매단가 = 매입단가 x (1 + 마진율) + 가공비 (1천원 이하 절사)

{/* 하단 액션 버튼 (sticky) */}
{isEdit ? ( <> {canDelete && ( )} {canUpdate && ( )} ) : ( canCreate && ( ) )}
{/* 삭제 확인 다이얼로그 */} {isEdit && ( )}
); }