Files
Aislo/B09_Estimation/B09_Estimation_UI_Check.ts
T
eomsangdonandClaude Opus 5.5 7cd083a2d7 feat(B09): 좌측 「데이터 점검」 묶음 — 이름 · 설명 지움 · [업데이트] · 낡음 점 배지 · 연결 점검 탭을 좌측 목록으로(203-1)
- 묶음 이름 「원가 단가」 → 「데이터 점검」 · 굳힌 시각 안내 글 지움(공용 호버로)
- [단가 갱신] → [업데이트] · 서버 book_stale(지금 M01 로 굳힌 책과 견줌) 까닭 있으면 점 배지
- 「연결 점검」 탭 = 좌측 목록(누르면 그 탭 그 줄 · 까닭 = 공용 호버) · 위 탭 줄에서 뺌 · 첫 탭 = 공사원가계산서
- test_203_1 새로 · test_144 · test_181_1 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-10 14:25:06 +09:00

94 lines
3.7 KiB
TypeScript

/* =============================================================================
* B09_Estimation_UI_Check.ts
* B09 좌측 「데이터 점검」 묶음(PLAN 203-1) — [업데이트] · 연결 점검(옛 「연결 점검」 탭 내용).
*
* [업데이트] = 단가를 지금 M01 값으로 새로 굳힘 · 서버가 낡았다고 보면(`payload.stale` 까닭 한 줄)
* 단추에 점 배지(`is-pending`) · 굳힌 시각 · 까닭 = 공용 호버
* 연결 점검 = 「연결 없음」 줄 목록(누르면 그 탭 그 줄로 · 까닭 = 공용 호버) + 서버 알림 · M01 에 없는 키
* ========================================================================== */
import { attachHoverInfo } from "@ui/ui_template_hover_info";
import type { Row } from "./B09_Estimation_Calc_Base";
import { tabName, type Link, type TabId } from "./B09_Estimation_UI_Tabs";
export interface CheckItem {
link: Link;
tab: string;
name: string;
why: string;
}
/** 빠진 연결 줄 · 알림 글(서버 알림 + M01 에 없는 키) */
export function checkItems(sheets: Row, payload: Row): { items: CheckItem[]; notes: string[] } {
const gaps: Row[] = sheets.gaps || [];
const items = gaps.map((g) => ({
link: { tab: g.tab as TabId, key: g.key },
tab: tabName(g.tab),
name: String(g.name ?? ""),
why: String(g.why ?? ""),
}));
const notes: string[] = [...(payload.notes || [])];
const missing: string[] = payload.inputs?.missing || [];
if (missing.length) notes.push(`M01 에 없는 키 ${missing.length} — ${missing.join(" · ")}`);
return { items, notes };
}
/** [업데이트] 호버 글 — 굳힌 시각 + 낡은 까닭 */
export function refreshTip(payload: Row | null): string {
const made = payload?.book?.made;
const when = made ? `단가 굳힘 ${String(made).replace("T", " ")}` : "단가 아직 안 굳힘";
const stale = payload?.stale;
return `${when} · 누르면 지금 M01 값으로 새로 굳힘${stale ? ` · 업데이트 필요 — ${stale}` : ""}`;
}
export interface CheckPanel {
/** 묶음 안 연결 점검 자리 */
list: HTMLElement;
draw: (payload: Row, sheets: Row) => void;
}
export function createCheckPanel(refresh: HTMLElement, goto: (link: Link) => void): CheckPanel {
let payload: Row | null = null;
let items: CheckItem[] = [];
attachHoverInfo(refresh, () => refreshTip(payload));
const list = document.createElement("div");
list.className = "b09-check";
attachHoverInfo(list, (row) => items[Number(row.dataset.i)]?.why, "[data-i]");
const draw = (next: Row, sheets: Row): void => {
payload = next;
refresh.classList.toggle("is-pending", Boolean(next.stale));
const made = checkItems(sheets, next);
items = made.items;
const head = document.createElement("p");
head.className = "b09-check__head";
head.textContent = `연결 없음 ${items.length}곳`;
const rows = document.createElement("div");
rows.className = "ui-sidebar-list";
rows.append(
...items.map((item, i) => {
const row = document.createElement("button");
row.type = "button";
row.className = "ui-sidebar-row";
row.dataset.i = String(i);
const name = document.createElement("span");
name.textContent = item.name;
const tab = document.createElement("span");
tab.className = "b09-check__tab";
tab.textContent = item.tab;
row.append(name, tab);
row.addEventListener("click", () => goto(item.link));
return row;
}),
);
const notes = made.notes.map((text) => {
const p = document.createElement("p");
p.className = "b09-check__note";
p.textContent = text;
return p;
});
list.replaceChildren(head, ...(items.length ? [rows] : []), ...notes);
};
return { list, draw };
}