/* ============================================================================= * M02_Labor_Values_Model.ts * 넣을 값 셈(화면 없음 · 39-3). * 일위대가 입력 모달 · 프로젝트 고르기 모달 값 카드가 같은 값 칸 규칙(고르기 = 목록 · 범위 = 숫자 · * 빈 값 = 자동값)을 씀. 일위대가 입력값 = `{입력 이름: 값}`(빈 값은 안 둠 = 자동값 · 모르는 이름 버림). * ========================================================================== */ import type { ItemInput } from "./M02_MasterTemplete_Api_Fetch"; const text = (value: unknown): string => value === null || value === undefined ? "" : typeof value === "object" ? JSON.stringify(value) : String(value); /** 고르기 · 범위 표시 — 선택지 목록이면 「a / b / c」 · 범위(배열 둘 · {최소, 최대})면 「최소 ~ 최대」 */ export function valueHint(input: ItemInput): string { if (Array.isArray(input.고르기) && input.고르기.length) return input.고르기.map(text).join(" / "); const range = input.범위; if (Array.isArray(range) && range.length >= 2) return `${text(range[0])} ~ ${text(range[1])}`; if (range && typeof range === "object") { const r = range as Record; const lo = r.최소 ?? r.min; const hi = r.최대 ?? r.max; if (lo !== undefined || hi !== undefined) return `${text(lo)} ~ ${text(hi)}`; } return text(range); } /** 자동값 글 — 없으면 빈 글 */ export const autoText = (input: ItemInput): string => text(input.자동값); /** `입력값.json` — `{로직키: {입력이름: {값, 확인}}}` (서버 검사 모양 · 값은 수 · 글 · 빈 값) */ /** 입력 키 `{로직키}|{입력이름}` — 같은 이름의 입력도 로직마다 따로 */ export const inputKey = (logic: string, name: string): string => `${logic}|${name}`; export type ValueDoc = Record< string, Record >; const number = (value: unknown): number | undefined => typeof value === "number" && Number.isFinite(value) ? value : undefined; /** 범위 모양 둘(`[최소, 최대]` · `{최소, 최대}`)을 하나로 — 범위가 아니면 null */ export function rangeOf(input: ItemInput): { min?: number; max?: number } | null { const raw = input.범위; if (Array.isArray(raw)) return { min: number(raw[0]), max: number(raw[1]) }; if (raw && typeof raw === "object") { const r = raw as Record; return { min: number(r.최소 ?? r.min), max: number(r.최대 ?? r.max) }; } return null; } /** 고르기 목록 → 화면 값 · 이름 짝 — `raw` 는 원래 모양(숫자 목록이면 숫자로 저장) */ export function choicesOf(input: ItemInput): { 값: string; 이름: string; raw: string | number }[] { return (input.고르기 ?? []).map((one) => { const object = typeof one === "object" && one !== null ? (one as Record) : null; const raw = (object ? (object.값 ?? object.이름 ?? "") : one) as string | number; return { 값: String(raw), 이름: object ? String(object.이름 ?? raw) : String(one), raw }; }); } /** 범위 밖 글인가 — 빈 글 · 범위 없는 입력은 아님 */ export function isOutside(input: ItemInput, raw: string): boolean { const range = rangeOf(input); if (!range || raw.trim() === "") return false; const value = Number(raw); return ( !Number.isFinite(value) || (range.min !== undefined && value < range.min) || (range.max !== undefined && value > range.max) ); } /** 칸 글 → 저장 값 — 빈 글 = null(자동값) · 범위 = 수 · 고르기 = 원래 모양 · 그 밖 = 글 */ export function storedOf(input: ItemInput, raw: string): string | number | null { if (raw === "") return null; if (rangeOf(input)) return Number(raw); return choicesOf(input).find((c) => c.값 === raw)?.raw ?? raw; } /* ── 일위대가 입력값(39-3) — 로직 한 줄의 `{입력 이름: 값}` ───────────────────────────── */ export type LaborValues = Record; /** 로직 입력 목록에 있는 이름 · 빈 값 아닌 것만 남김 + 사라진 이름(로직 입력이 바뀌어 버려질 값) */ export function keepKnown( inputs: ItemInput[], values: LaborValues | null | undefined, ): { kept: LaborValues; lost: string[] } { const names = new Set(inputs.map((i) => i.이름)); const kept: LaborValues = {}; const lost: string[] = []; for (const [name, value] of Object.entries(values ?? {})) { if (!names.has(name)) lost.push(name); else if (value !== "" && value !== null && value !== undefined) kept[name] = value; } return { kept, lost }; } /** 줄 아래 작은 글 — 「지역 서울 · 규격 25mm」(넣은 값만 · 입력 차례) + 빈 값 수(자동값도 없는 입력) */ export function valueLine( inputs: ItemInput[], values: LaborValues | null | undefined, ): { text: string; empty: number } { const parts: string[] = []; let empty = 0; for (const input of inputs) { const value = values?.[input.이름]; if (value !== undefined && value !== "") { parts.push(`${input.이름} ${value}${input.단위 ? input.단위 : ""}`); } else if (autoText(input) === "") empty += 1; } return { text: parts.join(" · "), empty }; }