feat: [receiving] 수입검사 모달에 테스트입력/초기화 토글 버튼 추가

- 테스트입력: 전체 항목에 합격값 자동 채움 (tolerance/criteria 기반)
- 초기화: 입력값 전체 리셋
- 수입검사 타이틀 옆 눈에 띄는 색상 버튼
This commit is contained in:
김보곤
2026-03-18 22:54:29 +09:00
parent f5db1620e5
commit 46fcc64708

View File

@@ -233,6 +233,9 @@ export function ImportInspectionInputModal({
const [overallResult, setOverallResult] = useState<'pass' | 'fail' | null>(null);
const [remark, setRemark] = useState('');
// Test fill toggle
const [isTestFilled, setIsTestFilled] = useState(false);
// Photos
const [newPhotos, setNewPhotos] = useState<File[]>([]);
const [existingPhotos, setExistingPhotos] = useState<ExistingFile[]>([]);
@@ -453,6 +456,78 @@ export function ImportInspectionInputModal({
setCurrentTab(0);
}, []);
// ===== 테스트 데이터 채우기 (전체 합격값) =====
const generatePassingValue = useCallback(
(item: InspectionItem): string => {
const raw = rawDataMap.get(item.id);
const base = baseValueMap.get(item.id);
// 1. 기준값이 있으면 그대로 사용 (공차 범위 중앙)
if (base != null) return String(base);
// 2. Tolerance 기반
if (raw?.tolerance) {
switch (raw.tolerance.type) {
case 'range': {
const min = parseFloat(String(raw.tolerance.min || 0));
const max = parseFloat(String(raw.tolerance.max || 0));
return String(Math.round(((min + max) / 2) * 100) / 100);
}
case 'limit': {
const lv = parseFloat(String(raw.tolerance.value || 0));
if (raw.tolerance.op === 'gte') return String(lv);
if (raw.tolerance.op === 'gt') return String(Math.round((lv + 1) * 100) / 100);
if (raw.tolerance.op === 'lte') return String(lv);
if (raw.tolerance.op === 'lt') return String(Math.round((lv - 1) * 100) / 100);
break;
}
}
}
// 3. Criteria 기반
if (raw?.criteria) {
const { min, max } = raw.criteria;
if (min != null && max != null) return String(Math.round(((min + max) / 2) * 100) / 100);
if (min != null) return String(min);
if (max != null) return String(max);
}
return '100';
},
[rawDataMap, baseValueMap]
);
const fillTestData = useCallback(() => {
if (!template) return;
const targetItems = template.inspectionItems.filter(i => i.isFirstInItem !== false);
const newMeas: Record<string, Record<number, string>> = {};
const newOkng: Record<string, Record<number, 'ok' | 'ng' | null>> = {};
for (const item of targetItems) {
for (let n = 0; n < item.measurementCount; n++) {
if (item.measurementType === 'okng') {
if (!newOkng[item.id]) newOkng[item.id] = {};
newOkng[item.id][n] = 'ok';
} else if (item.measurementType === 'substitute') {
if (!newMeas[item.id]) newMeas[item.id] = {};
newMeas[item.id][n] = '정상';
} else {
if (!newMeas[item.id]) newMeas[item.id] = {};
newMeas[item.id][n] = generatePassingValue(item);
}
}
}
setMeasurements(newMeas);
setOkngValues(newOkng);
setIsTestFilled(true);
}, [template, generatePassingValue]);
const clearTestData = useCallback(() => {
resetForm();
setIsTestFilled(false);
}, [resetForm]);
// ===== 단일 측정값의 자동 판정 =====
const getMeasurementResult = useCallback(
(itemId: string, nIndex: number): 'ok' | 'ng' | null => {
@@ -760,7 +835,23 @@ export function ImportInspectionInputModal({
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[95vw] max-w-[500px] sm:max-w-[500px] max-h-[90vh] flex flex-col p-0">
<DialogHeader className="px-6 pt-6 pb-0 shrink-0">
<DialogTitle className="text-lg font-bold"></DialogTitle>
<div className="flex items-center gap-2">
<DialogTitle className="text-lg font-bold"></DialogTitle>
{template && (
<button
type="button"
onClick={isTestFilled ? clearTestData : fillTestData}
className={cn(
'px-3 py-1 text-xs font-bold rounded-full transition-colors',
isTestFilled
? 'bg-red-500 hover:bg-red-600 text-white'
: 'bg-amber-500 hover:bg-amber-600 text-white'
)}
>
{isTestFilled ? '초기화' : '테스트입력'}
</button>
)}
</div>
<VisuallyHidden><DialogDescription> </DialogDescription></VisuallyHidden>
</DialogHeader>