feat(M01): 옛 일위대가 로직 컨테이너는 호표 표 그대로 · 아래에 「로직 쉽게 보기·만들기」 컨테이너를 더해 방식 C + 새로 만들기 모달을 담음
- 일위대가 로직 컨테이너 = 로직을 누르면 옛 호표 표 + 시험 계산 칸이 바로 열림 · 보기 전환 단추와 보기 캐시 값 없앰 · 「새로 만들기」 는 옛 초안 방식 - 새 컨테이너 = 같은 목록·API 를 쓰되 거름 기억과 저장 안 한 것 캐시를 따로 둠 · 로직을 고르면 흐름 그림 · 새로 만들기 모달 · 본떠 만들기 · 상자 고치기 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
@@ -61,14 +61,13 @@ interface Opened extends Draft {
|
||||
values: Record<string, string>;
|
||||
}
|
||||
|
||||
const CACHE_KEY = "m01_logic_drafts";
|
||||
/** 기본 보기 = 흐름 그림 · 「고급」 = 옛 호표 표 + 시험 계산 칸 */
|
||||
const VIEW_KEY = "m01.logic.view";
|
||||
/** 두 컨테이너(옛 호표 화면 · 쉽게 보기·만들기)가 서로의 저장 안 한 것을 덮지 않게 캐시 칸을 가름 */
|
||||
const cacheKey = (easy: boolean): string => (easy ? "m01_logic_drafts_easy" : "m01_logic_drafts");
|
||||
const clone = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
|
||||
|
||||
function loadDrafts(): Record<string, Draft> {
|
||||
function loadDrafts(easy: boolean): Record<string, Draft> {
|
||||
try {
|
||||
return JSON.parse(sessionStorage.getItem(CACHE_KEY) ?? "{}") as Record<string, Draft>;
|
||||
return JSON.parse(sessionStorage.getItem(cacheKey(easy)) ?? "{}") as Record<string, Draft>;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
@@ -83,13 +82,14 @@ export async function mountM01Logic(
|
||||
host: HTMLElement,
|
||||
side: SideHandle,
|
||||
openKey?: string,
|
||||
easy = false,
|
||||
): Promise<LogicHandle> {
|
||||
let drafts = loadDrafts();
|
||||
let drafts = loadDrafts(easy);
|
||||
let files: LogicFile[] = [];
|
||||
let opened: Opened | null = null;
|
||||
let calcInputs = "";
|
||||
let view: "flow" | "advanced" =
|
||||
sessionStorage.getItem(VIEW_KEY) === "advanced" ? "advanced" : "flow";
|
||||
/** 옛 컨테이너 = 호표 표 + 시험 계산 칸 · 쉽게 보기·만들기 컨테이너 = 흐름 그림 */
|
||||
const view: "flow" | "advanced" = easy ? "flow" : "advanced";
|
||||
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
const errors = el("div", { className: "m01-logic__reasons", attrs: { hidden: "" } });
|
||||
@@ -103,7 +103,7 @@ export async function mountM01Logic(
|
||||
|
||||
const persist = (): void => {
|
||||
try {
|
||||
sessionStorage.setItem(CACHE_KEY, JSON.stringify(drafts));
|
||||
sessionStorage.setItem(cacheKey(easy), JSON.stringify(drafts));
|
||||
} catch {
|
||||
/* 캐시가 막혀도 화면 안의 고친 것은 남음 */
|
||||
}
|
||||
@@ -176,7 +176,7 @@ export async function mountM01Logic(
|
||||
const listing = !opened;
|
||||
list.root.hidden = !listing || !loaded;
|
||||
waiting.hidden = loaded;
|
||||
editor.hidden = back.hidden = viewButton.hidden = listing;
|
||||
editor.hidden = back.hidden = listing;
|
||||
calc.hidden = listing || view === "flow";
|
||||
if (!opened) return;
|
||||
if (!opened.row) {
|
||||
@@ -274,8 +274,8 @@ export async function mountM01Logic(
|
||||
// 왼쪽 「전체」 + 구분 → 상세구분 거름 — 고르면 목록으로 돌아와 그 범위만
|
||||
filterHost.replaceChildren(
|
||||
side.filter({
|
||||
id: "로직|",
|
||||
store: "m01.filter.로직",
|
||||
id: easy ? "쉬움|" : "로직|",
|
||||
store: easy ? "m01.filter.로직쉬움" : "m01.filter.로직",
|
||||
subs,
|
||||
total: list.total(),
|
||||
subLabel: L("M01_LaborSub"),
|
||||
@@ -291,7 +291,7 @@ export async function mountM01Logic(
|
||||
};
|
||||
|
||||
/** 「새로 만들기」 모달 — 저장·본떠 저장이 끝나면 그 새 자체 로직을 흐름 그림으로 바로 엶 */
|
||||
const onNew = (): void =>
|
||||
const openWizard = (): void =>
|
||||
openLogicWizard({
|
||||
onSaved: (key) =>
|
||||
void reload()
|
||||
@@ -299,6 +299,44 @@ export async function mountM01Logic(
|
||||
.catch(failed),
|
||||
});
|
||||
|
||||
/** 옛 컨테이너의 「새로 만들기」 — 빈 줄 하나를 초안으로 세움(저장 때 서버가 키를 줌) */
|
||||
const onDraftNew = (): void => {
|
||||
const f = files.find((x) => x.file === opened?.file) ?? files[0];
|
||||
if (!f) return;
|
||||
const id = `new:${Date.now()}`;
|
||||
// 원문번호는 절 번호만(부문은 구분 칸) · 키는 저장 때 서버가 줌
|
||||
const row: LogicRow = {
|
||||
키: "",
|
||||
원문번호: `${tx("New_Key")} ${Object.keys(drafts).length + 1}`,
|
||||
이름: "",
|
||||
결과단위: "원/",
|
||||
출처: "",
|
||||
소유: "공용",
|
||||
입력: [],
|
||||
중간: [],
|
||||
호표: [],
|
||||
덧줄: [],
|
||||
끝수: null,
|
||||
};
|
||||
const next: Opened = {
|
||||
id,
|
||||
sub: subOf(f),
|
||||
file: f.file,
|
||||
version: f.version,
|
||||
origKey: null,
|
||||
row,
|
||||
original: null,
|
||||
prices: {},
|
||||
reasons: [],
|
||||
lines: null,
|
||||
values: {},
|
||||
};
|
||||
drafts[id] = pick(next, clone(row));
|
||||
persist();
|
||||
show(next);
|
||||
};
|
||||
const onNew = easy ? openWizard : onDraftNew;
|
||||
|
||||
/** 「본떠 만들기」 — 정본을 자체 파일로 복제하고(계약 5장) 그 새 로직을 바로 엶 */
|
||||
const onCopy = async (from: Opened): Promise<void> => {
|
||||
if (!from.origKey) return;
|
||||
@@ -392,23 +430,6 @@ export async function mountM01Logic(
|
||||
}
|
||||
};
|
||||
|
||||
const viewButton = createButton({
|
||||
label: tx("View_Advanced"),
|
||||
variant: "ghost",
|
||||
onClick: () => {
|
||||
view = view === "flow" ? "advanced" : "flow";
|
||||
try {
|
||||
sessionStorage.setItem(VIEW_KEY, view);
|
||||
} catch {
|
||||
/* 캐시가 막혀도 이 판 안에서는 바뀐 보기가 남음 */
|
||||
}
|
||||
viewButton.textContent = view === "flow" ? tx("View_Advanced") : tx("View_Flow");
|
||||
editor.replaceChildren();
|
||||
drawEditor();
|
||||
drawCalc();
|
||||
},
|
||||
});
|
||||
viewButton.textContent = view === "flow" ? tx("View_Advanced") : tx("View_Flow");
|
||||
const saveButton = createButton({ label: tx("Bar_Save"), onClick: () => void onSave() });
|
||||
const discardButton = createButton({
|
||||
label: tx("Bar_Discard"),
|
||||
@@ -420,7 +441,6 @@ export async function mountM01Logic(
|
||||
children: [
|
||||
back,
|
||||
el("h2", { text: tx("Title") }),
|
||||
viewButton,
|
||||
pending,
|
||||
createButton({ label: tx("Bar_New"), variant: "ghost", onClick: onNew }),
|
||||
createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }),
|
||||
@@ -440,7 +460,7 @@ export async function mountM01Logic(
|
||||
],
|
||||
}),
|
||||
);
|
||||
side.logicHost.replaceChildren(filterHost);
|
||||
(easy ? side.easyHost : side.logicHost).replaceChildren(filterHost);
|
||||
show(null);
|
||||
showLoadingOverlay();
|
||||
const openKeyOn = async (key: string): Promise<void> => {
|
||||
|
||||
@@ -85,6 +85,7 @@ const TEXT = {
|
||||
Save_Failed: ["저장 못 함", "Save failed"],
|
||||
Save_Nothing: ["고친 것 없음", "Nothing to save"],
|
||||
Load_Failed: ["불러오지 못함", "Load failed"],
|
||||
Easy_Title: ["로직 쉽게 보기·만들기", "Logic — easy view & create"],
|
||||
Loading: ["불러오는 중", "Loading"],
|
||||
Confirm_Delete: [
|
||||
"로직 「{v}」 을 지울까요? [저장] 때 파일에서 빠짐",
|
||||
|
||||
@@ -54,6 +54,7 @@ function buildPage(): HTMLElement {
|
||||
let query = "";
|
||||
let dispose = (): void => {};
|
||||
let logicMounted = false;
|
||||
let easyMounted = false;
|
||||
|
||||
/* --- 우측: 머리(제목·찾기·저장) + 알림 + 본문 --- */
|
||||
const title = el("h2", { className: "m01-master__title", text: L("M01_PickFile") });
|
||||
@@ -69,6 +70,7 @@ function buildPage(): HTMLElement {
|
||||
children: [title, searchField.root, summary, drop, save],
|
||||
});
|
||||
const logicHost = el("div", { className: "m01-master__logic", attrs: { hidden: "" } });
|
||||
const easyHost = el("div", { className: "m01-master__logic", attrs: { hidden: "" } });
|
||||
const elementView = el("div", {
|
||||
className: "m01-master__elements",
|
||||
children: [head, notice, body],
|
||||
@@ -86,8 +88,9 @@ function buildPage(): HTMLElement {
|
||||
onDraftChange(refreshBar);
|
||||
refreshBar();
|
||||
|
||||
const showMode = (mode: "elements" | "logic"): void => {
|
||||
const showMode = (mode: "elements" | "logic" | "easy"): void => {
|
||||
logicHost.hidden = mode !== "logic";
|
||||
easyHost.hidden = mode !== "easy";
|
||||
elementView.hidden = mode !== "elements";
|
||||
};
|
||||
|
||||
@@ -193,8 +196,21 @@ function buildPage(): HTMLElement {
|
||||
});
|
||||
};
|
||||
|
||||
/** 「로직 쉽게 보기·만들기」 — 같은 로직 목록에 방식 C 흐름 그림 + 새로 만들기 모달 */
|
||||
const openEasyTab = (): void => {
|
||||
dispose();
|
||||
dispose = (): void => {};
|
||||
current = null;
|
||||
showMode("easy");
|
||||
if (easyMounted) return;
|
||||
easyMounted = true;
|
||||
void import("./M01_MasterData_UI_Logic_Page").then((m) =>
|
||||
m.mountM01Logic(easyHost, side, undefined, true),
|
||||
);
|
||||
};
|
||||
|
||||
/* --- 좌측: 컨테이너 --- */
|
||||
const side = buildSide(openFile, () => openLogicTab());
|
||||
const side = buildSide(openFile, () => openLogicTab(), openEasyTab);
|
||||
|
||||
const layout = el("div", { className: "ui-workflow-layout m01-master" });
|
||||
const main = el("main", {
|
||||
@@ -202,7 +218,7 @@ function buildPage(): HTMLElement {
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01-master__panel",
|
||||
children: [elementView, logicHost],
|
||||
children: [elementView, logicHost, easyHost],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createSelectField, el, showToast } from "@ui/ui_template_elements";
|
||||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree";
|
||||
|
||||
/** 컨테이너마다 기억하는 화면 상태 — 찾기 글 · 쪽 · 소요량·계수 표의 용도 대상 거름 (거름은 `m01.filter.*`) */
|
||||
@@ -74,6 +75,8 @@ export interface SideHandle {
|
||||
root: HTMLElement;
|
||||
/** 일위대가 로직 컨테이너 안(로직 목록이 들어갈 자리) */
|
||||
logicHost: HTMLElement;
|
||||
/** 「로직 쉽게 보기·만들기」 컨테이너 안(같은 로직 목록이 들어갈 자리) */
|
||||
easyHost: HTMLElement;
|
||||
/** 그룹의 파일 판본을 다시 받음(저장 뒤) — 돌려받는 것 = 새 목록 */
|
||||
refresh: (group: string) => Promise<FileInfo[]>;
|
||||
setActive: (id: string | null) => void;
|
||||
@@ -127,8 +130,13 @@ const LEAF: Group[] = ["환율", "요율"];
|
||||
/** 거름이 있는 컨테이너 — 펼치면 「전체」(또는 닫기 전 거름) 이 바로 열림 */
|
||||
const FILTERED: Group[] = ["인력", "기계", "소요량", "계수"];
|
||||
const LOGIC_ID = "로직|";
|
||||
const EASY_ID = "쉬움|";
|
||||
|
||||
export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): SideHandle {
|
||||
export function buildSide(
|
||||
onOpen: (pick: Pick) => void,
|
||||
onLogic: () => void,
|
||||
onEasy: () => void,
|
||||
): SideHandle {
|
||||
const lists = new Map<Group, FileInfo[]>();
|
||||
const bodies = new Map<Group, HTMLElement>();
|
||||
const buttons = new Map<string, HTMLElement>();
|
||||
@@ -403,10 +411,18 @@ export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): Si
|
||||
onLogic();
|
||||
});
|
||||
|
||||
const root = el("div", { className: "m01-side", children: [...groups, logic] });
|
||||
const easyHost = el("div", { className: "m01-side__items" });
|
||||
const easy = section(tx("Easy_Title"), easyHost);
|
||||
easy.querySelector(".ui-collapsible__title")?.addEventListener("click", () => {
|
||||
if (!easy.classList.contains("is-collapsed")) return;
|
||||
setActive(buttons.has(EASY_ID) ? EASY_ID : null);
|
||||
onEasy();
|
||||
});
|
||||
|
||||
const root = el("div", { className: "m01-side", children: [...groups, logic, easy] });
|
||||
attachCollapsible(root);
|
||||
void Promise.all(GROUPS.map(refresh)).catch((error) =>
|
||||
showToast(error instanceof Error ? error.message : L("M01_LoadFailed"), "error"),
|
||||
);
|
||||
return { root, logicHost, refresh, setActive, filter };
|
||||
return { root, logicHost, easyHost, refresh, setActive, filter };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user