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,13 +3,11 @@
/**
* 출하 등록 페이지
* API 연동 완료 (2025-12-26)
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
import { useState, useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { ArrowLeft, Truck, Loader2, AlertCircle } from 'lucide-react';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
@@ -22,7 +20,8 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { PageLayout } from '@/components/organisms/PageLayout';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { shipmentCreateConfig } from './shipmentConfig';
import {
createShipment,
getLotOptions,
@@ -177,63 +176,10 @@ export function ShipmentCreate() {
}
}, [formData, router]);
// 로딩 상태 표시
if (isLoading) {
return (
<PageLayout>
<ContentLoadingSpinner text="출하 등록 정보를 불러오는 중..." />
</PageLayout>
);
}
// 에러 상태 표시
if (error) {
return (
<PageLayout>
<div className="flex flex-col items-center justify-center min-h-[400px] gap-4">
<AlertCircle className="w-12 h-12 text-red-500" />
<p className="text-lg text-muted-foreground">{error}</p>
<Button onClick={loadOptions}> </Button>
</div>
</PageLayout>
);
}
return (
<PageLayout>
<div className="space-y-6">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon"
onClick={handleCancel}
disabled={isSubmitting}
>
<ArrowLeft className="w-5 h-5" />
</Button>
<Truck className="w-6 h-6" />
<h1 className="text-xl font-semibold"> </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-2 animate-spin" />
...
</>
) : (
'저장'
)}
</Button>
</div>
</div>
{/* Validation 에러 표시 */}
// 폼 컨텐츠 렌더링
const renderFormContent = useCallback(() => (
<div className="space-y-6">
{/* Validation 에러 표시 */}
{validationErrors.length > 0 && (
<Alert className="bg-red-50 border-red-200">
<AlertDescription className="text-red-900">
@@ -428,6 +374,35 @@ export function ShipmentCreate() {
</CardContent>
</Card>
</div>
</PageLayout>
), [formData, validationErrors, isSubmitting, lotOptions, logisticsOptions, vehicleTonnageOptions]);
// 로딩 또는 에러 상태 처리
if (error) {
return (
<IntegratedDetailTemplate
config={shipmentCreateConfig}
mode="create"
isLoading={false}
onBack={handleCancel}
renderForm={() => (
<Alert className="bg-red-50 border-red-200">
<AlertDescription className="text-red-900">{error}</AlertDescription>
</Alert>
)}
/>
);
}
return (
<IntegratedDetailTemplate
config={shipmentCreateConfig}
mode="create"
isLoading={isLoading}
isSubmitting={isSubmitting}
onBack={handleCancel}
onCancel={handleCancel}
onSubmit={handleSubmit}
renderForm={renderFormContent}
/>
);
}

View File

@@ -3,13 +3,11 @@
/**
* 출하 수정 페이지
* API 연동 완료 (2025-12-26)
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
import { useState, useCallback, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { ArrowLeft, Truck, Loader2, AlertCircle } from 'lucide-react';
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
@@ -23,8 +21,8 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { PageLayout } from '@/components/organisms/PageLayout';
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { shipmentEditConfig } from './shipmentConfig';
import {
getShipmentById,
getLogisticsOptions,
@@ -215,63 +213,23 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
}
}, [id, formData, router]);
// 로딩 상태 표시
if (isLoading) {
return (
<PageLayout>
<ContentLoadingSpinner text="출고 정보를 불러오는 중..." />
</PageLayout>
);
}
// 동적 config (로트번호 + 상태 표시)
const dynamicConfig = {
...shipmentEditConfig,
title: detail ? `출고 수정 (${detail.lotNo})` : '출고 수정',
};
// 에러 상태 표시
if (error || !detail) {
return (
<ServerErrorPage
title="출하 정보를 불러올 수 없습니다"
message={error || '출하 정보를 찾을 수 없습니다.'}
showBackButton={true}
showHomeButton={true}
/>
);
}
// 폼 컨텐츠 렌더링
const renderFormContent = useCallback(() => {
if (!detail) return null;
return (
<PageLayout>
return (
<div className="space-y-6">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon"
onClick={handleCancel}
disabled={isSubmitting}
>
<ArrowLeft className="w-5 h-5" />
</Button>
<Truck className="w-6 h-6" />
<h1 className="text-xl font-semibold"> </h1>
<span className="text-sm text-muted-foreground">{detail.lotNo}</span>
<Badge className={`text-xs ${SHIPMENT_STATUS_STYLES[detail.status]}`}>
{SHIPMENT_STATUS_LABELS[detail.status]}
</Badge>
</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-2 animate-spin" />
...
</>
) : (
'저장'
)}
</Button>
</div>
{/* 상태 배지 */}
<div className="flex items-center gap-2">
<Badge className={`text-xs ${SHIPMENT_STATUS_STYLES[detail.status]}`}>
{SHIPMENT_STATUS_LABELS[detail.status]}
</Badge>
</div>
{/* Validation 에러 표시 */}
@@ -536,6 +494,38 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
</CardContent>
</Card>
</div>
</PageLayout>
);
}, [detail, formData, validationErrors, isSubmitting, logisticsOptions, vehicleTonnageOptions]);
// 에러 상태 표시
if (error && !isLoading) {
return (
<IntegratedDetailTemplate
config={dynamicConfig}
mode="edit"
isLoading={false}
onBack={handleCancel}
renderForm={() => (
<Alert className="bg-red-50 border-red-200">
<AlertDescription className="text-red-900">
{error || '출하 정보를 찾을 수 없습니다.'}
</AlertDescription>
</Alert>
)}
/>
);
}
return (
<IntegratedDetailTemplate
config={dynamicConfig}
mode="edit"
isLoading={isLoading}
isSubmitting={isSubmitting}
onBack={handleCancel}
onCancel={handleCancel}
onSubmit={handleSubmit}
renderForm={renderFormContent}
/>
);
}

View File

@@ -1,3 +1,5 @@
'use client';
import { Truck } from 'lucide-react';
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
@@ -33,3 +35,31 @@ export const shipmentConfig: DetailConfig = {
},
},
};
/**
* 출하 등록 페이지 Config
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
*/
export const shipmentCreateConfig: DetailConfig = {
title: '출하 등록',
description: '새로운 출하를 등록합니다',
icon: Truck,
basePath: '/outbound/shipments',
fields: [],
actions: {
showBack: true,
showEdit: false,
showDelete: false,
showSave: true,
submitLabel: '저장',
},
};
/**
* 출하 수정 페이지 Config
*/
export const shipmentEditConfig: DetailConfig = {
...shipmentCreateConfig,
title: '출고 수정',
description: '출고 정보를 수정합니다',
};