refactor(M02): 띠 알림 · 빈 상태 · 보조 글 · 배지 · 탭 · 표 · 목록 줄 · 도구줄 · 체크를 부품으로 (PLAN 553-5)
- DataFlow: 표 넷 = 데이터표 · 함수 판 알림 · 선/알약 경고 · 끊긴 곳 = 띠 알림 · 알약 · 칸 종류 = 배지 · 고르는 줄 = 목록 줄 · 입력 넷 = 입력 · 도구줄 여섯 · 날 색 → 공통 값 - 구조물 옵션 · 구조물 · 도면 · 면적 · 품목 고르기: 판 띠 = 띠 알림 · 한눈/종류별/부품 = 탭 · 표 둘 = 데이터표 · 체크 둘 = 체크 · 카드 · 번호 = 카드 · 배지 · 도면 도구줄 - 일위대가 · 호표 · 실무자료 · 본 화면 · 서고: 알림 = 띠 알림 · 빈 상태 · 보조 글 · 도구줄 여섯 · 옛 보라 별칭 0 - M01 로직 입력칸 = 입력 부품(작은 칸) · 일위대가 상세 줄 수 고정 시험 693 · 지킴 시험 M02 몫 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import type { CfBox, CfLogic } from "./M02_DF_Model";
|
||||
import { portText } from "./M02_DF_Detail";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_table.css";
|
||||
|
||||
const TEXT = {
|
||||
Title: ["칸 연결 표", "Cell connections"],
|
||||
@@ -103,7 +105,8 @@ export function renderCellTable(
|
||||
el("td", {
|
||||
children: [
|
||||
el("span", {
|
||||
className: "m02-dfcells__kind",
|
||||
className: `ui-badge ui-badge--pill ui-badge--${r.kind === "in" ? "info" : r.kind === "var" ? "warning" : "success"} m02-dfcells__kind`,
|
||||
attrs: { "data-size": "20" },
|
||||
text: tt(r.kind === "in" ? "In" : r.kind === "var" ? "Var" : "Out"),
|
||||
}),
|
||||
],
|
||||
@@ -128,7 +131,7 @@ export function renderCellTable(
|
||||
host.replaceChildren(
|
||||
el("strong", { className: "m02-dfcells__title", text: tt("Title") }),
|
||||
el("table", {
|
||||
className: "m02-dfcells__table",
|
||||
className: "ui-table ui-table--data m02-dfcells__table",
|
||||
children: [el("thead", { children: [head] }), el("tbody", { children: body })],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -48,15 +48,6 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.m02-dfdg__note {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.m02-dfdg__hint {
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* 판 — 화면 위쪽 큰 영역 */
|
||||
.m02-dfdg__view {
|
||||
position: relative;
|
||||
|
||||
@@ -38,6 +38,8 @@ import {
|
||||
type TEXT,
|
||||
} from "./M02_DF_Diagram_Parts";
|
||||
import "./M02_DF_Diagram.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
export * from "./M02_DF_Diagram_Parts";
|
||||
|
||||
@@ -395,7 +397,7 @@ export function renderDiagram(
|
||||
: [];
|
||||
|
||||
const bar = el("div", {
|
||||
className: "m02-dfdg__bar",
|
||||
className: "ui-toolbar m02-dfdg__bar",
|
||||
children: [
|
||||
el("strong", { className: "m02-dfdg__title", text: tt("Title") }),
|
||||
...(ro
|
||||
@@ -425,12 +427,12 @@ export function renderDiagram(
|
||||
children: [
|
||||
bar,
|
||||
el("div", {
|
||||
className: "m02-dfdg__tools",
|
||||
className: "ui-toolbar m02-dfdg__tools",
|
||||
children: [
|
||||
...tools,
|
||||
el("span", { className: "m02-dfdg__note", text: st.note }),
|
||||
el("span", { className: "ui-text ui-text--error", text: st.note }),
|
||||
el("span", {
|
||||
className: "m02-dfdg__hint",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: tools.length || st.note ? "" : tt("Hint"),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { CfAnswer, CfQuery } from "./M02_DF_Api";
|
||||
import { fillTemplate } from "./M02_DF_InputPanel_Build";
|
||||
import type { CfBreak, CfValues } from "./M02_DF_Eval";
|
||||
import type { CfElementRef, CfLogic, CfPort } from "./M02_DF_Model";
|
||||
import "@ui/ui_template_text.css";
|
||||
|
||||
const TEXT = {
|
||||
Detail: ["상세 내용", "Details"],
|
||||
@@ -120,7 +121,7 @@ export function drawWarnings(d: DetailParts, pick: DfPick, all: CfBreak[]): void
|
||||
d.warn.replaceChildren(
|
||||
...(mine.length
|
||||
? [...new Set(mine.map((k) => k.text))].map((w) => el("li", { text: w }))
|
||||
: [el("li", { className: "m02-df__muted", text: tt("WarnNone") })]),
|
||||
: [el("li", { className: "ui-text ui-text--muted", text: tt("WarnNone") })]),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { recalcWorkbook } from "../../A00_Common/spreadsheet/spreadsheet_recalc"
|
||||
import { ifBody, splitArgs } from "../../A00_Common/spreadsheet/spreadsheet_condition_text";
|
||||
import type { CalcValue } from "../../A00_Common/spreadsheet/spreadsheet_types";
|
||||
import type { CfBox } from "./M02_DF_Model";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
export type CellScalar = number | string | boolean | null;
|
||||
|
||||
@@ -259,7 +260,7 @@ function fromCalc(v: CalcValue): CellScalar {
|
||||
|
||||
/** 식 보기 그리기 — 줄마다 이름 식 · 값 식 · 갈래(지금 갈래는 ▶ 와 「지금」 글) */
|
||||
export function renderFormulaView(host: HTMLElement, lines: FormulaLine[]): void {
|
||||
host.classList.add("m02-df-formula");
|
||||
host.classList.add("ui-card", "m02-df-formula");
|
||||
host.replaceChildren(
|
||||
el("strong", { className: "m02-df-formula__title", text: tv("Title") }),
|
||||
...(lines.length
|
||||
@@ -285,6 +286,6 @@ export function renderFormulaView(host: HTMLElement, lines: FormulaLine[]): void
|
||||
],
|
||||
}),
|
||||
)
|
||||
: [el("p", { className: "m02-df-formula__empty", text: tv("Empty") })]),
|
||||
: [createEmpty({ text: tv("Empty"), small: true })]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,28 +34,14 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.m02-dfpanel-cell {
|
||||
padding: 2px var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-pills);
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.m02-dfpanel-label {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.m02-dfpanel-note {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfpanel-form {
|
||||
/* 겉 = 카드 부품 · 여기는 아래 맞춤 · 안 여백만 */
|
||||
.m02-dfpanel-form.ui-card {
|
||||
align-items: flex-end;
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.m02-dfpanel-fieldbox {
|
||||
@@ -72,26 +58,18 @@
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.m02-dfpanel-why,
|
||||
/* 알림 = 띠 알림 부품 — 숨김은 부품 display 를 이김 */
|
||||
.m02-dfpanel-msg {
|
||||
margin: 0;
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border-left: 4px solid var(--color-warning);
|
||||
background: var(--color-surface-raised);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.m02-dfpanel-msg[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 받음 · 변수 · 출력 알약 = 배지 부품(info · warning · success) — 안 놓인 받음은 점선 */
|
||||
.m02-dfpanel-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-12);
|
||||
border: 2px solid;
|
||||
border-radius: var(--radius-pills);
|
||||
background: var(--color-surface);
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
button.m02-dfpanel-chip {
|
||||
@@ -102,22 +80,11 @@ button.m02-dfpanel-chip:focus-visible {
|
||||
outline: 2px solid var(--color-focus-ring);
|
||||
}
|
||||
|
||||
.m02-dfpanel-chip.is-in {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.m02-dfpanel-chip.is-in:not(.is-placed) {
|
||||
border-color: var(--badge-tone);
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.m02-dfpanel-chip.is-out {
|
||||
border-color: #27ae60;
|
||||
}
|
||||
|
||||
.m02-dfpanel-chip.is-var {
|
||||
border-color: #d99a00;
|
||||
}
|
||||
|
||||
/* 판 높이 크게 — 안쪽 세로 스크롤 없이 보이게 */
|
||||
.m02-dfpanel-sheet {
|
||||
position: relative;
|
||||
@@ -129,15 +96,15 @@ button.m02-dfpanel-chip:focus-visible {
|
||||
|
||||
/* 칸 위 표시 — 색 + 글자(받음 · 변수 · 출력) 둘 다 */
|
||||
.m02-dfpanel .aislo-cell[data-cf-kind="in"] {
|
||||
box-shadow: inset 0 0 0 2px #3b82f6;
|
||||
box-shadow: inset 0 0 0 2px var(--color-info);
|
||||
}
|
||||
|
||||
.m02-dfpanel .aislo-cell[data-cf-kind="var"] {
|
||||
box-shadow: inset 0 0 0 2px #d99a00;
|
||||
box-shadow: inset 0 0 0 2px var(--color-warning);
|
||||
}
|
||||
|
||||
.m02-dfpanel .aislo-cell[data-cf-kind="out"] {
|
||||
box-shadow: inset 0 0 0 2px #27ae60;
|
||||
box-shadow: inset 0 0 0 2px var(--color-success);
|
||||
}
|
||||
|
||||
.m02-dfpanel .aislo-cell[data-cf-kind]::after {
|
||||
@@ -196,20 +163,6 @@ button.m02-dfpanel-chip:focus-visible {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.m02-dfcells__table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfcells__table th,
|
||||
.m02-dfcells__table td {
|
||||
padding: var(--spacing-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.m02-dfcells__cell {
|
||||
width: 5em;
|
||||
}
|
||||
@@ -219,34 +172,15 @@ button.m02-dfpanel-chip:focus-visible {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.m02-dfcells__kind {
|
||||
padding: 0 var(--spacing-8);
|
||||
border: 2px solid;
|
||||
border-radius: var(--radius-pills);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m02-dfcells__row.is-in .m02-dfcells__kind {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.m02-dfcells__row.is-var .m02-dfcells__kind {
|
||||
border-color: #d99a00;
|
||||
}
|
||||
|
||||
.m02-dfcells__row.is-out .m02-dfcells__kind {
|
||||
border-color: #27ae60;
|
||||
}
|
||||
/* 칸 종류 = 배지 부품(받음 info · 변수 warning · 출력 success) */
|
||||
|
||||
/* 식 보기 */
|
||||
.m02-df-formula {
|
||||
/* 겉 = 카드 부품 · 여기는 쌓기 · 안 여백만 */
|
||||
.m02-df-formula.ui-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-cards);
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.m02-df-formula__title {
|
||||
@@ -274,8 +208,3 @@ button.m02-dfpanel-chip:focus-visible {
|
||||
.m02-df-formula__branches .is-now {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.m02-df-formula__empty {
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ import { createRoundRow } from "./M02_DF_RoundRow";
|
||||
import { createUnitPick, suggestPlaces } from "./M02_DF_UnitPick";
|
||||
import { ERR_KEY, FRAMES, TEXT, tf } from "./M02_DF_FuncText";
|
||||
import "./M02_DF_FuncPanel.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_banner.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
|
||||
type Handlers = { change(lg: CfLogic): void };
|
||||
|
||||
@@ -261,10 +264,14 @@ function drawHead(st: State): void {
|
||||
});
|
||||
st.parts.head.replaceChildren(
|
||||
el("strong", { className: "m02-dfpanel-title", text: box?.name ?? "" }),
|
||||
el("span", { className: "m02-dfpanel-cell", text: `${tf("Cell")} ${st.cell}` }),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "24" },
|
||||
text: `${tf("Cell")} ${st.cell}`,
|
||||
}),
|
||||
el("label", {
|
||||
className: "m02-dfpanel-unitbox",
|
||||
children: [el("span", { className: "m02-dfpanel-note", text: tf("Unit") }), unit],
|
||||
children: [el("span", { className: "ui-text ui-text--muted", text: tf("Unit") }), unit],
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -275,9 +282,9 @@ function drawGot(st: State): void {
|
||||
const chips = items.map(({ port, unit }) => {
|
||||
const at = f?.inputs.find((i) => i.port === port)?.cell;
|
||||
const b = el("button", {
|
||||
className: `m02-dfpanel-chip is-in${at ? " is-placed" : ""}`,
|
||||
className: `ui-badge ui-badge--pill ui-badge--info m02-dfpanel-chip is-in${at ? " is-placed" : ""}`,
|
||||
text: `${port} (${unit}) · ${at ? `${tf("Placed")} ${at}` : tf("Free")}`,
|
||||
attrs: { type: "button", draggable: st.readOnly ? "false" : "true" },
|
||||
attrs: { type: "button", "data-size": "24", draggable: st.readOnly ? "false" : "true" },
|
||||
});
|
||||
b.addEventListener("click", () => placePort(st, port, st.cell));
|
||||
b.addEventListener("dragstart", (e) => e.dataTransfer?.setData("text/plain", port));
|
||||
@@ -285,9 +292,11 @@ function drawGot(st: State): void {
|
||||
});
|
||||
st.parts.got.replaceChildren(
|
||||
el("span", { className: "m02-dfpanel-label", text: tf("Got") }),
|
||||
...(chips.length ? chips : [el("span", { className: "m02-dfpanel-note", text: tf("NoGot") })]),
|
||||
...(chips.length
|
||||
? chips
|
||||
: [el("span", { className: "ui-text ui-text--muted", text: tf("NoGot") })]),
|
||||
...(chips.length && !st.readOnly
|
||||
? [el("span", { className: "m02-dfpanel-note", text: tf("GotHint") })]
|
||||
? [el("span", { className: "ui-text ui-text--muted", text: tf("GotHint") })]
|
||||
: []),
|
||||
);
|
||||
}
|
||||
@@ -303,7 +312,7 @@ function formField(
|
||||
return el("label", {
|
||||
className: "m02-dfpanel-fieldbox",
|
||||
children: [
|
||||
el("span", { className: "m02-dfpanel-note", text: tf(label) }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: tf(label) }),
|
||||
createUnitPick({
|
||||
value: form.unit || "-",
|
||||
ariaLabel: tf(label),
|
||||
@@ -321,7 +330,7 @@ function formField(
|
||||
input.addEventListener("keydown", (e) => e.key === "Enter" && makeVariable(st));
|
||||
return el("label", {
|
||||
className: "m02-dfpanel-fieldbox",
|
||||
children: [el("span", { className: "m02-dfpanel-note", text: tf(label) }), input],
|
||||
children: [el("span", { className: "ui-text ui-text--muted", text: tf(label) }), input],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,7 +339,8 @@ function drawVars(st: State): void {
|
||||
const row: HTMLElement[] = [el("span", { className: "m02-dfpanel-label", text: tf("Vars") })];
|
||||
for (const v of f?.variables ?? []) {
|
||||
const chip = el("span", {
|
||||
className: "m02-dfpanel-chip is-var",
|
||||
className: "ui-badge ui-badge--pill ui-badge--warning m02-dfpanel-chip is-var",
|
||||
attrs: { "data-size": "24" },
|
||||
children: [`${v.name} (${v.unit}) · ${v.cell ?? ""}`],
|
||||
});
|
||||
if (!st.readOnly) {
|
||||
@@ -351,7 +361,7 @@ function drawVars(st: State): void {
|
||||
row.push(chip);
|
||||
}
|
||||
if (!f?.variables.length)
|
||||
row.push(el("span", { className: "m02-dfpanel-note", text: tf("NoVars") }));
|
||||
row.push(el("span", { className: "ui-text ui-text--muted", text: tf("NoVars") }));
|
||||
const parts: HTMLElement[] = [el("div", { className: "m02-dfpanel-row", children: row })];
|
||||
if (!st.readOnly) {
|
||||
const form = st.form;
|
||||
@@ -400,12 +410,15 @@ function drawVars(st: State): void {
|
||||
});
|
||||
parts.push(
|
||||
el("div", {
|
||||
className: "m02-dfpanel-row m02-dfpanel-form",
|
||||
className: "ui-card m02-dfpanel-row m02-dfpanel-form",
|
||||
children: [
|
||||
formField(st, "VarName", "name"),
|
||||
el("label", {
|
||||
className: "m02-dfpanel-fieldbox",
|
||||
children: [el("span", { className: "m02-dfpanel-note", text: tf("VarType") }), type],
|
||||
children: [
|
||||
el("span", { className: "ui-text ui-text--muted", text: tf("VarType") }),
|
||||
type,
|
||||
],
|
||||
}),
|
||||
formField(st, "VarUnit", "unit"),
|
||||
...(form.type === "number"
|
||||
@@ -413,7 +426,11 @@ function drawVars(st: State): void {
|
||||
: []),
|
||||
...(form.type === "choice" ? [formField(st, "VarChoices", "choices")] : []),
|
||||
formField(st, "VarExample", "example"),
|
||||
el("span", { className: "m02-dfpanel-cell", text: `${tf("VarCell")} ${st.cell}` }),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "24" },
|
||||
text: `${tf("VarCell")} ${st.cell}`,
|
||||
}),
|
||||
createButton({
|
||||
label: tf("Make"),
|
||||
variant: "filled",
|
||||
@@ -427,7 +444,7 @@ function drawVars(st: State): void {
|
||||
onClick: () => ((st.form = null), drawVars(st)),
|
||||
}),
|
||||
...(form.why
|
||||
? [el("span", { className: "m02-dfpanel-why", text: `⚠ ${form.why}` })]
|
||||
? [el("span", { className: "ui-text ui-text--warning", text: `⚠ ${form.why}` })]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
@@ -441,7 +458,7 @@ function drawBar(st: State): void {
|
||||
const f = funcOf(st);
|
||||
if (st.readOnly) {
|
||||
st.parts.bar.replaceChildren(
|
||||
el("span", { className: "m02-dfpanel-note", text: tf("ReadOnly") }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: tf("ReadOnly") }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -456,8 +473,12 @@ function drawBar(st: State): void {
|
||||
size: "sm",
|
||||
onClick: () => setOutput(st, st.cell),
|
||||
}),
|
||||
el("span", { className: "m02-dfpanel-chip is-out", text: outNow }),
|
||||
el("span", { className: "m02-dfpanel-note", text: tf("OutOnly") }),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--success m02-dfpanel-chip is-out",
|
||||
attrs: { "data-size": "24" },
|
||||
text: outNow,
|
||||
}),
|
||||
el("span", { className: "ui-text ui-text--muted", text: tf("OutOnly") }),
|
||||
],
|
||||
}),
|
||||
...(f?.output.cell
|
||||
@@ -482,7 +503,7 @@ function drawBar(st: State): void {
|
||||
onClick: () => putFormula(st, formula),
|
||||
}),
|
||||
),
|
||||
el("span", { className: "m02-dfpanel-note", text: tf("FrameHint") }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: tf("FrameHint") }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -647,7 +668,10 @@ export function renderFuncPanel(
|
||||
got: el("div", { className: "m02-dfpanel-row" }),
|
||||
vars: el("div", { className: "m02-dfpanel-vars" }),
|
||||
bar: el("div", { className: "m02-dfpanel-bar" }),
|
||||
msg: el("p", { className: "m02-dfpanel-msg", attrs: { role: "status" } }),
|
||||
msg: el("p", {
|
||||
className: "ui-banner ui-banner--warning m02-dfpanel-msg",
|
||||
attrs: { role: "status" },
|
||||
}),
|
||||
sheet: el("div", { className: "m02-dfpanel-sheet", text: tf("Loading") }),
|
||||
};
|
||||
const st: State = {
|
||||
|
||||
@@ -42,31 +42,9 @@
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
.m02-dfip__chip,
|
||||
.m02-dfip__row {
|
||||
/* 고르는 줄 = 목록 줄 부품(ui-list-row) — 여기는 안 여백 · 두 칸 벌림만 */
|
||||
button.m02-dfip__row {
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
.m02-dfip__chip {
|
||||
border-radius: var(--radius-pills);
|
||||
padding: var(--spacing-4) var(--spacing-12);
|
||||
}
|
||||
.m02-dfip__chip.is-on {
|
||||
border-color: var(--color-accent);
|
||||
outline: 2px solid var(--color-accent);
|
||||
}
|
||||
.m02-dfip__chip:hover,
|
||||
.m02-dfip__chip:focus-visible,
|
||||
.m02-dfip__row:hover,
|
||||
.m02-dfip__row:focus-visible {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
.m02-dfip__row {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
gap: var(--spacing-8);
|
||||
@@ -104,12 +82,9 @@
|
||||
.m02-dfip__value {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.m02-dfip__tag {
|
||||
/* 태그 = 배지 부품 — 이름 뒤 띄움만 */
|
||||
.m02-dfip__name .ui-badge {
|
||||
margin-left: var(--spacing-8);
|
||||
padding: 0 var(--spacing-8);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-pills);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
.m02-dfip__new,
|
||||
.m02-dfip__form {
|
||||
@@ -124,19 +99,17 @@
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
/* 미리 보기 = 표 부품 데이터표 — 여기는 줄바꿈 막기 · 가운데 · 찾은 줄만 */
|
||||
.m02-dfip__preview table {
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
width: auto;
|
||||
}
|
||||
.m02-dfip__preview th,
|
||||
.m02-dfip__preview td {
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--color-border);
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
.m02-dfip__preview tr.is-hit td {
|
||||
background: rgba(240, 190, 40, 0.35);
|
||||
background: color-mix(in srgb, var(--color-warning) 35%, transparent);
|
||||
font-weight: 700;
|
||||
}
|
||||
.m02-dfip__preview {
|
||||
|
||||
@@ -38,6 +38,10 @@ import { renderMaterial } from "./M02_DF_InputPanel_Material";
|
||||
import { renderTablePick } from "./M02_DF_InputPanel_Table";
|
||||
import { renderVariables } from "./M02_DF_InputPanel_Vars";
|
||||
import "./M02_DF_InputPanel.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
/** Api 모양 — 서버 Api 가 채워지면(사용자 요소 = Model 모양 · 값 물음 = ref) 그 모양을 그대로 씀 */
|
||||
interface Answer {
|
||||
@@ -125,7 +129,7 @@ export function renderInputPanel(
|
||||
host.className = "m02-dfip";
|
||||
const box = lg.boxes.find((b) => b.id === boxId);
|
||||
if (!box || box.kind !== "input") {
|
||||
host.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("NotInput") }));
|
||||
host.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("NotInput") }));
|
||||
return;
|
||||
}
|
||||
// 상자 단위가 고른 요소 · 변수 단위와 다르면(견본 등) 맞춰 다시 그림 — 맞으면 바로 지나감
|
||||
@@ -246,7 +250,15 @@ function drawChosen(
|
||||
el("span", {
|
||||
className: "m02-dfip__name",
|
||||
children: [
|
||||
...(rule ? [el("span", { className: "m02-dfip__tag", text: t("RuleTag") })] : []),
|
||||
...(rule
|
||||
? [
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: t("RuleTag"),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
el("strong", { text: ` ${e.name}` }),
|
||||
...(rule && e.rule
|
||||
? [
|
||||
@@ -274,7 +286,7 @@ function drawChosen(
|
||||
],
|
||||
});
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoneChosen") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoneChosen") })]),
|
||||
);
|
||||
const items = els
|
||||
.filter((e) => e.mode !== "rule")
|
||||
@@ -331,7 +343,7 @@ async function drawFind(
|
||||
let seq = 0;
|
||||
const load = async (): Promise<void> => {
|
||||
const mine = ++seq;
|
||||
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") }));
|
||||
list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") }));
|
||||
try {
|
||||
const word = q.input.value.trim();
|
||||
const page = await fetchRows(file, 1, 40, word, false, sub, detail);
|
||||
@@ -341,11 +353,11 @@ async function drawFind(
|
||||
list.replaceChildren(
|
||||
...(rows.length
|
||||
? rows.map((r) => rowOf(r))
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoRow") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoRow") })]),
|
||||
);
|
||||
} catch (e) {
|
||||
if (mine === seq)
|
||||
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: plain(e) }));
|
||||
list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: plain(e) }));
|
||||
}
|
||||
};
|
||||
const rowOf = (r: M01Row): HTMLElement => {
|
||||
@@ -361,7 +373,7 @@ async function drawFind(
|
||||
const v = valueOf(group, r, field.select.value || "가격");
|
||||
const unitText = group === "기계" ? "" : String(r["단위"] ?? "");
|
||||
const b = el("button", {
|
||||
className: "m02-dfip__row",
|
||||
className: "m02-dfip__row ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button" },
|
||||
children: [
|
||||
el("span", {
|
||||
@@ -390,7 +402,7 @@ async function drawFind(
|
||||
subBox.root.hidden = !subs.length;
|
||||
bar.push(subBox.root, detailBox.root);
|
||||
} catch (e) {
|
||||
host.replaceChildren(el("p", { className: "m02-dfip__muted", text: plain(e) }));
|
||||
host.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: plain(e) }));
|
||||
return;
|
||||
}
|
||||
let timer = 0;
|
||||
@@ -399,7 +411,7 @@ async function drawFind(
|
||||
() => (window.clearTimeout(timer), (timer = window.setTimeout(load, 250))),
|
||||
);
|
||||
host.replaceChildren(
|
||||
el("div", { className: "m02-dfip__bar", children: [...bar, q.root] }),
|
||||
el("div", { className: "ui-toolbar m02-dfip__bar", children: [...bar, q.root] }),
|
||||
count,
|
||||
list,
|
||||
);
|
||||
|
||||
@@ -35,6 +35,10 @@ import {
|
||||
wouldCycle,
|
||||
type MasterLogicBrief,
|
||||
} from "./M02_DF_InputPanel_Build";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
Help: [
|
||||
@@ -183,7 +187,7 @@ export function renderLogicInput(
|
||||
...(shown.length
|
||||
? shown.map((i) => {
|
||||
const b = el("button", {
|
||||
className: "m02-dfip__row",
|
||||
className: "m02-dfip__row ui-list-row ui-sidebar-row",
|
||||
attrs: {
|
||||
type: "button",
|
||||
...(i.blocked ? { disabled: "true", title: (i.reasons ?? []).join(" · ") } : {}),
|
||||
@@ -200,7 +204,8 @@ export function renderLogicInput(
|
||||
text: ` ${aliasNote(matchLogic(q.input.value, i).alias)}`,
|
||||
}),
|
||||
el("span", {
|
||||
className: "m02-dfip__tag",
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: i.blocked ? t("Blocked") : i.상세구분,
|
||||
}),
|
||||
],
|
||||
@@ -211,7 +216,7 @@ export function renderLogicInput(
|
||||
b.addEventListener("click", () => void pick(i));
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("None") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("None") })]),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -253,10 +258,13 @@ export function renderLogicInput(
|
||||
window.clearTimeout(timer);
|
||||
timer = window.setTimeout(() => ((page = 1), draw()), 200);
|
||||
});
|
||||
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") }));
|
||||
list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") }));
|
||||
host.replaceChildren(
|
||||
el("p", { className: "m02-dfip__muted", text: t("Help") }),
|
||||
el("div", { className: "m02-dfip__bar", children: [bookBox.root, chapterBox.root, q.root] }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: t("Help") }),
|
||||
el("div", {
|
||||
className: "ui-toolbar m02-dfip__bar",
|
||||
children: [bookBox.root, chapterBox.root, q.root],
|
||||
}),
|
||||
info,
|
||||
list,
|
||||
el("div", { className: "m02-dfip__chips", children: [prev, next] }),
|
||||
@@ -268,7 +276,7 @@ export function renderLogicInput(
|
||||
fillChapters();
|
||||
draw();
|
||||
},
|
||||
(e) => list.replaceChildren(el("p", { className: "m02-dfip__muted", text: plain(e) })),
|
||||
(e) => list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: plain(e) })),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -282,7 +290,7 @@ function showChosen(
|
||||
): void {
|
||||
const read = readNotes.get(ref.key);
|
||||
const values = new Map<string, HTMLElement>();
|
||||
const errors = el("p", { className: "m02-dfip__muted is-bad" });
|
||||
const errors = el("p", { className: "ui-text ui-text--error" });
|
||||
const line = (name: string, unit: string, valueNode?: HTMLElement, extra = ""): HTMLElement =>
|
||||
el("div", {
|
||||
className: "m02-dfip__item",
|
||||
@@ -300,7 +308,7 @@ function showChosen(
|
||||
el("h5", { text: `${t("Chosen")} — ${ref.name ?? ref.key}` }),
|
||||
...(read?.converted
|
||||
? [
|
||||
el("p", { className: "m02-dfip__muted", text: t("Converted") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: t("Converted") }),
|
||||
...(read.notes.length
|
||||
? [
|
||||
el("details", {
|
||||
@@ -320,14 +328,14 @@ function showChosen(
|
||||
values.set(o.name, v);
|
||||
return line(o.name, o.unit, v);
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoOut") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoOut") })]),
|
||||
errors,
|
||||
el("h5", { text: `${t("Designs")} · ${ref.designs.length}` }),
|
||||
...(ref.designs.length
|
||||
? ref.designs.map((d) =>
|
||||
line(d.name, d.unit, undefined, String(ref.examples?.[d.name] ?? "")),
|
||||
)
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoDesign") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoDesign") })]),
|
||||
el("div", {
|
||||
className: "m02-dfip__chips",
|
||||
children: [
|
||||
|
||||
@@ -30,6 +30,10 @@ import {
|
||||
variablePills,
|
||||
type MatItem,
|
||||
} from "./M02_DF_InputPanel_Build";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
Chosen: ["넣은 재료 — 알약마다 이름 · 값 · 단위", "Chosen materials"],
|
||||
@@ -159,7 +163,10 @@ function scopeBoxes(
|
||||
() => undefined,
|
||||
);
|
||||
return {
|
||||
root: el("div", { className: "m02-dfip__bar", children: [subBox.root, detailBox.root] }),
|
||||
root: el("div", {
|
||||
className: "ui-toolbar m02-dfip__bar",
|
||||
children: [subBox.root, detailBox.root],
|
||||
}),
|
||||
get: () => ({ ...state }),
|
||||
};
|
||||
}
|
||||
@@ -190,7 +197,7 @@ function itemList(
|
||||
});
|
||||
const load = async (): Promise<void> => {
|
||||
const mine = ++seq;
|
||||
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") }));
|
||||
list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") }));
|
||||
try {
|
||||
const r = await searchMaterials({ q: q.input.value.trim(), ...scope(), page, size: 12 });
|
||||
if (mine !== seq) return;
|
||||
@@ -208,7 +215,7 @@ function itemList(
|
||||
...(r.items.length
|
||||
? r.items.map((it) => {
|
||||
const b = el("button", {
|
||||
className: "m02-dfip__row",
|
||||
className: "m02-dfip__row ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button" },
|
||||
children: [
|
||||
el("span", {
|
||||
@@ -227,11 +234,11 @@ function itemList(
|
||||
b.addEventListener("click", () => onPick(it));
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoRow") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoRow") })]),
|
||||
);
|
||||
} catch {
|
||||
if (mine === seq)
|
||||
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Failed") }));
|
||||
list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Failed") }));
|
||||
}
|
||||
};
|
||||
let timer = 0;
|
||||
@@ -398,9 +405,13 @@ export function renderMaterial(
|
||||
el("span", {
|
||||
className: "m02-dfip__name",
|
||||
children: [
|
||||
el("span", { className: "m02-dfip__tag", text: kind }),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: kind,
|
||||
}),
|
||||
el("strong", { text: ` ${e.name}` }),
|
||||
el("div", { className: "m02-dfip__muted", text: summaryOf(e, lg) }),
|
||||
el("div", { className: "ui-text ui-text--muted", text: summaryOf(e, lg) }),
|
||||
note,
|
||||
],
|
||||
}),
|
||||
@@ -435,7 +446,7 @@ export function renderMaterial(
|
||||
);
|
||||
return row;
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("None") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("None") })]),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -455,7 +466,7 @@ export function renderMaterial(
|
||||
const drawBody = (): void => {
|
||||
if (mode === "fixed") {
|
||||
body.replaceChildren(
|
||||
el("p", { className: "m02-dfip__muted", text: t("FixedHelp") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: t("FixedHelp") }),
|
||||
itemList((it) =>
|
||||
on.change(
|
||||
addEl(lg, {
|
||||
@@ -520,7 +531,7 @@ export function renderMaterial(
|
||||
onChange: (v) => ((how = v as "plain" | "template"), drawBody()),
|
||||
});
|
||||
const tpl = templateBox(lg, () => void refresh());
|
||||
const defNote = el("p", { className: "m02-dfip__muted" });
|
||||
const defNote = el("p", { className: "ui-text ui-text--muted" });
|
||||
const build = (): CfElementRef => {
|
||||
const sc = scope.get();
|
||||
if (mode === "rule" && howBox.select.value === "template") {
|
||||
@@ -603,7 +614,7 @@ export function renderMaterial(
|
||||
? [tpl.root]
|
||||
: pills.length
|
||||
? [fieldBox.root, opBox.root, pillBox.root]
|
||||
: [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })]),
|
||||
: [el("p", { className: "ui-text ui-text--error", text: t("NoPill") })]),
|
||||
]
|
||||
: [
|
||||
el("h5", { text: t("Default") }),
|
||||
|
||||
@@ -21,6 +21,10 @@ import {
|
||||
variablePills,
|
||||
type GroupRuleField,
|
||||
} from "./M02_DF_InputPanel_Build";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
Help: [
|
||||
@@ -128,11 +132,11 @@ export function renderGroupRule(
|
||||
},
|
||||
});
|
||||
host.replaceChildren(
|
||||
el("p", { className: "m02-dfip__muted", text: t("Help") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: t("Help") }),
|
||||
name.root,
|
||||
...(pills.length
|
||||
? [field.root, pill.root, note, add]
|
||||
: [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })]),
|
||||
: [el("p", { className: "ui-text ui-text--error", text: t("NoPill") })]),
|
||||
);
|
||||
if (pills.length) void refresh();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ import {
|
||||
} from "@ui/ui_template_elements";
|
||||
import type { CfGroup, CfLogic } from "./M02_DF_Model";
|
||||
import { setTable } from "./M02_DF_InputPanel_Build";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
import "@ui/ui_template_table.css";
|
||||
|
||||
const TEXT = {
|
||||
Book: ["적용 품셈", "Standard"],
|
||||
@@ -120,6 +125,7 @@ function previewOf(tb: RawTable, row?: number): HTMLElement {
|
||||
className: "m02-dfip__preview",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "ui-table ui-table--data",
|
||||
children: [
|
||||
el("tr", { children: cols.map((c) => el("th", { text: c })) }),
|
||||
...body.map((r, i) =>
|
||||
@@ -131,7 +137,7 @@ function previewOf(tb: RawTable, row?: number): HTMLElement {
|
||||
],
|
||||
}),
|
||||
...(tb.줄.length > body.length
|
||||
? [el("p", { className: "m02-dfip__muted", text: `… ${tb.줄.length} 줄` })]
|
||||
? [el("p", { className: "ui-text ui-text--muted", text: `… ${tb.줄.length} 줄` })]
|
||||
: []),
|
||||
...(tb.주?.length
|
||||
? [
|
||||
@@ -172,7 +178,7 @@ export function renderTablePick(
|
||||
let seq = 0;
|
||||
const load = async (): Promise<void> => {
|
||||
const mine = ++seq;
|
||||
list.replaceChildren(el("p", { className: "m02-dfip__muted", text: t("Loading") }));
|
||||
list.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: t("Loading") }));
|
||||
try {
|
||||
const r = await get<{ sections: SectionItem[] }>("/sections", {
|
||||
book,
|
||||
@@ -184,7 +190,7 @@ export function renderTablePick(
|
||||
...(r.sections.length
|
||||
? r.sections.map((s) => {
|
||||
const b = el("button", {
|
||||
className: "m02-dfip__row",
|
||||
className: "m02-dfip__row ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button" },
|
||||
children: [
|
||||
el("strong", { text: `${s.section} ${s.title}` }),
|
||||
@@ -197,7 +203,7 @@ export function renderTablePick(
|
||||
b.addEventListener("click", () => void pickSection(s));
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoSection") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoSection") })]),
|
||||
);
|
||||
} catch (e) {
|
||||
if (mine === seq) showToast(e instanceof Error ? e.message : t("Failed"), "error");
|
||||
@@ -218,7 +224,7 @@ export function renderTablePick(
|
||||
...(heads.length
|
||||
? heads.map((h) => {
|
||||
const b = el("button", {
|
||||
className: "m02-dfip__row",
|
||||
className: "m02-dfip__row ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button" },
|
||||
children: [
|
||||
el("strong", { text: h.이름 }),
|
||||
@@ -242,7 +248,7 @@ export function renderTablePick(
|
||||
});
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoTable") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoTable") })]),
|
||||
);
|
||||
} catch (e) {
|
||||
showToast(e instanceof Error ? e.message : t("Failed"), "error");
|
||||
|
||||
@@ -8,6 +8,10 @@ import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createInputField, createSelectField, el } from "@ui/ui_template_elements";
|
||||
import type { CfLogic, CfPort } from "./M02_DF_Model";
|
||||
import { templateSlots, variablePills } from "./M02_DF_InputPanel_Build";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
Template: ["검색어 틀 — 자리는 {이름} 으로", "Search template — slots as {name}"],
|
||||
@@ -68,8 +72,8 @@ export function templateBox(lg: CfLogic, onChange: () => void): TemplateBox {
|
||||
picks.set(n, sel.select);
|
||||
return sel.root;
|
||||
})
|
||||
: [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })]
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("NoSlot") })]),
|
||||
: [el("p", { className: "ui-text ui-text--error", text: t("NoPill") })]
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("NoSlot") })]),
|
||||
);
|
||||
};
|
||||
tpl.input.addEventListener("input", () => (draw(), onChange()));
|
||||
@@ -77,7 +81,11 @@ export function templateBox(lg: CfLogic, onChange: () => void): TemplateBox {
|
||||
return {
|
||||
root: el("div", {
|
||||
className: "m02-dfip__stack",
|
||||
children: [el("p", { className: "m02-dfip__muted", text: t("Help") }), tpl.root, slotBox],
|
||||
children: [
|
||||
el("p", { className: "ui-text ui-text--muted", text: t("Help") }),
|
||||
tpl.root,
|
||||
slotBox,
|
||||
],
|
||||
}),
|
||||
get: () => ({
|
||||
template: tpl.input.value.trim(),
|
||||
|
||||
@@ -9,6 +9,10 @@ import { createButton, createInputField, createSelectField, el } from "@ui/ui_te
|
||||
import type { CfLogic, CfVariable } from "./M02_DF_Model";
|
||||
import { NO_UNIT, createUnitPick } from "./M02_DF_UnitPick";
|
||||
import { checkVariable, putVariable, removeVariable } from "./M02_DF_InputPanel_Build";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
List: ["변수 — 알약마다 하나", "Variables — one pill each"],
|
||||
@@ -201,7 +205,7 @@ export function renderVariables(
|
||||
],
|
||||
}),
|
||||
)
|
||||
: [el("p", { className: "m02-dfip__muted", text: t("None") })];
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: t("None") })];
|
||||
host.replaceChildren(el("h5", { text: t("List") }), ...list, form());
|
||||
};
|
||||
draw();
|
||||
|
||||
@@ -24,24 +24,13 @@
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dflink-value {
|
||||
padding: 2px var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-pills);
|
||||
background: var(--color-surface-raised);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.m02-dflink-num {
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
/* 겉 = 띠 알림 부품 */
|
||||
.m02-dflink-warn {
|
||||
margin: 0;
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border-left: 4px solid var(--color-warning);
|
||||
background: var(--color-surface-raised);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
/* 알약 판 */
|
||||
@@ -72,11 +61,3 @@
|
||||
gap: var(--spacing-8);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.m02-dfpill-rule {
|
||||
padding: 0 var(--spacing-8);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-pills);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ import {
|
||||
} from "./M02_DF_Detail";
|
||||
import { suggestPlaces } from "./M02_DF_UnitPick";
|
||||
import "./M02_DF_LinkPanel.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_banner.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
|
||||
const TEXT = {
|
||||
Title: ["선 규칙", "Line rule"],
|
||||
@@ -248,7 +251,8 @@ function lookupEditor(ctx: Ctx, rule: Extract<CfRule, { kind: "lookup" }>): HTML
|
||||
select(ctx, colOpts, rule.col, (v) => put({ col: v }), tk("Take")),
|
||||
),
|
||||
);
|
||||
if (!cols.length) out.push(el("p", { className: "m02-dflink-note", text: tk("NoHeader") }));
|
||||
if (!cols.length)
|
||||
out.push(el("p", { className: "ui-text ui-text--muted", text: tk("NoHeader") }));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -289,7 +293,7 @@ function gateValue(ctx: Ctx, c: CfGateCond, onPick: (v: number | string) => void
|
||||
if (range && Number.isFinite(n) && (n < range[0] || n > range[1])) {
|
||||
out.push(
|
||||
el("span", {
|
||||
className: "m02-dflink-warn",
|
||||
className: "ui-text ui-text--warning",
|
||||
text: `⚠ ${tk("OutRange", `${range[0]} ~ ${range[1]}`)}`,
|
||||
}),
|
||||
);
|
||||
@@ -302,7 +306,9 @@ function gateEditor(ctx: Ctx, rule: Extract<CfRule, { kind: "gate" }>): HTMLElem
|
||||
const put = (conds: CfGateCond[], join = g.join): void => setRule(ctx, makeGate(conds, join));
|
||||
const edit = (i: number, patch: Partial<CfGateCond>): void =>
|
||||
put(g.conds.map((c, j) => (j === i ? { ...c, ...patch } : c)));
|
||||
const out: HTMLElement[] = [el("p", { className: "m02-dflink-note", text: tk("GateHint") })];
|
||||
const out: HTMLElement[] = [
|
||||
el("p", { className: "ui-text ui-text--muted", text: tk("GateHint") }),
|
||||
];
|
||||
g.conds.forEach((c, i) => {
|
||||
out.push(
|
||||
row(
|
||||
@@ -380,7 +386,7 @@ function gateEditor(ctx: Ctx, rule: Extract<CfRule, { kind: "gate" }>): HTMLElem
|
||||
|
||||
function scaleEditor(ctx: Ctx, rule: Extract<CfRule, { kind: "scale" }>): HTMLElement[] {
|
||||
return [
|
||||
el("p", { className: "m02-dflink-note", text: tk("ScaleHint") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tk("ScaleHint") }),
|
||||
row(
|
||||
select(
|
||||
ctx,
|
||||
@@ -502,10 +508,18 @@ export function renderLinkPanel(
|
||||
parts.push(
|
||||
row(
|
||||
el("span", { className: "m02-dflink-label", text: tk("Before") }),
|
||||
el("span", { className: "m02-dflink-value", text: pv.before }),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: pv.before,
|
||||
}),
|
||||
"→",
|
||||
el("span", { className: "m02-dflink-label", text: tk("After") }),
|
||||
el("span", { className: "m02-dflink-value", text: pv.after }),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: pv.after,
|
||||
}),
|
||||
),
|
||||
row(
|
||||
el("span", { className: "m02-dflink-label", text: tk("Label") }),
|
||||
@@ -518,6 +532,10 @@ export function renderLinkPanel(
|
||||
if (!rule.conds.length) warns.push(tk("WarnLookup"));
|
||||
if (!rule.col) warns.push(tk("WarnTake"));
|
||||
}
|
||||
parts.push(...warns.map((w) => el("p", { className: "m02-dflink-warn", text: `⚠ ${w}` })));
|
||||
parts.push(
|
||||
...warns.map((w) =>
|
||||
el("p", { className: "ui-banner ui-banner--warning m02-dflink-warn", text: `⚠ ${w}` }),
|
||||
),
|
||||
);
|
||||
host.replaceChildren(...parts);
|
||||
}
|
||||
|
||||
@@ -53,23 +53,8 @@
|
||||
}
|
||||
|
||||
.m02-dfout__input {
|
||||
box-sizing: border-box;
|
||||
width: 160px;
|
||||
max-width: 100%;
|
||||
height: var(--control-height-sm);
|
||||
padding: 3px 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-text-body);
|
||||
font: inherit;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfout__warn {
|
||||
margin: var(--spacing-4) 0 0;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfout__muted {
|
||||
@@ -78,12 +63,6 @@
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-dfout__msg {
|
||||
margin: var(--spacing-4) 0 0;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-dfout__round {
|
||||
margin: var(--spacing-8) 0;
|
||||
color: var(--color-text-body);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { portsOut, type CfBox, type CfCost, type CfLogic, type CfRound } from ".
|
||||
import { defaultItemRound, roundText } from "./M02_DF_Round";
|
||||
import { createUnitPick, suggestPlaces } from "./M02_DF_UnitPick";
|
||||
import "./M02_DF_OutputPanel.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
|
||||
const TEXT = {
|
||||
Title: ["출력", "Output"],
|
||||
@@ -83,7 +84,7 @@ export function renderOutputPanel(
|
||||
host.replaceChildren();
|
||||
return;
|
||||
}
|
||||
const msg = el("p", { className: "m02-dfout__msg" });
|
||||
const msg = el("p", { className: "ui-text ui-text--error" });
|
||||
const emit = (make: () => CfLogic): void => {
|
||||
msg.textContent = "";
|
||||
try {
|
||||
@@ -133,14 +134,14 @@ export function renderOutputPanel(
|
||||
} else {
|
||||
got.replaceChildren(
|
||||
el("strong", { text: to("Got") }),
|
||||
el("p", { className: "m02-dfout__warn", text: to("NoLink") }),
|
||||
el("p", { className: "ui-text ui-text--error", text: to("NoLink") }),
|
||||
);
|
||||
}
|
||||
|
||||
/* 이름 · 비용 종류 · 단위 */
|
||||
const nameBox = el("input", {
|
||||
className: "m02-dfout__input",
|
||||
attrs: { type: "text", "aria-label": to("Name") },
|
||||
className: "ui-input m02-dfout__input",
|
||||
attrs: { "data-size": "32", type: "text", "aria-label": to("Name") },
|
||||
});
|
||||
nameBox.value = box.name;
|
||||
nameBox.addEventListener("change", () => {
|
||||
@@ -283,7 +284,9 @@ export function renderOutputPanel(
|
||||
],
|
||||
}),
|
||||
roundBlock,
|
||||
...(cost === "값" ? [el("p", { className: "m02-dfout__muted", text: to("PlainHint") })] : []),
|
||||
...(cost === "값"
|
||||
? [el("p", { className: "ui-text ui-text--muted", text: to("PlainHint") })]
|
||||
: []),
|
||||
msg,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { CfLogic } from "./M02_DF_Model";
|
||||
import { createPaper } from "./M02_DF_Paper";
|
||||
import { renderStandards } from "./M02_DF_Standards";
|
||||
import "./M02_DF_Panel.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
Big: ["크게 보기", "Full screen"],
|
||||
@@ -156,7 +157,7 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
|
||||
attrs: { open: "" },
|
||||
children: [el("summary", { text: tf("Cost") })],
|
||||
});
|
||||
const tools = el("div", { className: "m02-dfp__tools", children: [undo] });
|
||||
const tools = el("div", { className: "ui-toolbar m02-dfp__tools", children: [undo] });
|
||||
const open = el("div", {
|
||||
className: "m02-dfp__open",
|
||||
children: [head, tools, note, diagram, detail, costBox],
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
type DfPick,
|
||||
} from "./M02_DF_Entry_Detail";
|
||||
import "./M02_DF_Paper.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
|
||||
const TEXT = {
|
||||
Undo: ["실행 취소", "Undo"],
|
||||
@@ -133,7 +134,8 @@ export function createPaper(opts: PaperOpts): Paper {
|
||||
});
|
||||
lock.disabled = readOnly;
|
||||
detail.body.append(lock);
|
||||
if (readOnly) detail.root.prepend(el("p", { className: "m02-df__muted", text: tp("ReadOnly") }));
|
||||
if (readOnly)
|
||||
detail.root.prepend(el("p", { className: "ui-text ui-text--muted", text: tp("ReadOnly") }));
|
||||
|
||||
const evalNow = (): CfEvalResult => {
|
||||
try {
|
||||
|
||||
@@ -22,6 +22,9 @@ import {
|
||||
type DetailOptions,
|
||||
} from "./M02_DF_Detail";
|
||||
import "./M02_DF_LinkPanel.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_banner.css";
|
||||
import "@ui/ui_template_badge.css";
|
||||
|
||||
const TEXT = {
|
||||
Name: ["이름", "Name"],
|
||||
@@ -55,7 +58,7 @@ function steps(
|
||||
empty: string,
|
||||
onBox?: (id: string) => void,
|
||||
): HTMLElement {
|
||||
if (!list.length) return el("p", { className: "m02-dflink-note", text: empty });
|
||||
if (!list.length) return el("p", { className: "ui-text ui-text--muted", text: empty });
|
||||
const pill = (box: string, name: string): HTMLElement | string =>
|
||||
onBox
|
||||
? createButton({
|
||||
@@ -75,7 +78,14 @@ function steps(
|
||||
pill(link.from.box, link.from.name),
|
||||
"→",
|
||||
...(s.rule
|
||||
? [el("span", { className: "m02-dfpill-rule", text: `(${labelOf(s.rule)})` }), "→"]
|
||||
? [
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: `(${labelOf(s.rule)})`,
|
||||
}),
|
||||
"→",
|
||||
]
|
||||
: []),
|
||||
pill(link.to.box, link.to.name),
|
||||
]
|
||||
@@ -121,7 +131,14 @@ export function renderPillPanel(
|
||||
],
|
||||
}),
|
||||
line(tp("Name"), port),
|
||||
line(tp("Value"), el("span", { className: "m02-dflink-value", text: showValue(value) })),
|
||||
line(
|
||||
tp("Value"),
|
||||
el("span", {
|
||||
className: "ui-badge ui-badge--pill ui-badge--neutral",
|
||||
attrs: { "data-size": "20" },
|
||||
text: showValue(value),
|
||||
}),
|
||||
),
|
||||
line(tp("Unit"), unit || "-"),
|
||||
el("div", {
|
||||
children: [
|
||||
@@ -139,8 +156,13 @@ export function renderPillPanel(
|
||||
children: [
|
||||
el("div", { className: "m02-dflink-label", text: tp("Breaks") }),
|
||||
...(breaks.length
|
||||
? breaks.map((b) => el("p", { className: "m02-dflink-warn", text: `⚠ ${b}` }))
|
||||
: [el("p", { className: "m02-dflink-note", text: tp("NoBreak") })]),
|
||||
? breaks.map((b) =>
|
||||
el("p", {
|
||||
className: "ui-banner ui-banner--warning m02-dflink-warn",
|
||||
text: `⚠ ${b}`,
|
||||
}),
|
||||
)
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: tp("NoBreak") })]),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import type { CfRound } from "./M02_DF_Model";
|
||||
import "@ui/ui_template_text.css";
|
||||
|
||||
const TEXT = {
|
||||
Label: ["끝수", "Rounding"],
|
||||
@@ -65,7 +66,7 @@ export function createRoundRow(opts: RoundRowOptions): HTMLElement {
|
||||
});
|
||||
if (opts.suggest !== null && !now && !opts.readOnly) {
|
||||
row.append(
|
||||
el("span", { className: "m02-dfpanel-note", text: tr("Suggest", opts.suggest) }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: tr("Suggest", opts.suggest) }),
|
||||
createButton({
|
||||
label: tr("Apply"),
|
||||
variant: "ghost",
|
||||
|
||||
@@ -16,30 +16,13 @@
|
||||
margin: 0 auto 0 0;
|
||||
}
|
||||
|
||||
.m02-df-std__state,
|
||||
.m02-df-std__hint,
|
||||
.m02-df-std__line {
|
||||
color: var(--color-text-muted, #6b7280);
|
||||
}
|
||||
|
||||
.m02-df-std__hint {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.m02-df-std__scroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.m02-df-std__table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* 겉 = 표 부품 데이터표(ui-table-wrap) · 여기는 줄바꿈 막기 · 누르는 칸만 */
|
||||
.m02-df-std__table th,
|
||||
.m02-df-std__table td {
|
||||
border: 1px solid var(--color-border, #e5e7eb);
|
||||
padding: 2px 6px;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -47,10 +30,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-df-std__table th {
|
||||
background: var(--color-surface-muted, rgba(127, 127, 127, 0.08));
|
||||
}
|
||||
|
||||
.m02-df-std__notes {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
|
||||
@@ -15,6 +15,8 @@ import { cfStandards, cfStandardsReset, cfStandardsSave, type CfStandards } from
|
||||
import unitDefault from "./_standard/단위표준.json";
|
||||
import moneyDefault from "./_standard/금액단위표준.json";
|
||||
import "./M02_DF_Standards.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_table.css";
|
||||
|
||||
const TEXT = {
|
||||
Title: ["표준 표", "Standards"],
|
||||
@@ -198,7 +200,7 @@ export function renderStandards(
|
||||
): void {
|
||||
let work = clone(current);
|
||||
let dirty = false;
|
||||
const state = el("span", { className: "m02-df-std__state" });
|
||||
const state = el("span", { className: "ui-text ui-text--muted m02-df-std__state" });
|
||||
const save = createButton({ label: tx("Save"), size: "sm", onClick: () => void doSave() });
|
||||
const reset = createButton({
|
||||
label: tx("Reset"),
|
||||
@@ -250,10 +252,10 @@ export function renderStandards(
|
||||
|
||||
const table = (heads: string[], rows: HTMLElement[][]): HTMLElement =>
|
||||
el("div", {
|
||||
className: "m02-df-std__scroll",
|
||||
className: "ui-table-wrap m02-df-std__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m02-df-std__table",
|
||||
className: "ui-table ui-table--data m02-df-std__table",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [el("tr", { children: heads.map((h) => el("th", { text: h })) })],
|
||||
@@ -350,7 +352,7 @@ export function renderStandards(
|
||||
save,
|
||||
],
|
||||
}),
|
||||
el("p", { className: "m02-df-std__hint", text: `${tx("Source")} · ${tx("Hint")}` }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: `${tx("Source")} · ${tx("Hint")}` }),
|
||||
body,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -41,30 +41,12 @@
|
||||
|
||||
.m02-dfsum__scroll {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.m02-dfsum__table {
|
||||
width: 100%;
|
||||
min-width: 360px;
|
||||
margin-top: var(--spacing-4);
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfsum__table th,
|
||||
.m02-dfsum__table td {
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px solid var(--color-border);
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.m02-dfsum__table th {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
/* 겉 = 표 부품 데이터표(ui-table-wrap) · 여기는 최소 폭 · 수 칸 오른쪽만 */
|
||||
.m02-dfsum__table {
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
.m02-dfsum__table td.m02-dfsum__num {
|
||||
@@ -91,42 +73,3 @@
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfsum__input {
|
||||
box-sizing: border-box;
|
||||
width: 140px;
|
||||
height: var(--control-height-sm);
|
||||
padding: 3px 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-text-body);
|
||||
font: inherit;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfsum__muted {
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-dfsum__msg {
|
||||
margin: var(--spacing-4) 0 0;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-dfsum__problems {
|
||||
margin: 0 0 var(--spacing-12);
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border-left: 4px solid var(--color-danger);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text-body);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfsum__problems ul {
|
||||
margin: var(--spacing-4) 0 0;
|
||||
padding-left: var(--spacing-16);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
} from "./M02_DF_Round";
|
||||
import { renderVerify, type CfSel } from "./M02_DF_Verify";
|
||||
import "./M02_DF_Summary.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_table.css";
|
||||
|
||||
const TEXT = {
|
||||
Per: ["{per} 당", "per {per}"],
|
||||
@@ -115,10 +117,10 @@ export function renderSummary(
|
||||
children: [
|
||||
el("strong", { text: title }),
|
||||
el("div", {
|
||||
className: "m02-dfsum__scroll",
|
||||
className: "ui-table-wrap m02-dfsum__scroll",
|
||||
children: [
|
||||
el("table", {
|
||||
className: "m02-dfsum__table",
|
||||
className: "ui-table ui-table--data m02-dfsum__table",
|
||||
children: [
|
||||
el("thead", {
|
||||
children: [
|
||||
@@ -162,14 +164,14 @@ export function renderSummary(
|
||||
children: [
|
||||
el("strong", { text: ts("Plain") }),
|
||||
...plainValues.map(([k, v]) =>
|
||||
el("p", { className: "m02-dfsum__muted", text: `${k} · ${showValue(v)}` }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: `${k} · ${showValue(v)}` }),
|
||||
),
|
||||
],
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
el("p", {
|
||||
className: "m02-dfsum__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: `${ts("RoundTotal")}: ${roundText(sumRound)}`,
|
||||
}),
|
||||
verifyHost,
|
||||
|
||||
@@ -27,30 +27,15 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 단위 줄 = 목록 줄 부품 · 화살표 키로 고른 줄(is-on)만 마우스 꼴 */
|
||||
.m02-df-unitpick__item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-df-unitpick__item.is-on,
|
||||
.m02-df-unitpick__item:hover {
|
||||
background: var(--color-surface-raised);
|
||||
.m02-df-unitpick__item.is-on {
|
||||
background: var(--color-hover);
|
||||
}
|
||||
|
||||
.m02-df-unitpick__item.is-now {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.m02-df-unitpick__empty {
|
||||
padding: var(--spacing-8);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { cfStandards } from "./M02_DF_Api";
|
||||
import "./M02_DF_UnitPick.css";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
import "@ui/ui_template_list_row.css";
|
||||
|
||||
const TEXT = {
|
||||
None: ["없음", "none"],
|
||||
@@ -139,14 +141,14 @@ export function createUnitPick(opts: UnitPickOptions): HTMLElement {
|
||||
...(shown.length
|
||||
? shown.map((u, i) => {
|
||||
const b = el("button", {
|
||||
className: `m02-df-unitpick__item${i === at ? " is-on" : ""}${u === (opts.value || NO_UNIT) ? " is-now" : ""}`,
|
||||
className: `ui-list-row ui-sidebar-row m02-df-unitpick__item${i === at ? " is-on" : ""}${u === (opts.value || NO_UNIT) ? " is-now" : ""}`,
|
||||
text: label(u),
|
||||
attrs: { type: "button", role: "option" },
|
||||
});
|
||||
b.addEventListener("click", () => pick(u));
|
||||
return el("li", { children: [b] });
|
||||
})
|
||||
: [el("li", { className: "m02-df-unitpick__empty", text: tu("Empty") })]),
|
||||
: [el("li", { children: [createEmpty({ text: tu("Empty"), small: true })] })]),
|
||||
);
|
||||
};
|
||||
const close = (): void => {
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
color: var(--color-text-body);
|
||||
}
|
||||
|
||||
/* 끊긴 곳 = 띠 알림 부품(위험) · 여기는 제목 · 목록 쌓기만 */
|
||||
.m02-dfver__breaks {
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border-left: 4px solid var(--color-danger);
|
||||
background: var(--color-surface);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.m02-dfver__title {
|
||||
@@ -33,17 +32,8 @@
|
||||
}
|
||||
|
||||
.m02-dfver__input {
|
||||
box-sizing: border-box;
|
||||
width: 160px;
|
||||
max-width: 100%;
|
||||
height: var(--control-height-sm);
|
||||
padding: 3px 6px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-canvas);
|
||||
color: var(--color-text-body);
|
||||
font: inherit;
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-dfver__item {
|
||||
@@ -111,12 +101,6 @@
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-dfver__msg {
|
||||
margin: var(--spacing-4) 0 0;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
/* 쉬는 갈래 — 문제가 아니라 회색으로 */
|
||||
.m02-dfver__rest {
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
|
||||
@@ -20,6 +20,9 @@ import {
|
||||
type CfVariable,
|
||||
} from "./M02_DF_Model";
|
||||
import "./M02_DF_Verify.css";
|
||||
import "@ui/ui_template_text.css";
|
||||
import "@ui/ui_template_banner.css";
|
||||
import "@ui/ui_template_toolbar.css";
|
||||
|
||||
const TEXT = {
|
||||
Breaks: ["끊긴 곳", "Broken spots"],
|
||||
@@ -133,8 +136,9 @@ function sampleField(
|
||||
}).root;
|
||||
} else {
|
||||
const box = el("input", {
|
||||
className: "m02-dfver__input",
|
||||
className: "ui-input m02-dfver__input",
|
||||
attrs: {
|
||||
"data-size": "32",
|
||||
type: "text",
|
||||
inputmode: v.type === "number" ? "decimal" : "text",
|
||||
"aria-label": v.name,
|
||||
@@ -168,15 +172,20 @@ function slotRow(box: CfBox, e: CfElementRef, on: CfVerifyOn, lg: CfLogic): HTML
|
||||
const panel = el("div", { className: "m02-dfver__panel" });
|
||||
panel.hidden = true;
|
||||
const q = el("input", {
|
||||
className: "m02-dfver__input",
|
||||
attrs: { type: "text", placeholder: tv("Search"), "aria-label": tv("Search") },
|
||||
className: "ui-input m02-dfver__input",
|
||||
attrs: {
|
||||
"data-size": "32",
|
||||
type: "text",
|
||||
placeholder: tv("Search"),
|
||||
"aria-label": tv("Search"),
|
||||
},
|
||||
});
|
||||
q.value = e.scope?.찾는말 ?? "";
|
||||
const list = el("ul", { className: "m02-dfver__list" });
|
||||
const pager = el("div", { className: "m02-dfver__pager" });
|
||||
const pager = el("div", { className: "ui-toolbar m02-dfver__pager" });
|
||||
let page = 1;
|
||||
const load = async (): Promise<void> => {
|
||||
list.replaceChildren(el("li", { className: "m02-dfver__muted", text: tv("Loading") }));
|
||||
list.replaceChildren(el("li", { className: "ui-text ui-text--muted", text: tv("Loading") }));
|
||||
const got = await findMaterials({
|
||||
q: q.value.trim(),
|
||||
sub: e.scope?.구분,
|
||||
@@ -199,7 +208,7 @@ function slotRow(box: CfBox, e: CfElementRef, on: CfVerifyOn, lg: CfLogic): HTML
|
||||
],
|
||||
}),
|
||||
)
|
||||
: [el("li", { className: "m02-dfver__muted", text: tv("NoHit") })]),
|
||||
: [el("li", { className: "ui-text ui-text--muted", text: tv("NoHit") })]),
|
||||
);
|
||||
pager.replaceChildren(
|
||||
createButton({
|
||||
@@ -247,7 +256,7 @@ function slotRow(box: CfBox, e: CfElementRef, on: CfVerifyOn, lg: CfLogic): HTML
|
||||
});
|
||||
panel.append(
|
||||
el("div", {
|
||||
className: "m02-dfver__search",
|
||||
className: "ui-toolbar m02-dfver__search",
|
||||
children: [
|
||||
q,
|
||||
createButton({
|
||||
@@ -326,7 +335,7 @@ export function renderVerify(
|
||||
on: CfVerifyOn,
|
||||
opts: { values?: CfValues; logics?: CfEvalOptions["logics"] } = {},
|
||||
): void {
|
||||
const msg = el("p", { className: "m02-dfver__msg" });
|
||||
const msg = el("p", { className: "ui-text ui-text--error" });
|
||||
const safe = (make: () => CfLogic): void => {
|
||||
msg.textContent = "";
|
||||
try {
|
||||
@@ -355,7 +364,7 @@ export function renderVerify(
|
||||
}),
|
||||
),
|
||||
}),
|
||||
el("p", { className: "m02-dfver__muted", text: tv(hint) }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tv(hint) }),
|
||||
];
|
||||
const problems = spots.filter((s) => s.level !== "쉼");
|
||||
const resting = spots.filter((s) => s.level === "쉼");
|
||||
@@ -363,7 +372,7 @@ export function renderVerify(
|
||||
...(problems.length
|
||||
? [
|
||||
el("div", {
|
||||
className: "m02-dfver__block m02-dfver__breaks",
|
||||
className: "ui-banner ui-banner--danger m02-dfver__block m02-dfver__breaks",
|
||||
children: [el("strong", { text: tv("Breaks") }), ...spotList(problems, "BreaksHint")],
|
||||
}),
|
||||
]
|
||||
@@ -427,8 +436,13 @@ export function renderVerify(
|
||||
const logics = designs.flatMap((d) => {
|
||||
if (d.kind !== "다른 로직") return [];
|
||||
const input = el("input", {
|
||||
className: "m02-dfver__input",
|
||||
attrs: { type: "text", placeholder: tv("OwnSample"), "aria-label": d.port },
|
||||
className: "ui-input m02-dfver__input",
|
||||
attrs: {
|
||||
"data-size": "32",
|
||||
type: "text",
|
||||
placeholder: tv("OwnSample"),
|
||||
"aria-label": d.port,
|
||||
},
|
||||
});
|
||||
input.value = d.example === null || d.example === undefined ? "" : String(d.example);
|
||||
input.addEventListener("change", () => safe(() => putLogicExample(d.box, d.port, input.value)));
|
||||
@@ -479,7 +493,7 @@ export function renderVerify(
|
||||
children: [
|
||||
el("strong", { className: "m02-dfver__title", text: tv("Design") }),
|
||||
...design,
|
||||
el("p", { className: "m02-dfver__muted", text: tv("DesignHint") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tv("DesignHint") }),
|
||||
msg,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.m02-inputval__missing {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-inputval__logic {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -87,10 +81,9 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 겉 = 보조 글 부품(ui-text--error) · 여기는 한 줄 차지만 */
|
||||
.m02-inputval__error {
|
||||
flex: 1 0 100%;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-inputval__control[aria-invalid="true"] {
|
||||
|
||||
@@ -81,7 +81,7 @@ export function createValueControl(
|
||||
: ""
|
||||
: String(value);
|
||||
control.classList.toggle("is-auto", empty);
|
||||
const error = el("span", { className: "m02-inputval__error" });
|
||||
const error = el("span", { className: "ui-text ui-text--error m02-inputval__error" });
|
||||
const out: ValueControl = { control, error, wasBad: false };
|
||||
control.addEventListener(control instanceof HTMLSelectElement ? "change" : "input", () => {
|
||||
const raw = control.value;
|
||||
@@ -266,7 +266,7 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand
|
||||
const missing = data.list.빠진로직.length
|
||||
? [
|
||||
el("p", {
|
||||
className: "m02-inputval__missing",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: fill(VALUE_TEXT.missing, { value: data.list.빠진로직.join(", ") }),
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -52,7 +52,9 @@ export function mountAreaSheet(host: HTMLElement, opts: AreaSheetOptions): AreaS
|
||||
let picked = "A1";
|
||||
let moved = false; // 누른 사이 고름이 바뀜 — 머리 · 굴림 막대를 눌렀다 뗀 것은 안 고름
|
||||
let book: Workbook | null = null;
|
||||
host.replaceChildren(el("p", { className: "m02-cellpick__now", text: TEXT.loading }));
|
||||
host.replaceChildren(
|
||||
el("p", { className: "ui-text ui-text--muted m02-cellpick__now", text: TEXT.loading }),
|
||||
);
|
||||
const choose = async (): Promise<void> => {
|
||||
const at = picked;
|
||||
// 엔진 조각은 고를 때만(정적 import 금지 — 계약 `spreadsheet_types.ts` 머리)
|
||||
@@ -101,7 +103,7 @@ export function mountAreaSheet(host: HTMLElement, opts: AreaSheetOptions): AreaS
|
||||
if (done) return;
|
||||
host.replaceChildren(
|
||||
el("p", {
|
||||
className: "m02-cellpick__now",
|
||||
className: "ui-text ui-text--muted m02-cellpick__now",
|
||||
text: fill(TEXT.failed, error instanceof Error ? error.message : ""),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ export function createAreaDrawing(
|
||||
/** 연결별로 마지막에 도면에 비춘 칸 글(없으면 저장된 `값`) */
|
||||
const pushed = new Map<string, Record<string, string>>();
|
||||
const note = (text: string): void =>
|
||||
stage.replaceChildren(el("p", { className: "m02-apanel__note", text }));
|
||||
stage.replaceChildren(el("p", { className: "ui-text ui-text--muted m02-apanel__note", text }));
|
||||
|
||||
const send = createButton({ label: TEXT.send, size: "sm", variant: "ghost" });
|
||||
send.title = TEXT.sendTip;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { createButton, createInputField, el } from "@ui/ui_template_elements";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
import type { CalcValue, LinkInfo, SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
|
||||
const TEXT = {
|
||||
title: "칸 지정 — {value}",
|
||||
@@ -62,7 +63,7 @@ export function openAreaLink(opts: AreaLinkOptions): void {
|
||||
closeLabel: TEXT.close,
|
||||
dialogClass: "m02-modal m02-area-link",
|
||||
mount: (body) => {
|
||||
const now = el("p", { className: "m02-area-link__now" });
|
||||
const now = el("p", { className: "ui-text ui-text--muted m02-area-link__now" });
|
||||
const paint = (): void => {
|
||||
const text = linkedText(opts);
|
||||
now.textContent = `${TEXT.now} · ${text ?? TEXT.none}`;
|
||||
@@ -118,9 +119,7 @@ export function openAreaLink(opts: AreaLinkOptions): void {
|
||||
paint();
|
||||
body.append(
|
||||
now,
|
||||
...(opts.readOnly
|
||||
? [el("p", { className: "m02-struct__empty", text: TEXT.readOnly })]
|
||||
: []),
|
||||
...(opts.readOnly ? [createText(TEXT.readOnly)] : []),
|
||||
el("div", { className: "m02-area-link__row", children: [pick, unlink] }),
|
||||
el("div", { className: "m02-area-link__row", children: [address.root, link] }),
|
||||
);
|
||||
|
||||
@@ -19,13 +19,11 @@
|
||||
|
||||
.m02-apanel__note {
|
||||
margin: var(--spacing-12);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 칸 지정 모달 — 지금 연결 한 줄 · [지금 고른 칸으로] [풀기] · 주소 칸 [잇기] */
|
||||
.m02-area-link__now {
|
||||
margin: 0 0 var(--spacing-12);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.m02-area-link__row {
|
||||
@@ -54,7 +52,6 @@
|
||||
|
||||
.m02-cellpick__now {
|
||||
margin: 0 0 var(--spacing-8);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.m02-cellpick__sheet {
|
||||
|
||||
@@ -29,11 +29,9 @@
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
/* 겉 = 도구줄 부품(ui-toolbar) · 여기는 줄 사이 간격만 */
|
||||
.m02-drawing__toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8) var(--spacing-16, 16px);
|
||||
gap: var(--spacing-8) var(--spacing-16);
|
||||
}
|
||||
|
||||
.m02-drawing__title {
|
||||
@@ -77,7 +75,7 @@
|
||||
margin-top: 0;
|
||||
width: 23rem;
|
||||
max-width: calc(100vw - 16px);
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 25%);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
/* 탭 줄(82-1) — 그림 칸에 붙임 */
|
||||
|
||||
@@ -155,7 +155,8 @@ export function mountDrawingTemplate(
|
||||
const root = document.createElement("div");
|
||||
root.className = "m02-drawing";
|
||||
const toolbar = document.createElement("div");
|
||||
toolbar.className = "m02-drawing__toolbar";
|
||||
toolbar.className = "ui-toolbar m02-drawing__toolbar"; // 도구줄 부품 겉
|
||||
toolbar.setAttribute("role", "toolbar");
|
||||
|
||||
// 작도 영역 — 도면 내용이 이 칸 한가운데에 놓인다. 제목 줄에는 값이 다 보이는 단추 하나,
|
||||
// 누르면 칸 넷이 작은 창으로 펼쳐진다(칸은 창을 닫아도 그대로 살아 있음).
|
||||
|
||||
@@ -19,16 +19,8 @@
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.m02-hopyo__note {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
/* 겉 = 보조 글 부품(ui-text--warning) · 여기는 줄바꿈 살림만 */
|
||||
.m02-hopyo__warn {
|
||||
margin: 0;
|
||||
color: var(--color-warning);
|
||||
font-size: var(--text-body-sm);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -41,7 +33,7 @@
|
||||
}
|
||||
|
||||
.m02-hopyo__row:hover {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
.m02-hopyo__num {
|
||||
@@ -300,7 +292,7 @@
|
||||
|
||||
.m02-hopyo-sheet__item.is-current {
|
||||
border-left-color: var(--color-accent);
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
.m02-hopyo-sheet__no {
|
||||
@@ -318,7 +310,7 @@
|
||||
}
|
||||
|
||||
/* 목록 숫자 — 다 연결(359-2)이면 녹색 · [수정] 은 번호 바로 오른쪽 · 줄 높이 안 커짐 */
|
||||
.m02-hopyo-sheet__item .m02-hopyo__note.is-linked {
|
||||
.m02-hopyo-sheet__item .ui-text.is-linked {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
|
||||
@@ -194,7 +194,8 @@ const why = (error: unknown): string => (error instanceof Error ? error.message
|
||||
const won = (v: number | null | undefined): string =>
|
||||
v === null || v === undefined ? "" : v.toLocaleString("ko-KR");
|
||||
const fail = (error: unknown): void => showToast(`실패 — ${why(error)}`, "error");
|
||||
const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text });
|
||||
const note = (text: string): HTMLElement =>
|
||||
el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } });
|
||||
const td = (text: string, cls = ""): HTMLElement =>
|
||||
el("td", { text, className: cls, attrs: { title: text } });
|
||||
const openSheet = (o: HopyoSheetOptions): void =>
|
||||
@@ -503,7 +504,7 @@ export function buildHopyoCheck(opt: CheckOptions): HTMLElement {
|
||||
tools.push(all, go);
|
||||
}
|
||||
const head = el("div", {
|
||||
className: "m02-hopyo__bar",
|
||||
className: "ui-toolbar m02-hopyo__bar",
|
||||
children: [
|
||||
el("strong", { text: `제${h.번}호표 ${h.이름}` }),
|
||||
note(h.규격),
|
||||
@@ -545,7 +546,7 @@ export function buildHopyoCheck(opt: CheckOptions): HTMLElement {
|
||||
});
|
||||
return [
|
||||
el("label", {
|
||||
className: "m02-hopyo__bar m02-hopyo__whole",
|
||||
className: "ui-toolbar m02-hopyo__bar m02-hopyo__whole",
|
||||
children: [
|
||||
box,
|
||||
note(
|
||||
|
||||
@@ -149,7 +149,7 @@ function nameNotes(line: HopyoLine, parts: HopyoPart[], price: LinePrice | null)
|
||||
].filter(Boolean) as string[];
|
||||
return [
|
||||
...opts.map((t) => small(t, "m02-labrow__note m02-hopyo__opts")),
|
||||
...why.map((t) => small(`셈 못 함: ${t}`, "m02-labrow__empty")),
|
||||
...why.map((t) => small(`셈 못 함: ${t}`, "ui-text ui-text--error")),
|
||||
...(parts.some((p) => p.호표 !== undefined) ? [small("하위 호표 = 만든 뒤 셈")] : []),
|
||||
];
|
||||
}
|
||||
@@ -216,7 +216,7 @@ function logicRow(line: HopyoLine, ctx: RowsContext): HTMLElement {
|
||||
el("td", {
|
||||
className: "m02-labrow__name m02-hopyo__parts",
|
||||
children: [
|
||||
...(parts.length ? partLines(line, parts, ctx) : [small("—", "m02-labrow__muted")]),
|
||||
...(parts.length ? partLines(line, parts, ctx) : [small("—", "ui-text ui-text--muted")]),
|
||||
...nameNotes(line, parts, price),
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -30,7 +30,8 @@ const FOLD_TEXT: Record<string, string> = {
|
||||
"지역 변형": "하나 고르면 그 지역 · 「전국」으로 되돌림은 위",
|
||||
숫자: "이름 · 출처 안 닮음 · 숫자 지문만",
|
||||
};
|
||||
const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text });
|
||||
const note = (text: string): HTMLElement =>
|
||||
el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } });
|
||||
const num = (v: number): string => String(Math.round(v * 10000) / 10000);
|
||||
|
||||
/** 대상 글 — 「이름 [단위]」 · 자재는 규격도 · 호표 = 「제N호표」 */
|
||||
@@ -277,7 +278,7 @@ export function openHopyoChoose(opt: ChooseOptions): void {
|
||||
const excel = qtyText(line.맞춤.품?.엑셀);
|
||||
const basis = line.근거;
|
||||
const head = el("div", {
|
||||
className: "m02-hopyo__bar m02-hopyo__source",
|
||||
className: "ui-toolbar m02-hopyo__bar m02-hopyo__source",
|
||||
children: [
|
||||
...(excel ? [note(`엑셀 품 — ${excel}`)] : []),
|
||||
...(basis ? [note(`엑셀 근거 — ${sourceText(basis)}`)] : []),
|
||||
@@ -323,7 +324,7 @@ export function openHopyoChoose(opt: ChooseOptions): void {
|
||||
: "가까운 것 없음 — 아래 검색",
|
||||
),
|
||||
...sections,
|
||||
el("div", { className: "m02-hopyo__bar", children: [together, ...more] }),
|
||||
el("div", { className: "ui-toolbar m02-hopyo__bar", children: [together, ...more] }),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -122,7 +122,8 @@ export const filterRows = (rows: LedgerView[], q: string, kind: string): LedgerV
|
||||
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
|
||||
const td = (text: string, cls = ""): HTMLElement =>
|
||||
el("td", { text, className: cls, attrs: { title: text } });
|
||||
const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text });
|
||||
const note = (text: string): HTMLElement =>
|
||||
el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } });
|
||||
|
||||
export interface LedgerOptions {
|
||||
scope: HopyoScope;
|
||||
@@ -323,7 +324,7 @@ export function buildHopyoLedger(opt: LedgerOptions): HTMLElement {
|
||||
}
|
||||
root.replaceChildren(
|
||||
el("div", {
|
||||
className: "m02-hopyo__bar",
|
||||
className: "ui-toolbar m02-hopyo__bar",
|
||||
children: [
|
||||
el("strong", { text: LEDGER_TITLE }),
|
||||
note(`${LEDGER_HINT} · 사전조합 생성 때 남음 · 아래 층이 이김`),
|
||||
|
||||
@@ -80,7 +80,8 @@ const DANSAN = "단가산출";
|
||||
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
|
||||
const won = (v: number | null | undefined): string =>
|
||||
v === null || v === undefined ? "" : v.toLocaleString("ko-KR");
|
||||
const note = (text: string): HTMLElement => el("span", { className: "m02-hopyo__note", text });
|
||||
const note = (text: string): HTMLElement =>
|
||||
el("span", { className: "ui-text ui-text--muted", text, attrs: { "data-text": "14" } });
|
||||
|
||||
/** 목록 숫자 「연결된 줄 / 연결할 줄」(358-3 두 칸 · 없으면 옛 맞춤 [제안, 줄]) — 다 연결(같고 0 넘음)이면 참 ·
|
||||
* 0/0 은 색 없음 */
|
||||
@@ -144,8 +145,14 @@ export function openHopyoSheet(opt: HopyoSheetOptions): void {
|
||||
const items = new Map<number, HTMLElement>();
|
||||
const target = opt.target ?? opt.scope;
|
||||
|
||||
const head = el("div", { className: "m02-hopyo__bar", children: [note("엑셀을 읽는 중…")] });
|
||||
const warn = el("p", { className: "m02-hopyo__warn" });
|
||||
const head = el("div", {
|
||||
className: "ui-toolbar m02-hopyo__bar",
|
||||
children: [note("엑셀을 읽는 중…")],
|
||||
});
|
||||
const warn = el("p", {
|
||||
className: "ui-text ui-text--warning m02-hopyo__warn",
|
||||
attrs: { "data-text": "14" },
|
||||
});
|
||||
warn.hidden = true;
|
||||
const left = el("div", {
|
||||
className: "m02-hopyo-sheet__grid",
|
||||
@@ -165,7 +172,10 @@ export function openHopyoSheet(opt: HopyoSheetOptions): void {
|
||||
className: "m02-hopyo-sheet__side",
|
||||
children: [
|
||||
search,
|
||||
el("div", { className: "m02-hopyo__bar", children: [count, ...(opt.canWrite ? [go] : [])] }),
|
||||
el("div", {
|
||||
className: "ui-toolbar m02-hopyo__bar",
|
||||
children: [count, ...(opt.canWrite ? [go] : [])],
|
||||
}),
|
||||
list,
|
||||
result,
|
||||
],
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
||||
import { listHopyo, pageScope, uploadHopyo, type HopyoFile } from "./M02_MasterTemplete_Hopyo_Api";
|
||||
import { buildHopyoLedger, LEDGER_HINT, LEDGER_TITLE } from "./M02_MasterTemplete_Hopyo_Ledger";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
export const HOPYO_TITLE = "일위대가 실무자료";
|
||||
|
||||
@@ -94,7 +95,7 @@ export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
|
||||
row.addEventListener("click", () => void showSheet(f));
|
||||
return row;
|
||||
})
|
||||
: [el("p", { className: "m02-side__empty", text: "올린 실무자료 없음" })]),
|
||||
: [createEmpty({ text: "올린 실무자료 없음", small: true })]),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import type { ItemPart, ItemSource } from "./M02_MasterTemplete_ItemSource";
|
||||
import type { LaborDetailHandle } from "./M02_MasterTemplete_Labor_Detail";
|
||||
import "./M02_MasterTemplete_Structure.css";
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
|
||||
/** 집계표 열 하나에 딸린 문서 셋 — 번호 칸 · 이름 */
|
||||
export type EditKind = ItemPart;
|
||||
@@ -189,7 +190,8 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
body: HTMLElement,
|
||||
save: () => Promise<void>,
|
||||
) => {
|
||||
const no = el("span", { className: "m02-struct__no" });
|
||||
const no = createBadge("", "accent"); // 번호 = 배지 부품
|
||||
no.classList.add("m02-struct__no");
|
||||
const name = el("span", { className: "m02-struct__title" });
|
||||
const shell = cardShell({ title, body, sub: [no, name] });
|
||||
const saver = saveControl(
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
.m02-itempick__note {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-itempick__buttons {
|
||||
|
||||
@@ -220,7 +220,7 @@ export function buildTwoWay(opt: TwoWayOptions): HTMLElement {
|
||||
return el("div", {
|
||||
className: "m02-itempick__two",
|
||||
children: [
|
||||
el("p", { className: "m02-itempick__note", text: opt.note }),
|
||||
el("p", { className: "ui-text ui-text--muted m02-itempick__note", text: opt.note }),
|
||||
el("div", { className: "m02-itempick__buttons", children: [fresh, copy] }),
|
||||
below,
|
||||
],
|
||||
@@ -256,7 +256,10 @@ export function pickFrom(
|
||||
close();
|
||||
})(),
|
||||
});
|
||||
if (opt.note) body.append(el("p", { className: "m02-itempick__note", text: opt.note }));
|
||||
if (opt.note)
|
||||
body.append(
|
||||
el("p", { className: "ui-text ui-text--muted m02-itempick__note", text: opt.note }),
|
||||
);
|
||||
body.append(picker.root);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
.m02-labrow__row.is-open:hover {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
/* 이름 칸(45-7) — 칸은 표 칸 그대로(flex 면 칸 선이 끊김) · 코드 · 이름 · 설명은 글마다 한 줄 말줄임(전체 = title) */
|
||||
@@ -36,18 +36,12 @@
|
||||
}
|
||||
|
||||
.m02-labrow__key,
|
||||
.m02-labrow__note,
|
||||
.m02-labrow__muted {
|
||||
.m02-labrow__note {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-caption);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.m02-labrow__empty {
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
/* 로직 자재 줄(103-1) — 금액은 로직 단가 안(합에 안 더함 · 흐리게) · 바꾼 줄 = 소속 글 강조 */
|
||||
.m02-labrow__logic-mat .m02-labrow__money {
|
||||
color: var(--color-text-secondary);
|
||||
@@ -138,6 +132,7 @@
|
||||
|
||||
/* 단추 칸(51-14 · 102-1) — [▲ · ▼ 위아래][옵션|수정][삭제] · 옵션 · 삭제는 두 단추 높이(2행 병합 꼴) */
|
||||
.m02-labrow__tools {
|
||||
flex-wrap: nowrap; /* 도구줄 부품은 감김 — 여기는 한 줄 그대로 */
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
gap: var(--btn-group-gap);
|
||||
@@ -180,13 +175,13 @@
|
||||
.m02-labrow__optrow.is-open:hover,
|
||||
.m02-labrow__row.is-open:hover + .m02-labrow__optrow,
|
||||
.m02-labrow__row.is-open:has(+ .m02-labrow__optrow:hover) {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
/* 고른 줄(47-5) — 옵션 값 줄까지 한 덩이 · 왼쪽 띠(차례 칸 = 두 줄 병합 · 113-1) */
|
||||
.m02-labrow .m02-labrow__row.is-selected,
|
||||
.m02-labrow .m02-labrow__optrow.is-selected {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
.m02-labrow__row.is-selected > td:first-child {
|
||||
@@ -255,7 +250,7 @@
|
||||
/* 총합계 줄 전체 합계 칸(44-6) — 테두리 · 색 · 테두리는 안쪽 그림자(칸 선 굵기 안 바꿈 · 45-7) */
|
||||
.m02-labrow__sum .m02-labrow__total {
|
||||
box-shadow: inset 0 0 0 2px var(--color-primary);
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@@ -290,12 +285,6 @@
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-labin__lost {
|
||||
margin: 0;
|
||||
color: var(--color-danger);
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
/* 보기 갈래(면적산출 오른쪽 패널 · 일위대가 카드) */
|
||||
.m02-labview {
|
||||
display: flex;
|
||||
@@ -472,7 +461,7 @@
|
||||
|
||||
.m02-labrow__qtybtn:hover {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
/* 수량산출 모달(43-1) — 위 지금 수량 · 손 입력 · [연결 풀기] / 아래 면적산출 시트(읽기만) */
|
||||
|
||||
@@ -28,10 +28,6 @@
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.m02-labordg__hint {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.m02-labordg__viewport {
|
||||
position: relative;
|
||||
height: clamp(320px, 62vh, 640px);
|
||||
@@ -293,11 +289,6 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.m02-labordg__none {
|
||||
margin: var(--spacing-16);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.m02-labor__dg > summary {
|
||||
cursor: pointer;
|
||||
font-weight: var(--font-weight-bold);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { wheelZoomsIn } from "../A00_Common/touch_bridge";
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { formatMoney } from "../M01_MasterData/M01_MasterData_UI_Logic_Money";
|
||||
import "./M02_MasterTemplete_Labor_Diagram.css";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
const TEXT = {
|
||||
Inputs: ["넣을 값", "Inputs"],
|
||||
@@ -270,13 +271,13 @@ function mountShell(host: HTMLElement): State {
|
||||
};
|
||||
host.replaceChildren(
|
||||
el("div", {
|
||||
className: "m02-labordg__bar",
|
||||
className: "ui-toolbar m02-labordg__bar",
|
||||
children: [
|
||||
icon("−", "Zoom_Out", () => zoomAt(st, 1 / 1.2)),
|
||||
icon("+", "Zoom_In", () => zoomAt(st, 1.2)),
|
||||
icon("⤢", "Fit", () => fit(st, true)),
|
||||
label,
|
||||
el("span", { className: "m02-labordg__hint", text: td("Hint") }),
|
||||
el("span", { className: "ui-text ui-text--muted", text: td("Hint") }),
|
||||
],
|
||||
}),
|
||||
viewport,
|
||||
@@ -369,7 +370,7 @@ export function renderComboDiagram(
|
||||
}
|
||||
const { world } = st;
|
||||
if (!data.logics.length) {
|
||||
world.replaceChildren(el("p", { className: "m02-labordg__none", text: td("None") }));
|
||||
world.replaceChildren(createEmpty({ text: td("None"), small: true }));
|
||||
return;
|
||||
}
|
||||
const hasValue = data.logics.some((l) => isValue(l.out));
|
||||
|
||||
@@ -123,7 +123,7 @@ export function openLaborInput(opt: LaborInputOptions): void {
|
||||
...(opt.notice
|
||||
? [
|
||||
el("p", {
|
||||
className: "m02-labin__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: opt.notice,
|
||||
attrs: { "data-labor-notice": "" },
|
||||
}),
|
||||
@@ -132,14 +132,14 @@ export function openLaborInput(opt: LaborInputOptions): void {
|
||||
...(lost.length
|
||||
? [
|
||||
el("p", {
|
||||
className: "m02-labin__lost",
|
||||
className: "ui-text ui-text--error",
|
||||
text: fill(tc("Labor_Lost"), lost.join(", ")),
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(rows.length
|
||||
? rows
|
||||
: [el("p", { className: "m02-labin__muted", text: tc("In_None") })]),
|
||||
: [el("p", { className: "ui-text ui-text--muted", text: tc("In_None") })]),
|
||||
...(rate
|
||||
? [
|
||||
el("div", {
|
||||
|
||||
@@ -126,7 +126,9 @@ function matRow(
|
||||
text: mat.고름 ? `${owner} · ${tc("LogicMat_Picked")}` : owner,
|
||||
attrs: { title: owner },
|
||||
}),
|
||||
...(mat.까닭 ? [el("small", { className: "m02-labrow__empty", text: mat.까닭 })] : []),
|
||||
...(mat.까닭
|
||||
? [el("small", { className: "ui-text ui-text--error", text: mat.까닭 })]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
el("td", {
|
||||
|
||||
@@ -56,7 +56,7 @@ export function nationalRows(origin: LaborDoc["본뜬"]): HTMLElement[] {
|
||||
}),
|
||||
);
|
||||
return [
|
||||
el("p", { className: "m02-labrow__muted", text: say("Title") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: say("Title") }),
|
||||
el("div", {
|
||||
className: "ui-general-block__table-wrap",
|
||||
attrs: { "data-labor-national-table": "" },
|
||||
|
||||
@@ -107,7 +107,7 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
});
|
||||
} else
|
||||
host.replaceChildren(
|
||||
el("p", { className: "m02-labin__muted", text: tc("Labor_PickWait") }),
|
||||
el("p", { className: "ui-text ui-text--muted", text: tc("Labor_PickWait") }),
|
||||
);
|
||||
const ok = (): void => {
|
||||
const raw = hand.value.trim();
|
||||
|
||||
@@ -83,7 +83,7 @@ export async function openRefEdit(opt: RefEditOptions): Promise<void> {
|
||||
});
|
||||
body.append(
|
||||
el("p", {
|
||||
className: "m02-labin__muted",
|
||||
className: "ui-text ui-text--muted",
|
||||
text: notice,
|
||||
attrs: { "data-labor-notice": "" },
|
||||
}),
|
||||
|
||||
@@ -411,7 +411,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
...(values.empty
|
||||
? [
|
||||
el("small", {
|
||||
className: "m02-labrow__empty",
|
||||
className: "ui-text ui-text--error",
|
||||
text: tc("Labor_Empty").replace("{n}", String(values.empty)),
|
||||
}),
|
||||
]
|
||||
@@ -420,7 +420,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
...(why
|
||||
? [
|
||||
el("small", {
|
||||
className: "m02-labrow__empty",
|
||||
className: "ui-text ui-text--error",
|
||||
text: `${tc("Labor_Calc_Fail")}: ${why}`,
|
||||
}),
|
||||
]
|
||||
@@ -438,7 +438,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
className: "m02-labrow__move",
|
||||
children: [
|
||||
el("div", {
|
||||
className: "m02-labrow__tools",
|
||||
className: "ui-toolbar m02-labrow__tools",
|
||||
children: [
|
||||
...(move
|
||||
? [
|
||||
@@ -529,7 +529,8 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
...COSTS.map((c) => tc(HEAD[c])),
|
||||
...(tools ? [tc("Col_Move")] : []),
|
||||
];
|
||||
if (!data.rows.length) return el("p", { className: "m02-labrow__muted", text: tc("NoLogic") });
|
||||
if (!data.rows.length)
|
||||
return el("p", { className: "ui-text ui-text--muted", text: tc("NoLogic") });
|
||||
const out = table(heads, trs, foot);
|
||||
if (move) attachRowDrag(out, move);
|
||||
return out;
|
||||
@@ -582,7 +583,7 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
children: [
|
||||
el("strong", { text: mat.이름 ?? "" }),
|
||||
...(line?.까닭
|
||||
? [el("small", { className: "m02-labrow__empty", text: line.까닭 })]
|
||||
? [el("small", { className: "ui-text ui-text--error", text: line.까닭 })]
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
@@ -637,5 +638,5 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
const lead = data.lead ?? [];
|
||||
return data.mats.length || lead.length
|
||||
? table(heads, [...lead, ...trs], [])
|
||||
: el("p", { className: "m02-labrow__muted", text: tc("Labor_NoMat") });
|
||||
: el("p", { className: "ui-text ui-text--muted", text: tc("Labor_NoMat") });
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH
|
||||
calc = null;
|
||||
fail = null;
|
||||
if (!column) return void root.replaceChildren();
|
||||
root.replaceChildren(el("p", { className: "m02-labrow__muted", text: tc("Loading") }));
|
||||
root.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: tc("Loading") }));
|
||||
try {
|
||||
const got = await readTemplate(layer, "labor", column, null);
|
||||
if (mine !== seq) return;
|
||||
@@ -134,7 +134,7 @@ export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewH
|
||||
if (!doc) {
|
||||
root.replaceChildren(
|
||||
el("p", {
|
||||
className: "m02-labrow__empty",
|
||||
className: "ui-text ui-text--error",
|
||||
text: error instanceof Error ? error.message : tc("Load_Failed"),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -274,7 +274,7 @@ export function mountLibraryList(
|
||||
);
|
||||
if (opt.canEdit)
|
||||
tools.replaceChildren(
|
||||
el("span", { className: "m02-main__tools", children: [btnNew, btnCopy, btnDel] }),
|
||||
el("span", { className: "ui-toolbar m02-main__tools", children: [btnNew, btnCopy, btnDel] }),
|
||||
);
|
||||
void load();
|
||||
return { getDoc: () => null, destroy: () => undefined };
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type ComboList,
|
||||
type ComboOne,
|
||||
} from "./M02_MasterTemplete_Labor_Api";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
export interface PracticeSectionOptions {
|
||||
/** 저장 안 한 고침을 버릴지 확인 — 취소면 false */
|
||||
@@ -125,10 +126,11 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti
|
||||
});
|
||||
view.replaceChildren(
|
||||
el("div", {
|
||||
className: "m02-practice__bar",
|
||||
className: "ui-toolbar m02-practice__bar",
|
||||
children: [
|
||||
el("span", {
|
||||
className: "m02-practice__note",
|
||||
className: "ui-text ui-text--muted",
|
||||
attrs: { "data-text": "14" },
|
||||
text: L("M02_PracticeCount").replace("{n}", String(list.줄.length)),
|
||||
}),
|
||||
drop,
|
||||
@@ -145,7 +147,7 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti
|
||||
],
|
||||
rows,
|
||||
)
|
||||
: el("p", { className: "m02-practice__note", text: L("M02_PracticeEmpty") }),
|
||||
: createEmpty({ text: L("M02_PracticeEmpty"), small: true }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -171,7 +173,13 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti
|
||||
[L("M02_PracticeKey"), c.키],
|
||||
[L("M02_PracticeGroup"), c.구분 ?? ""],
|
||||
[L("M02_PracticeUnit"), c.단위 ?? ""],
|
||||
].map(([k, v]) => el("span", { className: "m02-practice__note", text: `${k} ${v}` }));
|
||||
].map(([k, v]) =>
|
||||
el("span", {
|
||||
className: "ui-text ui-text--muted",
|
||||
attrs: { "data-text": "14" },
|
||||
text: `${k} ${v}`,
|
||||
}),
|
||||
);
|
||||
const lines = one.줄.map((x) =>
|
||||
el("tr", {
|
||||
children: cells([
|
||||
@@ -185,7 +193,7 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti
|
||||
);
|
||||
view.replaceChildren(
|
||||
el("div", {
|
||||
className: "m02-practice__bar",
|
||||
className: "ui-toolbar m02-practice__bar",
|
||||
children: [back, el("strong", { text: c.이름 }), ...info],
|
||||
}),
|
||||
...(c.비고 ? [el("p", { className: "m02-practice__memo", text: c.비고 })] : []),
|
||||
@@ -200,7 +208,7 @@ export function buildPracticeSection(opt: PracticeSectionOptions): PracticeSecti
|
||||
],
|
||||
lines,
|
||||
)
|
||||
: el("p", { className: "m02-practice__note", text: L("M02_PracticeNoLogic") }),
|
||||
: createEmpty({ text: L("M02_PracticeNoLogic"), small: true }),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,14 +133,18 @@
|
||||
font-size: var(--text-body);
|
||||
}
|
||||
|
||||
/* 카드 번호 = 배지 부품(ui-badge) · 배지 아닌 옛 자리(일위대가 값 요약)만 옛 꼴 */
|
||||
.m02-struct__no {
|
||||
flex: none;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.m02-struct__no:not(.ui-badge) {
|
||||
padding: 0 var(--spacing-8);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--color-accent) 14%, transparent);
|
||||
color: var(--color-text);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 미리보기 — 웹캐드는 높이를 부모에서 받음(`.m02-drawing` 이 flex: 1 1 0).
|
||||
@@ -241,22 +245,10 @@
|
||||
margin: var(--spacing-8) 0;
|
||||
}
|
||||
|
||||
/* 줄 = 목록 줄 부품(ui-list-row) · 여기는 왼쪽부터 늘어놓기만 */
|
||||
.m02-pick__item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-12);
|
||||
padding: var(--spacing-8);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-pick__item:hover,
|
||||
.m02-pick__item:focus-visible {
|
||||
background: var(--color-surface-sunken);
|
||||
}
|
||||
|
||||
.m02-pick__key {
|
||||
@@ -266,16 +258,7 @@
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* 개인 자재 표(148-3) · 신규 자재 입력 */
|
||||
.m02-pick__tag {
|
||||
flex: none;
|
||||
padding: 0 var(--spacing-4);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: var(--color-surface-sunken);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* 신규 자재 입력 */
|
||||
.m02-pick__new {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
/* 층 단추 줄 오른쪽 끝 — 「N개 고름」 · 「+」(PLAN 30-2) */
|
||||
.m02-tree__tools {
|
||||
flex-wrap: nowrap; /* 도구줄 부품은 감김 — 여기는 한 줄 그대로 */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
@@ -64,7 +65,7 @@
|
||||
|
||||
/* 여러 개 고른 줄 배경 */
|
||||
.m02-tree__body .ui-tree__row.is-picked {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
/* 남의 회사 「개인」 사람 나무 — 고른 사람 줄 아래 그 사람 라이브러리(PLAN 29-6) */
|
||||
@@ -72,11 +73,9 @@
|
||||
padding-left: var(--spacing-16);
|
||||
}
|
||||
|
||||
/* 겉 = 보조 글 부품 · 여기는 여백만 */
|
||||
.m02-tree__hint {
|
||||
margin: 0;
|
||||
padding: var(--spacing-8);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-treedlg {
|
||||
|
||||
@@ -118,7 +118,12 @@ export interface TreePanelHandle {
|
||||
}
|
||||
|
||||
const why = (error: unknown): string => (error instanceof Error ? error.message : "");
|
||||
const hint = (text: string): HTMLElement => el("p", { className: "m02-tree__hint", text });
|
||||
const hint = (text: string): HTMLElement =>
|
||||
el("p", {
|
||||
className: "ui-text ui-text--muted m02-tree__hint",
|
||||
text,
|
||||
attrs: { "data-text": "14" },
|
||||
});
|
||||
|
||||
export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
const owner = opt.owner ?? null;
|
||||
@@ -162,7 +167,10 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
|
||||
multiBtn.classList.add("ui-touch-only");
|
||||
multiBtn.title = "여럿 고르기 (Ctrl 누르고 누르기와 같음)";
|
||||
multiBtn.setAttribute("aria-pressed", "false");
|
||||
const tools = el("span", { className: "m02-tree__tools", children: [status, multiBtn, add] });
|
||||
const tools = el("span", {
|
||||
className: "ui-toolbar m02-tree__tools",
|
||||
children: [status, multiBtn, add],
|
||||
});
|
||||
|
||||
/* --- 그리기 --- */
|
||||
const canCopy = !!api.paste;
|
||||
|
||||
@@ -38,6 +38,7 @@ import { helpParts } from "./M02_MasterTemplete_CardShell";
|
||||
import { isPrecombo } from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
import type { SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
export interface Selection {
|
||||
layer: Layer;
|
||||
projectId: string | null;
|
||||
@@ -149,7 +150,10 @@ export function createMain(
|
||||
void open({ ...at, kind });
|
||||
});
|
||||
const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴
|
||||
const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } });
|
||||
const notice = el("div", {
|
||||
className: "ui-banner ui-banner--danger m02-main__notice",
|
||||
attrs: { hidden: "" },
|
||||
});
|
||||
const host = el("div", { className: "m02-main__host" });
|
||||
const extra = el("div", { className: "m02-main__extra" }); // 표 밖 형제 자리 — 구조물 설계 컨테이너
|
||||
const column = el("div", {
|
||||
@@ -244,7 +248,7 @@ export function createMain(
|
||||
notice.hidden = nodes.length === 0;
|
||||
};
|
||||
const empty = (text: string): void => {
|
||||
host.replaceChildren(el("p", { className: "m02-main__empty", text }));
|
||||
host.replaceChildren(createEmpty({ text }));
|
||||
};
|
||||
/** 고를 것마다 고칠 권한 — 사전조합 층은 고름에 실림(134-3) · 그 밖 페이지 층 */
|
||||
const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin);
|
||||
@@ -617,7 +621,9 @@ export function createMain(
|
||||
title.textContent = label;
|
||||
host.replaceChildren(node);
|
||||
if (tools)
|
||||
slot.replaceChildren(el("span", { className: "m02-main__tools", children: [tools] }));
|
||||
slot.replaceChildren(
|
||||
el("span", { className: "ui-toolbar m02-main__tools", children: [tools] }),
|
||||
);
|
||||
return (d) => (dirty = d);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
PRECOMBO_TITLE,
|
||||
type PrecomboOptions,
|
||||
} from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
const KINDS: { kind: Kind; label: () => string }[] = [
|
||||
{ kind: "table", label: () => L("M02_KindTable") },
|
||||
@@ -225,7 +226,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
b.addEventListener("click", () => void select(info));
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m02-side__empty", text: L("M02_NoTemplates") })]),
|
||||
: [createEmpty({ text: L("M02_NoTemplates"), small: true })]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import { attachTreeDrag } from "./M02_MasterTemplete_TreePanel_Drag";
|
||||
import { folderIdOf, samePath } from "./M02_MasterTemplete_TreePanel_Model";
|
||||
import type { Target } from "./M02_MasterTemplete_TreePanel_Pick";
|
||||
import type { Selection } from "./M02_MasterTemplete_UI_Main";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
export type PrecomboKind = Extract<Kind, "precombo">;
|
||||
|
||||
@@ -278,9 +279,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle {
|
||||
targets.clear();
|
||||
const tree = buildPrecomboFolders(rows, view ?? { 폴더: [], 자리: {} });
|
||||
if (!tree.docs.length && !tree.folders.length)
|
||||
return void treeHost.replaceChildren(
|
||||
el("p", { className: "m02-side__empty", text: tc("Lib_None") }),
|
||||
);
|
||||
return void treeHost.replaceChildren(createEmpty({ text: tc("Lib_None"), small: true }));
|
||||
const canWrite = opt.scope().canWrite;
|
||||
treeHost.replaceChildren(
|
||||
...renderTree(kids(tree), (node, caret, depth, onClick) => {
|
||||
@@ -320,7 +319,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle {
|
||||
const mine = ++seq;
|
||||
const at = opt.scope();
|
||||
barHost.replaceChildren(opt.bar());
|
||||
treeHost.replaceChildren(el("p", { className: "m02-side__empty", text: tc("Loading") }));
|
||||
treeHost.replaceChildren(createEmpty({ text: tc("Loading"), small: true }));
|
||||
const [got, tree] = await Promise.all([
|
||||
// 못 받으면 빈 나무(목록표에서 까닭) · 폴더를 못 받으면 문서는 모두 뿌리
|
||||
listTemplates(at.layer, null, at.owner).then(
|
||||
|
||||
@@ -29,12 +29,6 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.m02-practice__note {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.m02-practice__memo {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary);
|
||||
@@ -47,7 +41,7 @@
|
||||
}
|
||||
|
||||
.m02-practice__row:hover {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
.m02-side__row {
|
||||
@@ -119,20 +113,15 @@ html.is-bm-route .m02-main__head {
|
||||
|
||||
/* 제목 줄 오른쪽 끝 도구(일위대가 「새 조합」 · PLAN 30-5) */
|
||||
.m02-main__tools {
|
||||
flex-wrap: nowrap; /* 도구줄 부품은 감김 — 여기는 한 줄 그대로 */
|
||||
display: inline-flex;
|
||||
gap: var(--spacing-8); /* M01 머리 줄(`m01-head`)과 같은 틈 */
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 겉 = 띠 알림 부품(ui-banner--danger) · 여기는 가운데 맞춤만 */
|
||||
.m02-main__notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
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);
|
||||
}
|
||||
|
||||
.m02-main__host {
|
||||
@@ -181,13 +170,6 @@ html.is-bm-route .m02-main__head {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.m02-main__empty {
|
||||
margin: 0;
|
||||
padding: var(--spacing-24);
|
||||
color: var(--color-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.m02-modal__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -12,6 +12,9 @@ import { openModal } from "@ui/ui_template_modal";
|
||||
import { searchMaterials } from "../M01_MasterData/M01_MasterData_Api_Fetch";
|
||||
import { call } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { createUnitPick, NO_UNIT } from "./DataFlow/M02_DF_UnitPick";
|
||||
import { createBadge } from "@ui/ui_template_badge";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
/** 고른 자재 한 줄 — M01 자재(키 `MT`) · 개인 자재(키 `PM`) */
|
||||
export interface MaterialItem {
|
||||
@@ -93,13 +96,14 @@ export function pickMaterial(done: (item: MaterialItem) => void, own?: OwnLayer)
|
||||
const search = createInputField({ placeholder: TEXT.search, type: "search" });
|
||||
const list = el("div", { className: "m02-pick__list" });
|
||||
const item = (m: MaterialItem, tags: string[] = []): HTMLElement => {
|
||||
// 목록 줄 부품 겉 · 꼬리표 = 배지 부품
|
||||
const row = el("button", {
|
||||
className: "m02-pick__item",
|
||||
attrs: { type: "button", "data-material": m.코드 },
|
||||
className: "m02-pick__item ui-list-row ui-sidebar-row",
|
||||
attrs: { type: "button", "data-material": m.코드, "data-size": "36" },
|
||||
children: [
|
||||
el("span", { className: "m02-pick__key", text: m.코드 }),
|
||||
`${m.이름} ${m.규격} ${m.단위}`.replace(/\s+/g, " "),
|
||||
...tags.map((t) => el("span", { className: "m02-pick__tag", text: t })),
|
||||
...tags.map((t) => createBadge(t, "neutral")),
|
||||
],
|
||||
});
|
||||
row.addEventListener("click", () => {
|
||||
@@ -111,7 +115,7 @@ export function pickMaterial(done: (item: MaterialItem) => void, own?: OwnLayer)
|
||||
let seq = 0;
|
||||
const paint = async (): Promise<void> => {
|
||||
const ticket = ++seq;
|
||||
list.replaceChildren(el("p", { className: "m02-struct__empty", text: TEXT.searching }));
|
||||
list.replaceChildren(createText(TEXT.searching));
|
||||
try {
|
||||
const q = search.input.value.trim();
|
||||
const [hits] = await Promise.all([searchMaterials(q), loaded]);
|
||||
@@ -122,9 +126,7 @@ export function pickMaterial(done: (item: MaterialItem) => void, own?: OwnLayer)
|
||||
.map((r) => item(toItem(r), r.값 === null ? [TEXT.own, TEXT.noPrice] : [TEXT.own]));
|
||||
const all = [...ownHits, ...hits.map((m) => item(m))];
|
||||
list.replaceChildren(
|
||||
...(all.length
|
||||
? all
|
||||
: [el("p", { className: "m02-struct__empty", text: TEXT.noMatch })]),
|
||||
...(all.length ? all : [createEmpty({ text: TEXT.noMatch, small: true })]),
|
||||
);
|
||||
} catch {
|
||||
if (ticket === seq) showToast(TEXT.searchFailed, "error");
|
||||
@@ -219,7 +221,7 @@ function newForm(
|
||||
children: [el("label", { className: "ui-field__label", text: TEXT.unit }), unitHost],
|
||||
}),
|
||||
price.root,
|
||||
el("p", { className: "m02-struct__empty", text: TEXT.key }),
|
||||
createText(TEXT.key),
|
||||
el("div", {
|
||||
className: "ui-btn-group",
|
||||
children: [createButton({ label: TEXT.back, variant: "ghost", onClick: back }), save],
|
||||
|
||||
@@ -38,6 +38,9 @@ import { checkBox, pickBox, textBox } from "./M02_SO_Def_Ui";
|
||||
import { addFromTemplate, templates } from "./M02_SO_Def_Add";
|
||||
import { buildGroupBlock } from "./M02_SO_Groups";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
import { createTabs } from "@ui/ui_template_tabs";
|
||||
|
||||
/** 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점) — 모든 종류에서 숨김(52-12 · 정의 화면 = 이름만) */
|
||||
const OLD_EDITOR = false;
|
||||
@@ -93,7 +96,11 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
const canEdit = (): boolean => opt.isAdmin && !past;
|
||||
const kindList = el("div", { className: "m02-opts__kinds", attrs: { "data-sod-kinds": "" } });
|
||||
const detail = el("div", { className: "m02-opts__list", attrs: { "data-sod-detail": "" } });
|
||||
const banner = el("div", { className: "m02-sod__banner", attrs: { "data-sod-banner": "" } });
|
||||
// 판 띠 = 띠 알림 부품 꼴(정보 · 지난 판 = 경고)
|
||||
const banner = el("div", {
|
||||
className: "ui-banner ui-banner--info m02-sod__banner",
|
||||
attrs: { "data-sod-banner": "" },
|
||||
});
|
||||
const issues = el("div", { className: "m02-sod__issues", attrs: { "data-sod-issues": "" } });
|
||||
const historyBox = el("div", {
|
||||
className: "m02-sod__history",
|
||||
@@ -161,8 +168,8 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
const off = t.enabled === false;
|
||||
const groups = d.그룹[t.type_id]?.length ?? 0;
|
||||
const b = el("button", {
|
||||
className: `m02-opts__kind${t.type_id === current ? " is-active" : ""}${off ? " is-off" : ""}`,
|
||||
attrs: { type: "button", "data-sod-kind": t.type_id },
|
||||
className: `m02-opts__kind ui-list-row ui-sidebar-row${t.type_id === current ? " is-active" : ""}${off ? " is-off" : ""}`,
|
||||
attrs: { type: "button", "data-sod-kind": t.type_id, "data-size": "32" },
|
||||
children: [
|
||||
el("span", { text: t.name }),
|
||||
...(groups ? [createTag(to("GroupTag", String(groups)), "accent")] : []),
|
||||
@@ -204,7 +211,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
const d = shown();
|
||||
const type = d.types.find((t) => t.type_id === current);
|
||||
if (!type) {
|
||||
detail.replaceChildren(el("p", { className: "m02-side__empty", text: to("Pick") }));
|
||||
detail.replaceChildren(createEmpty({ text: to("Pick"), small: true }));
|
||||
return;
|
||||
}
|
||||
const edit = canEdit();
|
||||
@@ -220,7 +227,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
attr: "data-sod-kind-name",
|
||||
cls: "m02-sod__name",
|
||||
}),
|
||||
el("small", { className: "m02-opts__key", text: type.type_id }),
|
||||
el("small", { className: "ui-text ui-text--muted m02-opts__key", text: type.type_id }),
|
||||
// 안 씀 = 목록 B06 만(enabled 는 목록 짝 · 322-2) · 숨김(hidden)은 한눈 표 목록 칸에서
|
||||
checkBox(
|
||||
to("Unused"),
|
||||
@@ -269,14 +276,17 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
["kinds", to("TabKinds")],
|
||||
["parts", to("TabParts")],
|
||||
];
|
||||
tabBar.replaceChildren(
|
||||
...pairs.map(([key, label]) => {
|
||||
const b = createButton({ label, variant: key === tab ? "filled" : "ghost", size: "sm" });
|
||||
b.setAttribute("data-sod-tab", key);
|
||||
b.addEventListener("click", () => ((tab = key), paint()));
|
||||
return b;
|
||||
}),
|
||||
);
|
||||
// 탭 부품(보조 탭) — 칸마다 data-sod-tab 그대로
|
||||
const tabs = createTabs({
|
||||
kind: "pill",
|
||||
tabs: pairs.map(([key, label]) => ({ key, label })),
|
||||
value: tab,
|
||||
onChange: (key) => ((tab = key as Tab), paint()),
|
||||
});
|
||||
tabs.root.querySelectorAll<HTMLElement>("[data-tab]").forEach((b) => {
|
||||
b.setAttribute("data-sod-tab", b.dataset.tab ?? "");
|
||||
});
|
||||
tabBar.replaceChildren(tabs.root);
|
||||
const edit = canEdit();
|
||||
if (tab === "table")
|
||||
body.replaceChildren(buildTable({ def: shown(), edit, changed, issues: allProblems() }));
|
||||
@@ -297,8 +307,10 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
]),
|
||||
);
|
||||
banner.classList.toggle("is-past", !!past);
|
||||
banner.classList.toggle("ui-banner--warning", !!past);
|
||||
banner.classList.toggle("ui-banner--info", !past);
|
||||
const list = allProblems();
|
||||
issues.replaceChildren(...list.map((t) => el("p", { className: "m02-sod__bad", text: t })));
|
||||
issues.replaceChildren(...list.map((t) => createText(t, "error")));
|
||||
save.disabled = !opt.isAdmin || !!past || !dirty || list.length > 0;
|
||||
};
|
||||
|
||||
@@ -366,7 +378,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
});
|
||||
return b;
|
||||
})
|
||||
: [el("p", { className: "m02-side__empty", text: to("PastNone") })]),
|
||||
: [createEmpty({ text: to("PastNone"), small: true })]),
|
||||
);
|
||||
};
|
||||
const history = createButton({ label: to("PastList"), variant: "ghost" });
|
||||
@@ -387,7 +399,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
const node = el("div", {
|
||||
className: "m02-opts m02-sod",
|
||||
children: [
|
||||
el("p", { className: "m02-side__empty", text: to("DefNote") }),
|
||||
createText(to("DefNote")),
|
||||
banner,
|
||||
tabBar,
|
||||
body,
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
import { checkBox, head, moveButtons, pickBox, textBox } from "./M02_SO_Def_Ui";
|
||||
import { buildPlaceSelect } from "./M02_SO_Groups";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
export interface OptionsBlockOptions {
|
||||
def: StructDef;
|
||||
@@ -113,8 +115,8 @@ function choicesBlock(opt: OptionsBlockOptions, o: OptionDef): HTMLElement {
|
||||
return el("div", {
|
||||
className: "m02-sod__choices",
|
||||
children: [
|
||||
el("p", { className: "m02-side__empty", text: to("ChoiceNote") }),
|
||||
...(rows.length ? rows : [el("p", { className: "m02-sod__bad", text: to("ChoiceNone") })]),
|
||||
createText(to("ChoiceNote")),
|
||||
...(rows.length ? rows : [createText(to("ChoiceNone"), "error")]),
|
||||
...(edit ? [add] : []),
|
||||
],
|
||||
});
|
||||
@@ -171,7 +173,7 @@ function optionRow(opt: OptionsBlockOptions, o: OptionDef): HTMLElement {
|
||||
attr: "data-sod-name",
|
||||
cls: "m02-sod__name",
|
||||
}),
|
||||
el("small", { className: "m02-opts__key", text: o.key }),
|
||||
el("small", { className: "ui-text ui-text--muted m02-opts__key", text: o.key }),
|
||||
...(o.phase === "detail" ? [createTag(to("Detail"), "accent")] : []),
|
||||
form,
|
||||
textBox(o.unit ?? "", (v) => (o.unit = v.trim() || null), changed, {
|
||||
@@ -224,7 +226,7 @@ export function buildOptionsBlock(opt: OptionsBlockOptions): HTMLElement {
|
||||
...head(to("OptionHead"), to("OptionNote")),
|
||||
...(type.options.length
|
||||
? type.options.map((o) => optionRow(opt, o))
|
||||
: [el("p", { className: "m02-side__empty", text: to("NoOptionKind") })]),
|
||||
: [createEmpty({ text: to("NoOptionKind"), small: true })]),
|
||||
...(edit ? [el("div", { className: "m02-side__row", children: [add] })] : []),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "./M02_SO_Def_Reflect";
|
||||
import { checkBox, head } from "./M02_SO_Def_Ui";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
|
||||
type Branch = keyof typeof PART_BOX;
|
||||
|
||||
@@ -40,7 +41,7 @@ export function buildParts(opt: {
|
||||
const blocks = (Object.keys(PART_BOX) as Branch[]).flatMap((branch) => [
|
||||
...head(branch),
|
||||
el("table", {
|
||||
className: "m02-sod__table m02-sod__table--wrap",
|
||||
className: "ui-table ui-table--data m02-sod__table m02-sod__table--wrap",
|
||||
attrs: { "data-sod-parts": branch },
|
||||
children: [
|
||||
el("thead", {
|
||||
@@ -99,6 +100,6 @@ export function buildParts(opt: {
|
||||
]);
|
||||
return el("div", {
|
||||
className: "m02-sod__tablewrap",
|
||||
children: [el("p", { className: "m02-side__empty", text: to("PartsNote") }), ...blocks],
|
||||
children: [createText(to("PartsNote")), ...blocks],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
} from "./M02_SO_Def_Model";
|
||||
import { head, pickBox } from "./M02_SO_Def_Ui";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
|
||||
export interface RulesBlockOptions {
|
||||
def: StructDef;
|
||||
@@ -42,15 +44,18 @@ function multiPick(
|
||||
className: "m02-sod__rulechoices",
|
||||
attrs: { [attr]: "" },
|
||||
children: all.map((c) => {
|
||||
const box = el("input", { attrs: { type: "checkbox", value: c } }) as HTMLInputElement;
|
||||
box.checked = on.has(c);
|
||||
box.disabled = !edit;
|
||||
box.addEventListener("change", () => {
|
||||
if (box.checked) on.add(c);
|
||||
else on.delete(c);
|
||||
set(all.filter((x) => on.has(x)));
|
||||
const { root } = createCheckbox(c, {
|
||||
checked: on.has(c),
|
||||
disabled: !edit,
|
||||
value: c,
|
||||
onChange: (checked) => {
|
||||
if (checked) on.add(c);
|
||||
else on.delete(c);
|
||||
set(all.filter((x) => on.has(x)));
|
||||
},
|
||||
});
|
||||
return el("label", { className: "m02-sod__check", children: [box, el("span", { text: c })] });
|
||||
root.classList.add("m02-sod__check");
|
||||
return root;
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -227,7 +232,7 @@ export function buildRulesBlock(opt: RulesBlockOptions): HTMLElement {
|
||||
...head(to("RuleHead"), to("RuleNote")),
|
||||
...(mine.length
|
||||
? mine.map((r) => ruleRow(opt, r, def.규칙.indexOf(r)))
|
||||
: [el("p", { className: "m02-side__empty", text: to("RuleNone") })]),
|
||||
: [createEmpty({ text: to("RuleNone"), small: true })]),
|
||||
...(edit ? [el("div", { className: "m02-side__row", children: [add] })] : []),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { hiddenKeys, readKeys, setShown } from "./M02_SO_Def_Reflect";
|
||||
import { checkBox, head, textBox } from "./M02_SO_Def_Ui";
|
||||
import { to, type OptionsTextKey } from "./M02_SO_Text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
const TAG: Record<NonNullable<SpecNameRow["tag"]>, OptionsTextKey> = {
|
||||
required: "SpecRequired",
|
||||
@@ -132,7 +133,7 @@ export function buildSpecBlock(opt: {
|
||||
cls: "m02-sod__name",
|
||||
})
|
||||
: el("span", { text: to("SpecLoose") }),
|
||||
el("small", { className: "m02-opts__key", text: row.key }),
|
||||
el("small", { className: "ui-text ui-text--muted m02-opts__key", text: row.key }),
|
||||
...(row.tag ? [createTag(to(TAG[row.tag]), "neutral")] : []),
|
||||
...shownBox(
|
||||
opt,
|
||||
@@ -164,7 +165,7 @@ export function buildSpecBlock(opt: {
|
||||
attrs: { "data-sod-spec-block": "" },
|
||||
children: [
|
||||
...head(to("SpecHead"), to("SpecNote")),
|
||||
...(rows.length ? rows : [el("p", { className: "m02-side__empty", text: to("SpecNone") })]),
|
||||
...(rows.length ? rows : [createEmpty({ text: to("SpecNone"), small: true })]),
|
||||
...head(to("SpecPreview")),
|
||||
el("div", {
|
||||
className: "m02-sod__specpreview",
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "./M02_SO_Def_Reflect";
|
||||
import { checkBox, pickBox, textBox } from "./M02_SO_Def_Ui";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
|
||||
type Pairs = ReadonlyArray<readonly [string, string]>;
|
||||
const LIST_PAIRS = (): Pairs => [
|
||||
@@ -89,7 +90,7 @@ function mark(changed: boolean, ...children: HTMLElement[]): HTMLElement {
|
||||
/** 형상칸 — 벽(형상 wall)만: 기본 형태(그림 형태 고르기) · 물빼기. */
|
||||
function shapeOptsCell(ctx: CellCtx): HTMLElement {
|
||||
const { def, type, row, edit, changed } = ctx;
|
||||
if (row.shape !== "wall") return el("span", { className: "m02-side__empty", text: "—" });
|
||||
if (row.shape !== "wall") return el("span", { className: "ui-text ui-text--muted", text: "—" });
|
||||
const form = String(row.shapeOpts.form ?? "");
|
||||
const forms = !form || REVET_FORMS.includes(form) ? REVET_FORMS : [form, ...REVET_FORMS];
|
||||
const box = pickBox(
|
||||
@@ -278,9 +279,7 @@ export function buildTable(opt: {
|
||||
el("td", {
|
||||
attrs: { "data-sod-table-check": "" },
|
||||
children: [
|
||||
bad.length
|
||||
? el("span", { className: "m02-sod__bad", text: bad.join(" · ") })
|
||||
: createTag(to("Ok"), "accent"),
|
||||
bad.length ? createText(bad.join(" · "), "error") : createTag(to("Ok"), "accent"),
|
||||
reset,
|
||||
],
|
||||
}),
|
||||
@@ -292,9 +291,9 @@ export function buildTable(opt: {
|
||||
return el("div", {
|
||||
className: "m02-sod__tablewrap",
|
||||
children: [
|
||||
el("p", { className: "m02-side__empty", text: to("TableNote") }),
|
||||
createText(to("TableNote")),
|
||||
el("table", {
|
||||
className: "m02-sod__table",
|
||||
className: "ui-table ui-table--data m02-sod__table",
|
||||
attrs: { "data-sod-table": "" },
|
||||
children: [
|
||||
el("thead", {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { step } from "./M02_SO_Def_Model";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
|
||||
/** 글 칸 — 글자마다 `set` · 떠날 때 `done` */
|
||||
export function textBox(
|
||||
@@ -69,13 +71,15 @@ export function checkBox(
|
||||
set: (v: boolean) => void,
|
||||
opt: { edit: boolean; attr?: string },
|
||||
): HTMLLabelElement {
|
||||
const box = el("input", {
|
||||
attrs: { type: "checkbox", ...(opt.attr ? { [opt.attr]: "" } : {}) },
|
||||
}) as HTMLInputElement;
|
||||
box.checked = on;
|
||||
box.disabled = !opt.edit;
|
||||
box.addEventListener("change", () => set(box.checked));
|
||||
return el("label", { className: "m02-sod__check", children: [box, el("span", { text: label })] });
|
||||
// 체크 부품 — 속성(data-sod-*)은 칸에 그대로
|
||||
const { root, input } = createCheckbox(label, {
|
||||
checked: on,
|
||||
disabled: !opt.edit,
|
||||
onChange: set,
|
||||
});
|
||||
if (opt.attr) input.setAttribute(opt.attr, "");
|
||||
root.classList.add("m02-sod__check");
|
||||
return root;
|
||||
}
|
||||
|
||||
/** 고르기 — [값, 글] 짝 */
|
||||
@@ -117,5 +121,5 @@ export function moveButtons<T>(
|
||||
/** 절 제목 + 안내 한 줄 */
|
||||
export const head = (title: string, note?: string): HTMLElement[] => [
|
||||
el("h4", { className: "m02-opts__group", text: title }),
|
||||
...(note ? [el("p", { className: "m02-side__empty", text: note })] : []),
|
||||
...(note ? [createText(note)] : []),
|
||||
];
|
||||
|
||||
@@ -11,6 +11,8 @@ import { createButton, el, showConfirmDialog } from "@ui/ui_template_elements";
|
||||
import { SHAPE_KEYS, type GroupDef } from "./M02_SO_Def_Model";
|
||||
import { checkBox, textBox } from "./M02_SO_Def_Ui";
|
||||
import { to } from "./M02_SO_Text";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
const NAME_MAX = 40;
|
||||
|
||||
@@ -109,7 +111,7 @@ export function buildGroupBlock(opt: GroupBlockOptions): HTMLElement {
|
||||
children: [
|
||||
name,
|
||||
el("small", {
|
||||
className: "m02-opts__key",
|
||||
className: "ui-text ui-text--muted m02-opts__key",
|
||||
text: to("GroupCount", String(group.옵션.length)),
|
||||
}),
|
||||
...(opt.edit ? [up, down, del] : []),
|
||||
@@ -129,11 +131,12 @@ export function buildGroupBlock(opt: GroupBlockOptions): HTMLElement {
|
||||
opt.changed();
|
||||
});
|
||||
return el("div", {
|
||||
className: "m02-sog",
|
||||
className: "ui-card m02-sog",
|
||||
attrs: { "data-pad": "12" },
|
||||
children: [
|
||||
el("h4", { className: "m02-opts__group", text: to("GroupHead") }),
|
||||
el("p", { className: "m02-side__empty", text: to("GroupNote") }),
|
||||
...(rows.length ? rows : [el("p", { className: "m02-side__empty", text: to("GroupNone") })]),
|
||||
createText(to("GroupNote")),
|
||||
...(rows.length ? rows : [createEmpty({ text: to("GroupNone"), small: true })]),
|
||||
...(opt.edit ? [el("div", { className: "m02-side__row", children: [add] })] : []),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { openDefScreen } from "./M02_SO_Def";
|
||||
import "@ui/ui_template_card.css";
|
||||
import "./M02_SO_Style.css";
|
||||
import { to } from "./M02_SO_Text";
|
||||
|
||||
|
||||
@@ -37,28 +37,11 @@
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
/* 종류 줄 = 목록 줄 부품(ui-list-row) · 여기는 글 · 꼬리표 늘어놓기만 */
|
||||
.m02-opts__kind {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: none;
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-opts__kind:hover {
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.m02-opts__kind.is-active {
|
||||
border-color: var(--color-accent);
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
/* 옵션 줄 = 왼쪽 보이기 체크 · 라벨 · 키 · 표시 / 오른쪽 들어갈 자리(본체 · 그룹) */
|
||||
@@ -81,14 +64,11 @@
|
||||
}
|
||||
|
||||
/* 옵션 그룹(PLAN 46-2) — 그룹 줄 = 이름 · 옵션 수 · 위로 · 아래로 · 지우기 */
|
||||
/* 겉 = 카드 부품(ui-card · 여백 12) */
|
||||
.m02-sog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.m02-sog__row {
|
||||
@@ -108,9 +88,9 @@
|
||||
width: 11rem;
|
||||
}
|
||||
|
||||
/* 키 글 = 보조 글 부품(ui-text) · 글줄 안에 놓임 */
|
||||
.m02-opts__key {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 11px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* 구조물 정의 편집 화면(PLAN 48-2) — 판 띠 · 종류 머리 · 옵션 · 선택지 · 배치 격자 · 규칙 · 지난 판 */
|
||||
@@ -118,19 +98,9 @@
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
/* 판 띠 = 띠 알림 부품(정보 · 지난 판 = 경고) · 여기는 한 줄 늘어놓기만 */
|
||||
.m02-sod__banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.m02-sod__banner.is-past {
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
border: 1px solid var(--color-accent);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.m02-sod__family,
|
||||
@@ -267,7 +237,7 @@
|
||||
}
|
||||
|
||||
.m02-sod__cell.is-base {
|
||||
background: var(--color-mist-violet);
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
.m02-sod__cell.is-bad,
|
||||
@@ -328,11 +298,6 @@
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.m02-sod__bad {
|
||||
margin: 0;
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* 구조물 명세 이름 고치기(PLAN 52-11) — 컨테이너 › 항목 › 값 들여쓰기 */
|
||||
.m02-sod__specrow {
|
||||
display: flex;
|
||||
@@ -370,25 +335,20 @@
|
||||
}
|
||||
|
||||
/* ── 322-2 탭 셋 · 한눈 표 · 부품 ── */
|
||||
/* 탭 = 탭 부품(보조 탭) · 여기는 위아래 간격만 */
|
||||
.m02-sod__tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-4);
|
||||
margin: var(--spacing-8) 0;
|
||||
}
|
||||
.m02-sod__tablewrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.m02-sod__table {
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-body-sm);
|
||||
/* 표 = 표 부품 데이터표(ui-table--data) · 여기는 폭 · 줄바꿈만 */
|
||||
table.m02-sod__table {
|
||||
width: auto;
|
||||
min-width: 100%;
|
||||
}
|
||||
.m02-sod__table th,
|
||||
.m02-sod__table td {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--spacing-4);
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.m02-sod__familyrow td {
|
||||
@@ -434,7 +394,7 @@
|
||||
}
|
||||
|
||||
/* 부품 탭 — 긴 글은 줄바꿈해 표가 화면 안에(322-2 다듬기) */
|
||||
.m02-sod__table--wrap {
|
||||
table.m02-sod__table--wrap {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user