feat(M02): 47-4 일위대가 상세 오른쪽 「원문」 접는 패널 — 고른 줄 로직의 품셈 원문
- 공용 오른쪽 접는 패널(ui_template_side_panels · 47-3 부품) · 취향 키 m02-labor-side · 카드 갈래엔 없음 - 고른 줄 없음 = 안내 한 줄 · 고르면 그 로직 출처 원문(GET /m01/logic/source) - 원문 보기를 M01_MasterData_UI_LogicLab_Source 로 떼어 M01 로직 상세와 한 벌로 씀(M01 동작 그대로) - 입력 목록 길(GET /m02/logic-lib/inputs)에 로직 출처 칸 추가 - 시험: test_m02_labor_values 출처 확인 · 화면 시나리오 ui_checks/47-4_일위대가_원문패널.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -2,45 +2,26 @@
|
||||
* M01_MasterData_UI_LogicLab_Side.ts
|
||||
* 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 둘 「흐름도」(M02_DF_Panel) · 「원문」(품셈 본문 절)
|
||||
* 공용 오른쪽 접는 패널(ui_template_side_panels) — 하나만 열림 · 모두 접힘 됨 · 폭 공유 · 접힌 패널 = 세로 글 띠
|
||||
* 열린 패널 · 폭은 취향 키(`m01-logic-side`) · 원문은 출처 글로 서버가 찾음(`GET /m01/logic/source`)
|
||||
* 열린 패널 · 폭은 취향 키(`m01-logic-side`) · 원문 = 공용 원문 보기(`LogicLab_Source`)
|
||||
* ========================================================================== */
|
||||
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createSidePanels } from "@ui/ui_template_side_panels";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import type { SplitHandle } from "./M01_MasterData_UI_LogicLab_Flow";
|
||||
import { createSourceView } from "./M01_MasterData_UI_LogicLab_Source";
|
||||
import "../M02_MasterTemplete/DataFlow/M02_DF_Panel.css";
|
||||
import "./M01_MasterData_UI_LogicLab_Side.css";
|
||||
|
||||
const TEXT = {
|
||||
Flow: ["흐름도", "Flow"],
|
||||
Source: ["원문", "Source"],
|
||||
Loading: ["원문 찾는 중", "Looking up the source"],
|
||||
None: ["원문 연결 없음", "No linked source"],
|
||||
From: ["출처", "Source"],
|
||||
NoSource: ["출처 없음", "No source"],
|
||||
Near: ["{v} 절이 없어 위 절", "No {v} section — showing the parent"],
|
||||
} as const;
|
||||
|
||||
const ts = (key: keyof typeof TEXT, v = ""): string =>
|
||||
TEXT[key][currentLanguageIndex === 1 ? 1 : 0].replace("{v}", v);
|
||||
const ts = (key: keyof typeof TEXT): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0];
|
||||
|
||||
export type PanelPick = "flow" | "source" | "none";
|
||||
|
||||
interface Block {
|
||||
h?: number;
|
||||
text?: string;
|
||||
p?: string;
|
||||
table?: string[][];
|
||||
}
|
||||
interface Found {
|
||||
number: string;
|
||||
matched: string;
|
||||
file: string;
|
||||
blocks: Block[];
|
||||
}
|
||||
|
||||
export interface LogicSide {
|
||||
/** [왼쪽 지금 내용, 오른쪽 패널] 칸 */
|
||||
root: HTMLElement;
|
||||
@@ -54,42 +35,16 @@ export interface LogicSide {
|
||||
showSource(src: string): void;
|
||||
}
|
||||
|
||||
function blockNode(b: Block): HTMLElement {
|
||||
if (b.table)
|
||||
return el("div", {
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "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 function buildLogicSide(left: HTMLElement): LogicSide {
|
||||
const flowSlot = el("div", { className: "m01lab-side__slot" });
|
||||
const body = el("div", { className: "m01lab-src__body" });
|
||||
const title = el("strong", { className: "m02-dfp__title" });
|
||||
const source = createSourceView();
|
||||
const sp = createSidePanels({
|
||||
prefKey: "m01-logic-side",
|
||||
defaultWidth: 520,
|
||||
minWidth: 320,
|
||||
panels: [
|
||||
{ id: "flow", title: ts("Flow"), body: flowSlot },
|
||||
{
|
||||
id: "source",
|
||||
title: ts("Source"),
|
||||
body: el("div", { className: "m01lab-src", children: [title, body] }),
|
||||
},
|
||||
{ id: "source", title: ts("Source"), body: source.root },
|
||||
],
|
||||
onChange: () => {
|
||||
flowFold?.(sp.current() !== "flow"); // 다시 펴면 흐름도 판을 폭에 맞춤
|
||||
@@ -103,37 +58,10 @@ export function buildLogicSide(left: HTMLElement): LogicSide {
|
||||
|
||||
let flowFold: ((on: boolean) => void) | null = null;
|
||||
let want = "";
|
||||
let shown: string | null = null;
|
||||
|
||||
/** 원문 패널이 열려 있을 때만 찾음 */
|
||||
const load = (): void => {
|
||||
if (sp.current() !== "source" || shown === want) return;
|
||||
const src = (shown = want);
|
||||
title.textContent = src || ts("NoSource");
|
||||
body.replaceChildren(el("p", { className: "m01-logic__muted", text: ts("Loading") }));
|
||||
const none = (): void =>
|
||||
body.replaceChildren(
|
||||
el("p", { className: "m01lab-src__none", text: ts("None") }),
|
||||
el("p", { className: "m01-logic__muted", text: `${ts("From")} · ${src || "—"}` }),
|
||||
);
|
||||
if (!src.trim()) return none();
|
||||
void fetch(`${API_BASE_URL}/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: "m01-logic__muted m01lab-src__file",
|
||||
text: f.matched === f.number ? f.file : `${f.file} · ${ts("Near", f.number)}`,
|
||||
}),
|
||||
...f.blocks.map(blockNode),
|
||||
]),
|
||||
);
|
||||
});
|
||||
if (sp.current() === "source") source.show(want);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user