refactor: ShipmentDetail IntegratedDetailTemplate 마이그레이션
- PageLayout → IntegratedDetailTemplate 전환 - renderHeaderActions: 문서 미리보기, 삭제, 상태변경 버튼 - renderViewContent: 출고정보 Card 섹션들 - 템플릿 마이그레이션 현황 문서 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
260
claudedocs/[REF] template-migration-status.md
Normal file
260
claudedocs/[REF] template-migration-status.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# 템플릿 마이그레이션 현황
|
||||
|
||||
> 작성일: 2025-01-20
|
||||
> 목적: IntegratedListTemplate / IntegratedDetailTemplate 적용 현황 파악
|
||||
|
||||
---
|
||||
|
||||
## 📊 전체 통계
|
||||
|
||||
| 구분 | 수량 |
|
||||
|------|------|
|
||||
| 전체 Protected 페이지 | 203개 |
|
||||
| IntegratedListTemplate 사용 | 48개 |
|
||||
| IntegratedDetailTemplate 사용 | 57개 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 마이그레이션 완료
|
||||
|
||||
### 리스트 페이지 (IntegratedListTemplateV2)
|
||||
대부분의 리스트 페이지가 IntegratedListTemplateV2로 마이그레이션 완료.
|
||||
- 필터, 테이블, 페이지네이션, 헤더 버튼 공통화 적용
|
||||
|
||||
### 상세/수정/등록 페이지 (IntegratedDetailTemplate)
|
||||
|
||||
#### App 페이지 (17개)
|
||||
```
|
||||
src/app/[locale]/(protected)/settings/popup-management/new/page.tsx
|
||||
src/app/[locale]/(protected)/settings/popup-management/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/settings/accounts/new/page.tsx
|
||||
src/app/[locale]/(protected)/settings/accounts/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/sales/client-management-sales-admin/new/page.tsx
|
||||
src/app/[locale]/(protected)/sales/client-management-sales-admin/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/sales/quote-management/test/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/sales/order-management-sales/[id]/edit/page.tsx
|
||||
src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/board/board-management/new/page.tsx
|
||||
src/app/[locale]/(protected)/board/board-management/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/master-data/process-management/new/page.tsx
|
||||
src/app/[locale]/(protected)/master-data/process-management/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/hr/card-management/new/page.tsx
|
||||
src/app/[locale]/(protected)/hr/card-management/[id]/edit/page.tsx
|
||||
src/app/[locale]/(protected)/hr/card-management/[id]/page.tsx
|
||||
src/app/[locale]/(protected)/construction/order/base-info/labor/[id]/page.tsx
|
||||
```
|
||||
|
||||
#### 컴포넌트 (주요 40개)
|
||||
```
|
||||
# 회계
|
||||
src/components/accounting/BillManagement/BillDetail.tsx
|
||||
src/components/accounting/SalesManagement/SalesDetail.tsx
|
||||
src/components/accounting/PurchaseManagement/PurchaseDetail.tsx
|
||||
src/components/accounting/VendorLedger/VendorLedgerDetail.tsx
|
||||
src/components/accounting/VendorManagement/VendorDetail.tsx
|
||||
src/components/accounting/BadDebtCollection/BadDebtDetail.tsx
|
||||
src/components/accounting/WithdrawalManagement/WithdrawalDetailClientV2.tsx
|
||||
src/components/accounting/DepositManagement/DepositDetailClientV2.tsx
|
||||
|
||||
# 영업/고객
|
||||
src/components/clients/ClientDetailClientV2.tsx
|
||||
src/components/quotes/QuoteRegistrationV2.tsx
|
||||
src/components/orders/OrderSalesDetailView.tsx
|
||||
src/components/orders/OrderSalesDetailEdit.tsx
|
||||
|
||||
# 설정
|
||||
src/components/settings/PopupManagement/PopupDetailClientV2.tsx
|
||||
src/components/settings/PermissionManagement/PermissionDetail.tsx
|
||||
|
||||
# 건설/프로젝트
|
||||
src/components/business/construction/contract/ContractDetailForm.tsx
|
||||
src/components/business/construction/site-briefings/SiteBriefingForm.tsx
|
||||
src/components/business/construction/order-management/OrderDetailForm.tsx
|
||||
src/components/business/construction/handover-report/HandoverReportDetailForm.tsx
|
||||
src/components/business/construction/item-management/ItemDetailClient.tsx
|
||||
src/components/business/construction/estimates/EstimateDetailForm.tsx
|
||||
src/components/business/construction/management/ConstructionDetailClient.tsx
|
||||
src/components/business/construction/site-management/SiteDetailForm.tsx
|
||||
src/components/business/construction/partners/PartnerForm.tsx
|
||||
src/components/business/construction/structure-review/StructureReviewDetailForm.tsx
|
||||
src/components/business/construction/issue-management/IssueDetailForm.tsx
|
||||
src/components/business/construction/bidding/BiddingDetailForm.tsx
|
||||
src/components/business/construction/pricing-management/PricingDetailClientV2.tsx
|
||||
src/components/business/construction/labor-management/LaborDetailClientV2.tsx
|
||||
src/components/business/construction/progress-billing/ProgressBillingDetailForm.tsx
|
||||
|
||||
# 고객센터
|
||||
src/components/customer-center/NoticeManagement/NoticeDetail.tsx
|
||||
src/components/customer-center/InquiryManagement/InquiryDetail.tsx
|
||||
src/components/customer-center/EventManagement/EventDetail.tsx
|
||||
|
||||
# HR
|
||||
src/components/hr/EmployeeManagement/EmployeeDetail.tsx
|
||||
|
||||
# 생산/물류
|
||||
src/components/production/WorkOrders/WorkOrderDetail.tsx
|
||||
src/components/outbound/ShipmentManagement/ShipmentDetail.tsx
|
||||
src/components/material/ReceivingManagement/ReceivingDetail.tsx
|
||||
src/components/material/StockStatus/StockStatusDetail.tsx
|
||||
|
||||
# 품질
|
||||
src/components/quality/InspectionManagement/InspectionDetail.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❌ 마이그레이션 미완료
|
||||
|
||||
### App 페이지 (PageLayout 직접 사용)
|
||||
|
||||
| 경로 | 유형 | 비고 |
|
||||
|------|------|------|
|
||||
| `sales/order-management-sales/production-orders/[id]/page.tsx` | 상세 | 생산지시 상세 |
|
||||
| `sales/order-management-sales/[id]/production-order/page.tsx` | 상세 | 생산지시 |
|
||||
| `boards/[boardCode]/create/page.tsx` | 등록 | 게시판 글쓰기 |
|
||||
| `boards/[boardCode]/[postId]/edit/page.tsx` | 수정 | 게시판 글수정 |
|
||||
| `boards/[boardCode]/[postId]/page.tsx` | 상세 | 게시판 글상세 |
|
||||
| `test/popup/page.tsx` | 테스트 | 테스트 페이지 |
|
||||
| `dev/editable-table/page.tsx` | 개발 | 개발용 페이지 |
|
||||
|
||||
### 컴포넌트 (PageLayout 직접 사용)
|
||||
|
||||
#### 회계
|
||||
```
|
||||
src/components/accounting/DailyReport/index.tsx # 일일보고서 (특수 레이아웃)
|
||||
src/components/accounting/ReceivablesStatus/index.tsx # 미수금현황 (특수 레이아웃)
|
||||
src/components/accounting/WithdrawalManagement/WithdrawalDetail.tsx # V2로 대체됨
|
||||
src/components/accounting/DepositManagement/DepositDetail.tsx # V2로 대체됨
|
||||
```
|
||||
|
||||
#### 설정
|
||||
```
|
||||
src/components/settings/CompanyInfoManagement/index.tsx # 회사정보 (설정 페이지)
|
||||
src/components/settings/RankManagement/index.tsx # 직급관리 (설정 페이지)
|
||||
src/components/settings/LeavePolicyManagement/index.tsx # 휴가정책 (설정 페이지)
|
||||
src/components/settings/AccountInfoManagement/index.tsx # 계정정보 (설정 페이지)
|
||||
src/components/settings/NotificationSettings/index.tsx # 알림설정 (설정 페이지)
|
||||
src/components/settings/TitleManagement/index.tsx # 직책관리 (설정 페이지)
|
||||
src/components/settings/WorkScheduleManagement/index.tsx # 근무일정 (설정 페이지)
|
||||
src/components/settings/AttendanceSettingsManagement/index.tsx # 근태설정 (설정 페이지)
|
||||
src/components/settings/PopupManagement/PopupForm.tsx # V2로 대체됨
|
||||
src/components/settings/PopupManagement/PopupDetail.tsx # V2로 대체됨
|
||||
src/components/settings/AccountManagement/AccountDetail.tsx # V2로 대체됨
|
||||
src/components/settings/PermissionManagement/PermissionDetailClient.tsx # 레거시
|
||||
src/components/settings/SubscriptionManagement/SubscriptionManagement.tsx # 구독관리
|
||||
src/components/settings/SubscriptionManagement/SubscriptionClient.tsx
|
||||
```
|
||||
|
||||
#### 건설/프로젝트
|
||||
```
|
||||
src/components/business/construction/management/ProjectListClient.tsx # 리스트 (별도)
|
||||
src/components/business/construction/management/ProjectDetailClient.tsx # 레거시
|
||||
src/components/business/construction/category-management/index.tsx # 카테고리 (특수)
|
||||
src/components/business/construction/pricing-management/PricingDetailClient.tsx # V2로 대체됨
|
||||
src/components/business/construction/labor-management/LaborDetailClient.tsx # V2로 대체됨
|
||||
```
|
||||
|
||||
#### 게시판
|
||||
```
|
||||
src/components/board/BoardManagement/BoardForm.tsx # V2로 대체됨
|
||||
src/components/board/BoardManagement/BoardDetail.tsx # V2로 대체됨
|
||||
src/components/board/BoardDetail/index.tsx # 동적 게시판 상세
|
||||
src/components/board/BoardForm/index.tsx # 동적 게시판 폼
|
||||
```
|
||||
|
||||
#### HR
|
||||
```
|
||||
src/components/hr/DepartmentManagement/index.tsx # 부서관리 (트리 구조)
|
||||
src/components/hr/EmployeeManagement/EmployeeForm.tsx # 직원등록 폼
|
||||
src/components/hr/EmployeeManagement/CSVUploadPage.tsx # CSV 업로드 (특수)
|
||||
```
|
||||
|
||||
#### 생산
|
||||
```
|
||||
src/components/production/ProductionDashboard/index.tsx # 대시보드 (제외)
|
||||
src/components/production/WorkerScreen/index.tsx # 작업자화면 (특수 UI)
|
||||
src/components/production/WorkOrders/WorkOrderCreate.tsx # 작업지시 등록
|
||||
src/components/production/WorkOrders/WorkOrderEdit.tsx # 작업지시 수정
|
||||
```
|
||||
|
||||
#### 고객센터
|
||||
```
|
||||
src/components/customer-center/InquiryManagement/InquiryForm.tsx # 문의등록
|
||||
src/components/customer-center/FAQManagement/FAQList.tsx # FAQ 리스트
|
||||
```
|
||||
|
||||
#### 기타
|
||||
```
|
||||
src/components/clients/ClientDetail.tsx # V2로 대체됨
|
||||
src/components/process-management/ProcessForm.tsx # 공정등록
|
||||
src/components/process-management/ProcessDetail.tsx # V2로 대체됨
|
||||
src/components/outbound/ShipmentManagement/ShipmentEdit.tsx # 출고수정
|
||||
src/components/outbound/ShipmentManagement/ShipmentCreate.tsx # 출고등록
|
||||
src/components/items/ItemMasterDataManagement.tsx # 품목마스터 (특수)
|
||||
src/components/material/ReceivingManagement/InspectionCreate.tsx # 검수등록
|
||||
src/components/quality/InspectionManagement/InspectionCreate.tsx # 품질검사등록
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚫 마이그레이션 제외 대상
|
||||
|
||||
### 대시보드/특수 페이지
|
||||
```
|
||||
src/app/[locale]/(protected)/dashboard/page.tsx # CEO 대시보드
|
||||
src/app/[locale]/(protected)/production/dashboard/page.tsx # 생산 대시보드
|
||||
src/app/[locale]/(protected)/reports/comprehensive-analysis/page.tsx # 종합분석
|
||||
src/components/business/CEODashboard/CEODashboard.tsx # CEO 대시보드
|
||||
```
|
||||
|
||||
### 레거시 파일 (_legacy 폴더)
|
||||
```
|
||||
src/components/settings/AccountManagement/_legacy/AccountDetail.tsx
|
||||
src/components/hr/CardManagement/_legacy/CardDetail.tsx
|
||||
src/components/hr/CardManagement/_legacy/CardForm.tsx
|
||||
```
|
||||
|
||||
### 테스트/개발용
|
||||
```
|
||||
src/app/[locale]/(protected)/test/popup/page.tsx
|
||||
src/app/[locale]/(protected)/dev/editable-table/page.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 마이그레이션 우선순위 권장
|
||||
|
||||
### 높음 (실사용 페이지)
|
||||
1. `boards/[boardCode]/*` - 동적 게시판 페이지들
|
||||
2. `production/WorkOrders/WorkOrderCreate.tsx` - 작업지시 등록
|
||||
3. `production/WorkOrders/WorkOrderEdit.tsx` - 작업지시 수정
|
||||
4. `outbound/ShipmentManagement/ShipmentCreate.tsx` - 출고 등록
|
||||
5. `outbound/ShipmentManagement/ShipmentEdit.tsx` - 출고 수정
|
||||
|
||||
### 중간 (설정 페이지 - 템플릿 적용 검토 필요)
|
||||
- `settings/` 하위 관리 페이지들 (트리/특수 레이아웃 많음)
|
||||
|
||||
### 낮음 (V2 대체 완료)
|
||||
- V2 파일이 있는 레거시 컴포넌트들 (삭제 검토)
|
||||
|
||||
### 제외
|
||||
- 대시보드, 특수 UI, 테스트/개발 페이지
|
||||
|
||||
---
|
||||
|
||||
## 🔧 템플릿 수정 시 일괄 적용 범위
|
||||
|
||||
템플릿 파일 수정 시 아래 파일들에 자동 적용:
|
||||
|
||||
| 템플릿 | 영향 파일 수 |
|
||||
|--------|-------------|
|
||||
| `IntegratedListTemplateV2` | 48개 |
|
||||
| `IntegratedDetailTemplate` | 57개 |
|
||||
| **합계** | **105개** |
|
||||
|
||||
수정 가능 요소:
|
||||
- 타이틀 위치/스타일
|
||||
- 버튼 배치/디자인
|
||||
- 입력필드 공통 스타일
|
||||
- 레이아웃 구조
|
||||
- 반응형 처리
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user