@@ -637,7 +682,6 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
>
)}
- {/* ๋ฐฐ์ก์๋ฃ๋ก ๋ณ๊ฒฝ ์ - ๋์ฐฉ ํ์ธ ์๊ฐ */}
{targetStatus === 'completed' && (
diff --git a/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx b/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx
index 45752339..1684c607 100644
--- a/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx
+++ b/src/components/outbound/ShipmentManagement/ShipmentEdit.tsx
@@ -1,21 +1,27 @@
'use client';
/**
- * ์ถํ ์์ ํ์ด์ง
- * API ์ฐ๋ ์๋ฃ (2025-12-26)
- * IntegratedDetailTemplate ๋ง์ด๊ทธ๋ ์ด์
(2025-01-20)
+ * ์ถ๊ณ ์์ ํ์ด์ง
+ * 4๊ฐ ์น์
๊ตฌ์กฐ: ๊ธฐ๋ณธ์ ๋ณด(์ฝ๊ธฐ์ ์ฉ), ์์ฃผ/๋ฐฐ์ก์ ๋ณด, ๋ฐฐ์ฐจ์ ๋ณด, ์ ํ๋ด์ฉ(์ฝ๊ธฐ์ ์ฉ)
*/
import { useState, useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
+import { Plus, X as XIcon, ChevronDown, Search } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
-import { Textarea } from '@/components/ui/textarea';
-import { CurrencyInput } from '@/components/ui/currency-input';
-import { PhoneInput } from '@/components/ui/phone-input';
+import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription } from '@/components/ui/alert';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
import {
Select,
SelectContent,
@@ -23,6 +29,18 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { shipmentEditConfig } from './shipmentConfig';
import {
@@ -34,30 +52,51 @@ import {
import {
SHIPMENT_STATUS_LABELS,
SHIPMENT_STATUS_STYLES,
+ FREIGHT_COST_LABELS,
} from './types';
import type {
ShipmentDetail,
ShipmentEditFormData,
- ShipmentPriority,
DeliveryMethod,
+ FreightCostType,
+ VehicleDispatch,
LogisticsOption,
VehicleTonnageOption,
+ ProductGroup,
+ ProductPart,
} from './types';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
+import { useDaumPostcode } from '@/hooks/useDaumPostcode';
-// ๊ณ ์ ์ต์
(ํด๋ผ์ด์ธํธ์์ ๊ด๋ฆฌ)
-const priorityOptions: { value: ShipmentPriority; label: string }[] = [
- { value: 'urgent', label: '๊ธด๊ธ' },
- { value: 'normal', label: '์ผ๋ฐ' },
- { value: 'low', label: '๋ฎ์' },
-];
-
+// ๋ฐฐ์ก๋ฐฉ์ ์ต์
const deliveryMethodOptions: { value: DeliveryMethod; label: string }[] = [
- { value: 'pickup', label: '์์ฐจ (๋ฌผ๋ฅ์
์ฒด)' },
- { value: 'direct', label: '์ง์ ๋ฐฐ์ก (์์ฒด)' },
- { value: 'logistics', label: '๋ฌผ๋ฅ์ฌ' },
+ { value: 'direct_dispatch', label: '์ง์ ๋ฐฐ์ฐจ' },
+ { value: 'loading', label: '์์ฐจ' },
+ { value: 'kyungdong_delivery', label: '๊ฒฝ๋ํ๋ฐฐ' },
+ { value: 'daesin_delivery', label: '๋์ ํ๋ฐฐ' },
+ { value: 'kyungdong_freight', label: '๊ฒฝ๋ํ๋ฌผ' },
+ { value: 'daesin_freight', label: '๋์ ํ๋ฌผ' },
+ { value: 'self_pickup', label: '์ง์ ์๋ น' },
];
+// ์ด์๋น์ฉ ์ต์
+const freightCostOptions: { value: FreightCostType; label: string }[] = Object.entries(
+ FREIGHT_COST_LABELS
+).map(([value, label]) => ({ value: value as FreightCostType, label }));
+
+// ๋น ๋ฐฐ์ฐจ ํ ์์ฑ
+function createEmptyDispatch(): VehicleDispatch {
+ return {
+ id: `vd-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
+ logisticsCompany: '',
+ arrivalDateTime: '',
+ tonnage: '',
+ vehicleNo: '',
+ driverContact: '',
+ remarks: '',
+ };
+}
+
interface ShipmentEditProps {
id: string;
}
@@ -71,8 +110,16 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
// ํผ ์ํ
const [formData, setFormData] = useState
({
scheduledDate: '',
+ shipmentDate: '',
priority: 'normal',
- deliveryMethod: 'pickup',
+ deliveryMethod: 'direct_dispatch',
+ freightCost: undefined,
+ receiver: '',
+ receiverContact: '',
+ zipCode: '',
+ address: '',
+ addressDetail: '',
+ vehicleDispatches: [createEmptyDispatch()],
loadingManager: '',
logisticsCompany: '',
vehicleTonnage: '',
@@ -96,6 +143,20 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
const [error, setError] = useState(null);
const [validationErrors, setValidationErrors] = useState([]);
+ // ์์ฝ๋์ธ ์ํ
+ const [accordionValue, setAccordionValue] = useState([]);
+
+ // ์ฐํธ๋ฒํธ ์ฐพ๊ธฐ
+ const { openPostcode } = useDaumPostcode({
+ onComplete: (result) => {
+ setFormData(prev => ({
+ ...prev,
+ zipCode: result.zonecode,
+ address: result.address,
+ }));
+ },
+ });
+
// ๋ฐ์ดํฐ ๋ก๋
const loadData = useCallback(async () => {
setIsLoading(true);
@@ -115,8 +176,18 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
// ํผ ์ด๊ธฐ๊ฐ ์ค์
setFormData({
scheduledDate: shipmentDetail.scheduledDate,
+ shipmentDate: shipmentDetail.shipmentDate || '',
priority: shipmentDetail.priority,
deliveryMethod: shipmentDetail.deliveryMethod,
+ freightCost: shipmentDetail.freightCost,
+ receiver: shipmentDetail.receiver || '',
+ receiverContact: shipmentDetail.receiverContact || '',
+ zipCode: shipmentDetail.zipCode || '',
+ address: shipmentDetail.address || shipmentDetail.deliveryAddress || '',
+ addressDetail: shipmentDetail.addressDetail || '',
+ vehicleDispatches: shipmentDetail.vehicleDispatches.length > 0
+ ? shipmentDetail.vehicleDispatches
+ : [createEmptyDispatch()],
loadingManager: shipmentDetail.loadingManager || '',
logisticsCompany: shipmentDetail.logisticsCompany || '',
vehicleTonnage: shipmentDetail.vehicleTonnage || '',
@@ -130,13 +201,12 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
remarks: shipmentDetail.remarks || '',
});
} else {
- setError(detailResult.error || '์ถํ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค.');
+ setError(detailResult.error || '์ถ๊ณ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค.');
}
if (logisticsResult.success && logisticsResult.data) {
setLogisticsOptions(logisticsResult.data);
}
-
if (tonnageResult.success && tonnageResult.data) {
setVehicleTonnageOptions(tonnageResult.data);
}
@@ -149,7 +219,6 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
}
}, [id]);
- // ๋ฐ์ดํฐ ๋ก๋
useEffect(() => {
loadData();
}, [loadData]);
@@ -157,54 +226,69 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
// ํผ ์
๋ ฅ ํธ๋ค๋ฌ
const handleInputChange = (field: keyof ShipmentEditFormData, value: string | number | undefined) => {
setFormData(prev => ({ ...prev, [field]: value }));
- // ์
๋ ฅ ์ ์๋ฌ ํด๋ฆฌ์ด
- if (validationErrors.length > 0) {
- setValidationErrors([]);
- }
+ if (validationErrors.length > 0) setValidationErrors([]);
};
- // ์ทจ์
+ // ๋ฐฐ์ฐจ ์ ๋ณด ํธ๋ค๋ฌ
+ const handleDispatchChange = (index: number, field: keyof VehicleDispatch, value: string) => {
+ setFormData(prev => {
+ const newDispatches = [...prev.vehicleDispatches];
+ newDispatches[index] = { ...newDispatches[index], [field]: value };
+ return { ...prev, vehicleDispatches: newDispatches };
+ });
+ };
+
+ const handleAddDispatch = () => {
+ setFormData(prev => ({
+ ...prev,
+ vehicleDispatches: [...prev.vehicleDispatches, createEmptyDispatch()],
+ }));
+ };
+
+ const handleRemoveDispatch = (index: number) => {
+ setFormData(prev => ({
+ ...prev,
+ vehicleDispatches: prev.vehicleDispatches.filter((_, i) => i !== index),
+ }));
+ };
+
+ // ์์ฝ๋์ธ ์ ์ด
+ const handleExpandAll = useCallback(() => {
+ if (!detail) return;
+ const allIds = [
+ ...detail.productGroups.map(g => g.id),
+ ...(detail.otherParts.length > 0 ? ['other-parts'] : []),
+ ];
+ setAccordionValue(allIds);
+ }, [detail]);
+
+ const handleCollapseAll = useCallback(() => {
+ setAccordionValue([]);
+ }, []);
+
const handleCancel = useCallback(() => {
router.push(`/ko/outbound/shipments/${id}?mode=view`);
}, [router, id]);
- // validation ์ฒดํฌ
const validateForm = (): boolean => {
const errors: string[] = [];
-
- // ํ์ ํ๋ ์ฒดํฌ
- if (!formData.scheduledDate) {
- errors.push('์ถ๊ณ ์์ ์ผ์ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.');
- }
- if (!formData.priority) {
- errors.push('์ถ๊ณ ์ฐ์ ์์๋ ํ์ ์ ํ ํญ๋ชฉ์
๋๋ค.');
- }
- if (!formData.deliveryMethod) {
- errors.push('๋ฐฐ์ก๋ฐฉ์์ ํ์ ์ ํ ํญ๋ชฉ์
๋๋ค.');
- }
- if (!formData.changeReason.trim()) {
- errors.push('๋ณ๊ฒฝ ์ฌ์ ๋ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.');
- }
-
+ if (!formData.scheduledDate) errors.push('์ถ๊ณ ์์ ์ผ์ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.');
+ if (!formData.deliveryMethod) errors.push('๋ฐฐ์ก๋ฐฉ์์ ํ์ ์ ํ ํญ๋ชฉ์
๋๋ค.');
+ if (!formData.changeReason.trim()) errors.push('๋ณ๊ฒฝ ์ฌ์ ๋ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.');
setValidationErrors(errors);
return errors.length === 0;
};
- // ์ ์ฅ
const handleSubmit = useCallback(async () => {
- // validation ์ฒดํฌ
- if (!validateForm()) {
- return;
- }
+ if (!validateForm()) return;
setIsSubmitting(true);
try {
const result = await updateShipment(id, formData);
-
if (result.success) {
router.push(`/ko/outbound/shipments/${id}?mode=view`);
} else {
- setValidationErrors([result.error || '์ถํ ์์ ์ ์คํจํ์ต๋๋ค.']);
+ setValidationErrors([result.error || '์ถ๊ณ ์์ ์ ์คํจํ์ต๋๋ค.']);
}
} catch (err) {
if (isNextRedirectError(err)) throw err;
@@ -215,11 +299,36 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
}
}, [id, formData, router]);
- // ๋์ config (๋กํธ๋ฒํธ + ์ํ ํ์)
- // Note: IntegratedDetailTemplate์ด edit ๋ชจ๋์์ '์์ ' ์ ๋ฏธ์ฌ ์๋ ์ถ๊ฐ
+ // ์ ํ ๋ถํ ํ
์ด๋ธ ๋ ๋๋ง
+ const renderPartsTable = (parts: ProductPart[]) => (
+
+
+
+ ์๋ฒ
+ ํ๋ชฉ๋ช
+ ๊ท๊ฒฉ
+ ์๋
+ ๋จ์
+
+
+
+ {parts.map((part) => (
+
+ {part.seq}
+ {part.itemName}
+ {part.specification}
+ {part.quantity}
+ {part.unit}
+
+ ))}
+
+
+ );
+
+ // ๋์ config
const dynamicConfig = {
...shipmentEditConfig,
- title: detail?.lotNo ? `์ถํ (${detail.lotNo})` : '์ถํ',
+ title: detail?.lotNo ? `์ถ๊ณ (${detail.lotNo})` : '์ถ๊ณ ',
};
// ํผ ์ปจํ
์ธ ๋ ๋๋ง
@@ -246,10 +355,10 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
์
๋ ฅ ๋ด์ฉ์ ํ์ธํด์ฃผ์ธ์ ({validationErrors.length}๊ฐ ์ค๋ฅ)
- {validationErrors.map((error, index) => (
+ {validationErrors.map((err, index) => (
-
โข
- {error}
+ {err}
))}
@@ -259,7 +368,7 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
)}
- {/* ๊ธฐ๋ณธ ์ ๋ณด (์ฝ๊ธฐ ์ ์ฉ) */}
+ {/* ์นด๋ 1: ๊ธฐ๋ณธ ์ ๋ณด (์ฝ๊ธฐ์ ์ฉ) */}
๊ธฐ๋ณธ ์ ๋ณด
@@ -274,31 +383,35 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
{detail.lotNo}
- {/* ์ถ๊ณ ์ ๋ณด */}
+ {/* ์นด๋ 2: ์์ฃผ/๋ฐฐ์ก ์ ๋ณด */}