feat(M01): 로직 개선 시험 알약에서 마스터 화면으로 잇기 · 단가 없는 재료 알약에 이유 한 마디 (PLAN 5-3 · 5-4)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
type RelatedFind,
|
||||
type TableRow,
|
||||
} from "./M01_MasterData_UI_Logic_Note";
|
||||
import { jumpToMaster, type MasterJump } from "./M01_MasterData_UI_Side";
|
||||
import { pillBar, type Pill } from "./M01_MasterData_UI_LogicLab_Pill";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import "./M01_MasterData_UI_LogicLab_Pill.css";
|
||||
@@ -72,6 +73,7 @@ function tableView(
|
||||
className: "m01-logic__muted",
|
||||
text: `${find.source} → ${find.find.col} · ${hit ? tl("Modal_Hit") : tl("Modal_NoHit")}`,
|
||||
}),
|
||||
goButton({ kind: "table", key: table.키 }),
|
||||
el("div", {
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
@@ -93,6 +95,14 @@ function tableView(
|
||||
const groupOf = (ref: string): string =>
|
||||
ref.startsWith("LB") ? "인력" : ref.startsWith("M") ? "재료" : ref.startsWith("EQ") ? "기계" : "";
|
||||
|
||||
function goButton(target: MasterJump): HTMLElement {
|
||||
return createButton({
|
||||
label: tl("Pill_Go"),
|
||||
variant: "ghost",
|
||||
onClick: () => jumpToMaster(target),
|
||||
});
|
||||
}
|
||||
|
||||
function priceView(ref: string, brief: ElementBrief): HTMLElement {
|
||||
const cols = Object.entries(brief.값칸 ?? {});
|
||||
return el("div", {
|
||||
@@ -114,6 +124,7 @@ function priceView(ref: string, brief: ElementBrief): HTMLElement {
|
||||
...cols.flatMap(([k, v]) => [el("dt", { text: k }), el("dd", { text: String(v) })]),
|
||||
],
|
||||
}),
|
||||
goButton({ kind: "element", group: groupOf(ref), ref: brief.ref ?? ref, file: brief.file }),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -153,7 +164,11 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
} else if (target.element && !logicRef) {
|
||||
pills.push({
|
||||
label: `${groupOf(base)} ${target.title}`.trim(),
|
||||
view: () => el("p", { text: target.element }),
|
||||
view: () =>
|
||||
el("p", {
|
||||
className: "m01-logic__muted",
|
||||
text: `${target.element} — ${tl(target.element?.includes("{") ? "Pill_NoPick" : "Pill_NoValue")}`,
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (logicRef) {
|
||||
|
||||
@@ -52,6 +52,13 @@ const TEXT = {
|
||||
Modal_UnitPrice: ["단가", "Unit price"],
|
||||
Pill_Table: ["표", "Table"],
|
||||
Pill_Logic: ["로직", "Logic"],
|
||||
Pill_Go: ["마스터 화면에서 보기", "Open in master"],
|
||||
Pill_Back: ["← 로직 개선 시험으로 돌아가기", "← Back to logic lab"],
|
||||
Pill_NoPick: [
|
||||
"고르기 조건에 맞는 마스터 줄이 없음 — 단가를 못 찾음",
|
||||
"No master row matches the pick conditions",
|
||||
],
|
||||
Pill_NoValue: ["관리자 값 미확보 — 마스터에 단가가 없음", "No price in master yet"],
|
||||
Modal_Qty: ["수량 식", "Quantity formula"],
|
||||
Modal_Price: ["단가 자료", "Price source"],
|
||||
Modal_Value: ["값", "Value"],
|
||||
|
||||
@@ -20,7 +20,7 @@ import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
import { saveFiles } from "./M01_MasterData_Api_Fetch";
|
||||
import { saveFiles, type FileInfo } from "./M01_MasterData_Api_Fetch";
|
||||
import { discard, isStale, onDraftChange, payload, totalCount } from "./M01_MasterData_Draft";
|
||||
import { renderRows } from "./M01_MasterData_UI_Rows";
|
||||
import {
|
||||
@@ -28,10 +28,14 @@ import {
|
||||
fileLabel,
|
||||
loadView,
|
||||
saveView,
|
||||
setMasterJump,
|
||||
setPendingLogicKey,
|
||||
takePendingLogicKey,
|
||||
type MasterJump,
|
||||
type Pick,
|
||||
} from "./M01_MasterData_UI_Side";
|
||||
import { loadTable } from "./M01_MasterData_UI_Logic_Note";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import { renderTables } from "./M01_MasterData_UI_Tables";
|
||||
import type { LogicHandle } from "./M01_MasterData_UI_Logic_Page";
|
||||
import type { ComboHandle } from "./M01_MasterData_UI_Combo";
|
||||
@@ -227,6 +231,45 @@ function buildPage(): HTMLElement {
|
||||
});
|
||||
};
|
||||
|
||||
/** 알약 → 마스터 화면 — 찾기 글을 그 키로 두고 열고, 알림줄에 「시험으로 돌아가기」를 둠 */
|
||||
const jump = async (target: MasterJump): Promise<void> => {
|
||||
let group = target.kind === "element" ? target.group : "";
|
||||
let file: FileInfo | undefined;
|
||||
let ref = "";
|
||||
if (target.kind === "table") {
|
||||
const table = await loadTable(target.key);
|
||||
group =
|
||||
["소요량", "계수"].find((g) => side.files(g).some((f) => f.file === table?.file)) ?? "";
|
||||
file = side.files(group).find((f) => f.file === table?.file);
|
||||
ref = target.key;
|
||||
} else {
|
||||
const list = side.files(group);
|
||||
file = list.find((f) => f.file === target.file) ?? list[0];
|
||||
ref = target.ref;
|
||||
}
|
||||
if (!file) return showToast(L("M01_LoadFailed"), "error");
|
||||
side.setActive(null);
|
||||
openFile({ group, file, sub: "", detail: "", label: group, view: { q: ref, page: 1 } });
|
||||
const back = createButton({ label: tl("Pill_Back"), variant: "ghost" });
|
||||
back.addEventListener("click", () => {
|
||||
showNotice([]);
|
||||
openLabTab();
|
||||
});
|
||||
showNotice([back]);
|
||||
if (target.kind === "table") {
|
||||
const key = target.key;
|
||||
const seen = new MutationObserver(() => {
|
||||
const row = body.querySelector<HTMLElement>(`[data-key="${CSS.escape(key)}"]`);
|
||||
if (!row) return;
|
||||
seen.disconnect();
|
||||
row.click();
|
||||
});
|
||||
seen.observe(body, { childList: true, subtree: true });
|
||||
window.setTimeout(() => seen.disconnect(), 8000);
|
||||
}
|
||||
};
|
||||
setMasterJump((target) => void jump(target));
|
||||
|
||||
/* --- 좌측: 컨테이너 --- */
|
||||
const side = buildSide(openFile, () => openLogicTab(), openLabTab, openComboTab);
|
||||
|
||||
|
||||
@@ -59,6 +59,16 @@ export function takePendingLogicKey(): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
/** 「로직 개선 시험」 알약 → 마스터 화면 — 표(키) 또는 요소(그룹 · 키 · 파일) */
|
||||
export type MasterJump =
|
||||
{ kind: "table"; key: string } | { kind: "element"; group: string; ref: string; file?: string };
|
||||
|
||||
let masterJump: (target: MasterJump) => void = () => {};
|
||||
export const setMasterJump = (fn: (target: MasterJump) => void): void => {
|
||||
masterJump = fn;
|
||||
};
|
||||
export const jumpToMaster = (target: MasterJump): void => masterJump(target);
|
||||
|
||||
/** 요소 화면에서 열 것 — 파일 · 하위 거름(구분·세부분류) · 상세구분 · 제목 */
|
||||
export interface Pick {
|
||||
group: string;
|
||||
@@ -82,6 +92,8 @@ export interface SideHandle {
|
||||
labHost: HTMLElement;
|
||||
/** 그룹의 파일 판본을 다시 받음(저장 뒤) — 돌려받는 것 = 새 목록 */
|
||||
refresh: (group: string) => Promise<FileInfo[]>;
|
||||
/** 그룹의 파일 목록(이미 받은 것) */
|
||||
files: (group: string) => FileInfo[];
|
||||
setActive: (id: string | null) => void;
|
||||
/** 좁은 화면에서 사용자가 거름을 다 골랐을 때 부를 것 — 패널을 접어 목록을 드러냄 */
|
||||
setOnPicked: (fn: () => void) => void;
|
||||
@@ -456,5 +468,6 @@ export function buildSide(
|
||||
const setOnPicked = (fn: () => void): void => {
|
||||
onPicked = fn;
|
||||
};
|
||||
return { root, logicHost, comboHost, labHost, refresh, setActive, setOnPicked, filter };
|
||||
const files = (group: string): FileInfo[] => lists.get(group as Group) ?? [];
|
||||
return { root, logicHost, comboHost, labHost, refresh, files, setActive, setOnPicked, filter };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user