Merge remote-tracking branch 'origin/dev' into main_laptop_1

This commit is contained in:
2026-09-16 11:28:12 +09:00
6 changed files with 109 additions and 21 deletions
+20 -5
View File
@@ -132,8 +132,11 @@ export interface TableMeta {
formula?: Record<string, string>;
}
/** 덮개 판정 낱말 셋 — 서버가 줌 · 이것만 씀(2026-09-15 브레인). */
export type OverrideState = "edited" | "source_changed" | "orphan";
/**
* 덮개 판정 낱말 넷 — 서버가 줌 · 이것만 씀(2026-09-15~16 브레인).
* meaning_changed = 번호는 그대로인데 줄의 뜻이 바뀜(문화재목공 → 한식목공) — 같은 코드에 딴 물건.
*/
export type OverrideState = "edited" | "source_changed" | "meaning_changed" | "orphan";
export interface CellInfo {
editable: boolean;
@@ -143,8 +146,12 @@ export interface CellInfo {
original?: unknown;
/** 고친 칸의 서버 판정 — 안 고친 칸 null · 서버가 state 를 안 주면 「missing」(때우지 않음) */
state: OverrideState | "missing" | null;
/** source_changed 칸만 — 지금 원본 값 */
/** source_changed·meaning_changed 칸만 — 지금 원본 값 */
current?: unknown;
/** meaning_changed 칸만 — 고칠 때 줄 이름 */
was?: string;
/** meaning_changed 칸만 — 지금 줄 이름 */
now?: string;
rule: string | null;
/** 이 줄 값을 넣은 식 — 줄마다 다를 때만 옴 */
filled: string | null;
@@ -153,7 +160,11 @@ export interface CellInfo {
/** 칸 하나의 고침 상태 — 줄의 `@overrides`·`@formula` 는 해당할 때만 실려 옴. */
export function cellInfo(meta: TableMeta, row: Record<string, unknown>, key: string): CellInfo {
const overrides = row["@overrides"] as
Record<string, { original: unknown; current?: unknown; state?: OverrideState }> | undefined;
| Record<
string,
{ original: unknown; current?: unknown; state?: OverrideState; was?: string; now?: string }
>
| undefined;
const filled = row["@formula"] as Record<string, string> | undefined;
const override = overrides?.[key];
// 판정은 서버 state 만 — 화면이 값을 견줘 따로 정하면 판정이 두 벌이 됨(2026-09-15 브레인).
@@ -164,7 +175,11 @@ export function cellInfo(meta: TableMeta, row: Record<string, unknown>, key: str
overridden: override !== undefined,
...(override ? { original: override.original } : {}),
state,
...(state === "source_changed" ? { current: override?.current } : {}),
...(state === "source_changed" || state === "meaning_changed"
? { current: override?.current }
: {}),
...(override?.was !== undefined ? { was: override.was } : {}),
...(override?.now !== undefined ? { now: override.now } : {}),
rule: meta.formula?.[key] ?? null,
filled: filled?.[key] ?? null,
};
+36 -11
View File
@@ -7,7 +7,13 @@
* (2026-09-15 브레인 — 계산으로 나오는 칸은 못 고치고 왜 못 고치는지 옆에 보일 것).
* ========================================================================== */
import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements";
import {
createButton,
createInputField,
el,
showConfirmDialog,
showToast,
} from "@ui/ui_template_elements";
import { t as L } from "@ui/ui_template_locale";
import type { MasterRows, RowsQuery } from "./Z01_MasterData_Api_Fetch";
import {
@@ -58,6 +64,9 @@ function fill(key: Parameters<typeof L>[0], value: string): string {
function stateText(info: CellInfo, column: MasterColumn): string {
const original = valueWithUnit(info.original, column.unit);
switch (info.state) {
case "meaning_changed":
// 값이 틀린 것보다 나쁨 — 같은 번호에 딴 물건이 앉음. 옛 이름·지금 이름을 나란히.
return `${L("Z01_MasterData_State_MeaningChanged")}${fill("Z01_MasterData_State_Was", info.was ?? "?")} / ${fill("Z01_MasterData_State_Now", info.now ?? "?")} · ${fill("Z01_MasterData_Edit_OriginalWas", original)}`;
case "source_changed":
return `${fill("Z01_MasterData_State_SourceChanged", valueWithUnit(info.current, column.unit))} (${fill("Z01_MasterData_Edit_OriginalWas", original)})`;
case "orphan":
@@ -242,10 +251,11 @@ export function buildRowsView(): RowsView {
const td = el("td", { text: texts[i], attrs: { title: texts[i] } });
if (info.state) {
// 원본이 바뀐 칸은 줄째 또렷이 — 흐리면 옛 값이 조용히 계속 쓰임(2026-09-15 브레인).
td.classList.add("is-overridden", `is-${info.state.replace("_", "-")}`);
td.classList.add("is-overridden", `is-${info.state.replaceAll("_", "-")}`);
if (info.state !== "edited") {
td.textContent = `${texts[i]}`;
tr.classList.add("has-warning");
// 뜻 바뀜은 값 어긋남보다 나쁨 — 표시부터 더 세게(2026-09-16 브레인).
td.textContent = `${info.state === "meaning_changed" ? "⛔" : "⚠"} ${texts[i]}`;
tr.classList.add(info.state === "meaning_changed" ? "has-danger" : "has-warning");
}
td.title = stateText(info, column);
}
@@ -310,18 +320,21 @@ export function buildRowsView(): RowsView {
if (info.state !== "edited") stateLine.classList.add("is-warning");
lines.push(stateLine);
// 단추 글자에 무엇으로 바뀌는지 값을 넣음 — 안 보이고 누르면 금액이 말없이 바뀜(브레인).
// 뜻이 바뀐 줄에서 「새 원본 값으로 받기」는 위험 — 글자를 가르고 한 번 더 물음(브레인 ③).
const target =
info.state === "source_changed"
? fill("Z01_MasterData_Edit_TakeSource", valueWithUnit(info.current, column.unit))
: info.state === "orphan"
? null
: fill("Z01_MasterData_Edit_RevertTo", valueWithUnit(info.original, column.unit));
info.state === "meaning_changed"
? fill("Z01_MasterData_Edit_DropOnMeaning", valueWithUnit(info.current, column.unit))
: info.state === "source_changed"
? fill("Z01_MasterData_Edit_TakeSource", valueWithUnit(info.current, column.unit))
: info.state === "orphan"
? null
: fill("Z01_MasterData_Edit_RevertTo", valueWithUnit(info.original, column.unit));
if (target && source?.save) {
lines.push(
createButton({
label: target,
variant: "ghost",
onClick: () => void commit(index, column, null),
variant: info.state === "meaning_changed" ? "danger" : "ghost",
onClick: () => void drop(index, column, info),
}),
);
}
@@ -365,6 +378,18 @@ export function buildRowsView(): RowsView {
input.addEventListener("blur", () => finish(true));
}
/** 고침 버리기 — 뜻이 바뀐 줄은 한 번 더 묻고(딴 물건에 값이 붙은 자리라 되돌리기와 뜻이 다름). */
async function drop(index: number, column: MasterColumn, info: CellInfo): Promise<void> {
if (info.state === "meaning_changed") {
const ok = await showConfirmDialog(
`${fill("Z01_MasterData_State_Was", info.was ?? "?")} / ${fill("Z01_MasterData_State_Now", info.now ?? "?")}\n${L("Z01_MasterData_Edit_MeaningConfirm")}`,
L("Z01_MasterData_Edit_DropConfirm"),
);
if (!ok) return;
}
await commit(index, column, null);
}
/** 저장 — null 은 덮개에서 그 칸을 빼 원래 값으로 되돌림. */
async function commit(index: number, column: MasterColumn, value: unknown): Promise<void> {
const from = source;
+4 -3
View File
@@ -20,7 +20,7 @@ import {
saveBasePrice,
VALUES_TABLE_ID,
} from "./Z01_MasterData_Api_Fetch";
import { valueWithUnit } from "./Z01_MasterData_UI_Cells";
import { valueWithUnit, type OverrideState } from "./Z01_MasterData_UI_Cells";
import type { RowsView } from "./Z01_MasterData_UI_Rows";
export function buildSidePanel(groups: MasterGroup[], view: RowsView): HTMLElement {
@@ -118,11 +118,12 @@ export function buildSidePanel(groups: MasterGroup[], view: RowsView): HTMLEleme
return panel;
}
const STATE_LABEL = {
const STATE_LABEL: Record<OverrideState, Parameters<typeof L>[0]> = {
meaning_changed: "Z01_MasterData_List_MeaningChanged",
source_changed: "Z01_MasterData_List_SourceChanged",
orphan: "Z01_MasterData_List_Orphan",
edited: "Z01_MasterData_List_Edited",
} as const;
};
/**
* 고친 것 목록을 표 한 벌로 — 정렬(원본 바뀜 → 주인 없음 → 고쳐짐)과 쪽 나눔은 서버가 함.
+13 -1
View File
@@ -271,10 +271,22 @@
background: color-mix(in srgb, var(--color-danger) 8%, var(--color-canvas));
}
.z01-master__grid tr.has-warning td:first-child {
.z01-master__grid tr.has-warning td:first-child,
.z01-master__grid tr.has-danger td:first-child {
box-shadow: inset 4px 0 0 var(--color-danger);
}
/* 뜻 바뀜 — 값 어긋남보다 나쁨(같은 번호에 딴 물건). 줄째 더 진하게. */
.z01-master__grid tr.has-danger td {
background: color-mix(in srgb, var(--color-danger) 18%, var(--color-canvas));
}
.z01-master__grid tr td.is-meaning-changed {
background: var(--color-danger);
color: var(--color-primary-text);
font-weight: var(--font-weight-bold);
}
/* tr 을 넣어 줄 배경(tr.has-warning td)보다 무겁게 — 칸이 줄보다 더 진해야 함 */
.z01-master__grid tr td.is-source-changed,
.z01-master__grid tr td.is-orphan,