feat: [검사문서] TemplateInspectionContent 절곡(bending) save/restore 지원

- documentRecords prop 추가 (document_data EAV 레코드 복원용)
- getInspectionData()에 bending 분기 추가: 구성품별 field_key 인코딩
  (b{idx}_ok/ng, b{idx}_p{pt}_n1, b{idx}_n{n}, b{idx}_judgment, b{idx}_value)
- 비-bending 모드 기존 로직 guard 추가 (if !isBending)
- useEffect로 documentRecords에서 bending cellValues 복원 로직 구현
This commit is contained in:
2026-02-26 21:38:38 +09:00
parent 13d27553b9
commit 7527841fe0

View File

@@ -54,6 +54,14 @@ interface TemplateInspectionContentProps {
readOnly?: boolean;
workItems?: WorkItemData[];
inspectionDataMap?: InspectionDataMap;
/** 기존 document_data EAV 레코드 (문서 로딩 시 복원용) */
documentRecords?: Array<{
section_id: number | null;
column_id: number | null;
row_index: number;
field_key: string;
field_value: string | null;
}>;
}
// ===== 유틸 =====
@@ -301,7 +309,7 @@ function SectionImage({ section }: { section: { id: number; title?: string; name
// ===== 컴포넌트 =====
export const TemplateInspectionContent = forwardRef<InspectionContentRef, TemplateInspectionContentProps>(
function TemplateInspectionContent({ data: order, template, readOnly = false, workItems, inspectionDataMap }, ref) {
function TemplateInspectionContent({ data: order, template, readOnly = false, workItems, inspectionDataMap, documentRecords }, ref) {
const fullDate = getFullDate();
const { primaryAssignee } = getOrderInfo(order);
@@ -486,6 +494,84 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inspectionDataMap, workItems]);
// ===== Bending: document_data EAV 레코드에서 복원 =====
useEffect(() => {
if (!isBending || !documentRecords || documentRecords.length === 0 || bendingProducts.length === 0) return;
const initial: Record<string, CellValue> = {};
// field_key 패턴: b{productIdx}_ok, b{productIdx}_ng, b{productIdx}_p{pointIdx}_n1, b{productIdx}_n{n}, b{productIdx}_judgment
for (const rec of documentRecords) {
const fk = rec.field_key;
if (!fk.startsWith('b')) continue;
const val = rec.field_value;
if (val == null) continue;
// b{productIdx}_ok / b{productIdx}_ng → check status
const checkMatch = fk.match(/^b(\d+)_(ok|ng)$/);
if (checkMatch && rec.column_id) {
const productIdx = parseInt(checkMatch[1], 10);
const cellKey = `b-${productIdx}-${rec.column_id}`;
if (checkMatch[2] === 'ok' && val === 'OK') {
initial[cellKey] = { ...initial[cellKey], status: 'good' };
} else if (checkMatch[2] === 'ng' && val === 'NG') {
initial[cellKey] = { ...initial[cellKey], status: 'bad' };
}
continue;
}
// b{productIdx}_p{pointIdx}_n1 → gap measurement
const gapMatch = fk.match(/^b(\d+)_p(\d+)_n(\d+)$/);
if (gapMatch && rec.column_id) {
const productIdx = parseInt(gapMatch[1], 10);
const pointIdx = parseInt(gapMatch[2], 10);
const cellKey = `b-${productIdx}-p${pointIdx}-${rec.column_id}`;
initial[cellKey] = { measurements: [val, '', ''] };
continue;
}
// b{productIdx}_n{n} → complex measurement (길이/너비)
const complexMatch = fk.match(/^b(\d+)_n(\d+)$/);
if (complexMatch && rec.column_id) {
const productIdx = parseInt(complexMatch[1], 10);
const mIdx = parseInt(complexMatch[2], 10) - 1;
const cellKey = `b-${productIdx}-${rec.column_id}`;
const prev = initial[cellKey]?.measurements || ['', '', ''];
const m: [string, string, string] = [...prev] as [string, string, string];
m[mIdx] = val;
initial[cellKey] = { ...initial[cellKey], measurements: m };
continue;
}
// b{productIdx}_judgment → skip (자동 계산, 복원 불필요)
if (fk.match(/^b\d+_judgment$/)) continue;
// b{productIdx}_value → fallback value
const valMatch = fk.match(/^b(\d+)_value$/);
if (valMatch && rec.column_id) {
const productIdx = parseInt(valMatch[1], 10);
const cellKey = `b-${productIdx}-${rec.column_id}`;
initial[cellKey] = { value: val };
continue;
}
}
// overall_result, remark 복원
for (const rec of documentRecords) {
if (rec.field_key === 'overall_result' && rec.field_value) {
// overallResult는 자동 계산이므로 별도 처리 불필요
}
if (rec.field_key === 'remark' && rec.field_value) {
setInadequateContent(rec.field_value);
}
}
if (Object.keys(initial).length > 0) {
setCellValues(prev => ({ ...prev, ...initial }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [documentRecords, isBending, bendingProducts]);
const updateCell = (key: string, update: Partial<CellValue>) => {
setCellValues(prev => ({
...prev,
@@ -563,7 +649,99 @@ export const TemplateInspectionContent = forwardRef<InspectionContentRef, Templa
}
// ===== 2. 행별 검사 데이터 =====
effectiveWorkItems.forEach((wi, rowIdx) => {
// Bending 모드: 구성품별 데이터 (개소 단위, field_key에 구성품/포인트 인코딩)
if (isBending && bendingProducts.length > 0) {
bendingProducts.forEach((product, productIdx) => {
for (const col of template.columns) {
const label = col.label.trim();
const isGapCol = col.id === gapColumnId;
// text 컬럼 (분류/제품명, 타입) → bendingInfo에서 동적 생성이므로 저장 불필요
if (col.column_type === 'text') continue;
// 판정 컬럼 → 자동 계산 결과 저장
if (isJudgmentColumn(label)) {
const judgment = getBendingProductJudgment(productIdx);
if (judgment) {
records.push({
section_id: null, column_id: col.id, row_index: 0,
field_key: `b${productIdx}_judgment`,
field_value: judgment === '적' ? 'OK' : 'NG',
});
}
continue;
}
// 간격 컬럼 (per-point 데이터)
if (isGapCol) {
product.gapPoints.forEach((_gp, pointIdx) => {
const cellKey = `b-${productIdx}-p${pointIdx}-${col.id}`;
const cell = cellValues[cellKey];
if (cell?.measurements?.[0]) {
records.push({
section_id: null, column_id: col.id, row_index: 0,
field_key: `b${productIdx}_p${pointIdx}_n1`,
field_value: cell.measurements[0],
});
}
});
continue;
}
// 비간격 merged 컬럼
const cellKey = `b-${productIdx}-${col.id}`;
const cell = cellValues[cellKey];
// check 컬럼 (절곡상태)
if (col.column_type === 'check') {
records.push({
section_id: null, column_id: col.id, row_index: 0,
field_key: `b${productIdx}_ok`,
field_value: cell?.status === 'good' ? 'OK' : '',
});
records.push({
section_id: null, column_id: col.id, row_index: 0,
field_key: `b${productIdx}_ng`,
field_value: cell?.status === 'bad' ? 'NG' : '',
});
continue;
}
// complex 컬럼 (길이/너비 측정)
if (col.column_type === 'complex' && col.sub_labels) {
let inputIdx = 0;
for (const sl of col.sub_labels) {
const slLower = sl.toLowerCase();
if (slLower.includes('도면') || slLower.includes('기준')) continue;
if (slLower.includes('point') || slLower.includes('포인트')) continue;
const n = inputIdx + 1;
const val = cell?.measurements?.[inputIdx] || null;
if (val) {
records.push({
section_id: null, column_id: col.id, row_index: 0,
field_key: `b${productIdx}_n${n}`,
field_value: val,
});
}
inputIdx++;
}
continue;
}
// fallback
if (cell?.value) {
records.push({
section_id: null, column_id: col.id, row_index: 0,
field_key: `b${productIdx}_value`,
field_value: cell.value,
});
}
}
});
}
// 비-Bending 모드: 개소(WorkItem)별 데이터
if (!isBending) effectiveWorkItems.forEach((wi, rowIdx) => {
for (const col of template.columns) {
// 일련번호 컬럼 → 저장 (mng show에서 표시용)
if (isSerialColumn(col.label)) {