refactor: ShipmentDetail IntegratedDetailTemplate 마이그레이션

- PageLayout → IntegratedDetailTemplate 전환
- renderHeaderActions: 문서 미리보기, 삭제, 상태변경 버튼
- renderViewContent: 출고정보 Card 섹션들
- 템플릿 마이그레이션 현황 문서 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-20 17:16:35 +09:00
parent d12e2e0b4c
commit 6b0ffc810b
2 changed files with 709 additions and 462 deletions

View File

@@ -3,12 +3,12 @@
/**
* 출하 상세 페이지
* API 연동 완료 (2025-12-26)
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
import { useState, useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import {
Truck,
FileText,
Receipt,
ClipboardList,
@@ -16,12 +16,9 @@ import {
Printer,
X,
Loader2,
AlertCircle,
Trash2,
ArrowRight,
ChevronDown,
} from 'lucide-react';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -48,12 +45,6 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
DialogDescription,
DialogFooter,
@@ -62,7 +53,8 @@ import {
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
import { PageLayout } from '@/components/organisms/PageLayout';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { shipmentConfig } from './shipmentConfig';
import { getShipmentById, deleteShipment, updateShipmentStatus } from './actions';
import {
SHIPMENT_STATUS_LABELS,
@@ -111,7 +103,7 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
// API 데이터 상태
const [detail, setDetail] = useState<ShipmentDetailType | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [_error, setError] = useState<string | null>(null);
// API 데이터 로드
const loadData = useCallback(async () => {
@@ -241,480 +233,475 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
</div>
);
// 로딩 상태 표시
if (isLoading) {
return (
<PageLayout>
<ContentLoadingSpinner text="출하 정보를 불러오는 중..." />
</PageLayout>
);
}
// 에러 상태 표시
if (error || !detail) {
return (
<PageLayout>
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
<AlertCircle className="w-12 h-12 text-red-500" />
<p className="text-lg text-muted-foreground">{error || '데이터를 찾을 수 없습니다.'}</p>
<Button onClick={loadData}> </Button>
</div>
</PageLayout>
);
}
// 수정/삭제 가능 여부 (scheduled, ready 상태에서만)
const canEdit = detail.status === 'scheduled' || detail.status === 'ready';
const canDelete = detail.status === 'scheduled' || detail.status === '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(() => {
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 (
<PageLayout>
<div className="space-y-6">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Truck className="w-6 h-6" />
<h1 className="text-xl font-semibold"> </h1>
</div>
<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>
<div className="w-px h-6 bg-border mx-2" />
<Button variant="outline" onClick={handleGoBack}>
</Button>
{canEdit && (
<Button onClick={handleEdit}>
</Button>
)}
{canDelete && (
<Button
variant="destructive"
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"
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>
</div>
<>
<IntegratedDetailTemplate
config={shipmentConfig}
mode="view"
initialData={detail}
itemId={id}
isLoading={isLoading}
onBack={handleGoBack}
onEdit={canEdit ? handleEdit : undefined}
renderView={renderViewContent}
headerActions={renderHeaderActions()}
/>
{/* 메인 콘텐츠 */}
<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>
{/* 문서 미리보기 다이얼로그 - 작업일지 모달 패턴 적용 */}
<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>
{/* 발주처/배송 정보 */}
<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>
{/* 문서 미리보기 다이얼로그 - 작업일지 모달 패턴 적용 */}
<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' && '거래명세서'}
{/* 모달 헤더 - 작업일지 스타일 (인쇄 시 숨김) */}
<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' && '납품확인서'}
</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>
<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>
</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>
)}
</DialogContent>
</Dialog>
{/* 삭제 확인 다이얼로그 */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{detail.shipmentNo}() ?
<br />
.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}></AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
disabled={isDeleting}
className="bg-red-600 hover:bg-red-700"
>
{isDeleting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
'삭제'
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 상태 변경 다이얼로그 */}
<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' && (
{/* 삭제 확인 다이얼로그 */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
{detail?.shipmentNo}() ?
<br />
.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}></AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
disabled={isDeleting}
className="bg-red-600 hover:bg-red-700"
>
{isDeleting ? (
<>
<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>
<Input
id="driverContact"
placeholder="010-0000-0000"
value={statusFormData.driverContact}
onChange={(e) =>
setStatusFormData((prev) => ({ ...prev, driverContact: e.target.value }))
}
/>
</div>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
...
</>
) : (
'삭제'
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 배송완료로 변경 시 - 도착 확인 시간 */}
{targetStatus === 'completed' && (
{/* 상태 변경 다이얼로그 */}
<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="confirmedArrival"> ()</Label>
<Label htmlFor="vehicleNo"> ()</Label>
<Input
id="confirmedArrival"
type="datetime-local"
value={statusFormData.confirmedArrival}
id="vehicleNo"
placeholder="예: 12가 3456"
value={statusFormData.vehicleNo}
onChange={(e) =>
setStatusFormData((prev) => ({ ...prev, confirmedArrival: e.target.value }))
setStatusFormData((prev) => ({ ...prev, vehicleNo: e.target.value }))
}
/>
</div>
)}
</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>
<Input
id="driverContact"
placeholder="010-0000-0000"
value={statusFormData.driverContact}
onChange={(e) =>
setStatusFormData((prev) => ({ ...prev, driverContact: e.target.value }))
}
/>
</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>
</div>
</PageLayout>
{/* 배송완료로 변경 시 - 도착 확인 시간 */}
{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>
</>
);
}