Files
Aislo/M01_MasterData/M01_MasterData_UI_LogicLab_Source.ts
eomsangdonandClaude Opus 5.5 598b80eb26 refactor(794-1b): M01 흐름도 패널 걷음 — 로직 상세 오른쪽 = 원문만 · [신규 로직] = 빈 시트 줄 · 흐름 서버 길 · DataFlow 화면 지움
요구 651 · 653 — 계산 공식 참고용 흐름도(① M01 로직 상세 오른쪽 접는 패널)를 걷음. ② M02 일위대가 다이어그램은 그대로.

지움(64 파일 · 18,666 줄 · 되돌릴 길 = git 이력)
- M01 6 파일 1,064 줄 — Store_Flow.py · UI_LogicLab_Flow.ts/.css · FlowRows.ts · FlowView.ts/.css
- M02_MasterTemplete/DataFlow 43 파일 12,799 줄 — 흐름도 화면 · 셈(Compute · Convert · Lookup · Model · Eval …)
- 시험 15 파일 4,803 줄 — test_m01_logic_flow{view,_engine,_project,_save} · test_m02_df_*(값 찾기 values_old 뺌) — 지운 흐름 코드만 지키던 것

남김(바닥) — DataFlow M02_DF_UnitPick(일위대가 셋) · M02_DF_Values(M02_Item_Prices) · M02_DF_Round · _standard 표 ·
  M02_DF_Router = GET /api/m02/df/standards 하나 · M02_DF_Api = cfStandards 하나 · CfRound 꼴은 Round 안으로

고침
- M01 Router — /save = Store_Make.save(개정 확인 · 자체 파일 새 줄 GX · added 그대로) · /calc /text /logic/auto = 옛 셈만 · /logic/flow 둘 지움
- master_formula — 흐름 훅 지움 · 줄에 「흐름」 이 남으면 「흐름도 로직은 걷음」 까닭 오류(저장된 흐름 로직 0 — 마스터 · storage 다시 확인)
- Sangeun_Calc._defaults = 옛 자동값 · M02_Logic_Lib_Read /logic/flow 지움 · main.py 주석
- 화면 — LogicLab(startFlow · scheduleFlow · 예시값 잇기 지움 · [신규 로직] 줄 = 입력 · 중간 · 호표 빈 목록 + 끝수 null) ·
  Side(원문 패널 하나) · Source(머리 칸 이름 m01lab-src__top/__title · DF_Panel.css 안 씀) · Detail · View(nowInputs 지움) · Sheet(흐름 갈래 지움) · 글
- 시험 — 지운 파일을 가리키던 목록 넷(spreadsheet_size · 208_1 · 588 · input_prefs) · 717_1 흐름 길 두 줄 · values_old 흐름 셈 부분 지움
- test_713_3_list 기대 — 로직 1,461 → 1,515 · 묶음 307 → 335 · 작업량 21 → 65 · 부르는 506 → 512(782-1 · 2 · 3 건설 기초 로직 54 더함 ·
  옛 키만 세면 307 · 21 · 506 그대로)
- 새 지킴 test_794_1b_flow_gone — 흐름 길 404 · 표준 표 남음 · 신규 로직 자체 GX · 흐름 남은 줄 까닭 오류

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:38:39 +09:00

