fix(WEB): 검사성적서 보기 레이아웃 및 데이터 폴백 개선
- TemplateInspectionContent: 푸터 비고/종합판정 높이 동일 배치, 판정 표시 간소화 - InspectionReportModal: props 데이터 비어있을 때 API 로딩 데이터 폴백 처리
This commit is contained in:
@@ -135,9 +135,10 @@ export function InspectionReportModal({
|
||||
const [selfTemplateData, setSelfTemplateData] = useState<InspectionTemplateData | null>(null);
|
||||
|
||||
// props에서 목업 제외한 실제 개소만 사용 (WorkerScreen에서 apiItems + mockItems가 합쳐져 전달됨)
|
||||
// React에서는 개소 미등록 시 성적서 버튼 자체가 노출되지 않으므로 API fallback 불필요
|
||||
const effectiveWorkItems = propWorkItems?.filter(w => !w.id.startsWith('mock-'));
|
||||
const effectiveInspectionDataMap = propInspectionDataMap;
|
||||
// props 데이터가 없거나 비어있으면 API 로딩 데이터를 fallback으로 사용
|
||||
const propFiltered = propWorkItems?.filter(w => !w.id.startsWith('mock-'));
|
||||
const effectiveWorkItems = (propFiltered && propFiltered.length > 0) ? propFiltered : apiWorkItems ?? undefined;
|
||||
const effectiveInspectionDataMap = (propInspectionDataMap && propInspectionDataMap.size > 0) ? propInspectionDataMap : apiInspectionDataMap ?? undefined;
|
||||
|
||||
// 목업 WorkOrder 생성
|
||||
const createMockOrder = (id: string, pType: ProcessType): WorkOrder => ({
|
||||
|
||||
@@ -156,6 +156,17 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
[dataSections]
|
||||
);
|
||||
|
||||
// sectionItem.id → section.id 역매핑
|
||||
const itemSectionMap = useMemo(() => {
|
||||
const map = new Map<number, number>();
|
||||
for (const s of dataSections) {
|
||||
for (const item of s.items) {
|
||||
map.set(item.id, s.id);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [dataSections]);
|
||||
|
||||
// 컬럼 → 섹션 아이템 매핑 (라벨 정규화 비교)
|
||||
const columnItemMap = useMemo(() => {
|
||||
const map = new Map<number, InspectionTemplateSectionItem>();
|
||||
@@ -183,6 +194,8 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
const effectiveWorkItems = workItems || [];
|
||||
|
||||
// inspectionDataMap에서 초기값 복원
|
||||
// InspectionInputModal 저장 키: section_{sectionId}_item_{itemId} / 값: "ok"|"ng"|number
|
||||
// TemplateInspectionContent 내부 키: {rowIdx}-{colId} / 값: CellValue
|
||||
useEffect(() => {
|
||||
if (!inspectionDataMap || !workItems) return;
|
||||
const initial: Record<string, CellValue> = {};
|
||||
@@ -192,14 +205,54 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
for (const col of template.columns) {
|
||||
const sectionItem = columnItemMap.get(col.id);
|
||||
if (!sectionItem) continue;
|
||||
const key = `${rowIdx}-${col.id}`;
|
||||
const val = itemData.templateValues?.[`item_${sectionItem.id}`];
|
||||
if (val && typeof val === 'object') {
|
||||
initial[key] = val as CellValue;
|
||||
const cellKey = `${rowIdx}-${col.id}`;
|
||||
const sectionId = itemSectionMap.get(sectionItem.id);
|
||||
|
||||
// InspectionInputModal 키 형식으로 조회
|
||||
const inputKey = sectionId != null
|
||||
? `section_${sectionId}_item_${sectionItem.id}`
|
||||
: null;
|
||||
// 레거시 키 형식 폴백
|
||||
const legacyKey = `item_${sectionItem.id}`;
|
||||
|
||||
const rawVal = (inputKey ? itemData.templateValues?.[inputKey] : undefined)
|
||||
?? itemData.templateValues?.[legacyKey];
|
||||
|
||||
if (rawVal == null) continue;
|
||||
|
||||
// 값 형식 변환: InputModal 형식 → CellValue 형식
|
||||
// complex 컬럼(측정값)은 measurements 배열, check 컬럼은 status, 나머지는 value
|
||||
const isComplex = col.column_type === 'complex' && col.sub_labels && col.sub_labels.length > 0;
|
||||
|
||||
if (typeof rawVal === 'object') {
|
||||
// 이미 CellValue 객체
|
||||
initial[cellKey] = rawVal as CellValue;
|
||||
} else if (rawVal === 'ok') {
|
||||
initial[cellKey] = { status: 'good' };
|
||||
} else if (rawVal === 'ng') {
|
||||
initial[cellKey] = { status: 'bad' };
|
||||
} else if (isComplex) {
|
||||
// complex 컬럼: 숫자/문자열을 첫 번째 측정값으로 매핑
|
||||
const strVal = typeof rawVal === 'number' ? String(rawVal) : String(rawVal);
|
||||
initial[cellKey] = { measurements: [strVal, '', ''] };
|
||||
} else if (typeof rawVal === 'number') {
|
||||
initial[cellKey] = { value: String(rawVal) };
|
||||
} else if (typeof rawVal === 'string') {
|
||||
initial[cellKey] = { value: rawVal };
|
||||
}
|
||||
}
|
||||
});
|
||||
if (Object.keys(initial).length > 0) setCellValues(initial);
|
||||
|
||||
// 부적합 내용 복원: 각 개소의 nonConformingContent를 수집
|
||||
const remarks: string[] = [];
|
||||
workItems.forEach((wi) => {
|
||||
const itemData = inspectionDataMap.get(wi.id);
|
||||
if (itemData?.nonConformingContent) {
|
||||
remarks.push(itemData.nonConformingContent);
|
||||
}
|
||||
});
|
||||
if (remarks.length > 0) setInadequateContent(remarks.join('\n'));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [inspectionDataMap, workItems]);
|
||||
|
||||
@@ -265,7 +318,11 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
const sectionItem = columnItemMap.get(col.id);
|
||||
if (!sectionItem) return acc;
|
||||
const key = `${idx}-${col.id}`;
|
||||
acc[`item_${sectionItem.id}`] = cellValues[key] || null;
|
||||
const sectionId = itemSectionMap.get(sectionItem.id);
|
||||
const saveKey = sectionId != null
|
||||
? `section_${sectionId}_item_${sectionItem.id}`
|
||||
: `item_${sectionItem.id}`;
|
||||
acc[saveKey] = cellValues[key] || null;
|
||||
return acc;
|
||||
}, {} as Record<string, CellValue | null>),
|
||||
}));
|
||||
@@ -608,16 +665,16 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ===== 푸터: 비고(좌) + 종합판정(우) 병렬 배치 ===== */}
|
||||
<div className="mt-4 flex gap-4">
|
||||
{/* ===== 푸터: 비고(좌) + 종합판정(우) 높이 동일 배치 ===== */}
|
||||
<div className="mt-4 flex items-stretch gap-4">
|
||||
<div className="flex-1">
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<table className="w-full h-full border-collapse text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-3 py-2 bg-gray-100 font-medium" style={{ width: 100 }}>
|
||||
<td className="border border-gray-400 px-3 py-2 bg-gray-100 font-medium align-top" style={{ width: 100 }}>
|
||||
{template.footer_remark_label || '비고'}
|
||||
</td>
|
||||
<td className="border border-gray-400 px-3 py-2">
|
||||
<td className="border border-gray-400 px-3 py-2 align-top">
|
||||
<textarea
|
||||
value={inadequateContent}
|
||||
onChange={e => !readOnly && setInadequateContent(e.target.value)}
|
||||
@@ -631,7 +688,7 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<table className="border-collapse text-xs">
|
||||
<table className="h-full border-collapse text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-4 py-2 bg-gray-100 font-medium">
|
||||
@@ -640,27 +697,14 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-4 py-3 text-center">
|
||||
{template.footer_judgement_options?.filter(o => o.trim()).length ? (
|
||||
template.footer_judgement_options.filter(o => o.trim()).map((option, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className={`inline-block mx-1 px-2 py-0.5 border rounded ${
|
||||
overallResult === option
|
||||
? 'border-blue-500 bg-blue-50 text-blue-600 font-bold'
|
||||
: overallResult && overallResult !== option
|
||||
? 'border-gray-200 text-gray-300'
|
||||
: 'border-gray-300 text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
{overallResult ? (
|
||||
<span className={`font-bold text-sm ${
|
||||
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
|
||||
overallResult === '합격' ? 'text-blue-600' : 'text-red-600'
|
||||
}`}>
|
||||
{overallResult || '-'}
|
||||
{overallResult}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-300 text-sm"> </span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user