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:
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 기안 문서 작성/수정 페이지 설정
|
||||
* IntegratedDetailTemplate 마이그레이션 (2025-01-20)
|
||||
*/
|
||||
|
||||
import { FileText } from 'lucide-react';
|
||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||
|
||||
export const documentCreateConfig: DetailConfig = {
|
||||
title: '문서 작성',
|
||||
description: '새로운 결재 문서를 작성합니다',
|
||||
icon: FileText,
|
||||
basePath: '/approval/draft',
|
||||
fields: [],
|
||||
actions: {
|
||||
showBack: true,
|
||||
showEdit: false,
|
||||
showDelete: false, // 커스텀 삭제 버튼 사용
|
||||
showSave: false, // 상신/임시저장 버튼 사용
|
||||
},
|
||||
};
|
||||
|
||||
export const documentEditConfig: DetailConfig = {
|
||||
...documentCreateConfig,
|
||||
title: '문서 수정',
|
||||
description: '기존 결재 문서를 수정합니다',
|
||||
// actions는 documentCreateConfig에서 상속 (커스텀 버튼 사용)
|
||||
};
|
||||
|
||||
export const documentCopyConfig: DetailConfig = {
|
||||
...documentCreateConfig,
|
||||
title: '문서 복제',
|
||||
description: '복제된 문서를 수정 후 상신합니다',
|
||||
};
|
||||
@@ -3,13 +3,16 @@
|
||||
import { useState, useCallback, useEffect, useTransition, useRef } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { format } from 'date-fns';
|
||||
import { FileText, Trash2, Send, Save, ArrowLeft, Eye } from 'lucide-react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Trash2, Send, Save, Eye, Loader2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import {
|
||||
documentCreateConfig,
|
||||
documentEditConfig,
|
||||
documentCopyConfig,
|
||||
} from './documentCreateConfig';
|
||||
import {
|
||||
getExpenseEstimateItems,
|
||||
getEmployees,
|
||||
createApproval,
|
||||
createAndSubmitApproval,
|
||||
getApprovalById,
|
||||
@@ -18,7 +21,6 @@ import {
|
||||
deleteApproval,
|
||||
} from './actions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { BasicInfoSection } from './BasicInfoSection';
|
||||
import { ApprovalLineSection } from './ApprovalLineSection';
|
||||
import { ReferenceSection } from './ReferenceSection';
|
||||
@@ -33,7 +35,6 @@ import type {
|
||||
ExpenseEstimateDocumentData,
|
||||
} from '@/components/approval/DocumentDetail/types';
|
||||
import type {
|
||||
DocumentType,
|
||||
BasicInfo,
|
||||
ApprovalPerson,
|
||||
ProposalData,
|
||||
@@ -416,7 +417,7 @@ export function DocumentCreate() {
|
||||
approvers,
|
||||
drafter,
|
||||
};
|
||||
default:
|
||||
default: {
|
||||
// 이미 업로드된 파일 URL (Next.js 프록시 사용) + 새로 추가된 파일 미리보기 URL
|
||||
const uploadedFileUrls = (proposalData.uploadedFiles || []).map(f =>
|
||||
`/api/proxy/files/${f.id}/download`
|
||||
@@ -436,6 +437,7 @@ export function DocumentCreate() {
|
||||
approvers,
|
||||
drafter,
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [basicInfo, approvalLine, proposalData, expenseReportData, expenseEstimateData]);
|
||||
|
||||
@@ -453,75 +455,63 @@ export function DocumentCreate() {
|
||||
}
|
||||
};
|
||||
|
||||
// 문서 로딩 중
|
||||
if (isLoadingDocument) {
|
||||
// 현재 모드에 맞는 config 선택
|
||||
const currentConfig = isEditMode
|
||||
? documentEditConfig
|
||||
: isCopyMode
|
||||
? documentCopyConfig
|
||||
: documentCreateConfig;
|
||||
|
||||
// 헤더 액션 버튼 렌더링
|
||||
const renderHeaderActions = useCallback(() => {
|
||||
return (
|
||||
<div className="container mx-auto py-6 px-4 max-w-4xl">
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="ghost" size="icon" onClick={handleBack}>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<CardTitle>문서 불러오는 중...</CardTitle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
<ContentLoadingSpinner text="문서를 불러오는 중..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6 px-4 max-w-4xl">
|
||||
{/* 헤더 */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="ghost" size="icon" onClick={handleBack}>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<CardTitle>{isEditMode ? '문서 수정' : isCopyMode ? '문서 복제' : '문서 작성'}</CardTitle>
|
||||
<CardDescription>
|
||||
{isEditMode ? '기존 문서를 수정합니다' : isCopyMode ? '복제된 문서를 수정 후 상신합니다' : '새로운 문서를 작성합니다'}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{/* 액션 버튼 (스텝) */}
|
||||
<div className="flex items-center justify-center gap-2 mb-6">
|
||||
<Button variant="outline" className="min-w-[80px]" onClick={handlePreview}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={handlePreview}>
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
상세
|
||||
미리보기
|
||||
</Button>
|
||||
<Button variant="outline" className="min-w-[80px]" onClick={handleDelete} disabled={isPending}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleDelete}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-1" />
|
||||
삭제
|
||||
</Button>
|
||||
<Button variant="default" className="min-w-[80px]" onClick={handleSubmit} disabled={isPending}>
|
||||
{isPending ? <Loader2 className="w-4 h-4 mr-1 animate-spin" /> : <Send className="w-4 h-4 mr-1" />}
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={handleSubmit}
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|
||||
) : (
|
||||
<Send className="w-4 h-4 mr-1" />
|
||||
)}
|
||||
상신
|
||||
</Button>
|
||||
<Button variant="secondary" className="min-w-[80px]" onClick={handleSaveDraft} disabled={isPending}>
|
||||
{isPending ? <Loader2 className="w-4 h-4 mr-1 animate-spin" /> : <Save className="w-4 h-4 mr-1" />}
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleSaveDraft}
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending ? (
|
||||
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|
||||
) : (
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
)}
|
||||
{isEditMode ? '저장' : '임시저장'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [handlePreview, handleDelete, handleSubmit, handleSaveDraft, isPending, isEditMode]);
|
||||
|
||||
{/* 폼 영역 */}
|
||||
// 폼 컨텐츠 렌더링
|
||||
const renderFormContent = useCallback(() => {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
<BasicInfoSection data={basicInfo} onChange={setBasicInfo} />
|
||||
@@ -535,27 +525,19 @@ export function DocumentCreate() {
|
||||
{/* 문서 유형별 폼 */}
|
||||
{renderDocumentTypeForm()}
|
||||
</div>
|
||||
);
|
||||
}, [basicInfo, approvalLine, references, renderDocumentTypeForm]);
|
||||
|
||||
{/* 하단 고정 버튼 (모바일) */}
|
||||
<div className="fixed bottom-0 left-0 right-0 p-4 bg-white border-t md:hidden">
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" className="flex-1" onClick={handleDelete} disabled={isPending}>
|
||||
<Trash2 className="w-4 h-4 mr-1" />
|
||||
삭제
|
||||
</Button>
|
||||
<Button variant="secondary" className="flex-1" onClick={handleSaveDraft} disabled={isPending}>
|
||||
{isPending ? <Loader2 className="w-4 h-4 mr-1 animate-spin" /> : <Save className="w-4 h-4 mr-1" />}
|
||||
{isEditMode ? '저장' : '임시저장'}
|
||||
</Button>
|
||||
<Button variant="default" className="flex-1" onClick={handleSubmit} disabled={isPending}>
|
||||
{isPending ? <Loader2 className="w-4 h-4 mr-1 animate-spin" /> : <Send className="w-4 h-4 mr-1" />}
|
||||
상신
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 모바일 하단 여백 */}
|
||||
<div className="h-20 md:hidden" />
|
||||
return (
|
||||
<>
|
||||
<IntegratedDetailTemplate
|
||||
config={currentConfig}
|
||||
mode={isEditMode ? 'edit' : 'create'}
|
||||
isLoading={isLoadingDocument}
|
||||
onBack={handleBack}
|
||||
renderForm={renderFormContent}
|
||||
headerActions={renderHeaderActions()}
|
||||
/>
|
||||
|
||||
{/* 미리보기 모달 */}
|
||||
<DocumentDetailModal
|
||||
@@ -577,6 +559,6 @@ export function DocumentCreate() {
|
||||
handleSubmit();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user