@@ -358,27 +340,14 @@ export function InspectionReportDocument({ data }: InspectionReportDocumentProps
if (measuredValueCoverage.covered.has(flatIdx)) {
return null;
}
- // 편집 가능한 측정값 셀 → input 렌더
- if (row.editable) {
- return (
-
- handleMeasuredValueChange(flatIdx, e.target.value)}
- className="w-full h-full px-1 py-0.5 text-center text-[11px] border border-gray-300 rounded-sm focus:outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-200"
- placeholder=""
- />
- |
- );
- }
+ // 읽기 전용 - 측정값 표시만
return (
{row.measuredValue || ''}
|
);
})()}
- {/* 판정 */}
+ {/* 판정 - 읽기 전용 */}
{renderJudgment && (
row.hideJudgment ? (
-
-
+
|
)
diff --git a/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx b/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx
index ebfda657..eabe523f 100644
--- a/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx
+++ b/src/components/quality/InspectionManagement/documents/InspectionReportModal.tsx
@@ -1,30 +1,155 @@
'use client';
/**
- * 제품검사성적서 모달
+ * 제품검사성적서 모달 (읽기전용)
* DocumentViewer를 사용하여 문서 표시 + 인쇄/PDF 기능 제공
+ * 검사 입력은 별도 ProductInspectionInputModal에서 진행
+ *
+ * 페이지네이션: 층(orderItem)별로 검사성적서 표시
*/
-import { Save } from 'lucide-react';
+import { useState, useCallback, useMemo, useEffect } from 'react';
import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DocumentViewer } from '@/components/document-system';
import { InspectionReportDocument } from './InspectionReportDocument';
-import type { InspectionReportDocument as InspectionReportDocumentType } from '../types';
+import type {
+ InspectionReportDocument as InspectionReportDocumentType,
+ OrderSettingItem,
+ ProductInspection
+} from '../types';
+import { buildReportDocumentDataForItem } from '../mockData';
interface InspectionReportModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
data: InspectionReportDocumentType | null;
- onSave?: () => void;
+ /** 페이지네이션용: 원본 inspection 데이터 */
+ inspection?: ProductInspection | null;
+ /** 페이지네이션용: orderItems (수정 모드에서는 formData.orderItems) */
+ orderItems?: OrderSettingItem[];
}
export function InspectionReportModal({
open,
onOpenChange,
data,
- onSave,
+ inspection,
+ orderItems,
}: InspectionReportModalProps) {
- if (!data) return null;
+ const [currentPage, setCurrentPage] = useState(1);
+ const [inputPage, setInputPage] = useState('1');
+
+ // 총 페이지 수 (orderItems가 있으면 그 길이, 아니면 1)
+ const totalPages = useMemo(() => {
+ if (orderItems && orderItems.length > 0) {
+ return orderItems.length;
+ }
+ return 1;
+ }, [orderItems]);
+
+ // 모달 열릴 때 페이지 초기화
+ useEffect(() => {
+ if (open) {
+ setCurrentPage(1);
+ setInputPage('1');
+ }
+ }, [open]);
+
+ // 현재 페이지에 해당하는 문서 데이터
+ const currentData = useMemo(() => {
+ // 페이지네이션이 가능한 경우 (inspection과 orderItems 모두 있음)
+ if (inspection && orderItems && orderItems.length > 0) {
+ const currentItem = orderItems[currentPage - 1];
+ if (currentItem) {
+ return buildReportDocumentDataForItem(inspection, currentItem);
+ }
+ }
+ // 기본: data prop 사용
+ return data;
+ }, [inspection, orderItems, currentPage, data]);
+
+ // 이전 페이지
+ const handlePrevPage = useCallback(() => {
+ if (currentPage > 1) {
+ const newPage = currentPage - 1;
+ setCurrentPage(newPage);
+ setInputPage(String(newPage));
+ }
+ }, [currentPage]);
+
+ // 다음 페이지
+ const handleNextPage = useCallback(() => {
+ if (currentPage < totalPages) {
+ const newPage = currentPage + 1;
+ setCurrentPage(newPage);
+ setInputPage(String(newPage));
+ }
+ }, [currentPage, totalPages]);
+
+ // 페이지 입력 후 이동
+ const handleGoToPage = useCallback(() => {
+ const pageNum = parseInt(inputPage, 10);
+ if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {
+ setCurrentPage(pageNum);
+ } else {
+ // 잘못된 입력 → 현재 페이지로 복원
+ setInputPage(String(currentPage));
+ }
+ }, [inputPage, totalPages, currentPage]);
+
+ // 엔터키로 이동
+ const handleKeyDown = useCallback((e: React.KeyboardEvent
) => {
+ if (e.key === 'Enter') {
+ handleGoToPage();
+ }
+ }, [handleGoToPage]);
+
+ if (!currentData) return null;
+
+ // 페이지네이션 UI 컴포넌트
+ const paginationUI = totalPages > 1 ? (
+
+
+
+ setInputPage(e.target.value)}
+ onKeyDown={handleKeyDown}
+ className="w-14 h-8 text-center"
+ />
+ / {totalPages}
+
+
+
+
+ ) : null;
return (
-
- 저장
-
- }
+ toolbarExtra={paginationUI}
>
-
+
);
}
diff --git a/src/components/quality/InspectionManagement/mockData.ts b/src/components/quality/InspectionManagement/mockData.ts
index 15f3f660..bfad9d0c 100644
--- a/src/components/quality/InspectionManagement/mockData.ts
+++ b/src/components/quality/InspectionManagement/mockData.ts
@@ -8,6 +8,7 @@ import type {
InspectionRequestDocument,
InspectionReportDocument,
ReportInspectionItem,
+ ProductInspectionData,
} from './types';
// ===== 상태/색상 매핑 =====
@@ -76,6 +77,8 @@ const defaultOrderItems: OrderSettingItem[] = [
{
id: 'oi-1',
orderNumber: '123123',
+ siteName: '현장명',
+ deliveryDate: '2026-01-01',
floor: '1층',
symbol: '부호명',
orderWidth: 4100,
@@ -87,6 +90,8 @@ const defaultOrderItems: OrderSettingItem[] = [
{
id: 'oi-2',
orderNumber: '123123',
+ siteName: '현장명',
+ deliveryDate: '2026-01-01',
floor: '2층',
symbol: '부호명',
orderWidth: 4100,
@@ -452,25 +457,152 @@ export const mockReportInspectionItems: ReportInspectionItem[] = [
{ no: 9, category: '내충격시험', criteria: '방화상 유해한 파괴, 박리 탈락 유무', method: '', frequency: '' },
];
+/** pass/fail → 적합/부적합 변환 */
+const convertJudgment = (value: 'pass' | 'fail' | null): '적합' | '부적합' | undefined => {
+ if (value === 'pass') return '적합';
+ if (value === 'fail') return '부적합';
+ return undefined;
+};
+
+/** 검사 데이터를 검사항목에 매핑 */
+const mapInspectionDataToItems = (
+ items: ReportInspectionItem[],
+ inspectionData?: ProductInspectionData
+): ReportInspectionItem[] => {
+ if (!inspectionData) return items;
+
+ return items.map((item) => {
+ const newItem = { ...item };
+
+ // 겉모양 검사 매핑
+ if (item.category === '겉모양') {
+ if (item.subCategory === '가공상태') {
+ newItem.judgment = convertJudgment(inspectionData.appearanceProcessing);
+ } else if (item.subCategory === '재봉상태') {
+ newItem.judgment = convertJudgment(inspectionData.appearanceSewing);
+ } else if (item.subCategory === '조립상태') {
+ newItem.judgment = convertJudgment(inspectionData.appearanceAssembly);
+ } else if (item.subCategory === '연기차단재') {
+ newItem.judgment = convertJudgment(inspectionData.appearanceSmokeBarrier);
+ } else if (item.subCategory === '하단마감재') {
+ newItem.judgment = convertJudgment(inspectionData.appearanceBottomFinish);
+ }
+ }
+
+ // 모터 매핑
+ if (item.category === '모터') {
+ newItem.judgment = convertJudgment(inspectionData.motor);
+ }
+
+ // 재질 매핑
+ if (item.category === '재질') {
+ newItem.judgment = convertJudgment(inspectionData.material);
+ }
+
+ // 치수 검사 매핑
+ if (item.category === '치수\n(오픈사이즈)') {
+ if (item.subCategory === '길이') {
+ newItem.measuredValue = inspectionData.lengthValue?.toString() || '';
+ newItem.judgment = convertJudgment(inspectionData.lengthJudgment);
+ } else if (item.subCategory === '높이') {
+ newItem.measuredValue = inspectionData.heightValue?.toString() || '';
+ newItem.judgment = convertJudgment(inspectionData.heightJudgment);
+ } else if (item.subCategory === '가이드레일 간격') {
+ newItem.measuredValue = inspectionData.guideRailGapValue?.toString() || '';
+ newItem.judgment = convertJudgment(inspectionData.guideRailGap);
+ } else if (item.subCategory === '하단막대 간격') {
+ newItem.measuredValue = inspectionData.bottomFinishGapValue?.toString() || '';
+ newItem.judgment = convertJudgment(inspectionData.bottomFinishGap);
+ }
+ }
+
+ // 내화시험 매핑
+ if (item.category === '내화시험' && item.judgmentSpan) {
+ newItem.judgment = convertJudgment(inspectionData.fireResistanceTest);
+ }
+
+ // 차연시험 매핑
+ if (item.category === '차연시험') {
+ newItem.judgment = convertJudgment(inspectionData.smokeLeakageTest);
+ }
+
+ // 개폐시험 매핑
+ if (item.category === '개폐시험') {
+ newItem.judgment = convertJudgment(inspectionData.openCloseTest);
+ }
+
+ // 내충격시험 매핑
+ if (item.category === '내충격시험') {
+ newItem.judgment = convertJudgment(inspectionData.impactTest);
+ }
+
+ return newItem;
+ });
+};
+
/** ProductInspection → InspectionReportDocument 변환 */
export const buildReportDocumentData = (
- inspection: ProductInspection
-): InspectionReportDocument => ({
- documentNumber: `RPT-${inspection.qualityDocNumber}`,
- createdDate: inspection.receptionDate,
- approvalLine: [
- { role: '작성', name: inspection.scheduleInfo.inspector || inspection.author, department: '' },
- { role: '승인', name: '', department: '' },
- ],
- productName: '방화스크린',
- productLotNo: inspection.qualityDocNumber,
- productCode: '',
- lotSize: String(inspection.locationCount),
- client: inspection.client,
- inspectionDate: inspection.scheduleInfo.startDate,
- siteName: inspection.siteName,
- inspector: inspection.scheduleInfo.inspector,
- inspectionItems: mockReportInspectionItems,
- specialNotes: '',
- finalJudgment: inspection.status === '완료' ? '합격' : '합격',
-});
+ inspection: ProductInspection,
+ orderItems?: OrderSettingItem[]
+): InspectionReportDocument => {
+ // 검사 데이터가 있는 첫 번째 orderItem 찾기
+ const items = orderItems || inspection.orderItems;
+ const itemWithData = items.find((item) => item.inspectionData);
+ const inspectionData = itemWithData?.inspectionData;
+
+ // 검사 항목에 검사 데이터 매핑
+ const mappedInspectionItems = mapInspectionDataToItems(mockReportInspectionItems, inspectionData);
+
+ return {
+ documentNumber: `RPT-${inspection.qualityDocNumber}`,
+ createdDate: inspection.receptionDate,
+ approvalLine: [
+ { role: '작성', name: inspection.scheduleInfo.inspector || inspection.author, department: '' },
+ { role: '승인', name: '', department: '' },
+ ],
+ productName: inspectionData?.productName || '방화스크린',
+ productLotNo: inspection.qualityDocNumber,
+ productCode: '',
+ lotSize: String(inspection.locationCount),
+ client: inspection.client,
+ inspectionDate: inspection.scheduleInfo.startDate,
+ siteName: inspection.siteName,
+ inspector: inspection.scheduleInfo.inspector,
+ productImages: inspectionData?.productImages || [],
+ inspectionItems: mappedInspectionItems,
+ specialNotes: inspectionData?.specialNotes || '',
+ finalJudgment: inspection.status === '완료' ? '합격' : '합격',
+ };
+};
+
+/** 특정 OrderItem에 대한 InspectionReportDocument 빌드 (페이지네이션용) */
+export const buildReportDocumentDataForItem = (
+ inspection: ProductInspection,
+ orderItem: OrderSettingItem
+): InspectionReportDocument => {
+ const inspectionData = orderItem.inspectionData;
+
+ // 검사 항목에 검사 데이터 매핑
+ const mappedInspectionItems = mapInspectionDataToItems(mockReportInspectionItems, inspectionData);
+
+ return {
+ documentNumber: `RPT-${inspection.qualityDocNumber}-${orderItem.floor}`,
+ createdDate: inspection.receptionDate,
+ approvalLine: [
+ { role: '작성', name: inspection.scheduleInfo.inspector || inspection.author, department: '' },
+ { role: '승인', name: '', department: '' },
+ ],
+ productName: inspectionData?.productName || '방화스크린',
+ productLotNo: `${inspection.qualityDocNumber}-${orderItem.floor}`,
+ productCode: orderItem.symbol || '',
+ lotSize: '1',
+ client: inspection.client,
+ inspectionDate: inspection.scheduleInfo.startDate,
+ siteName: `${inspection.siteName} (${orderItem.floor})`,
+ inspector: inspection.scheduleInfo.inspector,
+ productImages: inspectionData?.productImages || [],
+ inspectionItems: mappedInspectionItems,
+ specialNotes: inspectionData?.specialNotes || '',
+ finalJudgment: inspection.status === '완료' ? '합격' : '합격',
+ };
+};
diff --git a/src/components/quality/InspectionManagement/types.ts b/src/components/quality/InspectionManagement/types.ts
index 94209887..58f92207 100644
--- a/src/components/quality/InspectionManagement/types.ts
+++ b/src/components/quality/InspectionManagement/types.ts
@@ -55,10 +55,12 @@ export interface InspectionScheduleInfo {
// ===== 수주 관련 =====
-// 수주 설정 항목 (상세 페이지 테이블)
+// 수주 설정 항목 (상세 페이지 아코디언 내부 테이블)
export interface OrderSettingItem {
id: string;
orderNumber: string; // 수주번호
+ siteName: string; // 현장명
+ deliveryDate: string; // 납품일
floor: string; // 층수
symbol: string; // 부호
orderWidth: number; // 수주 규격 - 가로
@@ -66,6 +68,50 @@ export interface OrderSettingItem {
constructionWidth: number; // 시공 규격 - 가로
constructionHeight: number; // 시공 규격 - 세로
changeReason: string; // 변경사유
+ // 검사 결과 데이터
+ inspectionData?: ProductInspectionData;
+}
+
+// 수주 그룹 (아코디언 상위 레벨)
+export interface OrderGroup {
+ orderNumber: string; // 수주번호
+ siteName: string; // 현장명
+ deliveryDate: string; // 납품일
+ locationCount: number; // 개소
+ items: OrderSettingItem[]; // 하위 항목들
+}
+
+// 제품검사 입력 데이터
+export interface ProductInspectionData {
+ productName: string;
+ specification: string;
+ // 제품 사진
+ productImages: string[];
+ // 겉모양 검사
+ appearanceProcessing: 'pass' | 'fail' | null; // 가공상태
+ appearanceSewing: 'pass' | 'fail' | null; // 재봉상태
+ appearanceAssembly: 'pass' | 'fail' | null; // 조립상태
+ appearanceSmokeBarrier: 'pass' | 'fail' | null; // 연기차단재
+ appearanceBottomFinish: 'pass' | 'fail' | null; // 하단마감재
+ motor: 'pass' | 'fail' | null; // 모터
+ // 재질/치수 검사
+ material: 'pass' | 'fail' | null; // 재질
+ lengthValue: number | null; // 길이 측정값
+ lengthJudgment: 'pass' | 'fail' | null; // 길이 판정
+ heightValue: number | null; // 높이 측정값
+ heightJudgment: 'pass' | 'fail' | null; // 높이 판정
+ guideRailGapValue: number | null; // 가이드레일 홈간격 측정값
+ guideRailGap: 'pass' | 'fail' | null; // 가이드레일 홈간격 판정
+ bottomFinishGapValue: number | null; // 하단마감재 간격 측정값
+ bottomFinishGap: 'pass' | 'fail' | null; // 하단마감재 간격 판정
+ // 시험 검사
+ fireResistanceTest: 'pass' | 'fail' | null; // 내화시험
+ smokeLeakageTest: 'pass' | 'fail' | null; // 차연시험
+ openCloseTest: 'pass' | 'fail' | null; // 개폐시험
+ impactTest: 'pass' | 'fail' | null; // 내충격시험
+ // 특이사항
+ hasSpecialNotes: boolean;
+ specialNotes: string;
}
// 수주 선택 모달 항목
@@ -227,7 +273,7 @@ export interface InspectionReportDocument {
inspectionDate: string; // 검사일자
siteName: string; // 현장명
inspector: string; // 검사자
- productImage?: string; // 제품 사진 URL
+ productImages?: string[]; // 제품 사진 URLs (2개)
inspectionItems: ReportInspectionItem[];
specialNotes: string; // 특이사항
finalJudgment: JudgmentResult; // 최종 판정