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

@@ -2,6 +2,7 @@
/**
* 수주 상세 보기 컴포넌트 (View Mode)
* IntegratedDetailTemplate 마이그레이션 완료 (2026-01-20)
*
* - 문서 모달: 계약서, 거래명세서, 발주서
* - 기본 정보, 수주/배송 정보, 비고
@@ -9,7 +10,7 @@
* - 상태별 버튼 차이
*/
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo, useCallback } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
@@ -23,19 +24,16 @@ import {
} from "@/components/ui/table";
import {
FileText,
ArrowLeft,
Edit,
Factory,
XCircle,
FileSpreadsheet,
FileCheck,
ClipboardList,
Eye,
CheckCircle2,
} from "lucide-react";
import { toast } from "sonner";
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { IntegratedDetailTemplate } from "@/components/templates/IntegratedDetailTemplate";
import { orderSalesConfig } from "./orderSalesConfig";
import { BadgeSm } from "@/components/atoms/BadgeSm";
import { formatAmount } from "@/utils/formatAmount";
import {
@@ -219,95 +217,66 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
};
// 문서 모달 열기
const openDocumentModal = (type: OrderDocumentType) => {
const openDocumentModal = useCallback((type: OrderDocumentType) => {
setDocumentType(type);
setDocumentModalOpen(true);
};
}, []);
// 동적 config (상태별 수정 버튼 표시)
const dynamicConfig = useMemo(() => {
const canEdit = order?.status !== "shipped" && order?.status !== "cancelled";
return {
...orderSalesConfig,
actions: {
...orderSalesConfig.actions,
showEdit: canEdit,
},
};
}, [order?.status]);
// 커스텀 헤더 액션 (상태별 버튼들)
const customHeaderActions = useMemo(() => {
if (!order) return null;
const showConfirmButton = order.status === "order_registered";
const showProductionCreateButton =
order.status !== "shipped" &&
order.status !== "cancelled" &&
order.status !== "production_ordered";
const showCancelButton =
order.status !== "shipped" &&
order.status !== "cancelled" &&
order.status !== "production_ordered";
if (loading) {
return (
<PageLayout>
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
</div>
</PageLayout>
<>
{showConfirmButton && (
<Button onClick={handleConfirmOrder} className="bg-green-600 hover:bg-green-700">
<CheckCircle2 className="h-4 w-4 mr-2" />
</Button>
)}
{showProductionCreateButton && (
<Button onClick={handleProductionOrder}>
<Factory className="h-4 w-4 mr-2" />
</Button>
)}
{showCancelButton && (
<Button variant="outline" onClick={handleCancel} className="border-orange-200 text-orange-600 hover:border-orange-300">
<XCircle className="h-4 w-4 mr-2" />
</Button>
)}
</>
);
}
}, [order, handleConfirmOrder, handleProductionOrder, handleCancel]);
// 폼 콘텐츠 렌더링
const renderFormContent = useCallback(() => {
if (!order) return null;
if (!order) {
return (
<ServerErrorPage
title="수주 정보를 불러올 수 없습니다"
message="수주 정보를 찾을 수 없습니다."
showBackButton={true}
showHomeButton={true}
/>
);
}
// 상태별 버튼 표시 여부
const showEditButton = order.status !== "shipped" && order.status !== "cancelled";
// 수주 확정 버튼: 수주등록 상태에서만 표시
const showConfirmButton = order.status === "order_registered";
// 생산지시 생성 버튼: 출하완료, 취소, 생산지시완료 제외하고 표시
// (수주등록, 수주확정, 생산중, 재작업중, 작업완료에서 표시)
const showProductionCreateButton =
order.status !== "shipped" &&
order.status !== "cancelled" &&
order.status !== "production_ordered";
// 생산지시 보기 버튼: 생산지시완료 상태에서 숨김 (기획서 오류로 제거)
const showProductionViewButton = false;
const showCancelButton =
order.status !== "shipped" &&
order.status !== "cancelled" &&
order.status !== "production_ordered";
return (
<PageLayout>
{/* 헤더 */}
<PageHeader
title="수주 상세"
icon={FileText}
actions={
<div className="flex items-center gap-2 flex-wrap">
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="h-4 w-4 mr-2" />
</Button>
{showEditButton && (
<Button variant="outline" onClick={handleEdit}>
<Edit className="h-4 w-4 mr-2" />
</Button>
)}
{showConfirmButton && (
<Button onClick={handleConfirmOrder} className="bg-green-600 hover:bg-green-700">
<CheckCircle2 className="h-4 w-4 mr-2" />
</Button>
)}
{showProductionCreateButton && (
<Button onClick={handleProductionOrder}>
<Factory className="h-4 w-4 mr-2" />
</Button>
)}
{showProductionViewButton && (
<Button onClick={handleViewProductionOrder}>
<Eye className="h-4 w-4 mr-2" />
</Button>
)}
{showCancelButton && (
<Button variant="outline" onClick={handleCancel} className="border-orange-200 text-orange-600 hover:border-orange-300">
<XCircle className="h-4 w-4 mr-2" />
</Button>
)}
</div>
}
/>
<div className="space-y-6">
{/* 수주 정보 헤더 */}
<Card>
@@ -473,9 +442,37 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
</CardContent>
</Card>
</div>
);
}, [order, openDocumentModal]);
// 에러 상태
if (!loading && !order) {
return (
<ServerErrorPage
title="수주 정보를 불러올 수 없습니다"
message="수주 정보를 찾을 수 없습니다."
showBackButton={true}
showHomeButton={true}
/>
);
}
return (
<>
<IntegratedDetailTemplate
config={dynamicConfig}
mode="view"
initialData={order || {}}
itemId={orderId}
isLoading={loading}
headerActions={customHeaderActions}
renderView={() => renderFormContent()}
renderForm={() => renderFormContent()}
/>
{/* 문서 모달 */}
<OrderDocumentModal
{order && (
<OrderDocumentModal
open={documentModalOpen}
onOpenChange={setDocumentModalOpen}
documentType={documentType}
@@ -497,8 +494,10 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
remarks: order.remarks,
}}
/>
)}
{/* 취소 확인 다이얼로그 */}
{order && (
<Dialog open={isCancelDialogOpen} onOpenChange={setIsCancelDialogOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
@@ -591,8 +590,10 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
</DialogFooter>
</DialogContent>
</Dialog>
)}
{/* 수주 확정 다이얼로그 */}
{order && (
<Dialog open={isConfirmDialogOpen} onOpenChange={setIsConfirmDialogOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
@@ -658,6 +659,7 @@ export function OrderSalesDetailView({ orderId }: OrderSalesDetailViewProps) {
</DialogFooter>
</DialogContent>
</Dialog>
</PageLayout>
)}
</>
);
}