feat(WEB): IntegratedDetailTemplate 통합 템플릿 구현 및 Phase 1~8 마이그레이션

- Phase 1: 기안함(DocumentCreate) 마이그레이션
- Phase 2: 작업지시(WorkOrderCreate/Edit) 마이그레이션
- Phase 3: 출하(ShipmentCreate/Edit) 마이그레이션
- Phase 4: 사원(EmployeeForm) 마이그레이션
- Phase 5: 게시판(BoardForm) 마이그레이션
- Phase 6: 1:1문의(InquiryForm) 마이그레이션
- Phase 7: 공정(ProcessForm) 마이그레이션
- Phase 8: 수입검사/품질검사(InspectionCreate) 마이그레이션
- DetailActions에 showSave 옵션 추가
- 각 도메인별 config 파일 생성

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-20 19:31:07 +09:00
parent 6b0ffc810b
commit 62ef2b1ff9
24 changed files with 861 additions and 534 deletions

View File

@@ -3,11 +3,12 @@
/**
* 작업지시 등록 페이지
* API 연동 완료 (2025-12-26)
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { ArrowLeft, FileText, X, Edit2, Loader2 } from 'lucide-react';
import { X, Edit2, FileText } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
@@ -21,13 +22,14 @@ import {
SelectValue,
} from '@/components/ui/select';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { PageLayout } from '@/components/organisms/PageLayout';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { SalesOrderSelectModal } from './SalesOrderSelectModal';
import { AssigneeSelectModal } from './AssigneeSelectModal';
import { toast } from 'sonner';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { createWorkOrder, getProcessOptions, type ProcessOption } from './actions';
import { PROCESS_TYPE_LABELS, type ProcessType, type SalesOrder } from './types';
import { type SalesOrder } from './types';
import { workOrderCreateConfig } from './workOrderConfig';
// Validation 에러 타입
interface ValidationErrors {
@@ -208,31 +210,9 @@ export function WorkOrderCreate() {
return selectedProcess?.processCode || '-';
};
return (
<PageLayout>
{/* 헤더 */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" onClick={handleCancel}>
<ArrowLeft className="w-5 h-5" />
</Button>
<h1 className="text-2xl font-bold flex items-center gap-2">
<FileText className="w-5 h-5" />
</h1>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={handleCancel} disabled={isSubmitting}>
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting && <Loader2 className="w-4 h-4 mr-1.5 animate-spin" />}
</Button>
</div>
</div>
<div className="space-y-6">
// 폼 컨텐츠 렌더링
const renderFormContent = useCallback(() => (
<div className="space-y-6">
{/* Validation 에러 표시 */}
{Object.keys(validationErrors).length > 0 && (
<Alert className="bg-red-50 border-red-200">
@@ -497,6 +477,19 @@ export function WorkOrderCreate() {
/>
</section>
</div>
), [mode, formData, validationErrors, processOptions, isLoadingProcesses, assigneeNames, getSelectedProcessCode]);
return (
<>
<IntegratedDetailTemplate
config={workOrderCreateConfig}
mode="create"
isSubmitting={isSubmitting}
onBack={handleCancel}
onCancel={handleCancel}
onSubmit={handleSubmit}
renderForm={renderFormContent}
/>
{/* 수주 선택 모달 */}
<SalesOrderSelectModal
@@ -515,6 +508,6 @@ export function WorkOrderCreate() {
setAssigneeNames(names);
}}
/>
</PageLayout>
</>
);
}

View File

@@ -3,12 +3,11 @@
/**
* 작업지시 수정 페이지
* WorkOrderCreate 패턴 기반
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
import { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { ArrowLeft, FileText, Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
@@ -20,13 +19,13 @@ import {
SelectValue,
} from '@/components/ui/select';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { PageLayout } from '@/components/organisms/PageLayout';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { AssigneeSelectModal } from './AssigneeSelectModal';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { toast } from 'sonner';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { getWorkOrderById, updateWorkOrder, getProcessOptions, type ProcessOption } from './actions';
import type { WorkOrder } from './types';
import { workOrderEditConfig } from './workOrderConfig';
// Validation 에러 타입
interface ValidationErrors {
@@ -199,41 +198,15 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
return selectedProcess?.processCode || '-';
};
// 로딩 상태
if (isLoading) {
return <ContentLoadingSpinner text="작업지시 정보를 불러오는 중..." />;
}
// 동적 config (작업지시 번호 포함)
const dynamicConfig = {
...workOrderEditConfig,
title: `작업지시 수정 ${workOrder ? `(${workOrder.workOrderNo})` : ''}`,
};
if (!workOrder) {
return null;
}
return (
<PageLayout>
{/* 헤더 */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" onClick={handleCancel}>
<ArrowLeft className="w-5 h-5" />
</Button>
<h1 className="text-2xl font-bold flex items-center gap-2">
<FileText className="w-5 h-5" />
</h1>
<span className="text-muted-foreground">({workOrder.workOrderNo})</span>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={handleCancel} disabled={isSubmitting}>
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting && <Loader2 className="w-4 h-4 mr-1.5 animate-spin" />}
</Button>
</div>
</div>
<div className="space-y-6">
// 폼 컨텐츠 렌더링
const renderFormContent = useCallback(() => (
<div className="space-y-6">
{/* Validation 에러 표시 */}
{Object.keys(validationErrors).length > 0 && (
<Alert className="bg-red-50 border-red-200">
@@ -386,6 +359,20 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
/>
</section>
</div>
), [formData, validationErrors, processOptions, isLoadingProcesses, assigneeNames, getSelectedProcessCode]);
return (
<>
<IntegratedDetailTemplate
config={dynamicConfig}
mode="edit"
isLoading={isLoading}
isSubmitting={isSubmitting}
onBack={handleCancel}
onCancel={handleCancel}
onSubmit={handleSubmit}
renderForm={renderFormContent}
/>
{/* 담당자 선택 모달 */}
<AssigneeSelectModal
@@ -397,6 +384,6 @@ export function WorkOrderEdit({ orderId }: WorkOrderEditProps) {
setAssigneeNames(names);
}}
/>
</PageLayout>
</>
);
}

View File

@@ -1,3 +1,5 @@
'use client';
import { FileText } from 'lucide-react';
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
@@ -28,3 +30,35 @@ export const workOrderConfig: DetailConfig = {
editLabel: '수정',
},
};
/**
* 작업지시 등록 페이지 Config
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
export const workOrderCreateConfig: DetailConfig = {
title: '작업지시 등록',
description: '새로운 작업지시를 등록합니다',
icon: FileText,
basePath: '/production/work-orders',
fields: [],
actions: {
showBack: true,
showEdit: false,
showDelete: false,
showSave: true,
submitLabel: '등록',
},
};
/**
* 작업지시 수정 페이지 Config
*/
export const workOrderEditConfig: DetailConfig = {
...workOrderCreateConfig,
title: '작업지시 수정',
description: '작업지시 정보를 수정합니다',
actions: {
...workOrderCreateConfig.actions,
submitLabel: '저장',
},
};