diff --git a/claudedocs/dev/[REF] all-pages-test-urls.md b/claudedocs/dev/[REF] all-pages-test-urls.md index eaeb7a3a..73dc2b84 100644 --- a/claudedocs/dev/[REF] all-pages-test-urls.md +++ b/claudedocs/dev/[REF] all-pages-test-urls.md @@ -159,9 +159,11 @@ http://localhost:3000/ko/vehicle-management/forklift # ๐Ÿ†• ์ง€๊ฒŒ์ฐจ ๊ด€ | ํŽ˜์ด์ง€ | URL | ์ƒํƒœ | |--------|-----|------| | **์ถœํ•˜ ๋ชฉ๋ก** | `/ko/outbound/shipments` | ๐Ÿ†• NEW | +| **๋ฐฐ์ฐจ์ฐจ๋Ÿ‰ ๋ชฉ๋ก** | `/ko/outbound/vehicle-dispatches` | ๐Ÿ†• NEW | ``` -http://localhost:3000/ko/outbound/shipments # ๐Ÿ†• ์ถœํ•˜๊ด€๋ฆฌ +http://localhost:3000/ko/outbound/shipments # ๐Ÿ†• ์ถœํ•˜๊ด€๋ฆฌ +http://localhost:3000/ko/outbound/vehicle-dispatches # ๐Ÿ†• ๋ฐฐ์ฐจ์ฐจ๋Ÿ‰๊ด€๋ฆฌ ``` --- @@ -372,7 +374,8 @@ http://localhost:3000/ko/quality/inspections # ๐Ÿ†• ๊ฒ€์‚ฌ๊ด€๋ฆฌ ### Outbound ``` -http://localhost:3000/ko/outbound/shipments # ๐Ÿ†• ์ถœํ•˜๊ด€๋ฆฌ +http://localhost:3000/ko/outbound/shipments # ๐Ÿ†• ์ถœํ•˜๊ด€๋ฆฌ +http://localhost:3000/ko/outbound/vehicle-dispatches # ๐Ÿ†• ๋ฐฐ์ฐจ์ฐจ๋Ÿ‰๊ด€๋ฆฌ ``` ### Vehicle Management (์ฐจ๋Ÿ‰/์ง€๊ฒŒ์ฐจ) @@ -488,6 +491,7 @@ http://localhost:3000/ko/dev/editable-table # Editable Table ํ…Œ์ŠคํŠธ // Outbound (์ถœ๊ณ ๊ด€๋ฆฌ) '/outbound/shipments' // ์ถœํ•˜๊ด€๋ฆฌ (๐Ÿ†• NEW) +'/outbound/vehicle-dispatches' // ๋ฐฐ์ฐจ์ฐจ๋Ÿ‰๊ด€๋ฆฌ (๐Ÿ†• NEW) // Vehicle Management (์ฐจ๋Ÿ‰/์ง€๊ฒŒ์ฐจ) '/vehicle-management/vehicle' // ์ฐจ๋Ÿ‰๊ด€๋ฆฌ (๐Ÿ†• NEW) @@ -551,4 +555,4 @@ http://localhost:3000/ko/dev/editable-table # Editable Table ํ…Œ์ŠคํŠธ ## ์ž‘์„ฑ์ผ - ์ตœ์ดˆ ์ž‘์„ฑ: 2025-12-06 -- ์ตœ์ข… ์—…๋ฐ์ดํŠธ: 2026-01-28 (์ฐจ๋Ÿ‰/์ง€๊ฒŒ์ฐจ ๋ฉ”๋‰ด ์ถ”๊ฐ€) +- ์ตœ์ข… ์—…๋ฐ์ดํŠธ: 2026-02-02 (๋ฐฐ์ฐจ์ฐจ๋Ÿ‰๊ด€๋ฆฌ ์ถ”๊ฐ€) diff --git a/src/app/[locale]/(protected)/outbound/vehicle-dispatches/[id]/page.tsx b/src/app/[locale]/(protected)/outbound/vehicle-dispatches/[id]/page.tsx new file mode 100644 index 00000000..27c8bd49 --- /dev/null +++ b/src/app/[locale]/(protected)/outbound/vehicle-dispatches/[id]/page.tsx @@ -0,0 +1,28 @@ +'use client'; + +/** + * ๋ฐฐ์ฐจ์ฐจ๋Ÿ‰๊ด€๋ฆฌ - ์ƒ์„ธ/์ˆ˜์ • ํŽ˜์ด์ง€ + * URL: /outbound/vehicle-dispatches/[id] + * ?mode=edit๋กœ ์ˆ˜์ • ๋ชจ๋“œ ์ „ํ™˜ + */ + +import { use } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { VehicleDispatchDetail, VehicleDispatchEdit } from '@/components/outbound/VehicleDispatchManagement'; + +interface VehicleDispatchDetailPageProps { + params: Promise<{ id: string }>; +} + +export default function VehicleDispatchDetailPage({ params }: VehicleDispatchDetailPageProps) { + const { id } = use(params); + const searchParams = useSearchParams(); + const mode = searchParams.get('mode'); + const isEditMode = mode === 'edit'; + + if (isEditMode) { + return ; + } + + return ; +} diff --git a/src/app/[locale]/(protected)/outbound/vehicle-dispatches/page.tsx b/src/app/[locale]/(protected)/outbound/vehicle-dispatches/page.tsx new file mode 100644 index 00000000..89f5d9f8 --- /dev/null +++ b/src/app/[locale]/(protected)/outbound/vehicle-dispatches/page.tsx @@ -0,0 +1,12 @@ +'use client'; + +/** + * ๋ฐฐ์ฐจ์ฐจ๋Ÿ‰๊ด€๋ฆฌ - ๋ชฉ๋ก ํŽ˜์ด์ง€ + * URL: /outbound/vehicle-dispatches + */ + +import { VehicleDispatchList } from '@/components/outbound/VehicleDispatchManagement'; + +export default function VehicleDispatchesPage() { + return ; +} diff --git a/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx b/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx index 8bb16a60..ed300c2f 100644 --- a/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx +++ b/src/app/[locale]/(protected)/quality/qms/components/InspectionModalV2.tsx @@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'; import { AlertCircle, Loader2 } from 'lucide-react'; import { DocumentViewer } from '@/components/document-system'; import { Document, DocumentItem } from '../types'; -import { MOCK_ORDER_DATA, MOCK_WORK_ORDER, MOCK_SHIPMENT_DETAIL } from '../mockData'; +import { MOCK_ORDER_DATA, MOCK_SHIPMENT_DETAIL } from '../mockData'; // ๊ธฐ์กด ๋ฌธ์„œ ์ปดํฌ๋„ŒํŠธ import import { DeliveryConfirmation } from '@/components/outbound/ShipmentManagement/documents/DeliveryConfirmation'; @@ -14,14 +14,22 @@ import { ShippingSlip } from '@/components/outbound/ShipmentManagement/documents import { ImportInspectionDocument, ProductInspectionDocument, - ScreenInspectionDocument, - BendingInspectionDocument, - SlatInspectionDocument, JointbarInspectionDocument, QualityDocumentUploader, } from './documents'; import type { ImportInspectionTemplate } from './documents/ImportInspectionDocument'; +// ์ž‘์—…์ผ์ง€ + ์ค‘๊ฐ„๊ฒ€์‚ฌ ์„ฑ์ ์„œ ๋ฌธ์„œ ์ปดํฌ๋„ŒํŠธ import (๊ณต์ •๋ณ„ ์‹ ๊ทœ ๋ฒ„์ „) +import { + ScreenWorkLogContent, + SlatWorkLogContent, + BendingWorkLogContent, + ScreenInspectionContent, + SlatInspectionContent, + BendingInspectionContent, +} from '@/components/production/WorkOrders/documents'; +import type { WorkOrder } from '@/components/production/WorkOrders/types'; + // ๊ฒ€์‚ฌ ํ…œํ”Œ๋ฆฟ API import { getInspectionTemplate } from '@/components/material/ReceivingManagement/actions'; @@ -203,128 +211,43 @@ const OrderDocument = () => { ); }; -// ์ž‘์—…์ผ์ง€ ๋ฌธ์„œ ์ปดํฌ๋„ŒํŠธ (๊ฐ„์†Œํ™” ๋ฒ„์ „) -const WorkLogDocument = () => { - const order = MOCK_WORK_ORDER; - const today = new Date().toLocaleDateString('ko-KR').replace(/\. /g, '-').replace('.', ''); - const documentNo = `WL-${order.processCode.toUpperCase().slice(0, 3)}`; - const lotNo = `KD-TS-${new Date().toISOString().slice(2, 10).replace(/-/g, '')}-01`; - - const items = [ - { no: 1, name: order.productName, location: '1์ธต/A-01', spec: '3000ร—2500', qty: 1, status: '์™„๋ฃŒ' }, - { no: 2, name: order.productName, location: '2์ธต/A-02', spec: '3000ร—2500', qty: 1, status: '์ž‘์—…์ค‘' }, - { no: 3, name: order.productName, location: '3์ธต/A-03', spec: '-', qty: 1, status: '๋Œ€๊ธฐ' }, - ]; - - return ( -
- {/* ํ—ค๋” */} -
-
- KD - ๊ฒฝ๋™๊ธฐ์—… -
-
-

