-
*
+
+ {/* 왼쪽: 정보 (항목명 + 기준값/범위) */}
+
+
+
+ {item.detail_label || item.detail_content}
+
+ {isRequired && !isCompleted && *}
+
+ {rangeText && (
+
+ {rangeText}
+
+ )}
+
+
+ {/* 오른쪽: 입력 (측정값, 체크박스, OK/NG) */}
+
+
- )}
-
-
);
}
+/** 기준값/범위 텍스트 생성 */
+function buildRangeText(item: WorkResultRow): string {
+ const parts: string[] = [];
+ const lower = item.lower_limit;
+ const upper = item.upper_limit;
+ const unit = item.unit || '';
+
+ if (lower && upper) {
+ parts.push(`${lower}~${upper}${unit ? ' ' + unit : ''}`);
+ } else if (item.spec_value) {
+ parts.push(`기준: ${item.spec_value}`);
+ }
+
+ if (item.is_required === "Y") parts.push("필수");
+
+ const dt = item.detail_type ?? "";
+ if (dt === "check") parts.push("체크");
+ else if (dt.startsWith("inspect")) {
+ const inputType = item.input_type ?? "";
+ if (inputType === "ox") parts.push("O/X 판정");
+ else if (inputType === "select") parts.push("선택형");
+ else if (inputType === "text") parts.push("텍스트");
+ }
+
+ return parts.join(" | ");
+}
+
+// ========================================
+// 그룹 하단 작업완료 버튼
+// ========================================
+
+function GroupCompleteButton({
+ group,
+ currentItems,
+ isGroupStarted,
+ onComplete,
+ onNavigateNext,
+}: {
+ group: WorkGroup;
+ currentItems: WorkResultRow[];
+ isGroupStarted: boolean;
+ onComplete: () => void;
+ onNavigateNext: () => void;
+}) {
+ const [confirmOpen, setConfirmOpen] = useState(false);
+ const requiredItems = currentItems.filter((r) => r.is_required === "Y");
+ const allRequiredDone = requiredItems.length === 0 || requiredItems.every((r) => r.status === "completed");
+ const allDone = currentItems.every((r) => r.status === "completed");
+ const isDisabled = !isGroupStarted || (!allRequiredDone);
+
+ if (confirmOpen) {
+ return (
+
+ setConfirmOpen(false)}
+ style={{ flex: 1, minHeight: 64, fontSize: 18, borderRadius: 16 }}
+ >
+ 취소
+
+ {
+ setConfirmOpen(false);
+ onComplete();
+ }}
+ style={{ flex: 2, minHeight: 64, fontSize: 20, borderRadius: 16 }}
+ >
+
+ 정말 작업완료
+
+
+ );
+ }
+
+ return (
+
{
+ if (allDone) {
+ setConfirmOpen(true);
+ } else {
+ // 필수만 완료된 경우 → 다음 공정으로
+ onNavigateNext();
+ }
+ }}
+ style={{ width: '100%', minHeight: 64, fontSize: 20, borderRadius: 16 }}
+ >
+
+ {isDisabled
+ ? `작업완료 (필수 항목 미완료)`
+ : allDone
+ ? `작업완료`
+ : `다음 단계로`
+ }
+
+ );
+}
+
// ========================================
// 체크리스트 개별 항목 (라우터)
// ========================================
@@ -2349,6 +2522,334 @@ function ChecklistItem({ item, saving, disabled, onSave }: ChecklistItemProps) {
}
}
+// ========================================
+// 체크리스트 입력부 (우측 영역 전용 - 분할 레이아웃)
+// ========================================
+
+function ChecklistItemInput({ item, saving, disabled, onSave }: ChecklistItemProps) {
+ const isDisabled = disabled || saving;
+ const dt = item.detail_type ?? "";
+
+ if (dt.startsWith("inspect")) {
+ const normalized = { ...item, detail_type: "inspect" } as WorkResultRow;
+ if (!normalized.input_type && dt.includes("_")) {
+ const suffix = dt.split("_").slice(1).join("_");
+ const typeMap: Record
= { numeric: "numeric_range", ox: "ox", text: "text", select: "select" };
+ normalized.input_type = typeMap[suffix] ?? suffix;
+ }
+ return ;
+ }
+
+ switch (dt) {
+ case "check":
+ return ;
+ case "input":
+ return ;
+ case "procedure":
+ return ;
+ case "material":
+ return ;
+ case "result":
+ return ;
+ case "info":
+ return {item.detail_label || item.detail_content};
+ default:
+ return 알 수 없는 유형;
+ }
+}
+
+// === 입력 전용 inspect 라우터 ===
+function InspectInputRouter(props: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const inputType = props.item.input_type ?? "numeric_range";
+ switch (inputType) {
+ case "ox":
+ return ;
+ case "select":
+ return ;
+ case "text":
+ return ;
+ default:
+ return ;
+ }
+}
+
+// === 수치 입력 (우측 전용) ===
+function InspectNumericInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const [inputVal, setInputVal] = useState(item.result_value ?? "");
+ const lower = parseFloat(item.lower_limit ?? "");
+ const upper = parseFloat(item.upper_limit ?? "");
+ const hasRange = !isNaN(lower) && !isNaN(upper);
+
+ const handleSave = () => {
+ if (!inputVal || disabled) return;
+ const numVal = parseFloat(inputVal);
+ let passed: string | null = null;
+ if (hasRange) passed = numVal >= lower && numVal <= upper ? "Y" : "N";
+ onSave(item.id, inputVal, passed, "completed");
+ };
+
+ return (
+ <>
+ setInputVal(e.target.value)}
+ onBlur={handleSave}
+ disabled={disabled}
+ placeholder="입력"
+ />
+ {item.unit && {item.unit}}
+ {item.is_passed === "Y" && !saving && PASS}
+ {item.is_passed === "N" && !saving && FAIL}
+ {item.status === "completed" && item.is_passed === null && !saving && 완료}
+ {!item.status || (item.status !== "completed" && !saving) ? (
+
+ 저장
+
+ ) : null}
+ {saving && }
+ >
+ );
+}
+
+// === O/X (우측 전용) ===
+function InspectOXInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const criteriaOK = item.spec_value ?? "OK";
+ const handleSelect = (value: string) => {
+ if (disabled) return;
+ const passed = value === criteriaOK ? "Y" : "N";
+ onSave(item.id, value, passed, "completed");
+ };
+
+ return (
+ <>
+ handleSelect("OK")}
+ disabled={disabled}
+ style={{
+ minHeight: 56, minWidth: 130, fontSize: 20,
+ ...(item.result_value === "OK" ? { boxShadow: '0 0 0 3px #22c55e, 0 4px 12px rgba(34,197,94,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
+ }}
+ >
+ O
+
+ handleSelect("NG")}
+ disabled={disabled}
+ style={{
+ minHeight: 56, minWidth: 130, fontSize: 20,
+ ...(item.result_value === "NG" ? { boxShadow: '0 0 0 3px #ef4444, 0 4px 12px rgba(239,68,68,0.3), inset 0 1px 0 rgba(255,255,255,0.4)' } : {}),
+ }}
+ >
+ X
+
+ {saving && }
+ >
+ );
+}
+
+// === 선택형 (우측 전용) ===
+function InspectSelectInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const currentValue = item.result_value ?? "";
+ let options: string[] = [];
+ let passValues: string[] = [];
+ try {
+ const parsed = JSON.parse(item.spec_value ?? "{}");
+ options = Array.isArray(parsed.options) ? parsed.options : [];
+ passValues = Array.isArray(parsed.passValues) ? parsed.passValues : [];
+ } catch {
+ options = (item.spec_value ?? "").split(",").filter(Boolean);
+ }
+ const handleSelect = (value: string) => {
+ if (disabled) return;
+ const passed = passValues.includes(value) ? "Y" : "N";
+ onSave(item.id, value, passed, "completed");
+ };
+
+ return (
+ <>
+ {options.map((opt) => (
+ handleSelect(opt)}
+ disabled={disabled}
+ style={{ minHeight: 52, minWidth: 100, fontSize: 16 }}
+ >
+ {opt}
+
+ ))}
+ {saving && }
+ >
+ );
+}
+
+// === 텍스트 + 수동판정 (우측 전용) ===
+function InspectTextInput({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const [inputVal, setInputVal] = useState(item.result_value ?? "");
+ const [judged, setJudged] = useState(item.is_passed);
+ const handleJudge = (passed: string) => {
+ if (disabled) return;
+ setJudged(passed);
+ onSave(item.id, inputVal || "-", passed, "completed");
+ };
+
+ return (
+ <>
+ setInputVal(e.target.value)}
+ disabled={disabled}
+ placeholder="내용 입력"
+ />
+ handleJudge("Y")}
+ disabled={disabled}
+ style={{ minHeight: 48, minWidth: 80, fontSize: 15 }}
+ >
+ 합격
+
+ handleJudge("N")}
+ disabled={disabled}
+ style={{ minHeight: 48, minWidth: 80, fontSize: 15 }}
+ >
+ 불합격
+
+ {saving && }
+ >
+ );
+}
+
+// === 체크박스 (우측 전용) ===
+function CheckInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const checked = item.result_value === "Y";
+ return (
+ <>
+ {item.status === "completed" && 완료}
+ {
+ const val = v ? "Y" : "N";
+ onSave(item.id, val, v ? "Y" : "N", v ? "completed" : "pending");
+ }}
+ />
+ {saving && }
+ >
+ );
+}
+
+// === 자유입력 (우측 전용) ===
+function InputOnlyItem({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const [inputVal, setInputVal] = useState(item.result_value ?? "");
+ const inputType = item.input_type === "number" ? "number" : "text";
+ const handleBlur = () => {
+ if (!inputVal || disabled) return;
+ onSave(item.id, inputVal, null, "completed");
+ };
+ return (
+ <>
+ setInputVal(e.target.value)}
+ onBlur={handleBlur}
+ disabled={disabled}
+ placeholder="값 입력"
+ />
+ {saving && }
+ >
+ );
+}
+
+// === 절차 확인 (우측 전용) ===
+function ProcedureInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const checked = item.result_value === "Y";
+ return (
+ <>
+ {
+ onSave(item.id, v ? "Y" : "N", null, v ? "completed" : "pending");
+ }}
+ />
+ 확인
+ {saving && }
+ {item.status === "completed" && !saving && 완료}
+ >
+ );
+}
+
+// === 자재/LOT (우측 전용) ===
+function MaterialInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ const [inputVal, setInputVal] = useState(item.result_value ?? "");
+ const handleSubmit = () => {
+ if (!inputVal || disabled) return;
+ onSave(item.id, inputVal, null, "completed");
+ };
+ return (
+ <>
+ setInputVal(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ disabled={disabled}
+ placeholder="LOT/바코드"
+ />
+
+ 확인
+
+ {saving && }
+ {item.status === "completed" && !saving && 투입}
+ >
+ );
+}
+
+// === 실적 입력 (우측 전용) ===
+function ResultInputOnly({ item, disabled, saving, onSave }: { item: WorkResultRow; disabled: boolean; saving: boolean; onSave: ChecklistItemProps["onSave"] }) {
+ let savedData: { good?: string; defect?: string; defectType?: string; note?: string } = {};
+ try { savedData = JSON.parse(item.result_value ?? "{}"); } catch { savedData = {}; }
+ const [good, setGood] = useState(savedData.good ?? "");
+ const [defect, setDefect] = useState(savedData.defect ?? "");
+ const handleSave = () => {
+ if (disabled) return;
+ const val = JSON.stringify({ good, defect });
+ onSave(item.id, val, null, "completed");
+ };
+ return (
+ <>
+
+ 양품
+ setGood(e.target.value)} disabled={disabled} placeholder="0" />
+
+
+ 불량
+ setDefect(e.target.value)} disabled={disabled} placeholder="0" />
+
+
+ 등록
+
+ {saving && }
+ >
+ );
+}
+
// ========================================
// check: 체크박스
// ========================================