refactor(M02): 일위대가 입력 목록 · 넣을 값 칸 부품 옮김(39-3 C2) — 선택 입력에서 M02_Labor_Inputs.py · M02_Labor_Values(_Model).ts 로 · 옛 자리는 다시 내보냄 · 입력 목록 길 logic-lib/inputs

- 서버: inputs · combo_logics · _num 몸을 M02_Labor_Inputs.py 로(logic_inputs = 로직 키들 입력 + 자동값) · M02_MasterTemplete_Inputs.py 는 그것을 부름
- 길: GET /api/m02/logic-lib/inputs?keys= (로그인 · 일위대가 입력 모달용)
- 화면: 값 카드(createInputsValueCard) · 값 함수 → M02_Labor_Values.ts · M02_Labor_Values_Model.ts · 값 칸 하나(createValueControl)를 떼어 값 카드와 입력 모달이 같이 씀 · CSS 도 옮김
- 옛 자리(M02_MasterTemplete_Inputs.ts · _Inputs_Model.ts)는 새 파일을 다시 내보냄 — B05 고르기 모달 그대로
- 시험: 새 test_m02_labor_values.py · 옮긴 자리 따라 test_item_picker_detail · test_m02_card_shell · helper_m02_inputs 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
2026-10-04 17:27:30 +09:00
co-authored by Claude Opus 5.5
parent 3d79d2dab8
commit 43a59a6893
13 changed files with 770 additions and 515 deletions
@@ -1,6 +1,7 @@
/* =============================================================================
* M02_MasterTemplete_Inputs_Model.ts
* 「선택 입력」 카드의 셈(화면 없음) — 입력 키 · 알약 키 · 자리 고름 · 줄 만들기 · 알약 목록 · 값 글.
* 「선택 입력」 카드의 셈(화면 없음) — 입력 키 · 알약 키 · 자리 고름 · 줄 만들기 · 알약 목록.
* 값 글 · 값 칸 규칙은 `M02_Labor_Values_Model.ts`(일위대가 입력 모달과 한 벌 · 여기서 다시 내보냄).
* 약속 = PLAN 25-1b: 입력 키 `{로직키}|{입력이름}` · 알약 키 `입력|{로직키}|{입력이름}` ·
* 같은 이름의 입력도 로직마다 따로. 처음은 모두 「안 씀」 — 프로그램이 추측해 나누지 않음.
* ========================================================================== */
@@ -78,62 +79,11 @@ export function pillsOf(
return out;
}
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<string, unknown>;
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.자동값);
/* ── 값 함수 — 일위대가 입력 모달과 한 벌(39-3 · `M02_Labor_Values_Model.ts` 로 옮김 · 옛 이름 그대로 내보냄) ── */
export { autoText, choicesOf, rangeOf, valueHint, type ValueDoc } from "./M02_Labor_Values_Model";
/* ── 프로젝트에서 값 받기(PLAN 25-2d) — 자리 「선택」 인 입력만 로직마다 묶어 값 · 확인 표시 ── */
/** `입력값.json` — `{로직키: {입력이름: {값, 확인}}}` (서버 검사 모양 · 값은 수 · 글 · 빈 값) */
export type ValueDoc = Record<
string,
Record<string, { 값: string | number | null; 확인: boolean }>
>;
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<string, unknown>;
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<string, unknown>) : null;
const raw = (object ? (object.값 ?? object.이름 ?? "") : one) as string | number;
return { 값: String(raw), 이름: object ? String(object.이름 ?? raw) : String(one), raw };
});
}
/** 자리가 「선택」 인 입력만 로직 차례로 묶음 — 값 받기 카드가 보이는 입력 */
export function pickedGroups(
list: ItemInputs,