feat(WEB): Phase 6 IntegratedDetailTemplate 마이그레이션 완료

Phase 6 마이그레이션 (41개 컴포넌트 완료):
- 건설/시공: 협력업체, 시공관리, 기성관리, 발주관리, 계약관리 등
- 영업: 견적관리(V2), 고객관리(V2), 수주관리
- 회계: 청구관리, 매입관리, 매출관리, 거래처관리, 악성채권 등
- 생산: 작업지시, 검수관리
- 출고: 출하관리
- 자재: 입고관리, 재고현황
- 고객센터: 문의관리, 이벤트관리, 공지관리
- 인사: 직원관리
- 설정: 권한관리

주요 변경사항:
- 34개 xxxConfig.ts 파일 생성 (설정 기반 페이지 구성)
- PageLayout/PageHeader → IntegratedDetailTemplate 통합
- 일관된 타이틀/버튼 영역 (목록, 상세, 수정, 삭제)
- 1112줄 코드 감소 (중복 제거)

프로젝트 공통화 현황 분석 문서 추가:
- 상세 페이지 62%, 목록 페이지 82% 공통화 달성
- 추가 공통화 기회 및 로드맵 정리

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-20 15:51:02 +09:00
parent 6f457b28f3
commit 61e3a0ed60
71 changed files with 4743 additions and 4402 deletions

View File

