feat(WEB): 자재/출고/생산/품질/단가 기능 대폭 개선 및 신규 페이지 추가
자재관리: - 입고관리 재고조정 다이얼로그 신규 추가, 상세/목록 기능 확장 - 재고현황 컴포넌트 리팩토링 출고관리: - 출하관리 생성/수정/목록/상세 개선 - 차량배차관리 상세/수정/목록 기능 보강 생산관리: - 작업지시서 WIP 생산 모달 신규 추가 - 벤딩WIP/슬랫조인트바 검사 콘텐츠 신규 추가 - 작업자화면 기능 대폭 확장 (카드/목록 개선) - 검사성적서 모달 개선 품질관리: - 실적보고서 관리 페이지 신규 추가 - 검사관리 문서/타입/목데이터 개선 단가관리: - 단가배포 페이지 및 컴포넌트 신규 추가 - 단가표 관리 페이지 및 컴포넌트 신규 추가 공통: - 권한 시스템 추가 개선 (PermissionContext, usePermission, PermissionGuard) - 메뉴 폴링 훅 개선, 레이아웃 수정 - 모바일 줌/패닝 CSS 수정 - locale 유틸 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -50,10 +50,6 @@ import {
|
||||
getLogisticsOptions,
|
||||
getVehicleTonnageOptions,
|
||||
} from './actions';
|
||||
import {
|
||||
FREIGHT_COST_LABELS,
|
||||
DELIVERY_METHOD_LABELS,
|
||||
} from './types';
|
||||
import type {
|
||||
ShipmentCreateFormData,
|
||||
DeliveryMethod,
|
||||
@@ -83,10 +79,12 @@ const deliveryMethodOptions: { value: DeliveryMethod; label: string }[] = [
|
||||
{ value: 'self_pickup', label: '직접수령' },
|
||||
];
|
||||
|
||||
// 운임비용 옵션
|
||||
const freightCostOptions: { value: FreightCostType; label: string }[] = Object.entries(
|
||||
FREIGHT_COST_LABELS
|
||||
).map(([value, label]) => ({ value: value as FreightCostType, label }));
|
||||
// 운임비용 옵션 (선불, 착불, 없음)
|
||||
const freightCostOptions: { value: FreightCostType; label: string }[] = [
|
||||
{ value: 'prepaid', label: '선불' },
|
||||
{ value: 'collect', label: '착불' },
|
||||
{ value: 'none', label: '없음' },
|
||||
];
|
||||
|
||||
// 빈 배차 행 생성
|
||||
function createEmptyDispatch(): VehicleDispatch {
|
||||
@@ -111,7 +109,7 @@ export function ShipmentCreate() {
|
||||
priority: 'normal',
|
||||
deliveryMethod: 'direct_dispatch',
|
||||
shipmentDate: '',
|
||||
freightCost: undefined,
|
||||
freightCost: 'none',
|
||||
receiver: '',
|
||||
receiverContact: '',
|
||||
zipCode: '',
|
||||
@@ -229,9 +227,22 @@ export function ShipmentCreate() {
|
||||
if (validationErrors.length > 0) setValidationErrors([]);
|
||||
}, [validationErrors]);
|
||||
|
||||
// 배송방식에 따라 운임비용 '없음' 고정 여부 판단
|
||||
const isFreightCostLocked = (method: DeliveryMethod) =>
|
||||
method === 'direct_dispatch' || method === 'self_pickup';
|
||||
|
||||
// 폼 입력 핸들러
|
||||
const handleInputChange = (field: keyof ShipmentCreateFormData, value: string) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
if (field === 'deliveryMethod') {
|
||||
const method = value as DeliveryMethod;
|
||||
if (isFreightCostLocked(method)) {
|
||||
setFormData(prev => ({ ...prev, deliveryMethod: method, freightCost: 'none' as FreightCostType }));
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, deliveryMethod: method }));
|
||||
}
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
}
|
||||
if (validationErrors.length > 0) setValidationErrors([]);
|
||||
};
|
||||
|
||||
@@ -455,7 +466,7 @@ export function ShipmentCreate() {
|
||||
<Select
|
||||
value={formData.freightCost || ''}
|
||||
onValueChange={(value) => handleInputChange('freightCost', value)}
|
||||
disabled={isSubmitting}
|
||||
disabled={isSubmitting || isFreightCostLocked(formData.deliveryMethod)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="선택" />
|
||||
@@ -549,7 +560,7 @@ export function ShipmentCreate() {
|
||||
<TableRow>
|
||||
<TableHead>물류업체</TableHead>
|
||||
<TableHead>입차일시</TableHead>
|
||||
<TableHead>톤수</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>차량번호</TableHead>
|
||||
<TableHead>기사연락처</TableHead>
|
||||
<TableHead>비고</TableHead>
|
||||
|
||||
@@ -338,18 +338,16 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
{renderInfoField('출고번호', detail.shipmentNo)}
|
||||
{renderInfoField('로트번호', detail.lotNo)}
|
||||
{renderInfoField('현장명', detail.siteName)}
|
||||
{renderInfoField('수주처', detail.customerName)}
|
||||
{renderInfoField('거래등급', detail.customerGrade)}
|
||||
{renderInfoField('작성자', detail.registrant)}
|
||||
{renderInfoField(
|
||||
'상태',
|
||||
<Badge className={SHIPMENT_STATUS_STYLES[detail.status]}>
|
||||
{SHIPMENT_STATUS_LABELS[detail.status]}
|
||||
</Badge>
|
||||
)}
|
||||
{renderInfoField('작성자', detail.registrant)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -408,7 +406,7 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
|
||||
<TableRow>
|
||||
<TableHead>물류업체</TableHead>
|
||||
<TableHead>입차일시</TableHead>
|
||||
<TableHead>톤수</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>차량번호</TableHead>
|
||||
<TableHead>기사연락처</TableHead>
|
||||
<TableHead>비고</TableHead>
|
||||
|
||||
@@ -52,7 +52,6 @@ import {
|
||||
import {
|
||||
SHIPMENT_STATUS_LABELS,
|
||||
SHIPMENT_STATUS_STYLES,
|
||||
FREIGHT_COST_LABELS,
|
||||
} from './types';
|
||||
import type {
|
||||
ShipmentDetail,
|
||||
@@ -79,10 +78,12 @@ const deliveryMethodOptions: { value: DeliveryMethod; label: string }[] = [
|
||||
{ value: 'self_pickup', label: '직접수령' },
|
||||
];
|
||||
|
||||
// 운임비용 옵션
|
||||
const freightCostOptions: { value: FreightCostType; label: string }[] = Object.entries(
|
||||
FREIGHT_COST_LABELS
|
||||
).map(([value, label]) => ({ value: value as FreightCostType, label }));
|
||||
// 운임비용 옵션 (선불, 착불, 없음)
|
||||
const freightCostOptions: { value: FreightCostType; label: string }[] = [
|
||||
{ value: 'prepaid', label: '선불' },
|
||||
{ value: 'collect', label: '착불' },
|
||||
{ value: 'none', label: '없음' },
|
||||
];
|
||||
|
||||
// 빈 배차 행 생성
|
||||
function createEmptyDispatch(): VehicleDispatch {
|
||||
@@ -174,12 +175,13 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
setDetail(shipmentDetail);
|
||||
|
||||
// 폼 초기값 설정
|
||||
const lockedFreight = shipmentDetail.deliveryMethod === 'direct_dispatch' || shipmentDetail.deliveryMethod === 'self_pickup';
|
||||
setFormData({
|
||||
scheduledDate: shipmentDetail.scheduledDate,
|
||||
shipmentDate: shipmentDetail.shipmentDate || '',
|
||||
priority: shipmentDetail.priority,
|
||||
deliveryMethod: shipmentDetail.deliveryMethod,
|
||||
freightCost: shipmentDetail.freightCost,
|
||||
freightCost: lockedFreight ? 'none' : shipmentDetail.freightCost,
|
||||
receiver: shipmentDetail.receiver || '',
|
||||
receiverContact: shipmentDetail.receiverContact || '',
|
||||
zipCode: shipmentDetail.zipCode || '',
|
||||
@@ -223,9 +225,22 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
|
||||
// 배송방식에 따라 운임비용 '없음' 고정 여부 판단
|
||||
const isFreightCostLocked = (method: DeliveryMethod) =>
|
||||
method === 'direct_dispatch' || method === 'self_pickup';
|
||||
|
||||
// 폼 입력 핸들러
|
||||
const handleInputChange = (field: keyof ShipmentEditFormData, value: string | number | undefined) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
if (field === 'deliveryMethod') {
|
||||
const method = value as DeliveryMethod;
|
||||
if (isFreightCostLocked(method)) {
|
||||
setFormData(prev => ({ ...prev, deliveryMethod: method, freightCost: 'none' as FreightCostType }));
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, deliveryMethod: method }));
|
||||
}
|
||||
} else {
|
||||
setFormData(prev => ({ ...prev, [field]: value }));
|
||||
}
|
||||
if (validationErrors.length > 0) setValidationErrors([]);
|
||||
};
|
||||
|
||||
@@ -375,10 +390,6 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">출고번호</Label>
|
||||
<div className="font-medium">{detail.shipmentNo}</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">로트번호</Label>
|
||||
<div className="font-medium">{detail.lotNo}</div>
|
||||
@@ -388,12 +399,12 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
<div className="font-medium">{detail.siteName}</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">수주처</Label>
|
||||
<Label className="text-muted-foreground">회사명</Label>
|
||||
<div className="font-medium">{detail.customerName}</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">거래등급</Label>
|
||||
<div className="font-medium">{detail.customerGrade || '-'}</div>
|
||||
<Label className="text-muted-foreground">수주자</Label>
|
||||
<div className="font-medium">{detail.orderer || '-'}</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">작성자</Label>
|
||||
@@ -454,7 +465,7 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
key={`freight-${formData.freightCost}`}
|
||||
value={formData.freightCost || ''}
|
||||
onValueChange={(value) => handleInputChange('freightCost', value)}
|
||||
disabled={isSubmitting}
|
||||
disabled={isSubmitting || isFreightCostLocked(formData.deliveryMethod)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="선택" />
|
||||
@@ -548,7 +559,7 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
<TableRow>
|
||||
<TableHead>물류업체</TableHead>
|
||||
<TableHead>입차일시</TableHead>
|
||||
<TableHead>톤수</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>차량번호</TableHead>
|
||||
<TableHead>기사연락처</TableHead>
|
||||
<TableHead>비고</TableHead>
|
||||
|
||||
@@ -231,23 +231,17 @@ export function ShipmentList() {
|
||||
icon: Plus,
|
||||
},
|
||||
|
||||
// 테이블 컬럼 (18개 - 출고번호/로트번호 통합)
|
||||
// 테이블 컬럼 (11개)
|
||||
columns: [
|
||||
{ key: 'no', label: '번호', className: 'w-[50px] text-center' },
|
||||
{ key: 'shipmentNo', label: '출고번호/로트번호', className: 'min-w-[160px]' },
|
||||
{ key: 'scheduledDate', label: '출고예정일', className: 'w-[100px] text-center' },
|
||||
{ key: 'lotNo', label: '로트번호', className: 'min-w-[120px]' },
|
||||
{ key: 'siteName', label: '현장명', className: 'min-w-[100px]' },
|
||||
{ key: 'orderCustomer', label: '수주처', className: 'min-w-[100px]' },
|
||||
{ key: 'customerGrade', label: '거래등급', className: 'w-[80px] text-center' },
|
||||
{ key: 'receiver', label: '수신자', className: 'w-[80px] text-center' },
|
||||
{ key: 'receiverAddress', label: '수신주소', className: 'min-w-[140px]' },
|
||||
{ key: 'receiverCompany', label: '수신처', className: 'min-w-[100px]' },
|
||||
{ key: 'status', label: '상태', className: 'w-[80px] text-center' },
|
||||
{ key: 'dispatch', label: '배차', className: 'w-[80px] text-center' },
|
||||
{ key: 'arrivalDateTime', label: '입차일시', className: 'w-[130px] text-center' },
|
||||
{ key: 'tonnage', label: '톤수', className: 'w-[70px] text-center' },
|
||||
{ key: 'unloadingNo', label: '하차번호', className: 'w-[90px] text-center' },
|
||||
{ key: 'driverContact', label: '기사연락처', className: 'min-w-[110px] text-center' },
|
||||
{ key: 'writer', label: '작성자', className: 'w-[80px] text-center' },
|
||||
{ key: 'shipmentDate', label: '출고일', className: 'w-[100px] text-center' },
|
||||
],
|
||||
@@ -292,7 +286,7 @@ export function ShipmentList() {
|
||||
// 통계 카드
|
||||
stats,
|
||||
|
||||
// 테이블 행 렌더링 (19개 컬럼)
|
||||
// 테이블 행 렌더링 (11개 컬럼)
|
||||
renderTableRow: (
|
||||
item: ShipmentItem,
|
||||
index: number,
|
||||
@@ -312,16 +306,9 @@ export function ShipmentList() {
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-muted-foreground">{globalIndex}</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
<div>{item.shipmentNo}</div>
|
||||
{item.lotNo && item.lotNo !== item.shipmentNo && (
|
||||
<div className="text-xs text-muted-foreground">{item.lotNo}</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">{item.scheduledDate}</TableCell>
|
||||
<TableCell className="font-medium">{item.lotNo || item.shipmentNo || '-'}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate">{item.siteName}</TableCell>
|
||||
<TableCell>{item.orderCustomer || item.customerName || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.customerGrade || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.receiver || '-'}</TableCell>
|
||||
<TableCell className="max-w-[140px] truncate">{item.receiverAddress || '-'}</TableCell>
|
||||
<TableCell>{item.receiverCompany || '-'}</TableCell>
|
||||
@@ -331,10 +318,6 @@ export function ShipmentList() {
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">{item.dispatch || item.deliveryMethodLabel || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.arrivalDateTime || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.tonnage || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.unloadingNo || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.driverContact || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.writer || item.manager || '-'}</TableCell>
|
||||
<TableCell className="text-center">{item.shipmentDate || '-'}</TableCell>
|
||||
</TableRow>
|
||||
@@ -373,11 +356,14 @@ export function ShipmentList() {
|
||||
}
|
||||
infoGrid={
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<InfoField label="출고번호/로트번호" value={item.shipmentNo || item.lotNo} />
|
||||
<InfoField label="수주처" value={item.orderCustomer || item.customerName} />
|
||||
<InfoField label="출고예정일" value={item.scheduledDate} />
|
||||
<InfoField label="배송방식" value={item.deliveryMethodLabel || DELIVERY_METHOD_LABELS[item.deliveryMethod]} />
|
||||
<InfoField label="로트번호" value={item.lotNo || item.shipmentNo} />
|
||||
<InfoField label="현장명" value={item.siteName} />
|
||||
<InfoField label="수주처" value={item.orderCustomer || item.customerName || '-'} />
|
||||
<InfoField label="수신자" value={item.receiver || '-'} />
|
||||
<InfoField label="수신주소" value={item.receiverAddress || '-'} />
|
||||
<InfoField label="수신처" value={item.receiverCompany || '-'} />
|
||||
<InfoField label="배차" value={item.dispatch || item.deliveryMethodLabel || '-'} />
|
||||
<InfoField label="작성자" value={item.writer || item.manager || '-'} />
|
||||
<InfoField label="출고일" value={item.shipmentDate || '-'} />
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -41,11 +41,12 @@ export const PRIORITY_STYLES: Record<ShipmentPriority, string> = {
|
||||
};
|
||||
|
||||
// 운임비용 타입
|
||||
export type FreightCostType = 'prepaid' | 'collect' | 'free' | 'negotiable';
|
||||
export type FreightCostType = 'prepaid' | 'collect' | 'free' | 'negotiable' | 'none';
|
||||
|
||||
export const FREIGHT_COST_LABELS: Record<FreightCostType, string> = {
|
||||
prepaid: '선불',
|
||||
collect: '착불',
|
||||
none: '없음',
|
||||
free: '무료',
|
||||
negotiable: '협의',
|
||||
};
|
||||
@@ -137,6 +138,8 @@ export interface ShipmentItem {
|
||||
writer?: string; // 작성자
|
||||
shipmentDate?: string; // 출고일
|
||||
shipmentTime?: string; // 출고시간 (캘린더용)
|
||||
orderer?: string; // 수주자
|
||||
createdAt?: string; // 작성일
|
||||
}
|
||||
|
||||
// 출고 품목
|
||||
@@ -162,6 +165,7 @@ export interface ShipmentDetail {
|
||||
customerGrade: string; // 거래등급
|
||||
status: ShipmentStatus; // 상태
|
||||
registrant?: string; // 작성자
|
||||
orderer?: string; // 수주자
|
||||
|
||||
// 수주/배송 정보
|
||||
scheduledDate: string; // 출고 예정일
|
||||
|
||||
@@ -9,6 +9,14 @@ 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 {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { vehicleDispatchConfig } from './vehicleDispatchConfig';
|
||||
import { getVehicleDispatchById } from './actions';
|
||||
@@ -87,7 +95,7 @@ export function VehicleDispatchDetail({ id }: VehicleDispatchDetailProps) {
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
{renderInfoField('배차번호', detail.dispatchNo)}
|
||||
{renderInfoField('출고번호', detail.shipmentNo)}
|
||||
{renderInfoField('로트번호', detail.lotNo || detail.shipmentNo)}
|
||||
{renderInfoField('현장명', detail.siteName)}
|
||||
{renderInfoField('수주처', detail.orderCustomer)}
|
||||
{renderInfoField(
|
||||
@@ -107,20 +115,34 @@ export function VehicleDispatchDetail({ id }: VehicleDispatchDetailProps) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 카드 2: 배차 정보 */}
|
||||
{/* 카드 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 className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>물류업체</TableHead>
|
||||
<TableHead>입차일시</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>차량번호</TableHead>
|
||||
<TableHead>기사연락처</TableHead>
|
||||
<TableHead>비고</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>{detail.logisticsCompany || '-'}</TableCell>
|
||||
<TableCell>{detail.arrivalDateTime || '-'}</TableCell>
|
||||
<TableCell>{detail.tonnage || '-'}</TableCell>
|
||||
<TableCell>{detail.vehicleNo || '-'}</TableCell>
|
||||
<TableCell>{detail.driverContact || '-'}</TableCell>
|
||||
<TableCell>{detail.remarks || '-'}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -202,8 +202,8 @@ export function VehicleDispatchEdit({ id }: VehicleDispatchEditProps) {
|
||||
<div className="font-medium">{detail.dispatchNo}</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">출고번호</Label>
|
||||
<div className="font-medium">{detail.shipmentNo}</div>
|
||||
<Label className="text-muted-foreground">로트번호</Label>
|
||||
<div className="font-medium">{detail.lotNo || detail.shipmentNo}</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-muted-foreground">현장명</Label>
|
||||
@@ -275,11 +275,11 @@ export function VehicleDispatchEdit({ id }: VehicleDispatchEditProps) {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>톤수</Label>
|
||||
<Label>구분</Label>
|
||||
<Input
|
||||
value={formData.tonnage}
|
||||
onChange={(e) => handleInputChange('tonnage', e.target.value)}
|
||||
placeholder="예: 3.5톤"
|
||||
placeholder="예: 3.5 톤"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -163,23 +163,19 @@ export function VehicleDispatchList() {
|
||||
onEndDateChange: setEndDate,
|
||||
},
|
||||
|
||||
// 테이블 컬럼
|
||||
// 테이블 컬럼 (13개)
|
||||
columns: [
|
||||
{ key: 'no', label: '번호', className: 'w-[50px] text-center' },
|
||||
{ key: 'no', label: 'No.', className: 'w-[50px] text-center' },
|
||||
{ key: 'dispatchNo', label: '배차번호', className: 'min-w-[130px]' },
|
||||
{ key: 'shipmentNo', label: '출고번호', className: 'min-w-[130px]' },
|
||||
{ key: 'lotNo', label: '로트번호', className: 'min-w-[120px]' },
|
||||
{ key: 'siteName', label: '현장명', className: 'min-w-[100px]' },
|
||||
{ key: 'orderCustomer', label: '수주처', className: 'min-w-[100px]' },
|
||||
{ key: 'logisticsCompany', label: '물류업체', className: 'min-w-[90px]' },
|
||||
{ key: 'tonnage', label: '톤수', className: 'w-[70px] text-center' },
|
||||
{ key: 'supplyAmount', label: '공급가액', className: 'w-[100px] text-right' },
|
||||
{ key: 'vat', label: '부가세', className: 'w-[90px] text-right' },
|
||||
{ key: 'totalAmount', label: '합계', className: 'w-[100px] text-right' },
|
||||
{ key: 'freightCostType', label: '선/착불', className: 'w-[70px] text-center' },
|
||||
{ key: 'vehicleNo', label: '차량번호', className: 'min-w-[100px]' },
|
||||
{ key: 'driverContact', label: '기사연락처', className: 'min-w-[110px]' },
|
||||
{ key: 'writer', label: '작성자', className: 'w-[80px] text-center' },
|
||||
{ key: 'arrivalDateTime', label: '입차일시', className: 'w-[130px] text-center' },
|
||||
{ key: 'status', label: '상태', className: 'w-[80px] text-center' },
|
||||
{ key: 'remarks', label: '비고', className: 'min-w-[100px]' },
|
||||
],
|
||||
@@ -201,15 +197,14 @@ export function VehicleDispatchList() {
|
||||
itemsPerPage: ITEMS_PER_PAGE,
|
||||
|
||||
// 검색
|
||||
searchPlaceholder: '배차번호, 출고번호, 현장명, 수주처, 차량번호 검색...',
|
||||
searchPlaceholder: '배차번호, 로트번호, 현장명, 수주처 검색...',
|
||||
searchFilter: (item: VehicleDispatchItem, search: string) => {
|
||||
const s = search.toLowerCase();
|
||||
return (
|
||||
item.dispatchNo.toLowerCase().includes(s) ||
|
||||
item.shipmentNo.toLowerCase().includes(s) ||
|
||||
(item.lotNo || item.shipmentNo).toLowerCase().includes(s) ||
|
||||
item.siteName.toLowerCase().includes(s) ||
|
||||
item.orderCustomer.toLowerCase().includes(s) ||
|
||||
item.vehicleNo.toLowerCase().includes(s)
|
||||
item.orderCustomer.toLowerCase().includes(s)
|
||||
);
|
||||
},
|
||||
|
||||
@@ -235,31 +230,29 @@ export function VehicleDispatchList() {
|
||||
onCheckedChange={handlers.onToggle}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-muted-foreground">{globalIndex}</TableCell>
|
||||
<TableCell className="font-medium">{item.dispatchNo}</TableCell>
|
||||
<TableCell>{item.shipmentNo}</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate">{item.siteName}</TableCell>
|
||||
<TableCell>{item.orderCustomer}</TableCell>
|
||||
<TableCell>{item.logisticsCompany}</TableCell>
|
||||
<TableCell className="text-center">{item.tonnage}</TableCell>
|
||||
<TableCell className="text-right">{formatAmount(item.supplyAmount)}</TableCell>
|
||||
<TableCell className="text-right">{formatAmount(item.vat)}</TableCell>
|
||||
<TableCell className="text-right font-medium">{formatAmount(item.totalAmount)}</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<Badge className={`text-xs ${FREIGHT_COST_STYLES[item.freightCostType]}`}>
|
||||
{FREIGHT_COST_LABELS[item.freightCostType]}
|
||||
</Badge>
|
||||
<TableCell className="w-[50px] text-center text-muted-foreground">{globalIndex}</TableCell>
|
||||
<TableCell className="min-w-[130px] font-medium">{item.dispatchNo}</TableCell>
|
||||
<TableCell className="min-w-[120px]">{item.lotNo || item.shipmentNo}</TableCell>
|
||||
<TableCell className="min-w-[100px] truncate">{item.siteName}</TableCell>
|
||||
<TableCell className="min-w-[100px]">{item.orderCustomer}</TableCell>
|
||||
<TableCell className="min-w-[90px]">{item.logisticsCompany}</TableCell>
|
||||
<TableCell className="w-[100px] text-right">{formatAmount(item.supplyAmount || 0)}</TableCell>
|
||||
<TableCell className="w-[90px] text-right">{formatAmount(item.vat || 0)}</TableCell>
|
||||
<TableCell className="w-[100px] text-right font-medium">{formatAmount(item.totalAmount || 0)}</TableCell>
|
||||
<TableCell className="w-[70px] text-center">
|
||||
{item.freightCostType ? (
|
||||
<Badge className={`text-xs ${FREIGHT_COST_STYLES[item.freightCostType]}`}>
|
||||
{FREIGHT_COST_LABELS[item.freightCostType]}
|
||||
</Badge>
|
||||
) : '-'}
|
||||
</TableCell>
|
||||
<TableCell>{item.vehicleNo}</TableCell>
|
||||
<TableCell>{item.driverContact}</TableCell>
|
||||
<TableCell className="text-center">{item.writer}</TableCell>
|
||||
<TableCell className="text-center">{item.arrivalDateTime}</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<TableCell className="w-[80px] text-center">{item.writer || '-'}</TableCell>
|
||||
<TableCell className="w-[80px] text-center">
|
||||
<Badge className={`text-xs ${VEHICLE_DISPATCH_STATUS_STYLES[item.status]}`}>
|
||||
{VEHICLE_DISPATCH_STATUS_LABELS[item.status]}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[100px] truncate">{item.remarks || '-'}</TableCell>
|
||||
<TableCell className="min-w-[100px] truncate">{item.remarks || '-'}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
@@ -296,17 +289,16 @@ export function VehicleDispatchList() {
|
||||
}
|
||||
infoGrid={
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<InfoField label="출고번호" value={item.shipmentNo} />
|
||||
<InfoField label="로트번호" value={item.lotNo || item.shipmentNo} />
|
||||
<InfoField label="수주처" value={item.orderCustomer} />
|
||||
<InfoField label="물류업체" value={item.logisticsCompany} />
|
||||
<InfoField label="톤수" value={item.tonnage} />
|
||||
<InfoField label="공급가액" value={`${formatAmount(item.supplyAmount)}원`} />
|
||||
<InfoField label="합계" value={`${formatAmount(item.totalAmount)}원`} />
|
||||
<InfoField
|
||||
label="선/착불"
|
||||
value={FREIGHT_COST_LABELS[item.freightCostType]}
|
||||
/>
|
||||
<InfoField label="차량번호" value={item.vehicleNo} />
|
||||
<InfoField label="입차일시" value={item.arrivalDateTime} />
|
||||
<InfoField label="작성자" value={item.writer} />
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface VehicleDispatchItem {
|
||||
id: string;
|
||||
dispatchNo: string; // 배차번호
|
||||
shipmentNo: string; // 출고번호
|
||||
lotNo?: string; // 로트번호
|
||||
siteName: string; // 현장명
|
||||
orderCustomer: string; // 수주처
|
||||
logisticsCompany: string; // 물류업체
|
||||
@@ -57,6 +58,7 @@ export interface VehicleDispatchDetail {
|
||||
// 기본 정보
|
||||
dispatchNo: string; // 배차번호
|
||||
shipmentNo: string; // 출고번호
|
||||
lotNo?: string; // 로트번호
|
||||
siteName: string; // 현장명
|
||||
orderCustomer: string; // 수주처
|
||||
freightCostType: FreightCostType; // 운임비용
|
||||
|
||||
Reference in New Issue
Block a user