155 lines
5.6 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_LogicLab_Source.ts
* 원문 보기(품셈 본문 절) — 출처 글로 서버가 찾음(`GET /m01/logic/source`) · 늦게 온 답은 버림
* M01 로직 상세 「원문」 패널 · M02 일위대가 상세 「원문」 패널(47-4)이 같이 씀 ·
* 길은 부르는 쪽이 줌(85-4 · M02 = `/m02/logic-lib/source` + 제목 행 [원문 다운로드] pdf 통째)
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { el } from "@ui/ui_template_elements";
import { API_BASE_URL } from "@config/config_frontend";
import "./M01_MasterData_UI_LogicLab_Side.css";
const TEXT = {
Loading: ["원문 찾는 중", "Looking up the source"],
None: ["원문 연결 없음", "No linked source"],
From: ["출처", "Source"],
NoSource: ["출처 없음", "No source"],
Near: ["{v} 절이 없어 위 절", "No {v} section — showing the parent"],
Download: ["원문 다운로드", "Download source"],
} as const;
const ts = (key: keyof typeof TEXT, v = ""): string =>
TEXT[key][currentLanguageIndex === 1 ? 1 : 0].replace("{v}", v);
interface Block {
h?: number;
text?: string;
p?: string;
table?: string[][];
/** 출처가 가리킨 항 · 주 — 강조 */
hit?: boolean;
}
interface Found {
number: string;
matched: string;
file: string;
blocks: Block[];
item?: string;
}
function blockNode(b: Block): HTMLElement {
const node = rawNode(b);
if (b.hit) node.classList.add("m01lab-src__hit");
return node;
}
function rawNode(b: Block): HTMLElement {
if (b.table)
return el("div", {
className: "m01-logic__scroll",
children: [
el("table", {
className: "ui-table ui-table--data m01-logic__grid m01lab-src__table",
children: [
el("tbody", {
children: b.table.map((r) =>
el("tr", { children: r.map((c) => el("td", { text: c })) }),
),
}),
],
}),
],
});
if (b.h) return el("h4", { className: "m01lab-src__head", text: b.text ?? "" });
return el("p", { className: "m01lab-src__p", text: b.p ?? "" });
}
export interface SourceView {
root: HTMLElement;
/** 그 출처 글의 원문을 그림 — 지금 보이는 것과 같으면 안 부름 */
show(src: string): void;
}
/** 출처 글 → 원문 pdf 책(85-4) — 모르면 null */
const bookOf = (src: string): string | null =>
src.startsWith("건설품셈") ? "건설" : src.startsWith("산림품셈") ? "산림" : null;
export interface SourceViewOptions {
/** 원문 절 길(기본 M01 `/m01/logic/source` — 시스템 관리자) */
path?: string;
/** 원문 pdf 길(`?book=`) — 주면 제목 행 오른쪽 [원문 다운로드] */
pdf?: string;
}
export function createSourceView(opts: SourceViewOptions = {}): SourceView {
const body = el("div", { className: "m01lab-src__body" });
const title = el("strong", { className: "m01lab-src__title" });
const download = opts.pdf
? (el("a", {
className: "ui-btn ui-btn--ghost ui-btn--sm",
text: ts("Download"),
attrs: { download: "", "data-source-pdf": "" },
}) as HTMLAnchorElement)
: null;
let shown: string | null = null;
const show = (src: string): void => {
if (shown === src) return;
shown = src;
title.textContent = src || ts("NoSource");
if (download) {
const book = bookOf(src);
download.hidden = !book;
download.href = book ? `${API_BASE_URL}${opts.pdf}?${new URLSearchParams({ book })}` : "";
}
body.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: ts("Loading") }));
const none = (): void =>
body.replaceChildren(
el("p", { className: "m01lab-src__none", text: ts("None") }),
el("p", { className: "ui-text ui-text--muted", text: `${ts("From")} · ${src || "—"}` }),
);
if (!src.trim()) return none();
void fetch(
`${API_BASE_URL}${opts.path ?? "/m01/logic/source"}?${new URLSearchParams({ src })}`,
{
credentials: "include",
},
)
.then((r) => (r.ok ? (r.json() as Promise<{ found: Found[] }>) : { found: [] }))
.catch(() => ({ found: [] as Found[] }))
.then(({ found }) => {
if (shown !== src) return;
if (!found.length) return none();
body.replaceChildren(
...found.flatMap((f) => [
el("p", {
className: "ui-text ui-text--muted m01lab-src__file",
text: [
f.file,
f.matched === f.number ? "" : ts("Near", f.number),
f.item ? (/^\d+$/.test(f.item) ? `${f.item}항` : f.item) : "",
]
.filter(Boolean)
.join(" · "),
}),
...f.blocks.map(blockNode),
]),
);
// 강조 항을 패널 몸 가운데로 — 몸만 굴림(85-5 · scrollIntoView 는 메인 창까지 굴림)
requestAnimationFrame(() => {
const hit = body.querySelector<HTMLElement>(".m01lab-src__hit");
const box = hit?.closest<HTMLElement>(".ui-side-panels__body");
if (!hit || !box) return hit?.scrollIntoView({ block: "nearest" });
const off = hit.getBoundingClientRect().top - box.getBoundingClientRect().top;
box.scrollTop += off - (box.clientHeight - hit.offsetHeight) / 2;
});
});
};
const head = download
? el("div", { className: "m01lab-src__top", children: [title, download] })
: title;
return { root: el("div", { className: "m01lab-src", children: [head, body] }), show };
}