From 3ec6fdd71b78c51346279b50d7c3c2eb68b02546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Wed, 18 Mar 2026 14:57:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[shipment]=20=EC=B6=9C=ED=95=98?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20UI=20=EA=B0=9C=EC=84=A0=20-=20cancelled=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B6=94=EA=B0=80,=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20=EC=A0=9C=EA=B1=B0,=20=EC=A0=9C?= =?UTF-8?q?=ED=92=88=EC=BD=94=EB=93=9C=20=ED=91=9C=EC=8B=9C,=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=B2=84=ED=8A=BC=20=EC=9B=8C=EB=94=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 출하 cancelled 상태 추가 및 UI 반영 - 출하 상세에서 삭제 버튼/기능 제거 - 출하 상세 제품그룹에 제품코드 표시 - 상태 변경 버튼 워딩 수정 --- .../ShipmentManagement/ShipmentDetail.tsx | 57 +------------------ .../outbound/ShipmentManagement/actions.ts | 18 ++---- .../ShipmentManagement/shipmentConfig.ts | 8 +-- .../outbound/ShipmentManagement/types.ts | 7 ++- 4 files changed, 13 insertions(+), 77 deletions(-) diff --git a/src/components/outbound/ShipmentManagement/ShipmentDetail.tsx b/src/components/outbound/ShipmentManagement/ShipmentDetail.tsx index a6214004..0696094a 100644 --- a/src/components/outbound/ShipmentManagement/ShipmentDetail.tsx +++ b/src/components/outbound/ShipmentManagement/ShipmentDetail.tsx @@ -12,7 +12,6 @@ import { ClipboardList, ArrowRight, Loader2, - Trash2, ChevronDown, } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -31,7 +30,6 @@ import { DialogContent, DialogTitle, } from '@/components/ui/dialog'; -import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog'; import { DialogDescription, DialogFooter, @@ -56,7 +54,7 @@ import { } from '@/components/ui/dropdown-menu'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { shipmentConfig } from './shipmentConfig'; -import { getShipmentById, deleteShipment, updateShipmentStatus } from './actions'; +import { getShipmentById, updateShipmentStatus } from './actions'; import { SHIPMENT_STATUS_LABELS, SHIPMENT_STATUS_STYLES, @@ -93,9 +91,6 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) { const router = useRouter(); const [previewDocument, setPreviewDocument] = useState<'shipping' | 'delivery' | null>(null); const [orderDetail, setOrderDetail] = useState(null); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); - // 상태 변경 관련 상태 const [showStatusDialog, setShowStatusDialog] = useState(false); const [targetStatus, setTargetStatus] = useState(null); @@ -165,25 +160,6 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) { router.push(`/ko/outbound/shipments/${id}?mode=edit`); }, [id, router]); - const handleDelete = useCallback(async () => { - setIsDeleting(true); - try { - const result = await deleteShipment(id); - if (result.success) { - router.push('/ko/outbound/shipments'); - } else { - toast.error(result.error || '삭제에 실패했습니다.'); - } - } catch (err) { - if (isNextRedirectError(err)) throw err; - console.error('[ShipmentDetail] handleDelete error:', err); - toast.error('삭제 중 오류가 발생했습니다.'); - } finally { - setIsDeleting(false); - setShowDeleteDialog(false); - } - }, [id, router]); - const handleOpenStatusDialog = useCallback((status: ShipmentStatus) => { setTargetStatus(status); setStatusFormData({ @@ -259,7 +235,6 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) { ); const canEdit = detail ? (detail.status === 'scheduled' || detail.status === 'ready') : false; - const canDelete = detail ? (detail.status === 'scheduled' || detail.status === 'ready') : false; // 제품 부품 테이블 렌더링 const renderPartsTable = (parts: ProductPart[]) => ( @@ -309,16 +284,6 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) { 납품확인서 보기 - {canDelete && ( - - )} {STATUS_TRANSITIONS[detail.status] && detail.canShip && ( )} {STATUS_TRANSITIONS[detail.status] && !detail.canShip && ( @@ -343,7 +308,7 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) { )} ); - }, [detail, canDelete, handleOpenStatusDialog]); + }, [detail, handleOpenStatusDialog]); // 컨텐츠 렌더링 const renderViewContent = useCallback((_data: Record) => { @@ -568,22 +533,6 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) { )} - {/* 삭제 확인 다이얼로그 */} - - 출고번호 {detail?.shipmentNo}을(를) 삭제하시겠습니까? -
- 이 작업은 되돌릴 수 없습니다. - - } - loading={isDeleting} - /> - {/* 상태 변경 다이얼로그 */} diff --git a/src/components/outbound/ShipmentManagement/actions.ts b/src/components/outbound/ShipmentManagement/actions.ts index d07ac3e0..982fb33d 100644 --- a/src/components/outbound/ShipmentManagement/actions.ts +++ b/src/components/outbound/ShipmentManagement/actions.ts @@ -125,6 +125,7 @@ interface ShipmentItemApiData { unit?: string; lot_no?: string; stock_lot_id?: number; + product_name?: string; remarks?: string; } @@ -191,20 +192,20 @@ function transformApiToDetail(data: ShipmentApiData): ShipmentDetail { // items를 floor_unit 기준으로 productGroups 자동 그룹핑 const rawItems = data.items || []; const items = rawItems.map(transformApiToProduct); - const groupMap = new Map(); + const groupMap = new Map(); for (let i = 0; i < items.length; i++) { const item = items[i]; const raw = rawItems[i]; const key = item.floorUnit || `item-${item.id}`; if (!groupMap.has(key)) { - groupMap.set(key, { productName: key, specification: '', parts: [] }); + groupMap.set(key, { productName: key, productCode: raw.product_name || '', parts: [] }); } groupMap.get(key)!.parts.push({ product: item, unit: raw.unit || '' }); } const productGroups = Array.from(groupMap.entries()).map(([key, g]) => ({ id: key, productName: g.productName, - specification: g.parts[0]?.product.specification || '', + specification: g.productCode, partCount: g.parts.length, parts: g.parts.map((p, i) => ({ id: p.product.id, @@ -465,17 +466,6 @@ export async function updateShipmentStatus( return { success: result.success, data: result.data, error: result.error }; } -// ===== 출고 삭제 ===== -export async function deleteShipment(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> { - const result = await executeServerAction({ - url: buildApiUrl(`/api/v1/shipments/${id}`), - method: 'DELETE', - errorMessage: '출고 삭제에 실패했습니다.', - }); - if (result.__authError) return { success: false, __authError: true }; - return { success: result.success, error: result.error }; -} - // ===== 물류사 옵션 조회 ===== export async function getLogisticsOptions(): Promise<{ success: boolean; data: LogisticsOption[]; error?: string; __authError?: boolean }> { const result = await executeServerAction({ diff --git a/src/components/outbound/ShipmentManagement/shipmentConfig.ts b/src/components/outbound/ShipmentManagement/shipmentConfig.ts index 0b318272..a8868c26 100644 --- a/src/components/outbound/ShipmentManagement/shipmentConfig.ts +++ b/src/components/outbound/ShipmentManagement/shipmentConfig.ts @@ -12,7 +12,6 @@ import type { DetailConfig } from '@/components/templates/IntegratedDetailTempla * * 특이사항: * - view 모드만 지원 (수정은 별도 /edit 페이지로 이동) - * - 삭제 기능 있음 (scheduled, ready 상태에서만) * - 문서 미리보기: 출고증, 거래명세서, 납품확인서 */ export const shipmentConfig: DetailConfig = { @@ -24,15 +23,10 @@ export const shipmentConfig: DetailConfig = { gridColumns: 2, actions: { showBack: true, - showDelete: true, // 상태에 따라 동적으로 처리 + showDelete: false, showEdit: true, backLabel: '목록', - deleteLabel: '삭제', editLabel: '수정', - deleteConfirmMessage: { - title: '출고 정보 삭제', - description: '이 출고 정보를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.', - }, }, }; diff --git a/src/components/outbound/ShipmentManagement/types.ts b/src/components/outbound/ShipmentManagement/types.ts index 1a30a24d..ca7a5f5e 100644 --- a/src/components/outbound/ShipmentManagement/types.ts +++ b/src/components/outbound/ShipmentManagement/types.ts @@ -5,9 +5,10 @@ // 출고 상태 export type ShipmentStatus = | 'scheduled' // 출고예정 - | 'ready' // 출고대기 (출고대기) + | 'ready' // 출고대기 | 'shipping' // 배송중 - | 'completed'; // 배송완료 (출고완료) + | 'completed' // 배송완료 (출고완료) + | 'cancelled'; // 취소 // 상태 라벨 export const SHIPMENT_STATUS_LABELS: Record = { @@ -15,6 +16,7 @@ export const SHIPMENT_STATUS_LABELS: Record = { ready: '출고대기', shipping: '배송중', completed: '출고완료', + cancelled: '취소', }; // 상태 스타일 @@ -23,6 +25,7 @@ export const SHIPMENT_STATUS_STYLES: Record = { ready: 'bg-yellow-100 text-yellow-800', shipping: 'bg-blue-100 text-blue-800', completed: 'bg-green-100 text-green-800', + cancelled: 'bg-red-100 text-red-800', }; // 출고 우선순위