- HeaderFavoritesBar 대폭 개선 - Sidebar/AuthenticatedLayout 소폭 수정 - ShipmentCreate, VehicleDispatch 출하 관련 개선 - WorkOrderCreate/Edit, WorkerScreen 생산 관련 개선 - InspectionCreate 자재 입고검사 개선 - DailyReport, VendorDetail 회계 수정 - CEO 대시보드: CardManagement/DailyProduction/DailyAttendance 섹션 개선 - useCEODashboard, expense transformer 정비 - DocumentViewer, PDF generate route 소폭 수정 - bill-prototype 개발 페이지 추가 - mockData 불필요 데이터 제거
155 lines
5.3 KiB
TypeScript
155 lines
5.3 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* 배차차량 상세 페이지
|
|
* 3개 섹션: 기본정보, 배차정보, 배송비정보
|
|
*/
|
|
|
|
import { useState, useCallback, useEffect } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
|
import { vehicleDispatchConfig } from './vehicleDispatchConfig';
|
|
import { getVehicleDispatchById } from './actions';
|
|
import {
|
|
VEHICLE_DISPATCH_STATUS_LABELS,
|
|
VEHICLE_DISPATCH_STATUS_STYLES,
|
|
FREIGHT_COST_LABELS,
|
|
FREIGHT_COST_STYLES,
|
|
} from './types';
|
|
import type { VehicleDispatchDetail as VehicleDispatchDetailType } from './types';
|
|
import { formatNumber as formatAmount } from '@/lib/utils/amount';
|
|
|
|
interface VehicleDispatchDetailProps {
|
|
id: string;
|
|
}
|
|
|
|
export function VehicleDispatchDetail({ id }: VehicleDispatchDetailProps) {
|
|
const router = useRouter();
|
|
|
|
// API 데이터 상태
|
|
const [detail, setDetail] = useState<VehicleDispatchDetailType | 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 getVehicleDispatchById(id);
|
|
if (result.success && result.data) {
|
|
setDetail(result.data);
|
|
} else {
|
|
setError(result.error || '배차차량 정보를 찾을 수 없습니다.');
|
|
}
|
|
} catch (err) {
|
|
console.error('[VehicleDispatchDetail] loadData error:', err);
|
|
setError('데이터를 불러오는 중 오류가 발생했습니다.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [id]);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, [loadData]);
|
|
|
|
const handleEdit = useCallback(() => {
|
|
router.push(`/ko/outbound/vehicle-dispatches/${id}?mode=edit`);
|
|
}, [id, router]);
|
|
|
|
// 정보 필드 렌더링 헬퍼
|
|
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>
|
|
);
|
|
|
|
// 컨텐츠 렌더링
|
|
const renderViewContent = useCallback((_data: Record<string, unknown>) => {
|
|
if (!detail) return null;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* 카드 1: 기본 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">기본 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
{renderInfoField('배차번호', detail.dispatchNo)}
|
|
{renderInfoField('로트번호', detail.lotNo || detail.shipmentNo)}
|
|
{renderInfoField('현장명', detail.siteName)}
|
|
{renderInfoField('수주처', detail.orderCustomer)}
|
|
{renderInfoField(
|
|
'운임비용',
|
|
<Badge className={FREIGHT_COST_STYLES[detail.freightCostType]}>
|
|
{FREIGHT_COST_LABELS[detail.freightCostType]}
|
|
</Badge>
|
|
)}
|
|
{renderInfoField(
|
|
'상태',
|
|
<Badge className={VEHICLE_DISPATCH_STATUS_STYLES[detail.status]}>
|
|
{VEHICLE_DISPATCH_STATUS_LABELS[detail.status]}
|
|
</Badge>
|
|
)}
|
|
{renderInfoField('작성자', detail.writer)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 카드 2: 배차 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">배차 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
|
|
{renderInfoField('물류업체', detail.logisticsCompany)}
|
|
{renderInfoField('입차일시', detail.arrivalDateTime)}
|
|
{renderInfoField('구분', detail.tonnage)}
|
|
{renderInfoField('차량번호', detail.vehicleNo)}
|
|
{renderInfoField('기사연락처', detail.driverContact)}
|
|
{renderInfoField('비고', detail.remarks)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 카드 3: 배송비 정보 */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">배송비 정보</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
|
|
{renderInfoField('공급가액', `${formatAmount(detail.supplyAmount)}원`)}
|
|
{renderInfoField('부가세', `${formatAmount(detail.vat)}원`)}
|
|
{renderInfoField(
|
|
'합계',
|
|
<span className="text-lg font-bold">{formatAmount(detail.totalAmount)}원</span>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}, [detail]);
|
|
|
|
return (
|
|
<IntegratedDetailTemplate
|
|
config={vehicleDispatchConfig}
|
|
mode="view"
|
|
initialData={(detail ?? undefined) as Record<string, unknown> | undefined}
|
|
itemId={id}
|
|
isLoading={isLoading}
|
|
onEdit={handleEdit}
|
|
renderView={renderViewContent}
|
|
/>
|
|
);
|
|
}
|