@@ -1,19 +1,12 @@
'use client';
import { FileText, List, Eye, Edit } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { Eye } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
import { progressBillingConfig } from './progressBillingConfig';
import { toast } from 'sonner';
import type { ProgressBillingDetail } from './types';
import { useProgressBillingDetailForm } from './hooks/useProgressBillingDetailForm';
import { ProgressBillingInfoCard } from './cards/ProgressBillingInfoCard';
@@ -35,6 +28,8 @@ export default function ProgressBillingDetailForm({
billingId,
initialData,
}: ProgressBillingDetailFormProps) {
const router = useRouter();
const {
// Mode flags
isViewMode,
@@ -43,15 +38,6 @@ export default function ProgressBillingDetailForm({
// Form data
formData,
// Loading state
isLoading,
// Dialog states
showSaveDialog,
setShowSaveDialog,
showDeleteDialog,
setShowDeleteDialog,
// Modal states
showDirectConstructionModal,
setShowDirectConstructionModal,
@@ -64,20 +50,9 @@ export default function ProgressBillingDetailForm({
selectedBillingItems,
selectedPhotoItems,
// Navigation handlers
handleBack,
handleEdit,
handleCancel,
// Form handlers
handleFieldChange,
// CRUD handlers
handleSave,
handleConfirmSave,
handleDelete,
handleConfirmDelete,
// Billing item handlers
handleBillingItemChange,
handleToggleBillingItemSelection,
@@ -96,56 +71,45 @@ export default function ProgressBillingDetailForm({
handleViewPhotoDocument,
} = useProgressBillingDetailForm({ mode, billingId, initialData });
// 헤더 액션 버튼
const headerActions = isViewMode ? (
<div className="flex items-center gap-2">
<Button variant="outline" onClick={handleBack}>
<List className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleViewDirectConstruction}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleViewIndirectConstruction}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleViewPhotoDocument}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button onClick={handleEdit}>
<Edit className="h-4 w-4 mr-2" />
</Button>
</div>
) : (
<div className="flex items-center gap-2">
<Button variant="outline" onClick={handleBack}>
<List className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleCancel}>
</Button>
<Button onClick={handleSave} disabled={isLoading}>
</Button>
</div>
);
// 저장 핸들러 (IntegratedDetailTemplate용)
const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => {
try {
// TODO: API 호출
console.log('Save billing data:', formData);
await new Promise((resolve) => setTimeout(resolve, 500));
toast.success('저장되었습니다.');
router.push('/ko/construction/billing/progress-billing-management/' + billingId);
return { success: true };
} catch (error) {
console.error('Save failed:', error);
return { success: false, error: '저장에 실패했습니다.' };
}
}, [formData, router, billingId]);
return (
<PageLayout>
<PageHeader
title="기성청구 상세"
description="기성청구를 등록하고 관리합니다"
icon={FileText}
onBack={handleBack}
actions={headerActions}
/>
// 커스텀 헤더 액션 (view 모드에서 직접/간접 공사 내역, 사진대지 버튼)
const customHeaderActions = useMemo(() => {
if (!isViewMode) return null;
return (
<>
<Button variant="outline" onClick={handleViewDirectConstruction}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleViewIndirectConstruction}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleViewPhotoDocument}>
<Eye className="h-4 w-4 mr-2" />
</Button>
</>
);
}, [isViewMode, handleViewDirectConstruction, handleViewIndirectConstruction, handleViewPhotoDocument]);
<div className="space-y-6">
// 폼 내용 렌더링 함수 (IntegratedDetailTemplate용)
const renderFormContent = () => (
<div className="space-y-6">
{/* 기성청구 정보 */}
<ProgressBillingInfoCard
formData={formData}
@@ -179,68 +143,43 @@ export default function ProgressBillingDetailForm({
onApplySelected={handleApplySelectedPhotoItems}
onPhotoSelect={handlePhotoSelect}
/>
</div>
</div>
);
{/* 저장 확인 다이얼로그 */}
<AlertDialog open={showSaveDialog} onOpenChange={setShowSaveDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmSave} disabled={isLoading}>
{isLoading ? '저장 중...' : '저장'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
return (
<>
<IntegratedDetailTemplate
config={progressBillingConfig}
mode={mode}
initialData={{}}
itemId={billingId}
isLoading={false}
onSubmit={handleSubmit}
headerActions={customHeaderActions}
renderView={() => renderFormContent()}
renderForm={() => renderFormContent()}
/>
{/* 삭제 확인 다이얼로그 */}
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
? .
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={handleConfirmDelete}
disabled={isLoading}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isLoading ? '삭제 중...' : '삭제'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* 직접 공사 내역서 모달 */}
{/* 직접 공사 내역서 모달 (특수 기능) */}
<DirectConstructionModal
open={showDirectConstructionModal}
onOpenChange={setShowDirectConstructionModal}
data={formData}
/>
{/* 간접 공사 내역서 모달 */}
{/* 간접 공사 내역서 모달 (특수 기능) */}
<IndirectConstructionModal
open={showIndirectConstructionModal}
onOpenChange={setShowIndirectConstructionModal}
data={formData}
/>
{/* 사진대지 모달 */}
{/* 사진대지 모달 (특수 기능) */}
<PhotoDocumentModal
open={showPhotoDocumentModal}
onOpenChange={setShowPhotoDocumentModal}
data={formData}
/>
</PageLayout>
</>
);
}

View File

@@ -0,0 +1,27 @@
import { FileText } from 'lucide-react';
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
/**
* 기성관리 상세 페이지 Config
*
* 참고: 이 config는 타이틀/버튼 영역만 정의
* 폼 내용은 기존 ProgressBillingDetailForm의 renderView/renderForm에서 처리
* (기성청구 내역 테이블, 사진대지, 직접/간접 공사 내역 모달 등 특수 기능 유지)
*/
export const progressBillingConfig: DetailConfig = {
title: '기성청구 상세',
description: '기성청구를 등록하고 관리합니다',
icon: FileText,
basePath: '/construction/billing/progress-billing-management',
fields: [], // renderView/renderForm 사용으로 필드 정의 불필요
gridColumns: 2,
actions: {
showBack: true,
showDelete: false, // 기성관리는 삭제 기능 없음
showEdit: true,
backLabel: '목록',
editLabel: '수정',
submitLabel: '저장',
cancelLabel: '취소',
},
};