์ž‘ ์—… ์ผ ์ง€

-

{documentNo}

-

์Šคํฌ๋ฆฐ ์ƒ์‚ฐ๋ถ€์„œ

-
- - - - - - - - - - - - - - - - - - - -
-
๊ฒฐ์žฌ
-
์ž‘์„ฑ๊ฒ€ํ† ์Šน์ธ
-
{order.assignees[0] || '-'}
-
ํŒ๋งค์ƒ์‚ฐํ’ˆ์งˆ
-
- - {/* ๊ธฐ๋ณธ ์ •๋ณด */} -
-
-
-
๋ฐœ์ฃผ์ฒ˜
-
{order.client}
-
-
-
ํ˜„์žฅ๋ช…
-
{order.projectName}
-
-
-
-
-
์ž‘์—…์ผ์ž
-
{today}
-
-
-
LOT NO.
-
{lotNo}
-
-
-
-
-
๋‚ฉ๊ธฐ์ผ
-
{order.dueDate}
-
-
-
์ง€์‹œ์ˆ˜๋Ÿ‰
-
{order.quantity} EA
-
-
-
- - {/* ํ’ˆ๋ชฉ ํ…Œ์ด๋ธ” */} -
-
-
No
-
ํ’ˆ๋ชฉ๋ช…
-
์ถœ/๋ถ€ํ˜ธ
-
๊ทœ๊ฒฉ
-
์ˆ˜๋Ÿ‰
-
์ƒํƒœ
-
- {items.map((item, index) => ( -
-
{item.no}
-
{item.name}
-
{item.location}
-
{item.spec}
-
{item.qty}
-
- {item.status} -
-
- ))} -
- - {/* ํŠน์ด์‚ฌํ•ญ */} -
-
ํŠน์ด์‚ฌํ•ญ
-
{order.instruction || '-'}
-
-
- ); -}; +// QMS์šฉ ์ž‘์—…์ผ์ง€ Mock WorkOrder ์ƒ์„ฑ +const createQmsMockWorkOrder = (subType?: string): WorkOrder => ({ + id: 'qms-wo-1', + workOrderNo: 'KD-WO-240924-01', + lotNo: 'KD-SS-240924-19', + processId: 1, + processName: subType === 'slat' ? '์Šฌ๋žซ' : subType === 'bending' ? '์ ˆ๊ณก' : '์Šคํฌ๋ฆฐ', + processCode: subType || 'screen', + processType: (subType || 'screen') as 'screen' | 'slat' | 'bending', + status: 'in_progress', + client: '์‚ผ์„ฑ๋ฌผ์‚ฐ(์ฃผ)', + projectName: '๊ฐ•๋‚จ ์•„ํŒŒํŠธ ๋‹จ์ง€', + dueDate: '2024-10-05', + assignee: '๊น€์ž‘์—…', + assignees: [ + { id: '1', name: '๊น€์ž‘์—…', isPrimary: true }, + { id: '2', name: '์ด์ƒ์‚ฐ', isPrimary: false }, + ], + orderDate: '2024-09-20', + scheduledDate: '2024-09-24', + shipmentDate: '2024-10-04', + salesOrderDate: '2024-09-18', + isAssigned: true, + isStarted: true, + priority: 3, + priorityLabel: '๊ธด๊ธ‰', + shutterCount: 5, + department: '์ƒ์‚ฐ๋ถ€', + items: [ + { id: '1', no: 1, status: 'completed', productName: '์™€์ด์–ด ์Šคํฌ๋ฆฐ', floorCode: '1์ธต/FSS-01', specification: '3000ร—2500', quantity: 2, unit: 'EA' }, + { id: '2', no: 2, status: 'in_progress', productName: '๋ฉ”์‰ฌ ์Šคํฌ๋ฆฐ', floorCode: '2์ธต/FSS-03', specification: '3000ร—2500', quantity: 3, unit: 'EA' }, + { id: '3', no: 3, status: 'waiting', productName: '๊ด‘ํญ ์™€์ด์–ด', floorCode: '3์ธต/FSS-05', specification: '12000ร—4500', quantity: 1, unit: 'EA' }, + ], + currentStep: 2, + issues: [], + note: 'ํ’ˆ์งˆ ๊ฒ€์ˆ˜ ์ฒ ์ €ํžˆ ์ง„ํ–‰', +}); // ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ const LoadingDocument = () => ( @@ -426,20 +349,39 @@ export const InspectionModalV2 = ({ console.log('[InspectionModalV2] ํ’ˆ์งˆ๊ด€๋ฆฌ์„œ PDF ์‚ญ์ œ'); }; - // ์ค‘๊ฐ„๊ฒ€์‚ฌ ์„ฑ์ ์„œ ์„œ๋ธŒํƒ€์ž…์— ๋”ฐ๋ฅธ ๋ Œ๋”๋ง - const renderReportDocument = () => { + // ์ž‘์—…์ผ์ง€ ๊ณต์ •๋ณ„ ๋ Œ๋”๋ง + const renderWorkLogDocument = () => { const subType = documentItem?.subType; + const mockOrder = createQmsMockWorkOrder(subType); + switch (subType) { case 'screen': - return ; - case 'bending': - return ; + return ; case 'slat': - return ; + return ; + case 'bending': + return ; + default: + // subType ๋ฏธ์ง€์ • ์‹œ ์Šคํฌ๋ฆฐ ๊ธฐ๋ณธ + return ; + } + }; + + // ์ค‘๊ฐ„๊ฒ€์‚ฌ ์„ฑ์ ์„œ ์„œ๋ธŒํƒ€์ž…์— ๋”ฐ๋ฅธ ๋ Œ๋”๋ง (์‹ ๊ทœ ๋ฒ„์ „ ํ†ต์ผ) + const renderReportDocument = () => { + const subType = documentItem?.subType; + const mockOrder = createQmsMockWorkOrder(subType || 'screen'); + switch (subType) { + case 'screen': + return ; + case 'bending': + return ; + case 'slat': + return ; case 'jointbar': return ; default: - return ; + return ; } }; @@ -463,7 +405,7 @@ export const InspectionModalV2 = ({ case 'order': return ; case 'log': - return ; + return renderWorkLogDocument(); case 'confirmation': return ; case 'shipping': diff --git a/src/app/[locale]/(protected)/quality/qms/mockData.ts b/src/app/[locale]/(protected)/quality/qms/mockData.ts index 172ee9c0..e209947f 100644 --- a/src/app/[locale]/(protected)/quality/qms/mockData.ts +++ b/src/app/[locale]/(protected)/quality/qms/mockData.ts @@ -247,10 +247,11 @@ export const MOCK_DOCUMENTS: Record = { id: 'doc-3', type: 'log', title: '์ž‘์—…์ผ์ง€', - count: 2, + count: 3, items: [ - { id: 'doc-3-1', title: '์ƒ์‚ฐ ์ž‘์—…์ผ์ง€', date: '2024-09-25', code: 'WL-2024-0925' }, - { id: 'doc-3-2', title: 'ํ›„๊ฐ€๊ณต ์ž‘์—…์ผ์ง€', date: '2024-09-26', code: 'WL-2024-0926' }, + { id: 'doc-3-1', title: '์Šคํฌ๋ฆฐ ์ž‘์—…์ผ์ง€', date: '2024-09-25', code: 'WL-2024-0925', subType: 'screen' as const }, + { id: 'doc-3-2', title: '์Šฌ๋žซ ์ž‘์—…์ผ์ง€', date: '2024-09-26', code: 'WL-2024-0926', subType: 'slat' as const }, + { id: 'doc-3-3', title: '์ ˆ๊ณก ์ž‘์—…์ผ์ง€', date: '2024-09-27', code: 'WL-2024-0927', subType: 'bending' as const }, ], }, { diff --git a/src/app/[locale]/(protected)/quality/qms/types.ts b/src/app/[locale]/(protected)/quality/qms/types.ts index d3da2363..6d33f08d 100644 --- a/src/app/[locale]/(protected)/quality/qms/types.ts +++ b/src/app/[locale]/(protected)/quality/qms/types.ts @@ -40,7 +40,7 @@ export interface DocumentItem { title: string; date: string; code?: string; - // ์ค‘๊ฐ„๊ฒ€์‚ฌ ์„ฑ์ ์„œ ์„œ๋ธŒํƒ€์ž… (report ํƒ€์ž…์ผ ๋•Œ๋งŒ ์‚ฌ์šฉ) + // ์ค‘๊ฐ„๊ฒ€์‚ฌ ์„ฑ์ ์„œ ๋ฐ ์ž‘์—…์ผ์ง€ ์„œ๋ธŒํƒ€์ž… (report, log ํƒ€์ž…์—์„œ ์‚ฌ์šฉ) subType?: 'screen' | 'bending' | 'slat' | 'jointbar'; } diff --git a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx index adce1dea..1724d3f2 100644 --- a/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx +++ b/src/app/[locale]/(protected)/sales/order-management-sales/[id]/page.tsx @@ -754,7 +754,7 @@ export default function OrderDetailPage() { // ๊ฒฌ์  ์ˆ˜์ • ํ•ธ๋“ค๋Ÿฌ const handleEditQuote = () => { if (order?.quoteId) { - router.push(`/sales/quotes/${order.quoteId}?mode=edit`); + router.push(`/sales/quote-management/${order.quoteId}?mode=edit`); } }; diff --git a/src/components/auth/LoginPage.tsx b/src/components/auth/LoginPage.tsx index 1b7eb1f2..dece0bf6 100644 --- a/src/components/auth/LoginPage.tsx +++ b/src/components/auth/LoginPage.tsx @@ -209,7 +209,7 @@ export function LoginPage() {

