feat(WEB): 자재/출고/생산/품질/단가 기능 대폭 개선 및 신규 페이지 추가
자재관리: - 입고관리 재고조정 다이얼로그 신규 추가, 상세/목록 기능 확장 - 재고현황 컴포넌트 리팩토링 출고관리: - 출하관리 생성/수정/목록/상세 개선 - 차량배차관리 상세/수정/목록 기능 보강 생산관리: - 작업지시서 WIP 생산 모달 신규 추가 - 벤딩WIP/슬랫조인트바 검사 콘텐츠 신규 추가 - 작업자화면 기능 대폭 확장 (카드/목록 개선) - 검사성적서 모달 개선 품질관리: - 실적보고서 관리 페이지 신규 추가 - 검사관리 문서/타입/목데이터 개선 단가관리: - 단가배포 페이지 및 컴포넌트 신규 추가 - 단가표 관리 페이지 및 컴포넌트 신규 추가 공통: - 권한 시스템 추가 개선 (PermissionContext, usePermission, PermissionGuard) - 메뉴 폴링 훅 개선, 레이아웃 수정 - 모바일 줌/패닝 CSS 수정 - locale 유틸 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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