- 공통 템플릿 타입 수정 (IntegratedDetailTemplate, UniversalListPage) - 페이지(app/[locale]) 타입 호환성 수정 (80개) - 재고/자재 모듈 타입 수정 (StockStatus, ReceivingManagement) - 생산 모듈 타입 수정 (WorkOrders, WorkerScreen, WorkResults) - 주문/출고 모듈 타입 수정 (ShipmentManagement, Orders) - 견적/단가 모듈 타입 수정 (Quotes, Pricing) - 건설 모듈 타입 수정 (49개, 17개 하위 모듈) - HR 모듈 타입 수정 (CardManagement, VacationManagement 등) - 설정 모듈 타입 수정 (PermissionManagement, AccountManagement 등) - 게시판 모듈 타입 수정 (BoardManagement, BoardList 등) - 회계 모듈 타입 수정 (VendorManagement, BadDebtCollection 등) - 기타 모듈 타입 수정 (CEODashboard, clients, vehicle 등) - 유틸/훅/API 타입 수정 (hooks, contexts, lib) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
684 lines
24 KiB
TypeScript
684 lines
24 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* 출하 상세 페이지
|
|
* API 연동 완료 (2025-12-26)
|
|
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
|
|
*/
|
|
|
|
import { useState, useCallback, useEffect } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import {
|
|
FileText,
|
|
Receipt,
|
|
ClipboardList,
|
|
Check,
|
|
Printer,
|
|
X,
|
|
Loader2,
|
|
Trash2,
|
|
ArrowRight,
|
|
} from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
|
|
import {
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
} from '@/components/ui/dialog';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { PhoneInput } from '@/components/ui/phone-input';
|
|
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
|
|
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
|
import { shipmentConfig } from './shipmentConfig';
|
|
import { getShipmentById, deleteShipment, updateShipmentStatus } from './actions';
|
|
import {
|
|
SHIPMENT_STATUS_LABELS,
|
|
SHIPMENT_STATUS_STYLES,
|
|
PRIORITY_LABELS,
|
|
PRIORITY_STYLES,
|
|
DELIVERY_METHOD_LABELS,
|
|
} from './types';
|
|
import type { ShipmentDetail as ShipmentDetailType, ShipmentStatus } from './types';
|
|
import { ShippingSlip } from './documents/ShippingSlip';
|
|
import { TransactionStatement } from './documents/TransactionStatement';
|
|
import { DeliveryConfirmation } from './documents/DeliveryConfirmation';
|
|
import { printArea } from '@/lib/print-utils';
|
|
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
|
|
|
interface ShipmentDetailProps {
|
|
id: string;
|
|
}
|
|
|
|
// 상태 전이 맵: 현재 상태 → 다음 가능한 상태
|
|
const STATUS_TRANSITIONS: Record<ShipmentStatus, ShipmentStatus | null> = {
|
|
scheduled: 'ready',
|
|
ready: 'shipping',
|
|
shipping: 'completed',
|
|
completed: null, // 최종 상태
|
|
};
|
|
|
|
export function ShipmentDetail({ id }: ShipmentDetailProps) {
|
|
const router = useRouter();
|
|
const [previewDocument, setPreviewDocument] = useState<'shipping' | 'transaction' | 'delivery' | null>(null);
|
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
|
|
// 상태 변경 관련 상태
|
|
const [showStatusDialog, setShowStatusDialog] = useState(false);
|
|
const [targetStatus, setTargetStatus] = useState<ShipmentStatus | null>(null);
|
|
const [isChangingStatus, setIsChangingStatus] = useState(false);
|
|
const [statusFormData, setStatusFormData] = useState({
|
|
loadingTime: '',
|
|
vehicleNo: '',
|
|
driverName: '',
|
|
driverContact: '',
|
|
confirmedArrival: '',
|
|
});
|
|
|
|
// API 데이터 상태
|
|
const [detail, setDetail] = useState<ShipmentDetailType | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [_error, setError] = useState<string | null>(null);
|
|
|
|
// API 데이터 로드
|
|
const loadData = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const result = await getShipmentById(id);
|
|
|
|
if (result.success && result.data) {
|
|
setDetail(result.data);
|
|
} else {
|
|
setError(result.error || '출하 정보를 찾을 수 없습니다.');
|
|
}
|
|
} catch (err) {
|
|
if (isNextRedirectError(err)) throw err;
|
|
console.error('[ShipmentDetail] loadData error:', err);
|
|
setError('데이터를 불러오는 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [id]);
|
|
|
|
// 데이터 로드
|
|
useEffect(() => {
|
|
loadData();
|
|
}, [loadData]);
|
|
|
|
// 목록으로 이동
|
|
const handleGoBack = useCallback(() => {
|
|
router.push('/ko/outbound/shipments');
|
|
}, [router]);
|
|
|
|
// 수정 페이지로 이동
|
|
const handleEdit = useCallback(() => {
|
|
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 {
|
|
alert(result.error || '삭제에 실패했습니다.');
|
|
}
|
|
} catch (err) {
|
|
if (isNextRedirectError(err)) throw err;
|
|
console.error('[ShipmentDetail] handleDelete error:', err);
|
|
alert('삭제 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsDeleting(false);
|
|
setShowDeleteDialog(false);
|
|
}
|
|
}, [id, router]);
|
|
|
|
// 인쇄
|
|
const handlePrint = useCallback(() => {
|
|
const docName = previewDocument === 'shipping' ? '출고증'
|
|
: previewDocument === 'transaction' ? '거래명세서'
|
|
: '납품확인서';
|
|
printArea({ title: `${docName} 인쇄` });
|
|
}, [previewDocument]);
|
|
|
|
// 상태 변경 다이얼로그 열기
|
|
const handleOpenStatusDialog = useCallback((status: ShipmentStatus) => {
|
|
setTargetStatus(status);
|
|
setStatusFormData({
|
|
loadingTime: '',
|
|
vehicleNo: '',
|
|
driverName: '',
|
|
driverContact: '',
|
|
confirmedArrival: '',
|
|
});
|
|
setShowStatusDialog(true);
|
|
}, []);
|
|
|
|
// 상태 변경 처리
|
|
const handleStatusChange = useCallback(async () => {
|
|
if (!targetStatus) return;
|
|
|
|
setIsChangingStatus(true);
|
|
try {
|
|
const additionalData: Record<string, string> = {};
|
|
|
|
// 상태별 추가 데이터 설정
|
|
if (targetStatus === 'ready' && statusFormData.loadingTime) {
|
|
additionalData.loadingTime = statusFormData.loadingTime;
|
|
}
|
|
if (targetStatus === 'shipping') {
|
|
if (statusFormData.vehicleNo) additionalData.vehicleNo = statusFormData.vehicleNo;
|
|
if (statusFormData.driverName) additionalData.driverName = statusFormData.driverName;
|
|
if (statusFormData.driverContact) additionalData.driverContact = statusFormData.driverContact;
|
|
}
|
|
if (targetStatus === 'completed' && statusFormData.confirmedArrival) {
|
|
additionalData.confirmedArrival = statusFormData.confirmedArrival;
|
|
}
|
|
|
|
const result = await updateShipmentStatus(
|
|
id,
|
|
targetStatus,
|
|
Object.keys(additionalData).length > 0 ? additionalData : undefined
|
|
);
|
|
|
|
if (result.success && result.data) {
|
|
setDetail(result.data);
|
|
setShowStatusDialog(false);
|
|
} else {
|
|
alert(result.error || '상태 변경에 실패했습니다.');
|
|
}
|
|
} catch (err) {
|
|
if (isNextRedirectError(err)) throw err;
|
|
console.error('[ShipmentDetail] handleStatusChange error:', err);
|
|
alert('상태 변경 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsChangingStatus(false);
|
|
}
|
|
}, [id, targetStatus, statusFormData]);
|
|
|
|
// 정보 영역 렌더링
|
|
const renderInfoField = (label: string, value: React.ReactNode, className?: string) => (
|
|
<div className={className}>
|
|
<div className="text-sm text-muted-foreground mb-1">{label}</div>
|
|
<div className="font-medium">{value}</div>
|
|
</div>
|
|
);
|
|
|
|
// 수정/삭제 가능 여부 (scheduled, ready 상태에서만)
|
|
const canEdit = detail ? (detail.status === 'scheduled' || detail.status === 'ready') : false;
|
|
const canDelete = detail ? (detail.status === 'scheduled' || detail.status === 'ready') : false;
|
|
|
|
// 헤더 액션 버튼 렌더링
|
|
const renderHeaderActions = useCallback(() => {
|
|
if (!detail) return null;
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
{/* 문서 미리보기 버튼 */}
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setPreviewDocument('shipping')}
|
|
>
|
|
<FileText className="w-4 h-4 mr-1" />
|
|
출고증
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setPreviewDocument('transaction')}
|
|
>
|
|
<Receipt className="w-4 h-4 mr-1" />
|
|
거래명세서
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setPreviewDocument('delivery')}
|
|
>
|
|
<ClipboardList className="w-4 h-4 mr-1" />
|
|
납품확인서
|
|
</Button>
|
|
{canDelete && (
|
|
<>
|
|
<div className="w-px h-6 bg-border mx-2" />
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
>
|
|
<Trash2 className="w-4 h-4 mr-1" />
|
|
삭제
|
|
</Button>
|
|
</>
|
|
)}
|
|
{/* 상태 변경 버튼 */}
|
|
{STATUS_TRANSITIONS[detail.status] && (
|
|
<>
|
|
<div className="w-px h-6 bg-border mx-2" />
|
|
<Button
|
|
variant="default"
|
|
size="sm"
|
|
className="bg-blue-600 hover:bg-blue-700"
|
|
onClick={() => handleOpenStatusDialog(STATUS_TRANSITIONS[detail.status]!)}
|
|
>
|
|
<ArrowRight className="w-4 h-4 mr-1" />
|
|
{SHIPMENT_STATUS_LABELS[STATUS_TRANSITIONS[detail.status]!]}으로 변경
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}, [detail, canDelete, handleOpenStatusDialog]);
|
|
|
|
// 컨텐츠 렌더링
|
|
const renderViewContent = useCallback((_data: Record<string, unknown>) => {
|
|
if (!detail) return null;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* 출고 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">출고 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
{renderInfoField('출고번호', detail.shipmentNo)}
|
|
{renderInfoField('출고예정일', detail.scheduledDate)}
|
|
{renderInfoField('로트번호', detail.lotNo)}
|
|
{renderInfoField(
|
|
'출고상태',
|
|
<Badge className={SHIPMENT_STATUS_STYLES[detail.status]}>
|
|
{SHIPMENT_STATUS_LABELS[detail.status]}
|
|
</Badge>
|
|
)}
|
|
{renderInfoField(
|
|
'출고 우선순위',
|
|
<Badge className={PRIORITY_STYLES[detail.priority]}>
|
|
{PRIORITY_LABELS[detail.priority]}
|
|
</Badge>
|
|
)}
|
|
{renderInfoField(
|
|
'배송방식',
|
|
<Badge variant="outline">
|
|
{DELIVERY_METHOD_LABELS[detail.deliveryMethod]}
|
|
</Badge>
|
|
)}
|
|
{renderInfoField(
|
|
'입금확인',
|
|
detail.depositConfirmed ? (
|
|
<span className="flex items-center gap-1 text-green-600">
|
|
<Check className="w-4 h-4" />
|
|
확인됨
|
|
</span>
|
|
) : (
|
|
<span className="text-muted-foreground">미확인</span>
|
|
)
|
|
)}
|
|
{renderInfoField(
|
|
'세금계산서',
|
|
detail.invoiceIssued ? (
|
|
<span className="flex items-center gap-1 text-green-600">
|
|
<Check className="w-4 h-4" />
|
|
발행됨
|
|
</span>
|
|
) : (
|
|
<span className="text-muted-foreground">미발행</span>
|
|
)
|
|
)}
|
|
{renderInfoField('거래처 등급', detail.customerGrade)}
|
|
{renderInfoField(
|
|
'출하가능',
|
|
detail.canShip ? (
|
|
<span className="flex items-center gap-1 text-green-600">
|
|
<Check className="w-4 h-4" />
|
|
가능
|
|
</span>
|
|
) : (
|
|
<span className="flex items-center gap-1 text-red-600">
|
|
<X className="w-4 h-4" />
|
|
불가
|
|
</span>
|
|
)
|
|
)}
|
|
{renderInfoField('상차담당자', detail.loadingManager || '-')}
|
|
{renderInfoField(
|
|
'상차완료',
|
|
detail.loadingCompleted ? (
|
|
<span className="flex items-center gap-1 text-green-600">
|
|
<Check className="w-4 h-4" />
|
|
완료 ({detail.loadingCompleted})
|
|
</span>
|
|
) : (
|
|
<span className="text-muted-foreground">-</span>
|
|
)
|
|
)}
|
|
{renderInfoField('등록자', detail.registrant || '-')}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 발주처/배송 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">발주처/배송 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
{renderInfoField('발주처', detail.customerName)}
|
|
{renderInfoField('현장명', detail.siteName)}
|
|
{renderInfoField('배송주소', detail.deliveryAddress, 'md:col-span-2')}
|
|
{renderInfoField('인수자', detail.receiver || '-')}
|
|
{renderInfoField('연락처', detail.receiverContact || '-')}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 출고 품목 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">출고 품목</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="w-12 text-center">No</TableHead>
|
|
<TableHead className="w-24">품목코드</TableHead>
|
|
<TableHead>품목명</TableHead>
|
|
<TableHead className="w-24">층/M호</TableHead>
|
|
<TableHead className="w-28">규격</TableHead>
|
|
<TableHead className="w-16 text-center">수량</TableHead>
|
|
<TableHead className="w-36">LOT번호</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{detail.products.map((product) => (
|
|
<TableRow key={product.id}>
|
|
<TableCell className="text-center">{product.no}</TableCell>
|
|
<TableCell>{product.itemCode}</TableCell>
|
|
<TableCell>{product.itemName}</TableCell>
|
|
<TableCell>{product.floorUnit}</TableCell>
|
|
<TableCell>{product.specification}</TableCell>
|
|
<TableCell className="text-center">{product.quantity}</TableCell>
|
|
<TableCell>{product.lotNo}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 배차 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">배차 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
{renderInfoField('배송방식', DELIVERY_METHOD_LABELS[detail.deliveryMethod])}
|
|
{renderInfoField('물류사', detail.logisticsCompany || '-')}
|
|
{renderInfoField('차량 톤수', detail.vehicleTonnage || '-')}
|
|
{renderInfoField('운송비', detail.shippingCost !== undefined ? `${detail.shippingCost.toLocaleString()}원` : '-')}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 차량/운전자 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">차량/운전자 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
{renderInfoField('차량번호', detail.vehicleNo || '-')}
|
|
{renderInfoField('운전자', detail.driverName || '-')}
|
|
{renderInfoField('운전자 연락처', detail.driverContact || '-')}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 비고 */}
|
|
{detail.remarks && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">비고</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm whitespace-pre-wrap">{detail.remarks}</p>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}, [detail]);
|
|
|
|
return (
|
|
<>
|
|
<IntegratedDetailTemplate
|
|
config={shipmentConfig}
|
|
mode="view"
|
|
initialData={(detail ?? undefined) as Record<string, unknown> | undefined}
|
|
itemId={id}
|
|
isLoading={isLoading}
|
|
onEdit={canEdit ? handleEdit : undefined}
|
|
renderView={renderViewContent}
|
|
headerActions={renderHeaderActions()}
|
|
/>
|
|
|
|
{/* 문서 미리보기 다이얼로그 - 작업일지 모달 패턴 적용 */}
|
|
<Dialog open={previewDocument !== null} onOpenChange={() => setPreviewDocument(null)}>
|
|
<DialogContent className="sm:max-w-5xl max-h-[90vh] overflow-y-auto p-0 gap-0 bg-gray-100">
|
|
{/* 접근성을 위한 숨겨진 타이틀 */}
|
|
<VisuallyHidden>
|
|
<DialogTitle>
|
|
{previewDocument === 'shipping' && '출고증'}
|
|
{previewDocument === 'transaction' && '거래명세서'}
|
|
{previewDocument === 'delivery' && '납품확인서'}
|
|
</DialogTitle>
|
|
</VisuallyHidden>
|
|
|
|
{/* 모달 헤더 - 작업일지 스타일 (인쇄 시 숨김) */}
|
|
<div className="print-hidden flex items-center justify-between px-6 py-4 border-b bg-white sticky top-0 z-10">
|
|
<div className="flex items-center gap-3">
|
|
<span className="font-semibold text-lg">
|
|
{previewDocument === 'shipping' && '출고증 미리보기'}
|
|
{previewDocument === 'transaction' && '거래명세서 미리보기'}
|
|
{previewDocument === 'delivery' && '납품확인서'}
|
|
</span>
|
|
{detail && (
|
|
<>
|
|
<span className="text-sm text-muted-foreground">
|
|
{detail.customerName}
|
|
</span>
|
|
<span className="text-sm text-muted-foreground">
|
|
({detail.shipmentNo})
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" size="sm" onClick={handlePrint}>
|
|
<Printer className="w-4 h-4 mr-1.5" />
|
|
인쇄
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={() => setPreviewDocument(null)}
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 문서 본문 - 흰색 카드 형태 (인쇄 시 이 영역만 출력) */}
|
|
{detail && (
|
|
<div className="print-area m-6 p-6 bg-white rounded-lg shadow-sm">
|
|
{previewDocument === 'shipping' && <ShippingSlip data={detail} />}
|
|
{previewDocument === 'transaction' && <TransactionStatement data={detail} />}
|
|
{previewDocument === 'delivery' && <DeliveryConfirmation data={detail} />}
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* 삭제 확인 다이얼로그 */}
|
|
<DeleteConfirmDialog
|
|
open={showDeleteDialog}
|
|
onOpenChange={setShowDeleteDialog}
|
|
onConfirm={handleDelete}
|
|
title="출하 정보 삭제"
|
|
description={
|
|
<>
|
|
출하번호 {detail?.shipmentNo}을(를) 삭제하시겠습니까?
|
|
<br />
|
|
이 작업은 되돌릴 수 없습니다.
|
|
</>
|
|
}
|
|
loading={isDeleting}
|
|
/>
|
|
|
|
{/* 상태 변경 다이얼로그 */}
|
|
<Dialog open={showStatusDialog} onOpenChange={setShowStatusDialog}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>출하 상태 변경</DialogTitle>
|
|
<DialogDescription>
|
|
{detail?.status && targetStatus && (
|
|
<span className="flex items-center gap-2 mt-2">
|
|
<Badge className={SHIPMENT_STATUS_STYLES[detail.status]}>
|
|
{SHIPMENT_STATUS_LABELS[detail.status]}
|
|
</Badge>
|
|
<ArrowRight className="w-4 h-4" />
|
|
<Badge className={SHIPMENT_STATUS_STYLES[targetStatus]}>
|
|
{SHIPMENT_STATUS_LABELS[targetStatus]}
|
|
</Badge>
|
|
</span>
|
|
)}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-4">
|
|
{/* 출하대기로 변경 시 - 상차 시간 */}
|
|
{targetStatus === 'ready' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="loadingTime">상차 시간 (선택)</Label>
|
|
<Input
|
|
id="loadingTime"
|
|
type="datetime-local"
|
|
value={statusFormData.loadingTime}
|
|
onChange={(e) =>
|
|
setStatusFormData((prev) => ({ ...prev, loadingTime: e.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* 배송중으로 변경 시 - 차량/운전자 정보 */}
|
|
{targetStatus === 'shipping' && (
|
|
<>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="vehicleNo">차량번호 (선택)</Label>
|
|
<Input
|
|
id="vehicleNo"
|
|
placeholder="예: 12가 3456"
|
|
value={statusFormData.vehicleNo}
|
|
onChange={(e) =>
|
|
setStatusFormData((prev) => ({ ...prev, vehicleNo: e.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="driverName">운전자명 (선택)</Label>
|
|
<Input
|
|
id="driverName"
|
|
placeholder="운전자 이름"
|
|
value={statusFormData.driverName}
|
|
onChange={(e) =>
|
|
setStatusFormData((prev) => ({ ...prev, driverName: e.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="driverContact">운전자 연락처 (선택)</Label>
|
|
<PhoneInput
|
|
id="driverContact"
|
|
placeholder="010-0000-0000"
|
|
value={statusFormData.driverContact}
|
|
onChange={(value) =>
|
|
setStatusFormData((prev) => ({ ...prev, driverContact: value }))
|
|
}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* 배송완료로 변경 시 - 도착 확인 시간 */}
|
|
{targetStatus === 'completed' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="confirmedArrival">도착 확인 시간 (선택)</Label>
|
|
<Input
|
|
id="confirmedArrival"
|
|
type="datetime-local"
|
|
value={statusFormData.confirmedArrival}
|
|
onChange={(e) =>
|
|
setStatusFormData((prev) => ({ ...prev, confirmedArrival: e.target.value }))
|
|
}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowStatusDialog(false)}
|
|
disabled={isChangingStatus}
|
|
>
|
|
취소
|
|
</Button>
|
|
<Button
|
|
onClick={handleStatusChange}
|
|
disabled={isChangingStatus}
|
|
className="bg-blue-600 hover:bg-blue-700"
|
|
>
|
|
{isChangingStatus ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
변경 중...
|
|
</>
|
|
) : (
|
|
'상태 변경'
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|