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>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_LogicLab.ts
|
||||
* 관리자 화면 — 로직 칸. 로직을 열면 두 칸 — 왼쪽 시트(794-1 · 식 고침 = 관리자) · 단가산출 상세 · 시험 계산 / 오른쪽 흐름도
|
||||
* 관리자 화면 — 로직 칸. 로직을 열면 두 칸 — 왼쪽 시트(794-1 · 식 고침 = 관리자) · 단가산출 상세 · 시험 계산 / 오른쪽 원문
|
||||
* 로직 목록(원문·장 고르기 · 찾기 · 막힘 거름)은 왼쪽 패널 「단가산출 로직」 컨테이너(`listHost`) 안
|
||||
*
|
||||
* 데이터 흐름(CLAUDE.md 5장) — 고친 것은 캐시(sessionStorage)에 쌓고 [저장] 한 번에 `POST /save`.
|
||||
@@ -38,18 +38,6 @@ import { buildLabDetail, isOwnLogic, trial } from "./M01_MasterData_UI_LogicLab_
|
||||
import { setLogicOpener } from "./M01_MasterData_UI_LogicLab_Modal";
|
||||
import { openCopyScreen } from "./M01_MasterData_UI_LogicLab_Copy";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import {
|
||||
emptyFlow,
|
||||
designFields,
|
||||
examplesOf,
|
||||
fetchFlow,
|
||||
fetchFlowFor,
|
||||
sameShape,
|
||||
withExamples,
|
||||
mountLogicFlow,
|
||||
tf,
|
||||
type LogicFlow,
|
||||
} from "./M01_MasterData_UI_LogicLab_Flow";
|
||||
import { askBasicInfo } from "./M01_MasterData_UI_LogicLab_Basic";
|
||||
import { saveRevised } from "./M01_MasterData_UI_LogicLab_Revise";
|
||||
import { buildLogicSide } from "./M01_MasterData_UI_LogicLab_Side";
|
||||
@@ -57,7 +45,6 @@ import { createViewPicker } from "./M01_MasterData_UI_LogicLab_View";
|
||||
import { createLabSheet } from "./M01_MasterData_UI_LogicLab_Sheet";
|
||||
import { buildHeadBar } from "./M01_MasterData_UI_Head";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
import type { CfLogic } from "../M02_MasterTemplete/DataFlow/M02_DF_Model";
|
||||
import "./M01_MasterData_UI_Logic_Style.css";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
@@ -83,20 +70,11 @@ interface Opened extends Draft {
|
||||
lastText?: TextAnswer;
|
||||
ownHost: HTMLElement;
|
||||
values: Record<string, string>;
|
||||
/** 흐름 로직의 왼쪽 「지금 내용」 자리 — 흐름도 왼쪽 보기가 그림 */
|
||||
flowHost: HTMLElement;
|
||||
/** 오른쪽 흐름도의 기준(예시값만 다른지 견줄 때) */
|
||||
flowBase?: CfLogic;
|
||||
}
|
||||
|
||||
/** 줄의 흐름(있으면 흐름도로 셈) */
|
||||
const flowOf = (row: LogicRow | null): CfLogic | null => (row?.흐름 as CfLogic | undefined) ?? null;
|
||||
const OWN_FILE = "로직_자체.json";
|
||||
/** 시험 계산에 넘길 줄 — 흐름 로직은 입력 칸을 흐름의 「설계 때 정할 것」으로(옛 식 칸은 서버가 지움) · 그 밖은 시험 고침(722-2) 덮은 사본 */
|
||||
const calcRow = (row: LogicRow | null): LogicRow | null => {
|
||||
const f = flowOf(row);
|
||||
return f && row ? { ...row, 입력: designFields(f) } : trial.apply(row);
|
||||
};
|
||||
/** 시험 계산에 넘길 줄 — 시험 고침(722-2) 덮은 사본 */
|
||||
const calcRow = (row: LogicRow | null): LogicRow | null => trial.apply(row);
|
||||
|
||||
const cacheKey = "m01_logic_drafts_lab";
|
||||
const ownHost = (): HTMLElement => el("div", { className: "m01lab__own" });
|
||||
@@ -123,20 +101,18 @@ export async function mountM01LogicLab(
|
||||
let drafts = loadDrafts();
|
||||
let opened: Opened | null = null;
|
||||
let calcInputs = "";
|
||||
/** 오른쪽 흐름도 패널 — 로직을 열 때마다 새로 */
|
||||
let flowNow: LogicFlow | null = null;
|
||||
const admin = await fetchSessionUser()
|
||||
.then((u) => u?.role === "SYSTEM_ADMIN")
|
||||
.catch(() => false);
|
||||
let recalc: number | undefined;
|
||||
const sheet = createLabSheet({
|
||||
admin,
|
||||
onInput: (name, v) => opened && syncLeft(opened, { [name]: v }, true),
|
||||
onInput: (name, v) => opened && syncLeft(opened, { [name]: v }),
|
||||
edited: () => (touch(), drawEditor(), drawCalc()), // 줄 사본 → 캐시 → [저장](기존 길)
|
||||
run: () => calc.querySelector<HTMLButtonElement>("[data-calc-run]")?.click(),
|
||||
});
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 둘(흐름도 · 원문 · 하나만 열림 · 폭 공유)
|
||||
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 「원문」
|
||||
const panes = buildLogicSide(editor);
|
||||
const views = createViewPicker(editor); // 보기 고르기 — 머리줄 [신규 로직] 왼쪽
|
||||
const errors = el("div", {
|
||||
@@ -195,7 +171,6 @@ export async function mountM01LogicLab(
|
||||
if (opened.origKey !== null && now === opened.original) delete drafts[opened.id];
|
||||
else drafts[opened.id] = pick(opened, clone(opened.row));
|
||||
persist();
|
||||
if (!flowOf(opened.row)) scheduleFlow(opened); // 왼쪽 편집기로 고침 — 오른쪽 흐름도도 따라감
|
||||
const inputs = JSON.stringify(calcRow(opened.row)?.입력 ?? []);
|
||||
if (inputs !== calcInputs) drawCalc();
|
||||
if (isOwnLogic(opened.row)) {
|
||||
@@ -263,120 +238,31 @@ export async function mountM01LogicLab(
|
||||
values: current.values,
|
||||
ownHost: current.ownHost,
|
||||
onChange: touch,
|
||||
onInput: (name, v) => syncLeft(current, { [name]: v }, true),
|
||||
onInput: (name, v) => syncLeft(current, { [name]: v }),
|
||||
onTrial: drawCalc,
|
||||
calcHost: calc,
|
||||
flowHost: flowOf(current.row) ? current.flowHost : undefined,
|
||||
admin,
|
||||
sheet: sheet.root,
|
||||
});
|
||||
sheet.draw(current.row as LogicRow, current.original, current.values);
|
||||
flowNow?.redrawLeft();
|
||||
};
|
||||
|
||||
/** 왼쪽 고침 뒤 잠시 쉬면 고친 줄을 서버가 흐름도로 바꿔 줌 → 오른쪽에 넣음 · 마지막 물음만 받음 */
|
||||
let flowTimer: number | undefined;
|
||||
let flowSeq = 0;
|
||||
const scheduleFlow = (o: Opened): void => {
|
||||
window.clearTimeout(flowTimer);
|
||||
flowTimer = window.setTimeout(() => {
|
||||
const seq = ++flowSeq;
|
||||
if (!o.row) return;
|
||||
void fetchFlowFor(o.row, o.origKey ?? "", o.file).then((got) => {
|
||||
if (!got || seq !== flowSeq || opened !== o || !flowNow || flowOf(o.row)) return;
|
||||
o.flowBase = withExamples(got.flow, o.values);
|
||||
flowNow.set(clone(o.flowBase));
|
||||
});
|
||||
}, 700);
|
||||
};
|
||||
|
||||
/** 오른쪽 흐름도 — 줄의 흐름 · 없으면 서버 변환(흐름 받기) · 그것도 없으면 빈 흐름 */
|
||||
const startFlow = async (o: Opened): Promise<void> => {
|
||||
const row = o.row;
|
||||
if (!row) return;
|
||||
panes.flowSplit.right.replaceChildren(
|
||||
el("p", { className: "m01lab-flow__wait", text: tf("Waiting") }),
|
||||
);
|
||||
const own = flowOf(row);
|
||||
const got = own || o.origKey === null ? null : await fetchFlow(o.origKey, o.file);
|
||||
// 시험 입력 한 벌 — 열 때 왼쪽 시험 계산 입력을 흐름 변수 예시값으로
|
||||
o.flowBase = withExamples(
|
||||
own ?? got?.flow ?? (await emptyFlow(row.이름, row.결과단위)),
|
||||
o.values,
|
||||
);
|
||||
if (opened !== o) return;
|
||||
try {
|
||||
const made = await mountLogicFlow({
|
||||
split: panes.flowSplit,
|
||||
folded: panes.flowFolded(),
|
||||
onFold: (on) => panes.pick(on ? "none" : "flow"),
|
||||
flow: clone(o.flowBase),
|
||||
title: row.이름 || row.키,
|
||||
readOnly: !admin,
|
||||
notes: got?.notes ?? [],
|
||||
converted: !!got?.converted,
|
||||
leftHost: () => (flowOf(o.row) ? o.flowHost : null),
|
||||
onChange: (lg) => {
|
||||
if (!o.row || opened !== o) return;
|
||||
const was = flowOf(o.row);
|
||||
if (sameShape(lg, was ?? o.flowBase ?? lg)) {
|
||||
// 예시값만 바뀜 — 고친 것 아님 · 왼쪽 시험 계산 입력에 같은 이름으로 넣고 다시 셈
|
||||
o.flowBase = lg;
|
||||
syncLeft(o, examplesOf(lg));
|
||||
return;
|
||||
}
|
||||
o.row.흐름 = lg;
|
||||
touch();
|
||||
if (!was) drawEditor(); // 이제 흐름 로직 — 왼쪽을 흐름 보기로
|
||||
},
|
||||
});
|
||||
if (opened !== o) return made.destroy();
|
||||
flowNow = made;
|
||||
panes.bindFlow(made.fold);
|
||||
made.setExamples(o.values); // 띄우는 사이에 온 자동값까지
|
||||
} catch (e) {
|
||||
panes.flowSplit.right.replaceChildren(
|
||||
el("p", { className: "ui-text ui-text--muted", text: tf("Failed", String(e)) }),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** 흐름 예시값 → 왼쪽 시험 계산 입력(이름이 있는 칸만) · 바뀌면 왼쪽을 다시 그리고 셈 */
|
||||
const syncLeft = (o: Opened, ex: Record<string, string>, loud = false): void => {
|
||||
/** 텍스트 수식 · 시트 입력 → 왼쪽 시험 계산 입력(이름이 있는 칸만) · 바뀌면 다시 그리고 셈 */
|
||||
const syncLeft = (o: Opened, ex: Record<string, string>): void => {
|
||||
const names = new Set((calcRow(o.row)?.입력 ?? []).map((x) => x.이름));
|
||||
const changed = Object.entries(ex).filter(([k, v]) => names.has(k) && o.values[k] !== v);
|
||||
if (!changed.length) return;
|
||||
quiet = !loud; // 텍스트 수식 고침(loud)은 흐름 예시값에도 — 시험 계산 칸 고침과 같은 길
|
||||
for (const [k, v] of changed) o.values[k] = v;
|
||||
quiet = false;
|
||||
drawCalc();
|
||||
calc.querySelector<HTMLButtonElement>("[data-calc-run]")?.click();
|
||||
};
|
||||
/** 왼쪽 시험 계산 입력 — 칸이 바뀌면 흐름 예시값으로(이 로직이 열려 있을 때만) */
|
||||
let quiet = false;
|
||||
const watchValues = (): Record<string, string> => {
|
||||
const self: Record<string, string> = new Proxy({} as Record<string, string>, {
|
||||
set(t, k, v) {
|
||||
const was = t[k as string];
|
||||
t[k as string] = v as string;
|
||||
if (!quiet && was !== v && opened?.values === self)
|
||||
flowNow?.setExamples({ [k as string]: v as string });
|
||||
return true;
|
||||
},
|
||||
});
|
||||
return self;
|
||||
};
|
||||
|
||||
const show = (next: Opened | null): void => {
|
||||
flowNow?.destroy();
|
||||
flowNow = null;
|
||||
panes.bindFlow(null);
|
||||
opened = next;
|
||||
panes.showSource(next?.row?.출처 ?? "");
|
||||
errors.hidden = true;
|
||||
drawEditor();
|
||||
drawCalc();
|
||||
if (next?.row) void startFlow(next);
|
||||
};
|
||||
|
||||
const open = async (id: string, sub: string, key: string): Promise<void> => {
|
||||
@@ -391,8 +277,7 @@ export async function mountM01LogicLab(
|
||||
lines: null,
|
||||
text: null,
|
||||
ownHost: ownHost(),
|
||||
values: watchValues(),
|
||||
flowHost: el("div"),
|
||||
values: {},
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -411,8 +296,7 @@ export async function mountM01LogicLab(
|
||||
lines: null,
|
||||
text: null,
|
||||
ownHost: ownHost(),
|
||||
values: watchValues(),
|
||||
flowHost: el("div"),
|
||||
values: {},
|
||||
});
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : tx("Load_Failed"), "error");
|
||||
@@ -557,7 +441,8 @@ export async function mountM01LogicLab(
|
||||
...info,
|
||||
입력: [],
|
||||
중간: [],
|
||||
흐름: await emptyFlow(info.이름, info.결과단위),
|
||||
호표: [], // 빈 시트 줄 — 자체 로직 편집기 · 시트로 채움
|
||||
끝수: null,
|
||||
};
|
||||
const id = logicId("자체", `새${Date.now()}`);
|
||||
drafts[id] = { sub: "자체", file: OWN_FILE, version: own?.version ?? "", origKey: null, row };
|
||||
@@ -595,7 +480,7 @@ export async function mountM01LogicLab(
|
||||
status: [pending],
|
||||
buttons: [
|
||||
views.root,
|
||||
createButton({ label: tf("New"), variant: "filled", onClick: () => void onNew() }),
|
||||
createButton({ label: tl("New"), variant: "filled", onClick: () => void onNew() }),
|
||||
copyButton,
|
||||
createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }),
|
||||
discardButton,
|
||||
|
||||
Reference in New Issue
Block a user