/** * 단가배포 상세/수정 페이지 * * mode 패턴: * - view: 상세 조회 (읽기 전용) → 하단: 단가표 보기, 최종확정, 수정 * - edit: 수정 모드 → 하단: 취소, 저장 */ 'use client'; import { useState, useEffect, useCallback } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { ArrowLeft, FileText, CheckCircle2, Edit3, Save, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useMenuStore } from '@/stores/menuStore'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; import { getPresetStyle } from '@/lib/utils/status-config'; import { formatNumber } from '@/lib/utils/amount'; import { Checkbox } from '@/components/ui/checkbox'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { PageLayout } from '@/components/organisms/PageLayout'; import { PageHeader } from '@/components/organisms/PageHeader'; import { toast } from 'sonner'; import type { PriceDistributionDetail as DetailType, PriceDistributionFormData, DistributionStatus, } from './types'; import { DISTRIBUTION_STATUS_LABELS, DISTRIBUTION_STATUS_STYLES, TRADE_GRADE_OPTIONS, } from './types'; import { getPriceDistributionById, updatePriceDistribution, finalizePriceDistribution, } from './actions'; import { PriceDistributionDocumentModal } from './PriceDistributionDocumentModal'; import { usePermission } from '@/hooks/usePermission'; interface Props { id: string; mode?: 'view' | 'edit'; } export function PriceDistributionDetail({ id, mode: propMode }: Props) { const router = useRouter(); const searchParams = useSearchParams(); const { canUpdate, canApprove } = usePermission(); const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed); const mode = propMode || (searchParams.get('mode') as 'view' | 'edit') || 'view'; const isEditMode = mode === 'edit'; const isViewMode = mode === 'view'; const [detail, setDetail] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [showFinalizeDialog, setShowFinalizeDialog] = useState(false); const [showDocumentModal, setShowDocumentModal] = useState(false); const [selectedItems, setSelectedItems] = useState>(new Set()); const [gradeFilter, setGradeFilter] = useState('all'); // 수정 가능 폼 데이터 const [formData, setFormData] = useState({ distributionName: '', documentNo: '', effectiveDate: '', officePhone: '', orderPhone: '', }); // 데이터 로드 const loadData = useCallback(async () => { setIsLoading(true); try { const result = await getPriceDistributionById(id); if (result.success && result.data) { setDetail(result.data); setFormData({ distributionName: result.data.distributionName, documentNo: result.data.documentNo, effectiveDate: result.data.effectiveDate, officePhone: result.data.officePhone, orderPhone: result.data.orderPhone, }); } else { toast.error(result.error || '데이터를 불러올 수 없습니다.'); } } finally { setIsLoading(false); } }, [id]); useEffect(() => { loadData(); }, [loadData]); // 폼 값 변경 const handleChange = (field: keyof PriceDistributionFormData, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); }; // 저장 const handleSave = async () => { setIsSaving(true); try { const result = await updatePriceDistribution(id, formData); if (result.success) { toast.success('저장되었습니다.'); router.push(`/master-data/price-distribution/${id}`); } else { toast.error(result.error || '저장에 실패했습니다.'); } } catch { toast.error('저장 중 오류가 발생했습니다.'); } finally { setIsSaving(false); } }; // 최종확정 const handleFinalize = async () => { try { const result = await finalizePriceDistribution(id); if (result.success) { toast.success('최종확정 되었습니다.'); setShowFinalizeDialog(false); loadData(); } else { toast.error(result.error || '최종확정에 실패했습니다.'); } } catch { toast.error('최종확정 중 오류가 발생했습니다.'); } }; // 수정 모드 전환 const handleEditMode = () => { router.push(`/master-data/price-distribution/${id}/edit`); }; // 취소 const handleCancel = () => { router.push(`/master-data/price-distribution/${id}`); }; // 목록으로 const handleBack = () => { router.push('/master-data/price-distribution'); }; // 체크박스 전체 선택/해제 const handleSelectAll = (checked: boolean) => { if (!detail) return; if (checked) { setSelectedItems(new Set(detail.items.map((item) => item.id))); } else { setSelectedItems(new Set()); } }; // 체크박스 개별 선택 const handleSelectItem = (itemId: string, checked: boolean) => { setSelectedItems((prev) => { const next = new Set(prev); if (checked) { next.add(itemId); } else { next.delete(itemId); } return next; }); }; // 상태 뱃지 const renderStatusBadge = (status: DistributionStatus) => { const style = DISTRIBUTION_STATUS_STYLES[status]; const label = DISTRIBUTION_STATUS_LABELS[status]; return ( {label} ); }; // 금액 포맷 const formatPrice = (price?: number) => { return formatNumber(price); }; if (isLoading) { return (

로딩 중...

); } if (!detail) { return (

데이터를 찾을 수 없습니다.

); } const isAllSelected = detail.items.length > 0 && selectedItems.size === detail.items.length; return ( {/* 기본 정보 */} 기본 정보
{/* 단가배포번호 */}

{detail.distributionNo}

{/* 단가배포명 */}
{isEditMode ? ( handleChange('distributionName', e.target.value)} className="h-8 text-sm" /> ) : (

{detail.distributionName}

)}
{/* 상태 */}
{/* 작성자 */}

{detail.author}

{/* 등록일 */}

{detail.createdAt}

{/* 적용시점 */}
{isEditMode ? ( handleChange('effectiveDate', date)} size="sm" /> ) : (

{detail.effectiveDate ? new Date(detail.effectiveDate).toLocaleDateString('ko-KR') : '-'}

)}
{/* 사무실 연락처 */}
{isEditMode ? ( handleChange('officePhone', e.target.value)} className="h-8 text-sm" placeholder="02-0000-0000" /> ) : (

{detail.officePhone || '-'}

)}
{/* 발주전용 연락처 */}
{isEditMode ? ( handleChange('orderPhone', e.target.value)} className="h-8 text-sm" placeholder="02-0000-0000" /> ) : (

{detail.orderPhone || '-'}

)}
{/* 단가 목록 테이블 */} 단가표 목록
총 {detail.items.length}건
handleSelectAll(!!checked)} /> 번호 단가번호 품목코드 품목유형 품목명 규격 단위 매입단가 가공비 마진율 판매단가 상태 작성자 변경일 {detail.items.length === 0 ? ( 단가 데이터가 없습니다. ) : ( detail.items.map((item, index) => ( handleSelectItem(item.id, !!checked)} /> {index + 1} {item.pricingCode} {item.itemCode} {item.itemType} {item.itemName} {item.specification} {item.unit} {formatPrice(item.purchasePrice)} {formatPrice(item.processingCost)} {item.marginRate}% {formatPrice(item.salesPrice)} {item.status} {item.author} {item.changedDate} )) )}
{/* 하단 버튼 (sticky 하단 바) */}
{/* 왼쪽: 목록으로 / 취소 */} {isViewMode ? ( ) : ( )} {/* 오른쪽: 액션 버튼들 */}
{isViewMode && ( <> {canApprove && ( )} {canUpdate && ( )} )} {isEditMode && canUpdate && ( )}
{/* 최종확정 다이얼로그 */} 최종확정 단가배포를 최종 확정하시겠습니까? 확정 후에는 수정이 불가합니다. 취소 확정 {/* 단가표 보기 모달 */}
); } export default PriceDistributionDetail;