feat(M01): 단가산출 로직 상세 차례 · 오른쪽 흐름도/원문 두 패널

- 좌측 차례 = 기본정보 › 시험 계산 › 단가산출 상세 › 텍스트 수식
- 오른쪽 패널 둘(흐름도 · 원문) — 하나만 열림 · 둘 다 접기 · 머리 토글 단추 · 열린 패널 계정 취향(m01-logic-panel)
- 흐름도 패널 접힘을 부르는 쪽이 정하는 길(folded · onFold · fold)
- 원문 패널 = 출처 글로 품셈 본문 절을 찾아 보임(GET /api/m01/logic/source) · 못 찾으면 「원문 연결 없음」 + 출처
- 시험 test_m01_logic_source.py · 화면 시나리오 ui_checks/44-4_로직상세_패널.js

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
2026-10-05 11:31:09 +09:00
co-authored by Claude Opus 5.5
parent 576b12eb7d
commit 7d39f90a0e
11 changed files with 520 additions and 11 deletions
+11 -4
View File
@@ -41,6 +41,9 @@ export interface FlowPanelOpts {
logics?(key: string): Promise<CfLogic | null>;
/** 패널 너비가 바뀜(끌기 · 접기 · 펴기) — 부르는 쪽이 칸 너비를 맞춤 */
onResize?(px: number): void;
/** 접힘을 부르는 쪽이 정함(처음 값) — 주면 접기 단추는 onFold 만 부르고 기억도 부르는 쪽 몫 */
folded?: boolean;
onFold?(on: boolean): void;
}
export interface FlowPanel {
@@ -49,6 +52,8 @@ export interface FlowPanel {
destroy(): void;
/** 크게 보기 열기 */
expand(): void;
/** 밖에서 접기 · 펴기(onFold 를 부르지 않음) */
fold(on: boolean): void;
}
/* ── 너비(DOM 없음) ─────────────────────────────────────────────────── */
@@ -105,8 +110,9 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
});
const { diagram, detail, summary, note, undo } = paper.parts;
let width = storedWidth(readStore(WIDTH_KEY));
let folded = readStore(FOLD_KEY) === "1";
const tell = (): void => opts.onResize?.(folded ? STRIP_WIDTH : (width ?? host.clientWidth));
let folded = opts.folded ?? readStore(FOLD_KEY) === "1";
const tell = (): void =>
opts.onResize?.(folded ? STRIP_WIDTH : (width ?? Math.max(PANEL_MIN, host.clientWidth)));
const sm = (label: string, onClick: () => void, variant: "ghost" | "filled" = "ghost") =>
createButton({ label, size: "sm", variant, onClick });
@@ -116,7 +122,7 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
label: tf("Toggle"),
size: "sm",
variant: "ghost",
onClick: () => setFolded(!folded),
onClick: () => (opts.onFold ? opts.onFold(!folded) : setFolded(!folded)),
});
toggle.classList.add("m02-dfp__toggle");
const head = el("header", {
@@ -215,7 +221,7 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
if (on && !folded && width === null && root.isConnected)
width = Math.round(root.getBoundingClientRect().width);
folded = on;
writeStore(FOLD_KEY, on ? "1" : "0");
if (!opts.onFold) writeStore(FOLD_KEY, on ? "1" : "0");
root.classList.toggle("is-folded", on);
open.hidden = on;
strip.hidden = !on;
@@ -290,5 +296,6 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
root.remove();
},
expand,
fold: (on) => on !== folded && setFolded(on),
};
}