fix(WEB):수입검사 입력 모달 개선

- ImportInspectionInputModal: UI/로직 개선
- actions: API 호출 수정
This commit is contained in:
2026-02-12 00:01:14 +09:00
parent 90e1d428c4
commit 87a9ac9092
2 changed files with 111 additions and 31 deletions

View File

@@ -278,6 +278,28 @@ export function ImportInspectionInputModal({
return map;
}, [resolveData, template]);
// ===== Item → section/row mapping for normalized records =====
const itemSectionMap = useMemo(() => {
if (!resolveData) return new Map<string, { sectionId: number; rowIndex: number }>();
const map = new Map<string, { sectionId: number; rowIndex: number }>();
for (const section of resolveData.template.sections) {
section.items.forEach((item, idx) => {
map.set(String(item.id), { sectionId: section.id, rowIndex: idx });
});
}
return map;
}, [resolveData]);
// ===== Complex column ID (measurements) and result column ID (judgment) =====
const { complexColumnId, resultColumnId } = useMemo(() => {
if (!resolveData) return { complexColumnId: null as number | null, resultColumnId: null as number | null };
const cols = resolveData.template.columns;
const complex = cols.find(c => c.input_type === 'complex') ?? null;
const result = cols.find(c => c.input_type === 'select' || c.input_type === 'check') ??
cols.find(c => c.label.includes('판정')) ?? null;
return { complexColumnId: complex?.id ?? null, resultColumnId: result?.id ?? null };
}, [resolveData]);
// ===== Load template on modal open =====
useEffect(() => {
if (!open || !itemId) return;
@@ -317,6 +339,14 @@ export function ImportInspectionInputModal({
return;
}
// Reverse map: (sectionId, rowIndex) → inspectionItem.id
const reverseMap = new Map<string, string>();
for (const section of resolve.template.sections) {
section.items.forEach((item, idx) => {
reverseMap.set(`${section.id}_${idx}`, String(item.id));
});
}
const meas: Record<string, Record<number, string>> = {};
const okng: Record<string, Record<number, 'ok' | 'ng' | null>> = {};
let savedRemark = '';
@@ -326,28 +356,61 @@ export function ImportInspectionInputModal({
const key = d.field_key;
const val = d.field_value || '';
// {itemId}_n{index} - 숫자 측정값
const nMatch = key.match(/^(\d+)_n(\d+)$/);
if (nMatch) {
const id = nMatch[1];
const idx = parseInt(nMatch[2]) - 1;
// Footer fields
if (key === 'remark' || key === 'footer_remark') { savedRemark = val; continue; }
if (key === 'overall_result' || key === 'footer_judgement') {
savedOverall = (val === 'pass' || val === '적합') ? 'pass'
: (val === 'fail' || val === '부적합') ? 'fail' : null;
continue;
}
// 정규화 형식: section_id가 있으면 reverse map으로 item 찾기
if (d.section_id != null) {
const itemId = reverseMap.get(`${d.section_id}_${d.row_index}`);
if (!itemId) continue;
const nMatch = key.match(/^n(\d+)$/);
if (nMatch) {
const idx = parseInt(nMatch[1]) - 1;
if (!meas[itemId]) meas[itemId] = {};
meas[itemId][idx] = val;
continue;
}
const okMatch = key.match(/^n(\d+)_ok$/);
if (okMatch && val === 'OK') {
const idx = parseInt(okMatch[1]) - 1;
if (!okng[itemId]) okng[itemId] = {};
okng[itemId][idx] = 'ok';
continue;
}
const ngMatch = key.match(/^n(\d+)_ng$/);
if (ngMatch && val === 'NG') {
const idx = parseInt(ngMatch[1]) - 1;
if (!okng[itemId]) okng[itemId] = {};
okng[itemId][idx] = 'ng';
continue;
}
continue;
}
// 레거시 형식 fallback: {itemId}_n{N}
const nLegacy = key.match(/^(\d+)_n(\d+)$/);
if (nLegacy) {
const id = nLegacy[1];
const idx = parseInt(nLegacy[2]) - 1;
if (!meas[id]) meas[id] = {};
meas[id][idx] = val;
continue;
}
// {itemId}_okng_n{index} - OK/NG 값
const okngMatch = key.match(/^(\d+)_okng_n(\d+)$/);
if (okngMatch) {
const id = okngMatch[1];
const idx = parseInt(okngMatch[2]) - 1;
// 레거시: {itemId}_okng_n{N}
const okngLegacy = key.match(/^(\d+)_okng_n(\d+)$/);
if (okngLegacy) {
const id = okngLegacy[1];
const idx = parseInt(okngLegacy[2]) - 1;
if (!okng[id]) okng[id] = {};
okng[id][idx] = val === 'ok' ? 'ok' : val === 'ng' ? 'ng' : null;
continue;
}
if (key === 'remark') savedRemark = val;
if (key === 'overall_result') savedOverall = val === 'pass' ? 'pass' : val === 'fail' ? 'fail' : null;
}
setMeasurements(meas);
@@ -505,44 +568,61 @@ export function ImportInspectionInputModal({
uploadedFileIds = (uploadResult.data || []).map((f) => f.id);
}
// 2. field_key 기반 데이터 배열 생성
// 2. 정규화 형식 데이터 배열 생성
const data: Array<{
section_id?: number | null;
section_id: number | null;
column_id: number | null;
row_index: number;
field_key: string;
field_value: string | null;
}> = [];
for (const item of template.inspectionItems) {
const mapping = itemSectionMap.get(item.id);
const sectionId = mapping?.sectionId ?? null;
const rowIndex = mapping?.rowIndex ?? 0;
const isOkng = item.measurementType === 'okng';
for (let n = 0; n < item.measurementCount; n++) {
if (isOkng) {
const val = okngValues[item.id]?.[n];
data.push({
row_index: 0,
field_key: `${item.id}_okng_n${n + 1}`,
field_value: okngValues[item.id]?.[n] || null,
section_id: sectionId, column_id: complexColumnId, row_index: rowIndex,
field_key: `n${n + 1}_ok`, field_value: val === 'ok' ? 'OK' : '',
});
data.push({
section_id: sectionId, column_id: complexColumnId, row_index: rowIndex,
field_key: `n${n + 1}_ng`, field_value: val === 'ng' ? 'NG' : '',
});
} else {
data.push({
row_index: 0,
field_key: `${item.id}_n${n + 1}`,
field_value: measurements[item.id]?.[n] || null,
section_id: sectionId, column_id: complexColumnId, row_index: rowIndex,
field_key: `n${n + 1}`, field_value: measurements[item.id]?.[n] || null,
});
}
}
// 항목별 판정
data.push({
row_index: 0,
field_key: `${item.id}_result`,
field_value: getItemResult(item),
});
// 항목별 판정 (판정 컬럼이 있을 때만)
if (resultColumnId) {
const itemResult = getItemResult(item);
data.push({
section_id: sectionId, column_id: resultColumnId, row_index: rowIndex,
field_key: 'value',
field_value: itemResult === 'ok' ? '적합' : itemResult === 'ng' ? '부적합' : null,
});
}
}
// 종합판정 + 비고
data.push({ row_index: 0, field_key: 'overall_result', field_value: overallResult });
data.push({ row_index: 0, field_key: 'remark', field_value: remark || null });
data.push({
section_id: null, column_id: null, row_index: 0,
field_key: 'overall_result',
field_value: overallResult === 'pass' ? '적합' : overallResult === 'fail' ? '부적합' : null,
});
data.push({
section_id: null, column_id: null, row_index: 0,
field_key: 'remark', field_value: remark || null,
});
// 3. 첨부파일 (기존 + 신규)
const attachments = [

View File

@@ -1900,7 +1900,7 @@ export async function saveInspectionData(params: {
templateId: number;
itemId: number;
title?: string;
data: Array<{ section_id?: number | null; row_index: number; field_key: string; field_value: string | null }>;
data: Array<{ section_id?: number | null; column_id?: number | null; row_index: number; field_key: string; field_value: string | null }>;
attachments?: Array<{ file_id: number; attachment_type: string; description?: string }>;
receivingId: string;
inspectionResult?: 'pass' | 'fail' | null;