{t('login')}

-

{tCommon('welcome')} SAM MES

+

{tCommon('welcome')} SAM ERP/MES

{error && ( diff --git a/src/components/common/ScheduleCalendar/CalendarHeader.tsx b/src/components/common/ScheduleCalendar/CalendarHeader.tsx index 35c148bb..e2827e48 100644 --- a/src/components/common/ScheduleCalendar/CalendarHeader.tsx +++ b/src/components/common/ScheduleCalendar/CalendarHeader.tsx @@ -20,8 +20,9 @@ export function CalendarHeader({ onViewChange, titleSlot, filterSlot, + availableViews, }: CalendarHeaderProps) { - const views: { value: CalendarView; label: string }[] = [ + const views: { value: CalendarView; label: string }[] = availableViews || [ { value: 'week', label: '์ฃผ' }, { value: 'month', label: '์›”' }, ]; @@ -84,9 +85,11 @@ export function CalendarHeader({
{/* 2์ค„: ๋ทฐ ์ „ํ™˜ ํƒญ */} -
- {renderViewTabs()} -
+ {views.length > 1 && ( +
+ {renderViewTabs()} +
+ )} {/* PC: ํƒ€์ดํ‹€ + ๋„ค๋น„๊ฒŒ์ด์…˜ | ๋ทฐ์ „ํ™˜ + ํ•„ํ„ฐ (ํ•œ ์ค„) */} @@ -125,7 +128,7 @@ export function CalendarHeader({ {/* ์šฐ์ธก: ๋ทฐ ์ „ํ™˜ + ํ•„ํ„ฐ */}
- {renderViewTabs('px-4 py-1.5')} + {views.length > 1 && renderViewTabs('px-4 py-1.5')} {filterSlot &&
{filterSlot}
}
diff --git a/src/components/common/ScheduleCalendar/ScheduleCalendar.tsx b/src/components/common/ScheduleCalendar/ScheduleCalendar.tsx index 96518c2d..cd7f74eb 100644 --- a/src/components/common/ScheduleCalendar/ScheduleCalendar.tsx +++ b/src/components/common/ScheduleCalendar/ScheduleCalendar.tsx @@ -5,6 +5,7 @@ import { cn } from '@/components/ui/utils'; import { CalendarHeader } from './CalendarHeader'; import { MonthView } from './MonthView'; import { WeekView } from './WeekView'; +import { WeekTimeView } from './WeekTimeView'; import type { ScheduleCalendarProps, CalendarView } from './types'; import { getNextMonth, getPrevMonth } from './utils'; @@ -44,6 +45,8 @@ export function ScheduleCalendar({ weekStartsOn = 0, isLoading = false, className, + availableViews, + timeRange, }: ScheduleCalendarProps) { // ๋‚ด๋ถ€ ์ƒํƒœ (controlled/uncontrolled ์ง€์›) const [internalDate, setInternalDate] = useState(() => new Date()); @@ -118,6 +121,7 @@ export function ScheduleCalendar({ onViewChange={handleViewChange} titleSlot={titleSlot} filterSlot={filterSlot} + availableViews={availableViews} /> {/* ๋ณธ๋ฌธ */} @@ -126,6 +130,16 @@ export function ScheduleCalendar({
+ ) : view === 'week-time' ? ( + ) : view === 'month' ? ( getWeekDays(currentDate, weekStartsOn), [currentDate, weekStartsOn]); + const weekdayHeaders = useMemo(() => getWeekdayHeaders(weekStartsOn), [weekStartsOn]); + + // ์‹œ๊ฐ„ ์Šฌ๋กฏ ์ƒ์„ฑ (AM ์‹œ๊ฐ„๋Œ€) + const timeSlots = useMemo(() => { + const slots: { hour: number; label: string }[] = []; + for (let h = timeRange.start; h <= timeRange.end; h++) { + slots.push({ + hour: h, + label: `AM ${h}์‹œ`, + }); + } + return slots; + }, [timeRange]); + + // ์ด๋ฒคํŠธ๋ฅผ ๋‚ ์งœ๋ณ„๋กœ ๋ถ„๋ฅ˜ + const eventsByDate = useMemo(() => { + const map = new Map(); + + weekDays.forEach((day) => { + const dateStr = formatDate(day, 'yyyy-MM-dd'); + map.set(dateStr, { allDay: [], timed: [] }); + }); + + events.forEach((event) => { + const eventStartDate = parseISO(event.startDate); + const eventEndDate = parseISO(event.endDate); + + weekDays.forEach((day) => { + const dateStr = formatDate(day, 'yyyy-MM-dd'); + // ์ด๋ฒคํŠธ๊ฐ€ ์ด ๋‚ ์งœ๋ฅผ ํฌํ•จํ•˜๋Š”์ง€ ํ™•์ธ + if (day >= eventStartDate && day <= eventEndDate) { + const bucket = map.get(dateStr); + if (bucket) { + if (event.startTime) { + bucket.timed.push(event); + } else { + bucket.allDay.push(event); + } + } + } + }); + }); + + return map; + }, [events, weekDays]); + + // all-day ์ด๋ฒคํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ + const hasAllDayEvents = useMemo(() => { + for (const [, bucket] of eventsByDate) { + if (bucket.allDay.length > 0) return true; + } + return false; + }, [eventsByDate]); + + // ์‹œ๊ฐ„ ๋ฌธ์ž์—ด์—์„œ hour ์ถ”์ถœ + const getHourFromTime = (time: string): number => { + const [hours] = time.split(':').map(Number); + return hours; + }; + + // ์ด๋ฒคํŠธ ์ƒ‰์ƒ ํด๋ž˜์Šค + const getEventColorClasses = (event: ScheduleEvent): string => { + const color = event.color || 'blue'; + return EVENT_COLORS[color] || EVENT_COLORS.blue; + }; + + return ( +
+
+ {/* ํ—ค๋”: ์‹œ๊ฐ„๋ผ๋ฒจ์ปฌ๋Ÿผ + ์š”์ผ/๋‚ ์งœ */} +
+ {/* ๋นˆ ์ฝ”๋„ˆ์…€ */} +
+ {/* ์š”์ผ + ๋‚ ์งœ */} + {weekDays.map((day, i) => { + const today = checkIsToday(day); + const selected = isSameDate(selectedDate, day); + return ( +
onDateClick(day)} + > +
+ {weekdayHeaders[i]} +
+
+ {format(day, 'd')} +
+
+ ); + })} +
+ + {/* All-day ์˜์—ญ */} + {hasAllDayEvents && ( +
+
+ ์ข…์ผ +
+ {weekDays.map((day, i) => { + const dateStr = formatDate(day, 'yyyy-MM-dd'); + const allDayEvents = eventsByDate.get(dateStr)?.allDay || []; + return ( +
+ {allDayEvents.map((event) => ( +
onEventClick(event)} + > + {event.title} +
+ ))} +
+ ); + })} +
+ )} + + {/* ์‹œ๊ฐ„ ๊ทธ๋ฆฌ๋“œ */} + {timeSlots.map((slot) => ( +
+ {/* ์‹œ๊ฐ„ ๋ผ๋ฒจ */} +
+ + {slot.label} + +
+ {/* ์š”์ผ๋ณ„ ์…€ */} + {weekDays.map((day, i) => { + const dateStr = formatDate(day, 'yyyy-MM-dd'); + const timedEvents = eventsByDate.get(dateStr)?.timed || []; + const slotEvents = timedEvents.filter((event) => { + if (!event.startTime) return false; + const eventHour = getHourFromTime(event.startTime); + return eventHour === slot.hour; + }); + + const today = checkIsToday(day); + + return ( +
+ {slotEvents.map((event) => ( +
onEventClick(event)} + > + {event.title} +
+ ))} +
+ ); + })} +
+ ))} +
+
+ ); +} diff --git a/src/components/common/ScheduleCalendar/index.ts b/src/components/common/ScheduleCalendar/index.ts index c912d611..d3eee941 100644 --- a/src/components/common/ScheduleCalendar/index.ts +++ b/src/components/common/ScheduleCalendar/index.ts @@ -8,6 +8,7 @@ export { ScheduleCalendar } from './ScheduleCalendar'; export { CalendarHeader } from './CalendarHeader'; export { MonthView } from './MonthView'; export { WeekView } from './WeekView'; +export { WeekTimeView } from './WeekTimeView'; export { DayCell } from './DayCell'; export { ScheduleBar } from './ScheduleBar'; export { MorePopover } from './MorePopover'; @@ -23,6 +24,7 @@ export type { MorePopoverProps, MonthViewProps, WeekViewProps, + WeekTimeViewProps, } from './types'; export { EVENT_COLORS, BADGE_COLORS } from './types'; diff --git a/src/components/common/ScheduleCalendar/types.ts b/src/components/common/ScheduleCalendar/types.ts index 987d5763..07e27d9e 100644 --- a/src/components/common/ScheduleCalendar/types.ts +++ b/src/components/common/ScheduleCalendar/types.ts @@ -5,7 +5,7 @@ /** * ๋‹ฌ๋ ฅ ๋ทฐ ๋ชจ๋“œ */ -export type CalendarView = 'week' | 'month'; +export type CalendarView = 'week' | 'month' | 'week-time'; /** * ์ผ์ • ์ด๋ฒคํŠธ @@ -23,6 +23,10 @@ export interface ScheduleEvent { color?: string; /** ์ด๋ฒคํŠธ ์ƒํƒœ */ status?: string; + /** ์‹œ์ž‘ ์‹œ๊ฐ„ (HH:mm ํ˜•์‹, week-time ๋ทฐ์šฉ) */ + startTime?: string; + /** ์ข…๋ฃŒ ์‹œ๊ฐ„ (HH:mm ํ˜•์‹, week-time ๋ทฐ์šฉ) */ + endTime?: string; /** ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ */ data?: unknown; } @@ -73,6 +77,10 @@ export interface ScheduleCalendarProps { isLoading?: boolean; /** ์ถ”๊ฐ€ ํด๋ž˜์Šค */ className?: string; + /** ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ทฐ ๋ชฉ๋ก (๊ธฐ๋ณธ: ์ฃผ/์›”) */ + availableViews?: { value: CalendarView; label: string }[]; + /** ์‹œ๊ฐ„์ถ• ๋ทฐ ์‹œ๊ฐ„ ๋ฒ”์œ„ (๊ธฐ๋ณธ: 1~12) */ + timeRange?: { start: number; end: number }; } /** @@ -87,6 +95,8 @@ export interface CalendarHeaderProps { /** ํƒ€์ดํ‹€ ์˜์—ญ (๋…„์›” ๋„ค๋น„๊ฒŒ์ด์…˜ ์™ผ์ชฝ) */ titleSlot?: React.ReactNode; filterSlot?: React.ReactNode; + /** ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ทฐ ๋ชฉ๋ก (๊ธฐ๋ณธ: [{value:'week',label:'์ฃผ'},{value:'month',label:'์›”'}]) */ + availableViews?: { value: CalendarView; label: string }[]; } /** @@ -146,6 +156,20 @@ export interface MonthViewProps { onEventClick: (event: ScheduleEvent) => void; } +/** + * ์‹œ๊ฐ„์ถ• ์ฃผ๊ฐ„ ๋ทฐ Props (week-time) + */ +export interface WeekTimeViewProps { + currentDate: Date; + events: ScheduleEvent[]; + selectedDate: Date | null; + weekStartsOn: 0 | 1; + /** ํ‘œ์‹œํ•  ์‹œ๊ฐ„ ๋ฒ”์œ„ (๊ธฐ๋ณธ: 1~12) */ + timeRange?: { start: number; end: number }; + onDateClick: (date: Date) => void; + onEventClick: (event: ScheduleEvent) => void; +} + /** * ์ฃผ๊ฐ„ ๋ทฐ Props */ diff --git a/src/components/document-system/components/ConstructionApprovalTable.tsx b/src/components/document-system/components/ConstructionApprovalTable.tsx index 5df7652d..ee06b46e 100644 --- a/src/components/document-system/components/ConstructionApprovalTable.tsx +++ b/src/components/document-system/components/ConstructionApprovalTable.tsx @@ -54,58 +54,58 @@ export function ConstructionApprovalTable({ }; return ( - +
{/* ํ—ค๋” ํ–‰ */} - - - - {/* ์ด๋ฆ„ ํ–‰ */} - - - - {/* ๋ถ€์„œ ํ–‰ */} - - - - diff --git a/src/components/orders/documents/SalesOrderDocument.tsx b/src/components/orders/documents/SalesOrderDocument.tsx index f571ef30..a45bf417 100644 --- a/src/components/orders/documents/SalesOrderDocument.tsx +++ b/src/components/orders/documents/SalesOrderDocument.tsx @@ -11,6 +11,7 @@ import { getTodayString } from "@/utils/date"; import { OrderItem } from "../actions"; import { ProductInfo } from "./OrderDocumentModal"; +import { ConstructionApprovalTable } from "@/components/document-system"; interface SalesOrderDocumentProps { documentNumber?: string; @@ -130,36 +131,9 @@ export function SalesOrderDocument({ {/* ๊ฒฐ์žฌ๋ž€ (์šฐ์ธก) */} -
๊ฒฐ
์žฌ
+ {labels.writer} + {labels.approver1} + {labels.approver2} + {labels.approver3}
+ {approvers.writer?.name || ''} + {approvers.approver1?.name || ''} + {approvers.approver2?.name || ''} + {approvers.approver3?.name || ''}
+ {approvers.writer?.department || '๋ถ€์„œ๋ช…'} + {approvers.approver1?.department || '๋ถ€์„œ๋ช…'} + {approvers.approver2?.department || '๋ถ€์„œ๋ช…'} + {approvers.approver3?.department || '๋ถ€์„œ๋ช…'}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
์ž‘์„ฑ์Šน์ธ์Šน์ธ์Šน์ธ
๊ณผ์žฅ
ํ™๊ธธ๋™์ด๋ฆ„์ด๋ฆ„์ด๋ฆ„
๋ถ€์„œ๋ช…๋ถ€์„œ๋ช…๋ถ€์„œ๋ช…๋ถ€์„œ๋ช…
+
{/* ์ƒํ’ˆ๋ช… / ์ œํ’ˆ๋ช… / ๋กœํŠธ๋ฒˆํ˜ธ / ์ธ์ •๋ฒˆํ˜ธ */} diff --git a/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx b/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx index 8ef7dfb5..8d1fa265 100644 --- a/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx +++ b/src/components/outbound/ShipmentManagement/ShipmentCreate.tsx @@ -1,19 +1,28 @@ 'use client'; /** - * ์ถœํ•˜ ๋“ฑ๋ก ํŽ˜์ด์ง€ - * API ์—ฐ๋™ ์™„๋ฃŒ (2025-12-26) - * IntegratedDetailTemplate ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (2025-01-20) + * ์ถœ๊ณ  ๋“ฑ๋ก ํŽ˜์ด์ง€ + * 4๊ฐœ ์„น์…˜ ๊ตฌ์กฐ: ๊ธฐ๋ณธ์ •๋ณด, ์ˆ˜์ฃผ/๋ฐฐ์†ก์ •๋ณด, ๋ฐฐ์ฐจ์ •๋ณด, ์ œํ’ˆ๋‚ด์šฉ */ import { useState, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; +import { Plus, X as XIcon, ChevronDown, Search } from 'lucide-react'; import { getTodayString } from '@/utils/date'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; import { Select, SelectContent, @@ -21,6 +30,18 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '@/components/ui/accordion'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { shipmentCreateConfig } from './shipmentConfig'; import { @@ -29,32 +50,57 @@ import { getLogisticsOptions, getVehicleTonnageOptions, } from './actions'; +import { + FREIGHT_COST_LABELS, + DELIVERY_METHOD_LABELS, +} from './types'; import type { ShipmentCreateFormData, - ShipmentPriority, DeliveryMethod, + FreightCostType, + VehicleDispatch, LotOption, LogisticsOption, VehicleTonnageOption, + ProductGroup, + ProductPart, } from './types'; import { isNextRedirectError } from '@/lib/utils/redirect-error'; import { toast } from 'sonner'; +import { useDaumPostcode } from '@/hooks/useDaumPostcode'; import { useDevFill } from '@/components/dev'; import { generateShipmentData } from '@/components/dev/generators/shipmentData'; +import { mockProductGroups, mockOtherParts } from './mockData'; -// ๊ณ ์ • ์˜ต์…˜ (ํด๋ผ์ด์–ธํŠธ์—์„œ ๊ด€๋ฆฌ) -const priorityOptions: { value: ShipmentPriority; label: string }[] = [ - { value: 'urgent', label: '๊ธด๊ธ‰' }, - { value: 'normal', label: '์ผ๋ฐ˜' }, - { value: 'low', label: '๋‚ฎ์Œ' }, -]; - +// ๋ฐฐ์†ก๋ฐฉ์‹ ์˜ต์…˜ const deliveryMethodOptions: { value: DeliveryMethod; label: string }[] = [ - { value: 'pickup', label: '์ƒ์ฐจ (๋ฌผ๋ฅ˜์—…์ฒด)' }, - { value: 'direct', label: '์ง์ ‘๋ฐฐ์†ก (์ž์ฒด)' }, - { value: 'logistics', label: '๋ฌผ๋ฅ˜์‚ฌ' }, + { value: 'direct_dispatch', label: '์ง์ ‘๋ฐฐ์ฐจ' }, + { value: 'loading', label: '์ƒ์ฐจ' }, + { value: 'kyungdong_delivery', label: '๊ฒฝ๋™ํƒ๋ฐฐ' }, + { value: 'daesin_delivery', label: '๋Œ€์‹ ํƒ๋ฐฐ' }, + { value: 'kyungdong_freight', label: '๊ฒฝ๋™ํ™”๋ฌผ' }, + { value: 'daesin_freight', label: '๋Œ€์‹ ํ™”๋ฌผ' }, + { value: 'self_pickup', label: '์ง์ ‘์ˆ˜๋ น' }, ]; +// ์šด์ž„๋น„์šฉ ์˜ต์…˜ +const freightCostOptions: { value: FreightCostType; label: string }[] = Object.entries( + FREIGHT_COST_LABELS +).map(([value, label]) => ({ value: value as FreightCostType, label })); + +// ๋นˆ ๋ฐฐ์ฐจ ํ–‰ ์ƒ์„ฑ +function createEmptyDispatch(): VehicleDispatch { + return { + id: `vd-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, + logisticsCompany: '', + arrivalDateTime: '', + tonnage: '', + vehicleNo: '', + driverContact: '', + remarks: '', + }; +} + export function ShipmentCreate() { const router = useRouter(); @@ -63,7 +109,15 @@ export function ShipmentCreate() { lotNo: '', scheduledDate: getTodayString(), priority: 'normal', - deliveryMethod: 'pickup', + deliveryMethod: 'direct_dispatch', + shipmentDate: '', + freightCost: undefined, + receiver: '', + receiverContact: '', + zipCode: '', + address: '', + addressDetail: '', + vehicleDispatches: [createEmptyDispatch()], logisticsCompany: '', vehicleTonnage: '', loadingTime: '', @@ -76,12 +130,30 @@ export function ShipmentCreate() { const [logisticsOptions, setLogisticsOptions] = useState([]); const [vehicleTonnageOptions, setVehicleTonnageOptions] = useState([]); + // ์ œํ’ˆ ๋ฐ์ดํ„ฐ (LOT ์„ ํƒ ์‹œ ํ‘œ์‹œ) + const [productGroups, setProductGroups] = useState([]); + const [otherParts, setOtherParts] = useState([]); + // ๋กœ๋”ฉ/์—๋Ÿฌ ์ƒํƒœ const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [validationErrors, setValidationErrors] = useState([]); + // ์•„์ฝ”๋””์–ธ ์ƒํƒœ + const [accordionValue, setAccordionValue] = useState([]); + + // ์šฐํŽธ๋ฒˆํ˜ธ ์ฐพ๊ธฐ + const { openPostcode } = useDaumPostcode({ + onComplete: (result) => { + setFormData(prev => ({ + ...prev, + zipCode: result.zonecode, + address: result.address, + })); + }, + }); + // ์˜ต์…˜ ๋ฐ์ดํ„ฐ ๋กœ๋“œ const loadOptions = useCallback(async () => { setIsLoading(true); @@ -97,11 +169,9 @@ export function ShipmentCreate() { if (lotsResult.success && lotsResult.data) { setLotOptions(lotsResult.data); } - if (logisticsResult.success && logisticsResult.data) { setLogisticsOptions(logisticsResult.data); } - if (tonnageResult.success && tonnageResult.data) { setVehicleTonnageOptions(tonnageResult.data); } @@ -114,7 +184,6 @@ export function ShipmentCreate() { } }, []); - // ์˜ต์…˜ ๋กœ๋“œ useEffect(() => { loadOptions(); }, [loadOptions]); @@ -123,85 +192,108 @@ export function ShipmentCreate() { useDevFill( 'shipment', useCallback(() => { - // lotOptions๋ฅผ generateShipmentData์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ๋ณ€ํ™˜ const lotOptionsForGenerator = lotOptions.map(o => ({ lotNo: o.value, customerName: o.customerName, siteName: o.siteName, })); - const logisticsOptionsForGenerator = logisticsOptions.map(o => ({ id: o.value, name: o.label, })); - const tonnageOptionsForGenerator = vehicleTonnageOptions.map(o => ({ value: o.value, label: o.label, })); - const sampleData = generateShipmentData({ lotOptions: lotOptionsForGenerator as unknown as LotOption[], logisticsOptions: logisticsOptionsForGenerator as unknown as LogisticsOption[], tonnageOptions: tonnageOptionsForGenerator, }); - - setFormData(sampleData); - toast.success('[Dev] ์ถœํ•˜ ํผ์ด ์ž๋™์œผ๋กœ ์ฑ„์›Œ์กŒ์Šต๋‹ˆ๋‹ค.'); + setFormData(prev => ({ ...prev, ...sampleData })); + toast.success('[Dev] ์ถœ๊ณ  ํผ์ด ์ž๋™์œผ๋กœ ์ฑ„์›Œ์กŒ์Šต๋‹ˆ๋‹ค.'); }, [lotOptions, logisticsOptions, vehicleTonnageOptions]) ); + // LOT ์„ ํƒ ์‹œ ํ˜„์žฅ๋ช…/์ˆ˜์ฃผ์ฒ˜ ์ž๋™ ๋งคํ•‘ + ๋ชฉ๋ฐ์ดํ„ฐ ์ œํ’ˆ ํ‘œ์‹œ + const handleLotChange = useCallback((lotNo: string) => { + setFormData(prev => ({ ...prev, lotNo })); + if (lotNo) { + // ๋ชฉ๋ฐ์ดํ„ฐ๋กœ ์ œํ’ˆ ๊ทธ๋ฃน ํ‘œ์‹œ + setProductGroups(mockProductGroups); + setOtherParts(mockOtherParts); + } else { + setProductGroups([]); + setOtherParts([]); + } + if (validationErrors.length > 0) setValidationErrors([]); + }, [validationErrors]); + // ํผ ์ž…๋ ฅ ํ•ธ๋“ค๋Ÿฌ const handleInputChange = (field: keyof ShipmentCreateFormData, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); - // ์ž…๋ ฅ ์‹œ ์—๋Ÿฌ ํด๋ฆฌ์–ด - if (validationErrors.length > 0) { - setValidationErrors([]); - } + if (validationErrors.length > 0) setValidationErrors([]); }; - // ์ทจ์†Œ + // ๋ฐฐ์ฐจ ์ •๋ณด ํ•ธ๋“ค๋Ÿฌ + const handleDispatchChange = (index: number, field: keyof VehicleDispatch, value: string) => { + setFormData(prev => { + const newDispatches = [...prev.vehicleDispatches]; + newDispatches[index] = { ...newDispatches[index], [field]: value }; + return { ...prev, vehicleDispatches: newDispatches }; + }); + }; + + const handleAddDispatch = () => { + setFormData(prev => ({ + ...prev, + vehicleDispatches: [...prev.vehicleDispatches, createEmptyDispatch()], + })); + }; + + const handleRemoveDispatch = (index: number) => { + setFormData(prev => ({ + ...prev, + vehicleDispatches: prev.vehicleDispatches.filter((_, i) => i !== index), + })); + }; + + // ์•„์ฝ”๋””์–ธ ์ œ์–ด + const handleExpandAll = useCallback(() => { + const allIds = [ + ...productGroups.map(g => g.id), + ...(otherParts.length > 0 ? ['other-parts'] : []), + ]; + setAccordionValue(allIds); + }, [productGroups, otherParts]); + + const handleCollapseAll = useCallback(() => { + setAccordionValue([]); + }, []); + const handleCancel = useCallback(() => { router.push('/ko/outbound/shipments'); }, [router]); - // validation ์ฒดํฌ const validateForm = (): boolean => { const errors: string[] = []; - - // ํ•„์ˆ˜ ํ•„๋“œ ์ฒดํฌ - if (!formData.lotNo) { - errors.push('๋กœํŠธ๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜ ์„ ํƒ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); - } - if (!formData.scheduledDate) { - errors.push('์ถœ๊ณ ์˜ˆ์ •์ผ์€ ํ•„์ˆ˜ ์ž…๋ ฅ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); - } - if (!formData.priority) { - errors.push('์ถœ๊ณ  ์šฐ์„ ์ˆœ์œ„๋Š” ํ•„์ˆ˜ ์„ ํƒ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); - } - if (!formData.deliveryMethod) { - errors.push('๋ฐฐ์†ก๋ฐฉ์‹์€ ํ•„์ˆ˜ ์„ ํƒ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); - } - + if (!formData.lotNo) errors.push('๋กœํŠธ๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜ ์„ ํƒ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); + if (!formData.scheduledDate) errors.push('์ถœ๊ณ ์˜ˆ์ •์ผ์€ ํ•„์ˆ˜ ์ž…๋ ฅ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); + if (!formData.deliveryMethod) errors.push('๋ฐฐ์†ก๋ฐฉ์‹์€ ํ•„์ˆ˜ ์„ ํƒ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.'); setValidationErrors(errors); return errors.length === 0; }; - // ์ €์žฅ const handleSubmit = useCallback(async () => { - // validation ์ฒดํฌ - if (!validateForm()) { - return; - } + if (!validateForm()) return; setIsSubmitting(true); try { const result = await createShipment(formData); - if (result.success) { router.push('/ko/outbound/shipments'); } else { - setValidationErrors([result.error || '์ถœํ•˜ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.']); + setValidationErrors([result.error || '์ถœ๊ณ  ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.']); } } catch (err) { if (isNextRedirectError(err)) throw err; @@ -212,44 +304,80 @@ export function ShipmentCreate() { } }, [formData, router]); + // ์ œํ’ˆ ๋ถ€ํ’ˆ ํ…Œ์ด๋ธ” ๋ Œ๋”๋ง + const renderPartsTable = (parts: ProductPart[]) => ( + + + + ์ˆœ๋ฒˆ + ํ’ˆ๋ชฉ๋ช… + ๊ทœ๊ฒฉ + ์ˆ˜๋Ÿ‰ + ๋‹จ์œ„ + + + + {parts.map((part) => ( + + {part.seq} + {part.itemName} + {part.specification} + {part.quantity} + {part.unit} + + ))} + +
+ ); + + // LOT์—์„œ ์„ ํƒํ•œ ์ •๋ณด ํ‘œ์‹œ + const selectedLot = lotOptions.find(o => o.value === formData.lotNo); + // ํผ ์ปจํ…์ธ  ๋ Œ๋”๋ง const renderFormContent = useCallback((_props: { formData: Record; onChange: (key: string, value: unknown) => void; mode: string; errors: Record }) => (
{/* Validation ์—๋Ÿฌ ํ‘œ์‹œ */} - {validationErrors.length > 0 && ( - - -
- โš ๏ธ -
- - ์ž…๋ ฅ ๋‚ด์šฉ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š” ({validationErrors.length}๊ฐœ ์˜ค๋ฅ˜) - -
    - {validationErrors.map((error, index) => ( -
  • - โ€ข - {error} -
  • - ))} -
-
+ {validationErrors.length > 0 && ( + + +
+ โš ๏ธ +
+ + ์ž…๋ ฅ ๋‚ด์šฉ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š” ({validationErrors.length}๊ฐœ ์˜ค๋ฅ˜) + +
    + {validationErrors.map((err, index) => ( +
  • + โ€ข + {err} +
  • + ))} +
- - - )} +
+
+
+ )} - {/* ์ˆ˜์ฃผ ์„ ํƒ */} - - - ์ˆ˜์ฃผ ์„ ํƒ - - -
- + {/* ์นด๋“œ 1: ๊ธฐ๋ณธ ์ •๋ณด */} + + + ๊ธฐ๋ณธ ์ •๋ณด + + +
+ {/* ์ถœ๊ณ ๋ฒˆํ˜ธ - ์ž๋™์ƒ์„ฑ */} +
+
์ถœ๊ณ ๋ฒˆํ˜ธ
+
์ž๋™์ƒ์„ฑ
+
+ {/* ๋กœํŠธ๋ฒˆํ˜ธ - Select */} +
+
๋กœํŠธ๋ฒˆํ˜ธ *
- - + {/* ํ˜„์žฅ๋ช… - LOT ์„ ํƒ ์‹œ ์ž๋™ ๋งคํ•‘ */} +
+
ํ˜„์žฅ๋ช…
+
{selectedLot?.siteName || '-'}
+
+ {/* ์ˆ˜์ฃผ์ฒ˜ - LOT ์„ ํƒ ์‹œ ์ž๋™ ๋งคํ•‘ */} +
+
์ˆ˜์ฃผ์ฒ˜
+
{selectedLot?.customerName || '-'}
+
+
+
+
- {/* ์ถœ๊ณ  ์ •๋ณด */} - - - ์ถœ๊ณ  ์ •๋ณด - - -
-
- - handleInputChange('scheduledDate', e.target.value)} - disabled={isSubmitting} - /> -
-
- - -
+ {/* ์นด๋“œ 2: ์ˆ˜์ฃผ/๋ฐฐ์†ก ์ •๋ณด */} + + + ์ˆ˜์ฃผ/๋ฐฐ์†ก ์ •๋ณด + + +
+
+ + handleInputChange('scheduledDate', e.target.value)} + disabled={isSubmitting} + /> +
+
+ + handleInputChange('shipmentDate', e.target.value)} + disabled={isSubmitting} + />
+
+ + +
+
+
+
+ + handleInputChange('receiver', e.target.value)} + placeholder="์ˆ˜์‹ ์ž๋ช…" + disabled={isSubmitting} + /> +
+
+ + handleInputChange('receiverContact', e.target.value)} + placeholder="์ˆ˜์‹ ์ฒ˜" + disabled={isSubmitting} + /> +
+
+ {/* ์ฃผ์†Œ */} +
+ +
+ + +
+ + handleInputChange('addressDetail', e.target.value)} + placeholder="์ƒ์„ธ์ฃผ์†Œ" + disabled={isSubmitting} + /> +
+
+
- {/* ์ƒ์ฐจ (๋ฌผ๋ฅ˜์—…์ฒด) - ๋ฐฐ์†ก๋ฐฉ์‹์ด ์ƒ์ฐจ ๋˜๋Š” ๋ฌผ๋ฅ˜์‚ฌ์ผ ๋•Œ ํ‘œ์‹œ */} - {(formData.deliveryMethod === 'pickup' || formData.deliveryMethod === 'logistics') && ( - - - ์ƒ์ฐจ (๋ฌผ๋ฅ˜์—…์ฒด) - - -
-
- - -
-
- - -
-
-
- + {/* ์นด๋“œ 3: ๋ฐฐ์ฐจ ์ •๋ณด */} + + + ๋ฐฐ์ฐจ ์ •๋ณด + + + + + + + ๋ฌผ๋ฅ˜์—…์ฒด + ์ž…์ฐจ์ผ์‹œ + ํ†ค์ˆ˜ + ์ฐจ๋Ÿ‰๋ฒˆํ˜ธ + ๊ธฐ์‚ฌ์—ฐ๋ฝ์ฒ˜ + ๋น„๊ณ  + + + + + {formData.vehicleDispatches.map((dispatch, index) => ( + + + + + handleInputChange('loadingTime', e.target.value)} - placeholder="์—ฐ๋„. ์›”. ์ผ. -- --:--" + value={dispatch.arrivalDateTime} + onChange={(e) => handleDispatchChange(index, 'arrivalDateTime', e.target.value)} + className="h-8" disabled={isSubmitting} /> -

- ๋ฌผ๋ฅ˜์—…์ฒด์™€ ์ž…์ฐจ์‹œ๊ฐ„ ํ™•์ • ํ›„ ์ž…๋ ฅํ•˜์„ธ์š”. -

- - - - )} +
+ + + + + handleDispatchChange(index, 'vehicleNo', e.target.value)} + placeholder="์ฐจ๋Ÿ‰๋ฒˆํ˜ธ" + className="h-8" + disabled={isSubmitting} + /> + + + handleDispatchChange(index, 'driverContact', e.target.value)} + placeholder="์—ฐ๋ฝ์ฒ˜" + className="h-8" + disabled={isSubmitting} + /> + + + handleDispatchChange(index, 'remarks', e.target.value)} + placeholder="๋น„๊ณ " + className="h-8" + disabled={isSubmitting} + /> + + + {formData.vehicleDispatches.length > 1 && ( + + )} + +
+ ))} +
+
+
+
-
- - handleInputChange('loadingManager', e.target.value)} - placeholder="์ƒ์ฐจ ์ž‘์—… ๋‹ด๋‹น์ž๋ช…" - disabled={isSubmitting} - /> + {/* ์นด๋“œ 4: ์ œํ’ˆ๋‚ด์šฉ (์ฝ๊ธฐ์ „์šฉ - LOT ์„ ํƒ ์‹œ ํ‘œ์‹œ) */} + + + ์ œํ’ˆ๋‚ด์šฉ + {productGroups.length > 0 && ( + + + + + + + ๋ชจ๋‘ ํŽผ์น˜๊ธฐ + + + ๋ชจ๋‘ ์ ‘๊ธฐ + + + + )} + + + {productGroups.length > 0 || otherParts.length > 0 ? ( + + {productGroups.map((group: ProductGroup) => ( + + +
+ {group.productName} + + ({group.specification}) + + + {group.partCount}๊ฐœ ๋ถ€ํ’ˆ + +
+
+ + {renderPartsTable(group.parts)} + +
+ ))} + {otherParts.length > 0 && ( + + +
+ ๊ธฐํƒ€๋ถ€ํ’ˆ + + {otherParts.length}๊ฐœ ๋ถ€ํ’ˆ + +
+
+ + {renderPartsTable(otherParts)} + +
+ )} +
+ ) : ( +
+ {formData.lotNo ? '์ œํ’ˆ ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...' : '๋กœํŠธ๋ฅผ ์„ ํƒํ•˜๋ฉด ์ œํ’ˆ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.'}
+ )} +
+
+
+ ), [ + formData, validationErrors, isSubmitting, lotOptions, logisticsOptions, + vehicleTonnageOptions, selectedLot, productGroups, otherParts, accordionValue, + handleLotChange, handleExpandAll, handleCollapseAll, openPostcode, + ]); -
- -