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:
@@ -3,12 +3,12 @@
|
||||
/**
|
||||
* 작업지시 상세 페이지
|
||||
* API 연동 완료 (2025-12-26)
|
||||
* IntegratedDetailTemplate 마이그레이션 완료 (2026-01-20)
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { FileText, List, AlertTriangle, Play, CheckCircle2, Loader2, Undo2, Pencil } from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { FileText, Play, CheckCircle2, Loader2, Undo2, Pencil } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
@@ -19,7 +19,8 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
|
||||
import { workOrderConfig } from './workOrderConfig';
|
||||
import { ServerErrorPage } from '@/components/common/ServerErrorPage';
|
||||
import { WorkLogModal } from '../WorkerScreen/WorkLogModal';
|
||||
import { toast } from 'sonner';
|
||||
@@ -301,109 +302,84 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {
|
||||
}
|
||||
}, [order, orderId]);
|
||||
|
||||
// 로딩 상태
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<h1 className="text-2xl font-bold mb-6">작업지시 상세</h1>
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
// 커스텀 헤더 액션 (상태 변경 버튼, 작업일지 버튼)
|
||||
const customHeaderActions = useMemo(() => {
|
||||
if (!order) return null;
|
||||
|
||||
if (!order) {
|
||||
return (
|
||||
<ServerErrorPage
|
||||
title="작업지시를 불러올 수 없습니다"
|
||||
message="작업지시를 찾을 수 없습니다."
|
||||
showBackButton={true}
|
||||
showHomeButton={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
{/* 헤더 */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-2xl font-bold">작업지시 상세</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 상태 변경 버튼 */}
|
||||
{order.status === 'waiting' && (
|
||||
<Button
|
||||
onClick={() => handleStatusChange('in_progress')}
|
||||
disabled={isStatusUpdating}
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<Play className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
작업 시작
|
||||
</Button>
|
||||
)}
|
||||
{order.status === 'in_progress' && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleStatusChange('waiting')}
|
||||
disabled={isStatusUpdating}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<Undo2 className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
작업 취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleStatusChange('completed')}
|
||||
disabled={isStatusUpdating}
|
||||
className="bg-purple-600 hover:bg-purple-700"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<CheckCircle2 className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
작업 완료
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{order.status === 'completed' && (
|
||||
<>
|
||||
{/* 상태 변경 버튼 */}
|
||||
{order.status === 'waiting' && (
|
||||
<Button
|
||||
onClick={() => handleStatusChange('in_progress')}
|
||||
disabled={isStatusUpdating}
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<Play className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
작업 시작
|
||||
</Button>
|
||||
)}
|
||||
{order.status === 'in_progress' && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleStatusChange('in_progress')}
|
||||
onClick={() => handleStatusChange('waiting')}
|
||||
disabled={isStatusUpdating}
|
||||
className="text-orange-600 hover:text-orange-700 border-orange-300 hover:bg-orange-50"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<Undo2 className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
되돌리기
|
||||
작업 취소
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" onClick={() => router.push(`/production/work-orders/${orderId}/edit`)}>
|
||||
<Pencil className="w-4 h-4 mr-1.5" />
|
||||
수정
|
||||
<Button
|
||||
onClick={() => handleStatusChange('completed')}
|
||||
disabled={isStatusUpdating}
|
||||
className="bg-purple-600 hover:bg-purple-700"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<CheckCircle2 className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
작업 완료
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{order.status === 'completed' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleStatusChange('in_progress')}
|
||||
disabled={isStatusUpdating}
|
||||
className="text-orange-600 hover:text-orange-700 border-orange-300 hover:bg-orange-50"
|
||||
>
|
||||
{isStatusUpdating ? (
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" />
|
||||
) : (
|
||||
<Undo2 className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
되돌리기
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => setIsWorkLogOpen(true)}>
|
||||
<FileText className="w-4 h-4 mr-1.5" />
|
||||
작업일지
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => router.push('/production/work-orders')}>
|
||||
<List className="w-4 h-4 mr-1.5" />
|
||||
목록
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Button variant="outline" onClick={() => setIsWorkLogOpen(true)}>
|
||||
<FileText className="w-4 h-4 mr-1.5" />
|
||||
작업일지
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}, [order, isStatusUpdating, handleStatusChange]);
|
||||
|
||||
// 폼 내용 렌더링
|
||||
const renderFormContent = () => {
|
||||
if (!order) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 기본 정보 */}
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-6">
|
||||
@@ -579,13 +555,42 @@ export function WorkOrderDetail({ orderId }: WorkOrderDetailProps) {
|
||||
{/* 이슈 섹션 */}
|
||||
<IssueSection order={order} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 데이터 없음
|
||||
if (!isLoading && !order) {
|
||||
return (
|
||||
<ServerErrorPage
|
||||
title="작업지시를 불러올 수 없습니다"
|
||||
message="작업지시를 찾을 수 없습니다."
|
||||
showBackButton={true}
|
||||
showHomeButton={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IntegratedDetailTemplate
|
||||
config={workOrderConfig}
|
||||
mode="view"
|
||||
initialData={{}}
|
||||
itemId={orderId}
|
||||
isLoading={isLoading}
|
||||
headerActions={customHeaderActions}
|
||||
renderView={() => renderFormContent()}
|
||||
renderForm={() => renderFormContent()}
|
||||
/>
|
||||
|
||||
{/* 작업일지 모달 */}
|
||||
<WorkLogModal
|
||||
open={isWorkLogOpen}
|
||||
onOpenChange={setIsWorkLogOpen}
|
||||
workOrderId={order.id}
|
||||
/>
|
||||
</PageLayout>
|
||||
{order && (
|
||||
<WorkLogModal
|
||||
open={isWorkLogOpen}
|
||||
onOpenChange={setIsWorkLogOpen}
|
||||
workOrderId={order.id}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
30
src/components/production/WorkOrders/workOrderConfig.ts
Normal file
30
src/components/production/WorkOrders/workOrderConfig.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { FileText } from 'lucide-react';
|
||||
import type { DetailConfig } from '@/components/templates/IntegratedDetailTemplate/types';
|
||||
|
||||
/**
|
||||
* 작업지시 상세 페이지 Config
|
||||
*
|
||||
* 참고: 이 config는 타이틀/버튼 영역만 정의
|
||||
* 폼 내용은 기존 WorkOrderDetail의 renderView에서 처리
|
||||
* (공정 진행 단계, 작업 품목 테이블, 상태 변경 버튼, 작업일지 모달 등 특수 기능 유지)
|
||||
*
|
||||
* 특이사항:
|
||||
* - view 모드만 지원 (수정은 별도 /edit 페이지로 이동)
|
||||
* - 삭제 기능 없음
|
||||
* - 상태 변경 버튼이 많음 (작업 시작, 완료, 되돌리기 등)
|
||||
*/
|
||||
export const workOrderConfig: DetailConfig = {
|
||||
title: '작업지시 상세',
|
||||
description: '작업지시 정보를 조회하고 관리합니다',
|
||||
icon: FileText,
|
||||
basePath: '/production/work-orders',
|
||||
fields: [], // renderView 사용으로 필드 정의 불필요
|
||||
gridColumns: 2,
|
||||
actions: {
|
||||
showBack: true,
|
||||
showDelete: false, // 작업지시는 삭제 기능 없음
|
||||
showEdit: true,
|
||||
backLabel: '목록',
|
||||
editLabel: '수정',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user