refactor(M01): 표 · 모달 · 띠 알림 · 배지 · 알약 · 보조 글 · 빈 상태를 부품으로 (PLAN 553-5)
- 표 둘 = 표 부품(m01-master__grid = 일반표 · m01-logic__grid = 데이터표) · 페이지 CSS 는 폭 · 머리 고정 · 칸 상태만 - 손 모달 다섯(조달 찾기 · 요소 고르기 · 표 바꾸기 · 복사 시트 · 표 고르기) = 모달 부품 openModal - 알림 띠 · 막힘 까닭 = 띠 알림 · 태그 = 배지 · 알약 · × · 삭제 · 로직 키 = 단추 부품 · muted 세 벌 = 보조 글 · 빈 줄 = 빈 상태 - M02 가 빌린 M01 클래스(표 · muted · 고르기 창)도 같은 부품으로 · 죽은 CSS 걷음 · 지킴 시험 test_553_5_m01_m02 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,7 +107,7 @@ export function buildPager(
|
||||
prev.addEventListener("click", () => goto(page - 1));
|
||||
next.addEventListener("click", () => goto(page + 1));
|
||||
return el("div", {
|
||||
className: "m01-master__pager",
|
||||
className: "ui-toolbar m01-master__pager",
|
||||
children: [
|
||||
el("span", {
|
||||
text: `${page} / ${last} ${L("M01_Page")} · ${total.toLocaleString("ko-KR")} ${L("M01_Rows")}`,
|
||||
|
||||
@@ -56,6 +56,7 @@ 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";
|
||||
|
||||
/** 저장 안 한 로직 하나 — origKey null = 새 로직 · row null = 지움 */
|
||||
interface Draft {
|
||||
@@ -129,12 +130,15 @@ export async function mountM01LogicLab(
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 둘(흐름도 · 원문 · 하나만 열림 · 폭 공유)
|
||||
const panes = buildLogicSide(editor);
|
||||
const errors = el("div", { className: "m01-logic__reasons", attrs: { hidden: "" } });
|
||||
const errors = el("div", {
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
attrs: { hidden: "" },
|
||||
});
|
||||
const calc = el("div", { className: "m01lab__calc" });
|
||||
const filterHost = el("div");
|
||||
const waiting = el("p", { className: "m01-logic__muted", text: tx("Loading") });
|
||||
const waiting = el("p", { className: "ui-text ui-text--muted", text: tx("Loading") });
|
||||
let loaded = false; // 첫 목록이 오기 전에는 「0 줄」 빈 표를 보이지 않음
|
||||
const pending = el("span", { className: "m01-logic__muted" });
|
||||
const pending = el("span", { className: "ui-text ui-text--muted" });
|
||||
const list = buildList(
|
||||
(item) => void open(item.id, item.sub, item.key),
|
||||
(item, alias) => void onAlias(item, alias),
|
||||
@@ -235,9 +239,7 @@ export async function mountM01LogicLab(
|
||||
back.hidden = listing;
|
||||
if (!opened) return;
|
||||
if (!opened.row) {
|
||||
editor.replaceChildren(
|
||||
el("p", { className: "m01-logic__empty", text: `● ${tx("List_Deleted")}` }),
|
||||
);
|
||||
editor.replaceChildren(createEmpty({ text: `● ${tx("List_Deleted")}`, small: true }));
|
||||
return;
|
||||
}
|
||||
const current = opened;
|
||||
@@ -321,7 +323,7 @@ export async function mountM01LogicLab(
|
||||
made.setExamples(o.values); // 띄우는 사이에 온 자동값까지
|
||||
} catch (e) {
|
||||
panes.flowSplit.right.replaceChildren(
|
||||
el("p", { className: "m01-logic__muted", text: tf("Failed", String(e)) }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tf("Failed", String(e)) }),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ export function askBasicInfo(units: string[]): Promise<BasicInfo | null> {
|
||||
compact: true,
|
||||
});
|
||||
const source = createInputField({ label: tb("Source"), value: "자체", compact: true });
|
||||
const why = el("p", { className: "m01-logic__muted", attrs: { role: "alert" } });
|
||||
const why = el("p", { className: "ui-text ui-text--muted", attrs: { role: "alert" } });
|
||||
const modal = openModal({
|
||||
title: tb("Title"),
|
||||
closeLabel: tb("Close"),
|
||||
@@ -84,7 +84,7 @@ export function askBasicInfo(units: string[]): Promise<BasicInfo | null> {
|
||||
source.root,
|
||||
why,
|
||||
el("div", {
|
||||
className: "m01-logic__bar",
|
||||
className: "ui-toolbar m01-logic__bar",
|
||||
children: [
|
||||
createButton({ label: tb("Ok"), size: "sm", onClick: ok }),
|
||||
createButton({
|
||||
|
||||
@@ -31,6 +31,9 @@ import { fetchTables, type TableHead } from "./M01_MasterData_Api_Fetch";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import "./M01_MasterData_UI_LogicLab_Style.css";
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
|
||||
const OWNERS = ["현장", "공용"] as const;
|
||||
const TABLE_GROUPS = ["소요량", "계수"] as const;
|
||||
@@ -78,29 +81,16 @@ function swapKey(raw: string, newKey: string): string {
|
||||
|
||||
/** 표 바꾸기 모달 — 소요량·계수 두 그룹을 함께 찾음 */
|
||||
function openTablePick(onPick: (table: TableHead) => void): void {
|
||||
const close = (): void => backdrop.remove();
|
||||
const search = createInputField({ type: "search", placeholder: tl("Copy_PickSearch") });
|
||||
const list = el("div", { className: "m01-logic__pick-list" });
|
||||
const dialog = el("div", {
|
||||
className: "m01-logic__pick ui-popup",
|
||||
attrs: { role: "dialog", "aria-label": tl("Copy_PickTitle") },
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01lab__modal-top",
|
||||
children: [
|
||||
el("h3", { text: tl("Copy_PickTitle") }),
|
||||
createButton({ label: tl("Modal_Close"), variant: "ghost", onClick: close }),
|
||||
],
|
||||
}),
|
||||
search.root,
|
||||
list,
|
||||
],
|
||||
// 모달 부품 — 제목줄 [닫기] · 바깥 누르기 · Esc · 위 고정 · 찾기 칸에 커서
|
||||
const { close } = openModal({
|
||||
title: tl("Copy_PickTitle"),
|
||||
closeLabel: tl("Modal_Close"),
|
||||
dialogClass: "m01-logic__pick",
|
||||
backdropClass: "m01-logic__backdrop",
|
||||
mount: (body) => body.append(search.root, list),
|
||||
});
|
||||
const backdrop = el("div", { className: "m01-logic__backdrop", children: [dialog] });
|
||||
backdrop.addEventListener("click", (ev) => ev.target === backdrop && close());
|
||||
backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && close());
|
||||
document.body.append(backdrop);
|
||||
search.input.focus();
|
||||
|
||||
let timer: number | undefined;
|
||||
const run = async (): Promise<void> => {
|
||||
@@ -113,11 +103,11 @@ function openTablePick(onPick: (table: TableHead) => void): void {
|
||||
...(hits.length
|
||||
? hits.map((t) => {
|
||||
const b = el("button", {
|
||||
className: "m01-logic__pick-row m01-logic__pick-row--button",
|
||||
attrs: { type: "button" },
|
||||
className: "m01-logic__pick-row ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button", "data-size": "32" },
|
||||
children: [
|
||||
el("span", { text: `${t.이름 || t.키} · ${t.원문번호 || ""}` }),
|
||||
el("span", { className: "m01-logic__muted", text: t.키 }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: t.키 }),
|
||||
],
|
||||
});
|
||||
b.addEventListener("click", () => {
|
||||
@@ -126,7 +116,7 @@ function openTablePick(onPick: (table: TableHead) => void): void {
|
||||
});
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m01-logic__muted", text: tl("Copy_PickNone") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: tl("Copy_PickNone") })]),
|
||||
);
|
||||
};
|
||||
search.input.addEventListener("input", () => {
|
||||
@@ -148,7 +138,7 @@ function aliasRow(a: FormulaAlias, onSwap: (raw: string) => void, sub: HTMLEleme
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("strong", { text: a.이름 }),
|
||||
el("span", { className: "m01-logic__muted", text: about }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: about }),
|
||||
],
|
||||
}),
|
||||
el("pre", { className: "m01lab__expr", text: a.원문 }),
|
||||
@@ -172,7 +162,7 @@ function logicAliasControls(
|
||||
): { root: HTMLElement } {
|
||||
let forked = false;
|
||||
const original = a.원문;
|
||||
const status = el("span", { className: "m01-logic__muted" });
|
||||
const status = el("span", { className: "ui-text ui-text--muted" });
|
||||
const refBtn = createButton({
|
||||
label: tl("Copy_Alias_Ref"),
|
||||
variant: "ghost",
|
||||
@@ -224,8 +214,10 @@ function lineBox(row: LogicRow, line: FormulaLine, onChange: () => void): HTMLEl
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("strong", { text: line.이름 || line.자리 }),
|
||||
el("span", { className: "m01-logic__tag", text: tag }),
|
||||
...(line.단위 ? [el("span", { className: "m01-logic__muted", text: line.단위 })] : []),
|
||||
createBadge(tag, "accent"),
|
||||
...(line.단위
|
||||
? [el("span", { className: "ui-text ui-text--muted", text: line.단위 })]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
box,
|
||||
@@ -287,9 +279,7 @@ export async function openCopyScreen(opt: CopyOptions): Promise<void> {
|
||||
onLines: () => undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
calcHost.replaceChildren(
|
||||
el("p", { className: "m01-logic__bad", text: error instanceof Error ? error.message : "" }),
|
||||
);
|
||||
calcHost.replaceChildren(createText(error instanceof Error ? error.message : "", "error"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -316,7 +306,7 @@ export async function openCopyScreen(opt: CopyOptions): Promise<void> {
|
||||
drawAliases();
|
||||
refreshCalc();
|
||||
|
||||
const close = (): void => backdrop.remove();
|
||||
let close = (): void => {};
|
||||
const onSave = async (): Promise<void> => {
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
@@ -337,55 +327,48 @@ export async function openCopyScreen(opt: CopyOptions): Promise<void> {
|
||||
}
|
||||
};
|
||||
|
||||
const sheet = el("div", {
|
||||
className: "m01-logic__pick m01copy__sheet",
|
||||
attrs: { role: "dialog", "aria-label": tl("Copy_Title") },
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01lab__modal-top",
|
||||
children: [
|
||||
el("h3", { text: `${tl("Copy_Title")} — ${row.이름}(${opt.key})` }),
|
||||
createButton({ label: tl("Copy_Cancel"), variant: "ghost", onClick: close }),
|
||||
],
|
||||
}),
|
||||
el("div", {
|
||||
className: "m01copy__head",
|
||||
children: [nameField.root, ownerField.root],
|
||||
}),
|
||||
el("section", {
|
||||
className: "m01-logic__section",
|
||||
children: [el("h3", { text: tl("Copy_Aliases") }), aliasHost],
|
||||
}),
|
||||
el("section", {
|
||||
className: "m01-logic__section",
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("h3", { text: tl("Copy_Lines") }),
|
||||
createButton({
|
||||
label: tl("Copy_AddExtra"),
|
||||
variant: "ghost",
|
||||
onClick: () => addExtra(row, refreshCalc, drawLines),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
linesHost,
|
||||
],
|
||||
}),
|
||||
el("section", { className: "m01-logic__section", children: [calcHost] }),
|
||||
el("div", {
|
||||
className: "m01-logic__pick-bar",
|
||||
children: [
|
||||
el("span", {}),
|
||||
el("span", {}),
|
||||
createButton({ label: tl("Copy_Save"), onClick: () => void onSave() }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const backdrop = el("div", { className: "m01-logic__backdrop", children: [sheet] });
|
||||
backdrop.addEventListener("click", (ev) => ev.target === backdrop && close());
|
||||
backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && close());
|
||||
document.body.append(backdrop);
|
||||
// 모달 부품 — 제목줄 [취소] · 바깥 누르기 · Esc · 위 고정 · 폭만 복사 시트 곁값
|
||||
({ close } = openModal({
|
||||
title: `${tl("Copy_Title")} — ${row.이름}(${opt.key})`,
|
||||
closeLabel: tl("Copy_Cancel"),
|
||||
dialogClass: "m01-logic__pick m01copy__sheet",
|
||||
backdropClass: "m01-logic__backdrop",
|
||||
mount: (body) =>
|
||||
body.append(
|
||||
el("div", {
|
||||
className: "m01copy__head",
|
||||
children: [nameField.root, ownerField.root],
|
||||
}),
|
||||
el("section", {
|
||||
className: "m01-logic__section",
|
||||
children: [el("h3", { text: tl("Copy_Aliases") }), aliasHost],
|
||||
}),
|
||||
el("section", {
|
||||
className: "m01-logic__section",
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("h3", { text: tl("Copy_Lines") }),
|
||||
createButton({
|
||||
label: tl("Copy_AddExtra"),
|
||||
variant: "ghost",
|
||||
onClick: () => addExtra(row, refreshCalc, drawLines),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
linesHost,
|
||||
],
|
||||
}),
|
||||
el("section", { className: "m01-logic__section", children: [calcHost] }),
|
||||
el("div", {
|
||||
className: "m01-logic__pick-bar",
|
||||
children: [
|
||||
el("span", {}),
|
||||
el("span", {}),
|
||||
createButton({ label: tl("Copy_Save"), onClick: () => void onSave() }),
|
||||
],
|
||||
}),
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill";
|
||||
import { tl, type LabTextKey } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import "./M01_MasterData_UI_LogicLab_Style.css";
|
||||
import "./M01_MasterData_UI_LogicLab_Pill.css";
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
|
||||
/** 서버 조각(`/text` 줄의 `조각`) → 수량 칸 조각 — 요소·표·로직은 알약(글 = 조각 글) · 나머지는 글자 */
|
||||
function toFragments(pieces: TextPiece[], ctx: DetailContext): Fragment[] {
|
||||
@@ -131,7 +132,7 @@ const section = (title: string, body: HTMLElement[], hint = ""): HTMLElement =>
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("h3", { text: title }),
|
||||
...(hint ? [el("span", { className: "m01-logic__muted", text: hint })] : []),
|
||||
...(hint ? [el("span", { className: "ui-text ui-text--muted", text: hint })] : []),
|
||||
],
|
||||
}),
|
||||
...body,
|
||||
@@ -151,14 +152,14 @@ function infoBox(ctx: DetailContext): HTMLElement {
|
||||
});
|
||||
return el("label", {
|
||||
className: "m01lab__cell",
|
||||
children: [el("span", { className: "m01-logic__muted", text: label }), box],
|
||||
children: [el("span", { className: "ui-text ui-text--muted", text: label }), box],
|
||||
});
|
||||
};
|
||||
const cell = (label: string, value: string): HTMLElement =>
|
||||
el("div", {
|
||||
className: "m01lab__cell",
|
||||
children: [
|
||||
el("span", { className: "m01-logic__muted", text: label }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: label }),
|
||||
el("span", { text: value || "—" }),
|
||||
],
|
||||
});
|
||||
@@ -172,7 +173,7 @@ function infoBox(ctx: DetailContext): HTMLElement {
|
||||
const reasons = ctx.reasons.length
|
||||
? [
|
||||
el("div", {
|
||||
className: "m01-logic__reasons",
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
children: [
|
||||
el("strong", { text: tx("Head_Blocked") }),
|
||||
...ctx.reasons.map((r) => el("div", { text: safeReason(r) })),
|
||||
@@ -182,7 +183,7 @@ function infoBox(ctx: DetailContext): HTMLElement {
|
||||
: [];
|
||||
return section(tl("Info_Title"), [
|
||||
el("p", {
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: tl(own ? "Info_Own" : ctx.admin ? "Info_Base" : "Info_ReadOnly"),
|
||||
}),
|
||||
...reasons,
|
||||
@@ -198,7 +199,10 @@ function infoBox(ctx: DetailContext): HTMLElement {
|
||||
cell(tx("Head_Owner"), row.소유 ?? ""),
|
||||
el("label", {
|
||||
className: "m01lab__cell m01lab__cell--wide",
|
||||
children: [el("span", { className: "m01-logic__muted", text: tx("Head_Note") }), note],
|
||||
children: [
|
||||
el("span", { className: "ui-text ui-text--muted", text: tx("Head_Note") }),
|
||||
note,
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
@@ -342,27 +346,15 @@ function lineRow(e: Entry): HTMLElement {
|
||||
el("td", {
|
||||
children: [
|
||||
el("div", { text: e.name }),
|
||||
...(e.share
|
||||
? [el("div", { className: "m01-logic__tag", text: `${e.share} ${tl("Share")}` })]
|
||||
: []),
|
||||
...(e.spec ? [el("div", { className: "m01-logic__muted", text: e.spec })] : []),
|
||||
...(e.warn
|
||||
? [
|
||||
el("div", {
|
||||
className: "m01-logic__tag m01lab__warn",
|
||||
text: `${tx("Calc_UnitWarn")} · ${e.warn}`,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(e.share ? [createBadge(`${e.share} ${tl("Share")}`, "accent")] : []),
|
||||
...(e.spec ? [el("div", { className: "ui-text ui-text--muted", text: e.spec })] : []),
|
||||
...(e.warn ? [createBadge(`${tx("Calc_UnitWarn")} · ${e.warn}`, "warning")] : []),
|
||||
],
|
||||
}),
|
||||
cellOf(e.unit),
|
||||
el("td", {
|
||||
className: "m01lab__qty-cell",
|
||||
children: [
|
||||
el("span", { className: "m01-logic__tag", text: e.qtyTag }),
|
||||
fragmentRow(e.qtyFrag),
|
||||
],
|
||||
children: [createBadge(e.qtyTag, "accent"), fragmentRow(e.qtyFrag)],
|
||||
}),
|
||||
cellOf(e.price, "m01-logic__money"),
|
||||
cellOf(e.amount === null ? "" : formatMoney(e.amount), "m01-logic__money"),
|
||||
@@ -379,7 +371,7 @@ function groupTable(group: Group, mine: Entry[], calculated: boolean): HTMLEleme
|
||||
children: [
|
||||
el("td", {
|
||||
attrs: { colspan: "5" },
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: tl("Empty_Group"),
|
||||
}),
|
||||
],
|
||||
@@ -405,7 +397,7 @@ function groupTable(group: Group, mine: Entry[], calculated: boolean): HTMLEleme
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-logic__grid m01lab__ho",
|
||||
className: "ui-table ui-table--data m01-logic__grid m01lab__ho",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [el("tr", { children: headers.map((h) => el("th", { text: h })) })],
|
||||
@@ -447,7 +439,7 @@ function hoBox(ctx: DetailContext): HTMLElement {
|
||||
const money = !("결과" in ctx.row) && (ctx.row.결과단위 ?? "").startsWith("원");
|
||||
if (!money) {
|
||||
return section(tl("Ho_Title"), [
|
||||
el("p", { className: "m01-logic__muted", text: tl("Not_Money") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tl("Not_Money") }),
|
||||
el("pre", { className: "m01lab__expr", text: ctx.row.결과?.식 ?? "" }),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,3 @@
|
||||
color: var(--color-text-body);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.m01flow__warn {
|
||||
margin: var(--spacing-4) 0 0;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
type FlowGroupKey,
|
||||
} from "./M01_MasterData_UI_LogicLab_FlowRows";
|
||||
import "./M01_MasterData_UI_LogicLab_FlowView.css";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
|
||||
const TEXT = {
|
||||
Detail: ["단가산출 상세", "Unit-price detail"],
|
||||
@@ -91,7 +92,7 @@ const groupTable = (g: FlowGroup): HTMLElement => {
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-logic__grid",
|
||||
className: "ui-table ui-table--data m01-logic__grid",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [
|
||||
@@ -159,7 +160,7 @@ export function buildFlowView(
|
||||
ev = opts.result ?? evalLogic(lg, values, { logics: opts.logics });
|
||||
} catch (e) {
|
||||
host.replaceChildren(
|
||||
section(tf("Detail"), [el("p", { className: "m01-logic__muted", text: String(e) })]),
|
||||
section(tf("Detail"), [el("p", { className: "ui-text ui-text--muted", text: String(e) })]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -168,17 +169,17 @@ export function buildFlowView(
|
||||
const detail = section(tf("Detail"), [
|
||||
...(rows.groups.length
|
||||
? rows.groups.map(groupTable)
|
||||
: [el("p", { className: "m01-logic__muted", text: tf("None") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: tf("None") })]),
|
||||
el("p", {
|
||||
className: "m01lab__total",
|
||||
attrs: { "data-sum": String(rows.total) },
|
||||
text: `${tf("Total")} ${rows.totalText}`,
|
||||
}),
|
||||
el("p", {
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${tf("TotalNote")}: ${roundText(totalRound)}`,
|
||||
}),
|
||||
...rows.errors.slice(0, 3).map((t) => el("p", { className: "m01flow__warn", text: t })),
|
||||
...rows.errors.slice(0, 3).map((t) => createText(t, "error", 14)),
|
||||
]);
|
||||
/** 함수 상자마다 식 두 줄(sub2 식 보기 · 받는 알약 값을 칸에 넣고 셈) — 못 그리면 이름 = 값만 */
|
||||
const linesOf = (id: string): FormulaLine[] | null => {
|
||||
@@ -193,7 +194,7 @@ export function buildFlowView(
|
||||
const bodies = rows.funcs.map((f) => funcBlock(f, linesOf(f.box)));
|
||||
const text = section(
|
||||
tf("Formula"),
|
||||
bodies.length ? bodies : [el("p", { className: "m01-logic__muted", text: tf("NoFunc") })],
|
||||
bodies.length ? bodies : [el("p", { className: "ui-text ui-text--muted", text: tf("NoFunc") })],
|
||||
);
|
||||
host.replaceChildren(detail, text);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface FormulaContext {
|
||||
text: PreviewTextAnswer | null;
|
||||
}
|
||||
|
||||
const muted = (text: string): HTMLElement => el("p", { className: "m01-logic__muted", text });
|
||||
const muted = (text: string): HTMLElement => el("p", { className: "ui-text ui-text--muted", text });
|
||||
|
||||
export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
|
||||
const text = ctx.text;
|
||||
@@ -30,7 +30,7 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
|
||||
if (!text.ok) {
|
||||
host.replaceChildren(
|
||||
el("div", {
|
||||
className: "m01-logic__reasons",
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
children: [
|
||||
el("strong", { text: tx("Calc_Stopped") }),
|
||||
el("div", { text: safeReason(text.reason) }),
|
||||
@@ -65,10 +65,15 @@ export function buildFormula(host: HTMLElement, ctx: FormulaContext): void {
|
||||
? [
|
||||
el("div", { text: `${line.이름} : ${line.글}` }),
|
||||
...(line.까닭
|
||||
? [el("div", { className: "m01-logic__muted", text: safeReason(line.까닭) })]
|
||||
? [
|
||||
el("div", {
|
||||
className: "ui-text ui-text--muted",
|
||||
text: safeReason(line.까닭),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
]
|
||||
: [el("div", { className: "m01-logic__muted", text: tl("Formula_Slot") })],
|
||||
: [el("div", { className: "ui-text ui-text--muted", text: tl("Formula_Slot") })],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import { explain, loadTable, type TableRow } from "./M01_MasterData_UI_Logic_Not
|
||||
import { jumpToMaster, type MasterJump } from "./M01_MasterData_UI_Side";
|
||||
import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
|
||||
/** 로직 알약 → 그 로직 열기 — 로직 화면이 자기 열기를 등록 · 열려 있는 모달은 다 닫음 */
|
||||
let logicOpener: (key: string) => void = () => {};
|
||||
@@ -138,11 +139,11 @@ function tableView(table: TableRow, target: TableTarget): HTMLElement {
|
||||
children: [
|
||||
el("strong", { text: `${table.원문번호 ?? ""} ${table.이름 ?? table.키} · ${table.키}` }),
|
||||
el("p", {
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: [`${tl("Modal_Source")} ${table.출처 ?? ""}`, use].filter(Boolean).join(" · "),
|
||||
}),
|
||||
el("p", {
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: hit ? tl("Modal_Hit") : target.col ? tl("Modal_ColOnly") : tl("Modal_NoHit"),
|
||||
}),
|
||||
goButton({ kind: "table", key: table.키 }),
|
||||
@@ -150,7 +151,7 @@ function tableView(table: TableRow, target: TableTarget): HTMLElement {
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-logic__grid m01lab__table",
|
||||
className: "ui-table ui-table--data m01-logic__grid m01lab__table",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [el("tr", { children: cols.map((c) => el("th", { text: c })) })],
|
||||
@@ -217,7 +218,7 @@ function openDialog(title: string, mount: (body: HTMLElement) => void): void {
|
||||
});
|
||||
}
|
||||
|
||||
const muted = (text: string): HTMLElement => el("p", { className: "m01-logic__muted", text });
|
||||
const muted = (text: string): HTMLElement => el("p", { className: "ui-text ui-text--muted", text });
|
||||
|
||||
/** 재료 고르기 줄 후보 목록 — 잡힌 품목(`pickedRef`)을 노란 줄로 · 후보가 100건을 넘으면 앞 100건만 옴 */
|
||||
function candidateView(
|
||||
@@ -249,7 +250,7 @@ function candidateView(
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-logic__grid m01lab__table",
|
||||
className: "ui-table ui-table--data m01-logic__grid m01lab__table",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [
|
||||
@@ -421,12 +422,10 @@ export function openNoteModal(title: string, text: string): void {
|
||||
/** 수량 칸 표 알약을 눌렀을 때 — 그 표만 보임(걸린 줄 강조) */
|
||||
export function openTableModal(target: TableTarget): void {
|
||||
openDialog(target.title, (body) => {
|
||||
body.replaceChildren(el("p", { className: "m01-logic__muted", text: "…" }));
|
||||
body.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: "…" }));
|
||||
void loadTable(target.table).then((table) => {
|
||||
body.replaceChildren(
|
||||
table
|
||||
? tableView(table, target)
|
||||
: el("p", { className: "m01-logic__bad", text: `${target.table} ?` }),
|
||||
table ? tableView(table, target) : createText(`${target.table} ?`, "error"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,39 +1,9 @@
|
||||
/* M01 로직 개선 시험 — 줄 모달의 알약 단추 */
|
||||
.m01lab__pills {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m01lab__pill-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 알약 = 단추 부품(보조 · 24) · 여기는 칸 폭 넘침만 */
|
||||
.m01lab__pill {
|
||||
padding: var(--spacing-4) var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 999px;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.m01lab__pill.is-open {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
.m01lab__pill-stage {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 계산 안 된 만약() 가지 조각 — 흐리게 */
|
||||
.m01lab__qty-dim {
|
||||
opacity: 0.45;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { createButton } from "@ui/ui_template_button";
|
||||
|
||||
export interface Pill {
|
||||
/** 알약 글 — 보기 「표 QF000421」 */
|
||||
@@ -16,11 +17,10 @@ export interface Pill {
|
||||
|
||||
/** 알약 하나 — 글자 조각 사이에 끼워 쓰는 단추 */
|
||||
export function pillButton(p: Pill): HTMLButtonElement {
|
||||
const b = el("button", {
|
||||
className: "m01lab__pill",
|
||||
text: p.label,
|
||||
attrs: { type: "button", "data-pill": p.label },
|
||||
});
|
||||
// 알약 = 단추 부품(보조 · 24) — data-pill 은 시험 · 촬영이 잡음
|
||||
const b = createButton({ label: p.label, variant: "ghost", size: 24 });
|
||||
b.classList.add("m01lab__pill");
|
||||
b.dataset.pill = p.label;
|
||||
b.addEventListener("click", (ev) => {
|
||||
ev.stopPropagation(); // 줄 전체 누르기(행 모달)와 안 겹치게
|
||||
p.onClick();
|
||||
|
||||
@@ -51,7 +51,7 @@ function rawNode(b: Block): HTMLElement {
|
||||
className: "m01-logic__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-logic__grid m01lab-src__table",
|
||||
className: "ui-table ui-table--data m01-logic__grid m01lab-src__table",
|
||||
children: [
|
||||
el("tbody", {
|
||||
children: b.table.map((r) =>
|
||||
@@ -104,11 +104,11 @@ export function createSourceView(opts: SourceViewOptions = {}): SourceView {
|
||||
download.hidden = !book;
|
||||
download.href = book ? `${API_BASE_URL}${opts.pdf}?${new URLSearchParams({ book })}` : "";
|
||||
}
|
||||
body.replaceChildren(el("p", { className: "m01-logic__muted", text: ts("Loading") }));
|
||||
body.replaceChildren(el("p", { className: "ui-text ui-text--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 || "—"}` }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: `${ts("From")} · ${src || "—"}` }),
|
||||
);
|
||||
if (!src.trim()) return none();
|
||||
void fetch(
|
||||
@@ -125,7 +125,7 @@ export function createSourceView(opts: SourceViewOptions = {}): SourceView {
|
||||
body.replaceChildren(
|
||||
...found.flatMap((f) => [
|
||||
el("p", {
|
||||
className: "m01-logic__muted m01lab-src__file",
|
||||
className: "ui-text ui-text--muted m01lab-src__file",
|
||||
text: [
|
||||
f.file,
|
||||
f.matched === f.number ? "" : ts("Near", f.number),
|
||||
|
||||
@@ -116,15 +116,7 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.m01lab__modal-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.m01lab__modal-body,
|
||||
.m01lab__tables {
|
||||
.m01lab__modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
@@ -157,7 +149,7 @@
|
||||
}
|
||||
|
||||
/* 복사해서 만들기(3-4) — 별칭 꼴 식 시트 */
|
||||
.m01copy__sheet {
|
||||
.ui-modal.m01copy__sheet {
|
||||
width: min(1040px, 94vw);
|
||||
--modal-max: 90vh;
|
||||
}
|
||||
@@ -177,7 +169,7 @@
|
||||
}
|
||||
|
||||
/* 시험 계산 — 재료 · 기계 고르기 표 모달 */
|
||||
.m01-logic__pick--table {
|
||||
.ui-modal.m01-logic__pick--table {
|
||||
width: min(1040px, 94vw);
|
||||
}
|
||||
|
||||
@@ -193,7 +185,3 @@
|
||||
.m01lab__pickrow.is-picked {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.m01lab__warn {
|
||||
color: var(--color-warning, #b45309);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
|
||||
import { formatMoney } from "./M01_MasterData_UI_Logic_Money";
|
||||
import { safeReason, tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
|
||||
/** 고르기 값이 마스터 키(자재·기계·직종)면 이름으로 바꿔 보임 — 값 칸은 키 그대로 보냄 */
|
||||
const KEY_RE = /^[A-Z]{2}\d{6}$/;
|
||||
@@ -197,7 +198,7 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
|
||||
el("h3", { text: tx("Calc_Title") }),
|
||||
...(fields.length || materials.length
|
||||
? [el("div", { className: "m01-logic__head", children: [...fields, ...materials] })] // 요소 표가 쓰는 auto-fill 그리드 재사용 — 2열
|
||||
: [el("p", { className: "m01-logic__muted", text: tx("Calc_NoInputs") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: tx("Calc_NoInputs") })]),
|
||||
runButton,
|
||||
out,
|
||||
);
|
||||
@@ -205,12 +206,14 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
|
||||
|
||||
/** `money` = 결과가 돈(원) — 그때만 결과 값도 정수 · 수량·비율 결과는 소수 그대로 */
|
||||
export function answerView(answer: CalcAnswer, draft: boolean, money: boolean): HTMLElement[] {
|
||||
const note = draft ? [el("p", { className: "m01-logic__muted", text: tx("Calc_Draft") })] : [];
|
||||
const note = draft
|
||||
? [el("p", { className: "ui-text ui-text--muted", text: tx("Calc_Draft") })]
|
||||
: [];
|
||||
if (!answer.ok) {
|
||||
return [
|
||||
...note,
|
||||
el("div", {
|
||||
className: "m01-logic__reasons",
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
children: [
|
||||
el("strong", { text: tx("Calc_Stopped") }),
|
||||
el("div", { text: safeReason(answer.reason) }),
|
||||
@@ -231,12 +234,7 @@ export function answerView(answer: CalcAnswer, draft: boolean, money: boolean):
|
||||
: pctView(line).name,
|
||||
}),
|
||||
...(line.단위경고
|
||||
? [
|
||||
el("span", {
|
||||
className: "m01-logic__tag m01lab__warn",
|
||||
text: `${tx("Calc_UnitWarn")} · ${line.단위경고}`,
|
||||
}),
|
||||
]
|
||||
? [createBadge(`${tx("Calc_UnitWarn")} · ${line.단위경고}`, "warning")]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
@@ -247,7 +245,7 @@ export function answerView(answer: CalcAnswer, draft: boolean, money: boolean):
|
||||
);
|
||||
parts.push(
|
||||
el("table", {
|
||||
className: "m01-logic__grid",
|
||||
className: "ui-table ui-table--data m01-logic__grid",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [
|
||||
|
||||
@@ -81,7 +81,7 @@ export function materialRows(
|
||||
.map(headWord)
|
||||
.filter((w, k, all) => w && all.indexOf(w) === k);
|
||||
const current = el("span", {
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${nowName || tx("Mat_NoPrice")} `,
|
||||
attrs: { "data-mat-idx": String(i), "data-ref": brief?.ref ?? "" },
|
||||
});
|
||||
@@ -141,7 +141,7 @@ export function machineControl(
|
||||
again: () => void,
|
||||
): HTMLElement | null {
|
||||
if (!isMachineInput(spec)) return null;
|
||||
const current = el("span", { className: "m01-logic__muted" });
|
||||
const current = el("span", { className: "ui-text ui-text--muted" });
|
||||
const paint = (): void => {
|
||||
const code = values[spec.이름] ?? "";
|
||||
const known = machines.get(code);
|
||||
|
||||
@@ -17,6 +17,9 @@ import type {
|
||||
NamedFormula,
|
||||
} from "./M01_MasterData_UI_Logic_Api";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createIconButton } from "@ui/ui_template_button";
|
||||
|
||||
export type PickMode = "element" | "table";
|
||||
export type PickDone = (item: ElementBrief, column?: string) => void;
|
||||
@@ -81,12 +84,8 @@ function choice(
|
||||
}
|
||||
|
||||
function dropButton(onClick: () => void): HTMLButtonElement {
|
||||
const button = el("button", {
|
||||
className: "m01-logic__drop",
|
||||
text: "×",
|
||||
attrs: { type: "button", title: tx("DeleteRow") },
|
||||
});
|
||||
button.addEventListener("click", onClick);
|
||||
const button = createIconButton({ glyph: "×", title: tx("DeleteRow"), size: 20, onClick });
|
||||
button.classList.add("m01-logic__drop");
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -100,7 +99,7 @@ function section(title: string, body: HTMLElement, onAdd?: () => void): HTMLElem
|
||||
}
|
||||
|
||||
function grid(headers: string[], rows: HTMLElement[][], className: string): HTMLElement {
|
||||
const table = el("table", { className: `m01-logic__grid ${className}` });
|
||||
const table = el("table", { className: `ui-table ui-table--data m01-logic__grid ${className}` });
|
||||
table.append(
|
||||
el("thead", { children: [el("tr", { children: headers.map((h) => el("th", { text: h })) })] }),
|
||||
);
|
||||
@@ -133,7 +132,7 @@ export function buildEditor(host: HTMLElement, ctx: EditContext): void {
|
||||
if (ctx.reasons.length) {
|
||||
host.append(
|
||||
el("div", {
|
||||
className: "m01-logic__reasons",
|
||||
className: "ui-banner ui-banner--danger m01-logic__reasons",
|
||||
children: [
|
||||
el("strong", { text: tx("Head_Blocked") }),
|
||||
...ctx.reasons.map((r) => el("div", { text: r })),
|
||||
@@ -191,7 +190,7 @@ function head(ctx: EditContext, set: (p: () => void) => void, rerender: () => vo
|
||||
field(tx("Head_File"), file, true),
|
||||
field(
|
||||
tx("Head_Key"),
|
||||
el("span", { className: "m01-logic__muted", text: row.키 || tx("New_Key") }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: row.키 || tx("New_Key") }),
|
||||
),
|
||||
field(
|
||||
tx("Head_Number"),
|
||||
@@ -276,11 +275,11 @@ function priceCell(ctx: EditContext, item: HoLine, i: number): HTMLElement {
|
||||
const line = ctx.lines?.[i];
|
||||
if (line) return el("span", { className: "m01-logic__money", text: formatNumber(line.단가) });
|
||||
if (item.종류 === "로직" || item.요소.includes("{")) {
|
||||
return el("span", { className: "m01-logic__muted", text: tx("Ho_PriceLater") });
|
||||
return el("span", { className: "ui-text ui-text--muted", text: tx("Ho_PriceLater") });
|
||||
}
|
||||
const brief = ctx.prices[item.요소];
|
||||
if (brief === undefined) return el("span", { className: "m01-logic__muted", text: "" });
|
||||
if (brief === null) return el("span", { className: "m01-logic__bad", text: tx("Ho_Missing") });
|
||||
if (brief === undefined) return el("span", { className: "ui-text ui-text--muted", text: "" });
|
||||
if (brief === null) return createText(tx("Ho_Missing"), "error");
|
||||
return el("span", { className: "m01-logic__money", text: formatNumber(brief.값) });
|
||||
}
|
||||
|
||||
@@ -314,7 +313,7 @@ function hoSection(
|
||||
set(() => (item.수량 = v));
|
||||
tag.textContent = qtyKind(v);
|
||||
});
|
||||
const tag = el("span", { className: "m01-logic__tag", text: qtyKind(item.수량) });
|
||||
const tag = createBadge(qtyKind(item.수량), "accent");
|
||||
const table = createButton({
|
||||
label: tx("Ho_PickTable"),
|
||||
variant: "ghost",
|
||||
@@ -339,7 +338,7 @@ function hoSection(
|
||||
className: "m01-logic__stack",
|
||||
children: [
|
||||
textBox(item.이름 ?? "", (v) => set(() => (item.이름 = v))),
|
||||
el("span", { className: "m01-logic__muted", text: spec }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: spec }),
|
||||
],
|
||||
}),
|
||||
textBox(item.단위 ?? "", (v) => set(() => (item.단위 = v)), "m01-logic__unit"),
|
||||
|
||||
@@ -67,7 +67,7 @@ export function buildList(
|
||||
const pick = { sub: "", detail: "" };
|
||||
const searchField = createInputField({ type: "search", placeholder: tx("List_Search") });
|
||||
const search = searchField.input;
|
||||
const grid = el("div", { className: "m01-master__grid-wrap" });
|
||||
const grid = el("div", { className: "ui-table-wrap m01-master__grid-wrap" });
|
||||
|
||||
const draw = (): void => {
|
||||
const q = search.value;
|
||||
@@ -89,7 +89,7 @@ export function buildList(
|
||||
pager.replaceWith((pager = buildPager(shown.length, SIZE, page, go)));
|
||||
grid.replaceChildren(
|
||||
el("table", {
|
||||
className: "m01-master__grid",
|
||||
className: "ui-table m01-master__grid",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [el("tr", { children: COLS.map((c) => el("th", { text: c })) })],
|
||||
|
||||
@@ -226,10 +226,12 @@ function tableView(find: RelatedFind, table: TableRow, ctx: EvalCtx): HTMLElemen
|
||||
: "";
|
||||
return [
|
||||
el("p", {
|
||||
className: "m01c__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${find.source} · ${table.원문번호 ?? table.키} ${table.이름 ?? ""} (${table.출처 ?? ""})`,
|
||||
}),
|
||||
...(use ? [el("p", { className: "m01c__muted", text: `${tx("Use_Of")} — ${use}` })] : []),
|
||||
...(use
|
||||
? [el("p", { className: "ui-text ui-text--muted", text: `${tx("Use_Of")} — ${use}` })]
|
||||
: []),
|
||||
el("table", {
|
||||
className: "m01c__table",
|
||||
children: [
|
||||
@@ -252,17 +254,20 @@ export function buildNote(host: HTMLElement, opts: NoteOptions): void {
|
||||
});
|
||||
const plain = explain(opts.expr);
|
||||
const head: HTMLElement[] = [
|
||||
el("p", { className: "m01c__muted", text: tx("Note_Plain") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tx("Note_Plain") }),
|
||||
el("p", { className: "m01c__plain", text: plain }),
|
||||
];
|
||||
if (!related.length) {
|
||||
host.replaceChildren(...head, advanced);
|
||||
return;
|
||||
}
|
||||
const tables = el("div", { className: "m01c__muted", text: `${tx("Note_Searching")}…` });
|
||||
const tables = el("div", {
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${tx("Note_Searching")}…`,
|
||||
});
|
||||
host.replaceChildren(
|
||||
...head,
|
||||
el("p", { className: "m01c__muted", text: tx("Note_Table") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tx("Note_Table") }),
|
||||
tables,
|
||||
advanced,
|
||||
);
|
||||
@@ -275,7 +280,7 @@ export function buildNote(host: HTMLElement, opts: NoteOptions): void {
|
||||
? tableView(r, table, opts.ctx)
|
||||
: [
|
||||
el("p", {
|
||||
className: "m01c__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: tx("Note_TableMissing", { v: r.find.table }),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from "./M01_MasterData_UI_Logic_Api";
|
||||
import { formatNumber, type PickDone, type PickMode } from "./M01_MasterData_UI_Logic_Edit";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
|
||||
const GROUPS: Record<PickMode, string[]> = {
|
||||
element: ["인력", "재료", "기계", "로직"],
|
||||
@@ -43,30 +44,23 @@ export function openPicker(mode: PickMode, group: string, done: PickDone): void
|
||||
const select = groupField.select;
|
||||
const searchField = createInputField({ type: "search", placeholder: tx("Pick_Search") });
|
||||
const search = searchField.input;
|
||||
const count = el("span", { className: "m01-logic__muted" });
|
||||
const count = el("span", { className: "ui-text ui-text--muted" });
|
||||
const list = el("div", { className: "m01-logic__pick-list" });
|
||||
const close = (): void => backdrop.remove();
|
||||
const dialog = el("div", {
|
||||
className: "m01-logic__pick ui-popup",
|
||||
attrs: { role: "dialog", "aria-label": tx("Pick_Title") },
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("h3", { text: tx("Pick_Title") }),
|
||||
createButton({ label: tx("Pick_Close"), variant: "ghost", onClick: close }),
|
||||
],
|
||||
}),
|
||||
el("div", {
|
||||
className: "m01-logic__pick-bar",
|
||||
children: [groupField.root, searchField.root, count],
|
||||
}),
|
||||
list,
|
||||
],
|
||||
// 모달 부품 — 제목줄 [닫기] · 바깥 누르기 · Esc · 위 고정
|
||||
const { close } = openModal({
|
||||
title: tx("Pick_Title"),
|
||||
closeLabel: tx("Pick_Close"),
|
||||
dialogClass: "m01-logic__pick",
|
||||
backdropClass: "m01-logic__backdrop",
|
||||
mount: (body) =>
|
||||
body.append(
|
||||
el("div", {
|
||||
className: "m01-logic__pick-bar",
|
||||
children: [groupField.root, searchField.root, count],
|
||||
}),
|
||||
list,
|
||||
),
|
||||
});
|
||||
const backdrop = el("div", { className: "m01-logic__backdrop", children: [dialog] });
|
||||
backdrop.addEventListener("click", (ev) => ev.target === backdrop && close());
|
||||
backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && close());
|
||||
|
||||
const choose = (item: ElementBrief, column?: string): void => {
|
||||
close();
|
||||
@@ -94,7 +88,6 @@ export function openPicker(mode: PickMode, group: string, done: PickDone): void
|
||||
timer = window.setTimeout(load, 250);
|
||||
});
|
||||
select.addEventListener("change", load);
|
||||
document.body.append(backdrop);
|
||||
search.focus();
|
||||
void load();
|
||||
}
|
||||
@@ -110,7 +103,7 @@ function itemRow(
|
||||
children: [
|
||||
el("strong", { text: `${item.이름 ?? ""} ${item.규격 ?? ""}`.trim() }),
|
||||
el("span", {
|
||||
className: "m01-logic__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: [item.ref, note].filter(Boolean).join(" · "),
|
||||
}),
|
||||
],
|
||||
@@ -128,8 +121,8 @@ function itemRow(
|
||||
const unit = typeof item.단위 === "string" ? item.단위 : "";
|
||||
const value = typeof item.값 === "object" && item.값 !== null ? "{…}" : formatNumber(item.값);
|
||||
const row = el("button", {
|
||||
className: "m01-logic__pick-row m01-logic__pick-row--button",
|
||||
attrs: { type: "button" },
|
||||
className: "m01-logic__pick-row ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button", "data-size": "32" },
|
||||
children: [
|
||||
text,
|
||||
el("span", { className: "m01-logic__money", text: `${value} ${unit}`.trim() }),
|
||||
@@ -276,7 +269,7 @@ function swapRow(one: LogicOne, line: HoLine, i: number, swaps: Swaps, onChange:
|
||||
className: "m01-test__pick",
|
||||
children: [
|
||||
el("strong", { text: `${line.종류} · ${line.이름 ?? String(line.요소)}${spec}` }),
|
||||
el("span", { className: "m01c__muted", text: `${line.단위 ?? ""}${cost}` }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: `${line.단위 ?? ""}${cost}` }),
|
||||
subRow,
|
||||
itemBox.root,
|
||||
],
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
* 검색 칸을 채워 열고 · 잡힌 품목 칠함 · 한 줄 누르면 고름 · 저장 안 함(읽기만)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements";
|
||||
import { createInputField, el, showToast } from "@ui/ui_template_elements";
|
||||
import { fetchRows, type Row } from "./M01_MasterData_Api_Fetch";
|
||||
import { buildPager, flatten, show } from "./M01_MasterData_UI_Cells";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
|
||||
export type PickTableKind = "재료" | "기계";
|
||||
|
||||
@@ -42,7 +43,6 @@ export interface PickTableOptions {
|
||||
}
|
||||
|
||||
export function openPickTable(opt: PickTableOptions): void {
|
||||
const close = (): void => back.remove();
|
||||
const searchField = createInputField({
|
||||
type: "search",
|
||||
placeholder: tx("Pick_Search"),
|
||||
@@ -50,24 +50,14 @@ export function openPickTable(opt: PickTableOptions): void {
|
||||
});
|
||||
const search = searchField.input;
|
||||
const host = el("div", { className: "m01-logic__pick-list" });
|
||||
const dialog = el("div", {
|
||||
className: "m01-logic__pick m01-logic__pick--table ui-popup",
|
||||
attrs: { role: "dialog", "aria-label": opt.title },
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m01-logic__section-head",
|
||||
children: [
|
||||
el("h3", { text: opt.title }),
|
||||
createButton({ label: tx("Pick_Close"), variant: "ghost", onClick: close }),
|
||||
],
|
||||
}),
|
||||
searchField.root,
|
||||
host,
|
||||
],
|
||||
// 모달 부품 — 제목줄 [닫기] · 바깥 누르기 · Esc · 위 고정 · 폭만 표 모달 곁값
|
||||
const { close } = openModal({
|
||||
title: opt.title,
|
||||
closeLabel: tx("Pick_Close"),
|
||||
dialogClass: "m01-logic__pick m01-logic__pick--table",
|
||||
backdropClass: "m01-logic__backdrop",
|
||||
mount: (body) => body.append(searchField.root, host),
|
||||
});
|
||||
const back = el("div", { className: "m01-logic__backdrop", children: [dialog] });
|
||||
back.addEventListener("click", (ev) => ev.target === back && close());
|
||||
back.addEventListener("keydown", (ev) => ev.key === "Escape" && close());
|
||||
|
||||
let page = 1;
|
||||
const tried = new Set([opt.seed]);
|
||||
@@ -109,10 +99,10 @@ export function openPickTable(opt: PickTableOptions): void {
|
||||
void load();
|
||||
}),
|
||||
el("div", {
|
||||
className: "m01-master__grid-wrap",
|
||||
className: "ui-table-wrap m01-master__grid-wrap",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-master__grid",
|
||||
className: "ui-table m01-master__grid",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [el("tr", { children: cols.map((c) => el("th", { text: c })) })],
|
||||
@@ -136,7 +126,5 @@ export function openPickTable(opt: PickTableOptions): void {
|
||||
void load();
|
||||
}, 300);
|
||||
});
|
||||
document.body.append(back);
|
||||
search.focus();
|
||||
void load(true);
|
||||
}
|
||||
|
||||
@@ -54,18 +54,6 @@
|
||||
background: var(--color-surface);
|
||||
}
|
||||
|
||||
.m01-logic__filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.m01-logic__check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.m01-logic__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -79,10 +67,6 @@
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.m01-logic__empty {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.m01-logic__head {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
@@ -156,23 +140,9 @@ input.m01-logic__input {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.m01-logic__grid {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.m01-logic__grid th,
|
||||
/* 겉 = 표 부품 데이터표(ui-table--data · 줄 28) · 여기는 위 맞춤만 */
|
||||
.m01-logic__grid td {
|
||||
padding: 2px 4px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.m01-logic__grid th {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.m01-logic__grid th,
|
||||
@@ -245,15 +215,6 @@ input.m01-logic__input {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.m01-logic__tag {
|
||||
flex: none;
|
||||
padding: 0 6px;
|
||||
border-radius: var(--radius-pills);
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-accent);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m01-logic__money {
|
||||
display: block;
|
||||
text-align: right;
|
||||
@@ -261,11 +222,6 @@ input.m01-logic__input {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m01-logic__muted {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
/* 목록 「별칭」 칸(208-1) — 칸 안 글 상자 · 테두리는 올렸을 때만 */
|
||||
.m01-logic__alias {
|
||||
width: 160px;
|
||||
@@ -283,29 +239,10 @@ input.m01-logic__input {
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
|
||||
.m01-logic__bad {
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m01-logic__drop {
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m01-logic__drop:hover {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* 겉 = 띠 알림 부품(ui-banner--danger) · 여기는 제목 · 까닭 줄 쌓기만 */
|
||||
.m01-logic__reasons {
|
||||
padding: var(--spacing-8);
|
||||
border-left: 3px solid var(--color-danger);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-danger);
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.m01-logic__sums {
|
||||
@@ -330,31 +267,7 @@ input.m01-logic__input {
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.m01-logic__backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
background-color: rgba(38, 17, 74, 0.35); /* 공용 창 틀과 같이 뒤를 가림 */
|
||||
}
|
||||
|
||||
.m01-logic__pick {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
width: min(720px, 92vw);
|
||||
/* 위에서 고정 거리 — 공용 .ui-modal 과 같은 규칙(찾기 칸이 안 움직임) */
|
||||
max-height: var(--modal-max, 80vh);
|
||||
margin-top: calc((100dvh - var(--modal-max, 80vh)) / 2);
|
||||
padding: var(--spacing-16);
|
||||
border-radius: var(--radius-xl);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface-raised);
|
||||
box-shadow: var(--shadow-lg);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
/* 고르기 창 = 모달 부품(ui-modal · 720 · 위 고정) — 여기는 덧붙임 없음 · 표 · 복사 창만 폭 곁값 */
|
||||
|
||||
.m01-logic__pick-bar {
|
||||
display: grid;
|
||||
@@ -367,6 +280,7 @@ input.m01-logic__input {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 고르는 줄 = 목록 줄 부품(button) · 열 고르기 줄(div)만 아래 선 */
|
||||
.m01-logic__pick-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -374,20 +288,9 @@ input.m01-logic__input {
|
||||
gap: var(--spacing-8);
|
||||
width: 100%;
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 0;
|
||||
}
|
||||
div.m01-logic__pick-row {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.m01-logic__pick-row--button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m01-logic__pick-row--button:hover {
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
.m01-logic__chips {
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import "@ui/ui_template_workflow_layout.css";
|
||||
// 부품 상자 겉(553-5) — 페이지 CSS 앞에 실어 페이지 곁값이 이김
|
||||
import "@ui/ui_template_banner.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_table.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
import {
|
||||
createButton,
|
||||
createInputField,
|
||||
@@ -71,7 +77,10 @@ function buildPage(): HTMLElement {
|
||||
const summary = el("span", { className: "m01-master__summary" });
|
||||
const save = createButton({ label: L("M01_Save"), variant: "filled" });
|
||||
const drop = createButton({ label: L("M01_Discard"), variant: "ghost" });
|
||||
const notice = el("div", { className: "m01-master__notice", attrs: { hidden: "" } });
|
||||
const notice = el("div", {
|
||||
className: "ui-banner ui-banner--danger m01-master__notice",
|
||||
attrs: { hidden: "" },
|
||||
});
|
||||
const body = el("div", { className: "m01-master__body" });
|
||||
// 제목 줄 · 찾기 줄 = 「단가산출 로직」 화면과 같은 공용 조각
|
||||
const headBar = buildHeadBar({ title, status: [summary], buttons: [drop, save] });
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import { fetchPick, type PickItem, type PickKind } from "./M01_MasterData_Api_Fetch";
|
||||
import { show } from "./M01_MasterData_UI_Cells";
|
||||
@@ -34,7 +37,6 @@ const field = (value: string, placeholder: string): ReturnType<typeof createInpu
|
||||
createInputField({ type: "search", placeholder, value });
|
||||
|
||||
export function openPickModal(opt: PickOptions): void {
|
||||
const close = (): void => back.remove();
|
||||
const searchField = field(opt.seed, L("M01_ProcureSearch"));
|
||||
const search = searchField.input;
|
||||
const list = el("div", { className: "m01-procure__list" });
|
||||
@@ -44,8 +46,6 @@ export function openPickModal(opt: PickOptions): void {
|
||||
opt.onPick(null);
|
||||
close();
|
||||
});
|
||||
const shut = button(L("M01_ProcureClose"));
|
||||
shut.addEventListener("click", close);
|
||||
|
||||
const condRow: HTMLElement[] = [];
|
||||
if (opt.cond) {
|
||||
@@ -61,22 +61,15 @@ export function openPickModal(opt: PickOptions): void {
|
||||
condRow.push(el("div", { className: "m01-procure__cond", children: [name, spec, go] }));
|
||||
}
|
||||
|
||||
const box = el("div", {
|
||||
className: "m01-procure__box",
|
||||
children: [
|
||||
el("h3", { text: opt.title }),
|
||||
el("p", { className: "m01-master__muted", text: opt.current }),
|
||||
searchField.root,
|
||||
list,
|
||||
...condRow,
|
||||
el("div", { className: "m01-procure__foot", children: [cut, shut] }),
|
||||
],
|
||||
// 모달 부품 — 제목줄 [닫기] · 바깥 누르기 · Esc · 위 고정(공용 꼴) · [연결 끊기] 는 머리 단추 자리
|
||||
const { close } = openModal({
|
||||
title: opt.title,
|
||||
closeLabel: L("M01_ProcureClose"),
|
||||
width: "40rem",
|
||||
dialogClass: "m01-procure__box",
|
||||
actions: [cut],
|
||||
mount: (body) => body.append(createText(opt.current), searchField.root, list, ...condRow),
|
||||
});
|
||||
const back = el("div", { className: "m01-procure", children: [box] });
|
||||
back.addEventListener("click", (e) => {
|
||||
if (e.target === back) close();
|
||||
});
|
||||
document.body.append(back);
|
||||
|
||||
const paint = (items: PickItem[], total: number): void => {
|
||||
list.replaceChildren(
|
||||
@@ -84,12 +77,12 @@ export function openPickModal(opt: PickOptions): void {
|
||||
? items.map((it) => {
|
||||
const tag = it.관급 ? ` · ${L("M01_Govt")}` : "";
|
||||
const b = el("button", {
|
||||
className: "m01-procure__item",
|
||||
attrs: { type: "button" },
|
||||
className: "m01-procure__item ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button", "data-size": "36" },
|
||||
children: [
|
||||
el("strong", { text: it.이름 }),
|
||||
el("span", { text: `${it.규격} · ${it.단위} · ${show(it.값)}${tag}` }),
|
||||
el("span", { className: "m01-master__muted", text: it.ref }),
|
||||
el("span", { className: "ui-text", text: it.ref }),
|
||||
],
|
||||
});
|
||||
b.addEventListener("click", () => {
|
||||
@@ -98,10 +91,8 @@ export function openPickModal(opt: PickOptions): void {
|
||||
});
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m01-master__empty", text: L("M01_NoRows") })]),
|
||||
...(total > items.length
|
||||
? [el("p", { className: "m01-master__muted", text: `${items.length} / ${total}` })]
|
||||
: []),
|
||||
: [createEmpty({ text: L("M01_NoRows"), small: true })]),
|
||||
...(total > items.length ? [createText(`${items.length} / ${total}`)] : []),
|
||||
);
|
||||
};
|
||||
let timer: number | undefined;
|
||||
@@ -123,5 +114,4 @@ export function openPickModal(opt: PickOptions): void {
|
||||
timer = window.setTimeout(() => void run(), 300);
|
||||
});
|
||||
void run(true);
|
||||
search.focus();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
show,
|
||||
withCell,
|
||||
} from "./M01_MasterData_UI_Cells";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
const SIZE = 50;
|
||||
const MARKET = "재료_자재품목.json";
|
||||
@@ -205,19 +206,17 @@ export function renderRows(host: HTMLElement, pick: Pick, q: string): () => void
|
||||
addRow(file, d.version, sub && col ? { ...fresh, [col]: sub } : fresh);
|
||||
});
|
||||
const empty =
|
||||
d.rows.length || adds.length
|
||||
? []
|
||||
: [el("p", { className: "m01-master__empty", text: L("M01_NoRows") })];
|
||||
d.rows.length || adds.length ? [] : [createEmpty({ text: L("M01_NoRows"), small: true })];
|
||||
host.replaceChildren(
|
||||
el("div", {
|
||||
className: "m01-master__bar",
|
||||
className: "ui-toolbar m01-master__bar",
|
||||
children: [add, ...only, buildPager(d.total, SIZE, d.page, go)],
|
||||
}),
|
||||
el("div", {
|
||||
className: "m01-master__grid-wrap",
|
||||
className: "ui-table-wrap m01-master__grid-wrap",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-master__grid",
|
||||
className: "ui-table m01-master__grid",
|
||||
children: [el("thead", { children: [head] }), body],
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -17,12 +17,6 @@
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.m01-side__muted,
|
||||
.m01-master__muted {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
/* ── 우측 ── */
|
||||
.m01-master__panel {
|
||||
display: flex;
|
||||
@@ -58,17 +52,12 @@ html.is-bm-route .m01-master__headbox {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* 겉 = 띠 알림 부품(ui-banner--danger) · 여기는 쌓기 · 높이만 */
|
||||
.m01-master__notice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
max-height: 30vh;
|
||||
overflow: auto;
|
||||
border-left: 4px solid var(--color-danger);
|
||||
background: color-mix(in srgb, var(--color-danger) 10%, var(--color-canvas));
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m01-master__notice p {
|
||||
@@ -95,25 +84,20 @@ html.is-bm-route .m01-master__headbox {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 겉 = 표 부품(ui-table-wrap · ui-table) · 여기는 높이 · 폭 · 머리 고정 · 칸 상태만 */
|
||||
.m01-master__grid-wrap {
|
||||
max-height: calc(100dvh - 300px);
|
||||
overflow: auto;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
}
|
||||
|
||||
.m01-master__grid {
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
table.m01-master__grid {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.m01-master__grid th,
|
||||
.m01-master__grid td {
|
||||
max-width: 320px;
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -122,9 +106,7 @@ html.is-bm-route .m01-master__headbox {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: var(--font-weight-medium);
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.m01-master__grid td.is-editable {
|
||||
@@ -161,77 +143,29 @@ html.is-bm-route .m01-master__headbox {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m01-master__table-row:hover {
|
||||
background: var(--color-paper);
|
||||
}
|
||||
|
||||
.m01-master__table-row.is-changed {
|
||||
background: color-mix(in srgb, var(--color-warning) 28%, var(--color-canvas));
|
||||
}
|
||||
|
||||
.m01-master__empty {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.m01-master__logic-key {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--color-accent);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 조달 찾기 모달 */
|
||||
.m01-procure {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
.m01-procure__box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: min(640px, 92vw);
|
||||
max-height: 80vh;
|
||||
margin-top: 10vh; /* 위에서 고정 거리 — 찾기 칸이 안 움직임 */
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--color-surface, #fff);
|
||||
}
|
||||
/* 조달 찾기 = 모달 부품(40rem) · 줄 = 목록 줄 부품 — 여기는 줄 안 세 줄 쌓기 · 목록 높이만 */
|
||||
.m01-procure__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: var(--spacing-4);
|
||||
overflow-y: auto;
|
||||
min-height: 120px;
|
||||
}
|
||||
.m01-procure__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--color-border, #ccc);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
.m01-procure__foot {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
gap: 0;
|
||||
padding-block: var(--spacing-4);
|
||||
}
|
||||
|
||||
.m01-procure__cond {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.m01-side__filter {
|
||||
|
||||
@@ -128,10 +128,10 @@ async function showList(
|
||||
(p) => void showList(host, pick, q, usage, p, open, onUsageChange),
|
||||
),
|
||||
el("div", {
|
||||
className: "m01-master__grid-wrap m01-master__tables",
|
||||
className: "ui-table-wrap m01-master__grid-wrap m01-master__tables",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-master__grid",
|
||||
className: "ui-table m01-master__grid",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [el("tr", { children: LIST_COLS.map((c) => el("th", { text: c })) })],
|
||||
@@ -184,11 +184,8 @@ function openTable(
|
||||
now.forEach((item, i) => {
|
||||
const tr = el("tr");
|
||||
tr.classList.toggle("is-new", item.o === null);
|
||||
const del = el("button", {
|
||||
className: "m01-master__row-btn",
|
||||
text: L("M01_RowRemove"),
|
||||
attrs: { type: "button" },
|
||||
});
|
||||
const del = createButton({ label: L("M01_RowRemove"), variant: "ghost", size: "sm" });
|
||||
del.classList.add("m01-master__row-btn");
|
||||
del.addEventListener("click", () => commit(now.filter((_, j) => j !== i)));
|
||||
tr.append(el("td", { children: [del] }));
|
||||
for (const c of cols) {
|
||||
@@ -217,32 +214,30 @@ function openTable(
|
||||
back();
|
||||
});
|
||||
const notes = ((table.주 ?? []) as string[]).map((n) =>
|
||||
el("p", { className: "m01-master__muted", text: n }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: n }),
|
||||
);
|
||||
const usage = table.용도 as TableUsage | undefined;
|
||||
const logicLinks = (usage?.로직키 ?? []).map((k, i) => {
|
||||
const btn = el("button", {
|
||||
className: "m01-master__logic-key",
|
||||
text: k,
|
||||
attrs: { type: "button", title: L("M01_OpenLogic") },
|
||||
});
|
||||
const btn = createButton({ label: k, variant: "link" });
|
||||
btn.classList.add("m01-master__logic-key");
|
||||
btn.title = L("M01_OpenLogic");
|
||||
btn.addEventListener("click", () => openLogic(k));
|
||||
return i === 0 ? btn : el("span", { text: ", ", children: [btn] });
|
||||
});
|
||||
host.replaceChildren(
|
||||
el("div", {
|
||||
className: "m01-master__bar",
|
||||
className: "ui-toolbar m01-master__bar",
|
||||
children: [
|
||||
backButton,
|
||||
el("strong", { text: String(table.이름 ?? key) }),
|
||||
el("span", {
|
||||
className: "m01-master__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${key} · ${table.기준 || "—"} · ${table.출처 || ""}`,
|
||||
}),
|
||||
...(usage
|
||||
? [
|
||||
el("span", {
|
||||
className: "m01-master__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${L("M01_TableUsage")}: ${usage.공종} · ${usage.대상.join(", ")} · `,
|
||||
children: logicLinks,
|
||||
}),
|
||||
@@ -252,10 +247,10 @@ function openTable(
|
||||
],
|
||||
}),
|
||||
el("div", {
|
||||
className: "m01-master__grid-wrap",
|
||||
className: "ui-table-wrap m01-master__grid-wrap",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m01-master__grid",
|
||||
className: "ui-table m01-master__grid",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [
|
||||
|
||||
Reference in New Issue
Block a user