Files
Aislo/B05_Profile/B05_Profile_UI_Structures_Summary_Picker.ts
T
eomsangdonandClaude Sonnet 5.5 71fac35dab feat(B05·B06): 구조물 좌측 패널 「집계 항목」 줄 · 옵션 보이기 · [수정] 옵션 보존 · 목록 지정 색(21-4)
- 폼 옵션 아래 「집계 항목」 줄(이름 · 미지정 · [고르기] · [해제]) — 구조물 · 관 계열 하위 폼 · B05 · B06 한 코드
- 옵션 `visible:false` 는 폼에서 숨김(정의 · 옛 값 그대로) · [수정]은 폼이 그린 칸만 갈아 끼우고 안 그린 칸(상세 · 숨긴 칸) 보존 — 옵션 덮어쓰기 흠 고침
- 목록 줄 `.is-assigned` 색 · 툴팁 항목 이름 · 캐시는 지금 초안 기록 칸(structures · pipes) 안 summary_item
- 미지정이면 칸을 안 실음 · 관은 서버 응답에 칸이 없으면 같은 측점에서 화면이 이어받음
- 항목 고르기 모달은 21-5 가 오기 전까지 같은 이름의 임시 함수 · Structures_Panel 700줄 넘어 옵션 그리기를 Panel_Options 로 나눔

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TULoa94ZFL26KU6ZqVpjkF
2026-09-29 12:19:31 +09:00

108 lines
4.2 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Structures_Summary_Picker.ts
* ⚠ **임시(가짜) 항목 고르기 모달** — 21-5(sub1) `ui_template/ui_template_item_picker.ts` 의
* `openItemPicker()` 가 오면 이 파일을 지우고 `Summary_Row` 를 쓰는 패널의 import 한 줄만 바꾼다.
*
* 이름 · 돌려주는 값은 21-5 와 맞춘 자리다:
* openItemPicker({ current }) → Promise<{ item } | { clear: true } | null>
* (item = 21-1 `summary_item` · clear = [해제] · null = 닫음)
* 목록은 `GET /api/m02/items?all=1`(21-2) 을 읽고, 아직 없으면(404 등) 같은 21-1 모양의 가짜 세 줄.
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
import { createButton, el } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
import {
sameSummaryItem,
summaryLabel,
type SummaryItem,
type SummaryLayer,
} from "../common_util/common_util_summary_item";
export type ItemPickResult = { item: SummaryItem } | { clear: true } | null;
interface ItemRow {
key: string;
이름?: string;
종류?: string;
분류1?: string;
layer?: SummaryLayer;
owner?: string | null;
}
/** 21-2 서버가 없을 때의 가짜 응답 — 21-1 모양. */
const FAKE_ITEMS: SummaryItem[] = [
{ layer: "system", owner: null, key: "SS000001", name: "돌기슭막이 메쌓기(가짜)" },
{ layer: "company", owner: "가짜회사", key: "SS000002", name: "옹벽 콘크리트(가짜)" },
{ layer: "personal", owner: "가짜회사/가짜사용자", key: "SS000003", name: "돌쌓기 찰쌓기(가짜)" },
];
async function loadItems(): Promise<SummaryItem[]> {
try {
const response = await fetch(`${API_BASE_URL}/m02/items?all=1`, { credentials: "include" });
if (!response.ok) return FAKE_ITEMS;
const rows = (await response.json()) as ItemRow[];
if (!Array.isArray(rows) || !rows.length) return FAKE_ITEMS;
return rows.map((row) => ({
layer: row.layer ?? "system",
owner: row.owner ?? null,
key: row.key,
name: row.분류1 ? `${row.종류 ?? ""} · ${row.분류1} · ${row.key}` : (row.종류 ?? row.key),
}));
} catch {
return FAKE_ITEMS;
}
}
export function openItemPicker(options: { current: SummaryItem | null }): Promise<ItemPickResult> {
return new Promise((resolve) => {
let settled = false;
const finish = (result: ItemPickResult, close?: () => void): void => {
if (settled) return;
settled = true;
close?.();
resolve(result);
};
const modal = openModal({
title: "집계 항목 고르기(임시)",
closeLabel: "닫기",
mount: (body, close) => {
const list = el("div", { className: "b05-structure__pick-list" });
const apply = createButton({ label: "적용", variant: "filled" });
apply.disabled = true;
let chosen: SummaryItem | null = null;
void loadItems().then((items) => {
items.forEach((item) => {
const row = createButton({ label: summaryLabel(item), variant: "ghost" });
row.dataset.b05PickItem = item.key;
// 지금 지정된 항목은 미리 표시(눌러서 바로 [적용] 가능)
if (sameSummaryItem(item, options.current)) {
chosen = item;
apply.disabled = false;
row.classList.add("is-picked");
}
row.addEventListener("click", () => {
chosen = item;
apply.disabled = false;
list
.querySelectorAll(".is-picked")
.forEach((node) => node.classList.remove("is-picked"));
row.classList.add("is-picked");
});
list.append(row);
});
});
apply.addEventListener("click", () => chosen && finish({ item: chosen }, close));
const clear = createButton({ label: "해제", variant: "ghost" });
clear.addEventListener("click", () => finish({ clear: true }, close));
body.append(list, el("div", { children: [apply, clear] }));
},
beforeClose: () => {
finish(null);
return true;
},
});
void modal;
});
}