feat(M02): 구조물집계표 넷째 카드 「선택 입력」 — 로직마다 입력 목록 · 자리 고르기(면적 · 재료 · 선택 · 안 씀) · 알약 줄에 뜸 (PLAN 25-2c)
- 카드 = 로직 이름 · 결과단위 묶음 아래 입력 줄(이름 · 설명 · 단위 · 고르기/범위 · 자동값 흐리게) + 자리 상자 · 처음은 모두 안 씀 - 저장 = 집계표 [저장]에 이어 PUT input-links(자동저장 없음) · 읽기만 층은 상자 잠금 - 면적 · 재료로 고른 입력은 그 시트 알약 줄에 「로직 이름 · 입력 이름」 · 자재 모드에서도 [목록에서 빼기] 막음(고정) - 알약 줄 시험을 합계 줄 아래 자리에 맞춤(25-0 뒤 어긋난 것) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Inputs_Model.ts
|
||||
* 「선택 입력」 카드의 셈(화면 없음) — 입력 키 · 알약 키 · 자리 고름 · 줄 만들기 · 알약 목록 · 값 글.
|
||||
* 약속 = PLAN 25-1b: 입력 키 `{로직키}|{입력이름}` · 알약 키 `입력|{로직키}|{입력이름}` ·
|
||||
* 같은 이름의 입력도 로직마다 따로. 처음은 모두 「안 씀」 — 프로그램이 추측해 나누지 않음.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { LinkItem } from "../A00_Common/spreadsheet/spreadsheet_links";
|
||||
import type {
|
||||
InputLine,
|
||||
InputLinks,
|
||||
InputSeat,
|
||||
ItemInput,
|
||||
ItemInputs,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
|
||||
/** 화면 글 ↔ 자리 — 「안 씀」 = 빈 글 */
|
||||
export const SEAT_OPTIONS: { value: "" | InputSeat; label: string }[] = [
|
||||
{ value: "", label: "안 씀" },
|
||||
{ value: "면적", label: "면적산출 알약" },
|
||||
{ value: "재료", label: "재료산출 알약" },
|
||||
{ value: "선택", label: "선택" },
|
||||
];
|
||||
|
||||
export const inputKey = (logic: string, name: string): string => `${logic}|${name}`;
|
||||
export const pillKey = (logic: string, name: string): string => `입력|${logic}|${name}`;
|
||||
|
||||
/** 자리 고름 표 — 키 = 입력 키 · 값 = 자리(안 쓰면 키 없음) */
|
||||
export type SeatMap = Map<string, InputSeat>;
|
||||
|
||||
export function seatsOf(links: InputLinks | null): SeatMap {
|
||||
const out: SeatMap = new Map();
|
||||
for (const line of links?.줄 ?? [])
|
||||
if (line.자리) out.set(inputKey(line.로직, line.입력), line.자리);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 저장할 줄 — 자리를 고른 것만(없는 것 = 안 씀) · 면적 · 재료는 알약 키를 같이 */
|
||||
export function linesOf(list: ItemInputs, seats: SeatMap): InputLine[] {
|
||||
const out: InputLine[] = [];
|
||||
for (const logic of list.로직)
|
||||
for (const input of logic.입력) {
|
||||
const seat = seats.get(inputKey(logic.로직, input.이름));
|
||||
if (!seat) continue;
|
||||
out.push({
|
||||
로직: logic.로직,
|
||||
입력: input.이름,
|
||||
자리: seat,
|
||||
...(seat === "선택" ? {} : { 알약: pillKey(logic.로직, input.이름) }),
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export const countOf = (list: ItemInputs): { logics: number; inputs: number } => ({
|
||||
logics: list.로직.length,
|
||||
inputs: list.로직.reduce((sum, logic) => sum + logic.입력.length, 0),
|
||||
});
|
||||
|
||||
/** 그 시트(면적산출 · 재료산출)의 알약 — 고른 자리가 그 시트인 입력만 · 「로직 이름 · 입력 이름」 */
|
||||
export function pillsOf(
|
||||
list: ItemInputs,
|
||||
links: InputLinks | null,
|
||||
seat: "면적" | "재료",
|
||||
): LinkItem[] {
|
||||
const seats = seatsOf(links);
|
||||
const out: LinkItem[] = [];
|
||||
for (const logic of list.로직)
|
||||
for (const input of logic.입력) {
|
||||
if (seats.get(inputKey(logic.로직, input.이름)) !== seat) continue;
|
||||
out.push({
|
||||
키: pillKey(logic.로직, input.이름),
|
||||
이름: `${logic.이름} · ${input.이름}`,
|
||||
...(input.단위 ? { 단위: input.단위 } : {}),
|
||||
고정: true,
|
||||
});
|
||||
}
|
||||
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.자동값);
|
||||
Reference in New Issue
Block a user