From dd6bd0620a05e34ed210b80bd356cbb1aae4de11 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 16 Sep 2026 08:46:57 +0900 Subject: [PATCH] =?UTF-8?q?feat(z01):=20=ED=91=9C=20=EB=A8=B8=EB=A6=AC=20?= =?UTF-8?q?=EC=84=B8=20=EC=A4=84=EC=9D=84=20=EB=AA=A8=EB=91=90=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EA=B8=80=EC=9E=90=EB=A1=9C=20=E2=80=94=20=EA=B3=A0?= =?UTF-8?q?=EC=B9=9C=20=EA=B0=92=EC=9D=B4=20=EB=8B=BF=EB=8A=94=20=EC=9E=90?= =?UTF-8?q?=EB=A6=AC(scope=5Fnotice=20=C2=B7=20=EB=B3=B4=EB=9D=BC=20?= =?UTF-8?q?=EA=B5=B5=EA=B2=8C)=20=C2=B7=20=EB=92=A4=EC=B2=98=EC=A7=84=20?= =?UTF-8?q?=EC=9B=90=EB=B3=B8=EB=A7=88=EB=8B=A4=20=ED=95=9C=20=EC=A4=84(?= =?UTF-8?q?=EC=B6=9C=EC=B2=98=EA=B0=80=20=EB=AA=A9=EB=A1=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=98=B4=20=C2=B7=20outdated=20=EC=B0=B8=EB=A7=8C?= =?UTF-8?q?=20=C2=B7=20null=20=EC=9D=80=20=EB=AA=A8=EB=A6=84=EC=9D=B4?= =?UTF-8?q?=EB=9D=BC=20=EC=95=88=20=EC=84=B8=EC=9B=80)=20=C2=B7=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=EC=9D=80=20=EC=84=9C=EB=B2=84=20notice(?= =?UTF-8?q?=EC=9A=94=EC=9C=A8=20=EB=AC=B8=EA=B5=AC=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EC=82=AC=EC=A0=84=20=EB=BA=8C=20=E2=80=94=20=EB=91=90=20?= =?UTF-8?q?=EB=B2=8C=20=EA=B8=88=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- Z01_MasterData/Z01_MasterData_UI_Cells.ts | 25 ++++++++--- Z01_MasterData/Z01_MasterData_UI_Rows.ts | 22 ++++++---- Z01_MasterData/Z01_MasterData_UI_Side.ts | 2 - Z01_MasterData/Z01_MasterData_UI_Style.css | 17 +++++++- resources/tester/test_z01_master_cells.py | 48 ++++++++++++++-------- ui_template/ui_template_locale_b3.ts | 4 -- 6 files changed, 81 insertions(+), 37 deletions(-) diff --git a/Z01_MasterData/Z01_MasterData_UI_Cells.ts b/Z01_MasterData/Z01_MasterData_UI_Cells.ts index e7a1edf3..77a7d5db 100644 --- a/Z01_MasterData/Z01_MasterData_UI_Cells.ts +++ b/Z01_MasterData/Z01_MasterData_UI_Cells.ts @@ -58,28 +58,41 @@ export function valueWithUnit(value: unknown, unit?: string | null): string { /** 자료 출처표 한 줄 — 어디서 받고 최신이 무엇인지(데스크탑 서브 출처표 · 메인이 표 수준에 실음). */ export interface TableSource { + source_id?: string; + /** 원본 이름 — 한 표가 원본 여럿을 모아 어느 것이 뒤처졌는지 가려야 함 */ + name?: string; publisher: string; where: string; cycle?: string; latest_published?: string; our_edition?: string; checked_at?: string; - /** 뒤처짐 판정 — 서버만 함 */ - outdated?: boolean; + /** 뒤처짐 판정 — 서버만 함 · null 은 「모름」이라 띠를 안 세움 */ + outdated?: boolean | null; } /** * 뒤처진 자료 띠 글자 — 서버가 outdated 라 할 때만. 화면은 날짜를 견주지 않음(판정 두 벌 금지). * 받는 자리를 꼭 적음 — 최신이 무엇인지·어디서 받는지가 없어 건설 노임이 반 년 뒤처졌음. */ -export function outdatedBanner(source?: TableSource): string | null { - if (source?.outdated !== true) return null; - return `이 자료는 ${source.our_edition ?? "판 모름"} 판 · 최신은 ${source.latest_published ?? "?"} 공표 · ${source.publisher} ${source.where}`; +export function outdatedBanner(sources?: TableSource[]): string[] { + return (sources ?? []) + .filter((source) => source.outdated === true) + .map((source) => { + const head = source.name ? `${source.name} — ` : ""; + const where = source.where ? ` ${source.where}` : ""; + return `${head}이 자료는 ${source.our_edition ?? "판 모름"} 판 · 최신은 ${source.latest_published ?? "?"} 공표 · ${source.publisher}${where}`; + }); } /** 기초단가 표 머리 — 열 하나에 한 문장이라 표에 한 번만 옴(2026-09-15 브레인 ④). */ export interface TableMeta { - source?: TableSource; + /** 한 표가 모은 원본마다 한 줄 */ + source?: TableSource[]; + /** 서버가 주는 알림 한 줄(요율 — 법이 정한 값) — 화면이 사전을 가지면 두 벌이 됨 */ + notice?: string; + /** 고친 값이 어디에 닿는지 — 새로 만드는 프로젝트부터 쓰임(서버가 줌) */ + scope_notice?: string; editable?: string[]; /** 열key → 왜 못 고치나 */ locked?: Record; diff --git a/Z01_MasterData/Z01_MasterData_UI_Rows.ts b/Z01_MasterData/Z01_MasterData_UI_Rows.ts index 737bdd81..0763909c 100644 --- a/Z01_MasterData/Z01_MasterData_UI_Rows.ts +++ b/Z01_MasterData/Z01_MasterData_UI_Rows.ts @@ -33,8 +33,6 @@ type Row = Record; export interface RowsSource { title: string; key: string; - /** 표 위 알림 한 줄(요율 — 법이 정한 값) */ - notice?: string; /** false 면 검색 칸을 숨김 — 서버에 검색이 없는 표(고친 것 모아 보기) */ searchable?: boolean; load: (query: RowsQuery) => Promise; @@ -100,7 +98,8 @@ export function buildRowsView(): RowsView { children: [hiddenToggle, L("Z01_MasterData_ShowHidden")], }); const notice = el("p", { className: "z01-master__notice" }); - const banner = el("p", { className: "z01-master__outdated" }); + const scope = el("p", { className: "z01-master__scope" }); + const banner = el("div", { className: "z01-master__outdated" }); const detail = el("div", { className: "z01-master__detail" }); const grid = el("div", { className: "z01-master__grid-wrap" }); const pageInput = el("input", { @@ -128,6 +127,7 @@ export function buildRowsView(): RowsView { className: "z01-master__panel", children: [ el("div", { children: [title, titleKey] }), + scope, banner, notice, toolbar, @@ -141,6 +141,7 @@ export function buildRowsView(): RowsView { detail.hidden = true; notice.hidden = true; banner.hidden = true; + scope.hidden = true; function goTo(page: number): void { const pages = pageCount(last?.total ?? 0, PAGE_SIZE); @@ -190,10 +191,15 @@ export function buildRowsView(): RowsView { function draw(): void { if (!last) return; const meta = last; - // 뒤처진 자료 띠 — 서버 outdated 만 봄(날짜 비교 안 함). + // 표 머리 세 줄 — 모두 서버가 주는 글자 그대로(화면이 사전을 가지면 두 벌이 됨). + // ① 고친 값이 닿는 자리 ② 뒤처진 원본(서버 outdated 만 · 날짜 비교 안 함) ③ 그 밖 알림 + scope.textContent = meta.scope_notice ?? ""; + scope.hidden = !meta.scope_notice; const outdated = outdatedBanner(meta.source); - banner.textContent = outdated ? `⚠ ${outdated}` : ""; - banner.hidden = !outdated; + banner.replaceChildren(...outdated.map((text) => el("p", { text: `⚠ ${text}` }))); + banner.hidden = outdated.length === 0; + notice.textContent = meta.notice ?? ""; + notice.hidden = !meta.notice; const columns = shownColumns(meta.columns, showHidden); const headRow = el("tr", { children: columns.map((column) => headCell(meta, column)) }); const body = el("tbody"); @@ -358,9 +364,9 @@ export function buildRowsView(): RowsView { state.page = 1; title.textContent = picked.title; titleKey.textContent = picked.key; - notice.textContent = picked.notice ?? ""; - notice.hidden = !picked.notice; + notice.hidden = true; banner.hidden = true; + scope.hidden = true; search.root.hidden = picked.searchable === false; toolbar.hidden = false; pager.hidden = false; diff --git a/Z01_MasterData/Z01_MasterData_UI_Side.ts b/Z01_MasterData/Z01_MasterData_UI_Side.ts index 7d1bb97a..56111cb7 100644 --- a/Z01_MasterData/Z01_MasterData_UI_Side.ts +++ b/Z01_MasterData/Z01_MasterData_UI_Side.ts @@ -44,8 +44,6 @@ export function buildSidePanel(groups: MasterGroup[], view: RowsView): HTMLEleme view.show({ title: `${baseTitle} › ${label}`, key: `base-prices/${kind}`, - // 요율은 구간 줄에 코드가 없어 구간 이름으로 이음 — 법이 바뀌면 고친 값이 주인을 잃기 쉬움. - notice: kind === "rate" ? L("Z01_MasterData_Rate_Notice") : undefined, load: (query) => fetchBasePrices(kind, query), save: (rowId, values) => saveBasePrice(kind, rowId, values), }); diff --git a/Z01_MasterData/Z01_MasterData_UI_Style.css b/Z01_MasterData/Z01_MasterData_UI_Style.css index d9b8be4e..c4894ce4 100644 --- a/Z01_MasterData/Z01_MasterData_UI_Style.css +++ b/Z01_MasterData/Z01_MasterData_UI_Style.css @@ -125,7 +125,22 @@ font-size: var(--text-body); } -/* 뒤처진 자료 띠 — 받는 자리까지 적힘 · 또렷이 */ +/* 고친 값이 닿는 자리 — 흐리면 「저장이 안 됐다」로 읽힘(2026-09-16 브레인) */ +.z01-master__scope { + margin: 0; + padding: var(--spacing-8) var(--spacing-12); + border-left: 4px solid var(--color-accent); + background: var(--color-mist-violet); + color: var(--color-accent); + font-size: var(--text-body-sm); + font-weight: var(--font-weight-bold); +} + +/* 뒤처진 자료 띠 — 받는 자리까지 적힘 · 또렷이 · 원본마다 한 줄 */ +.z01-master__outdated p { + margin: 0; +} + .z01-master__outdated { margin: 0; padding: var(--spacing-8) var(--spacing-12); diff --git a/resources/tester/test_z01_master_cells.py b/resources/tester/test_z01_master_cells.py index 3dbf2b1d..823fc746 100644 --- a/resources/tester/test_z01_master_cells.py +++ b/resources/tester/test_z01_master_cells.py @@ -29,18 +29,32 @@ import { } from "./Z01_MasterData_UI_Cells.js"; // 뒤처짐 띠 — 판정은 서버 outdated 만(화면이 날짜를 안 견줌) · 받는 자리를 띠에 적음(2026-09-15 브레인) -const laborSource = { - publisher: "대한건설협회", - where: "cak.or.kr [지원·사업]>[건설적산기준]>[건설임금]", - cycle: "반년", - latest_published: "2026-09-01", - our_edition: "2026-01-01", - checked_at: "2026-09-15", - outdated: true, -}; +// ⚠ 한 표가 원본 여럿을 모은 것이라 출처는 목록으로 옴(노임 = 건설업 + 제조업). +const laborSources = [ + { + source_id: "labor_const", + name: "건설업 시중노임단가", + publisher: "대한건설협회", + where: "cak.or.kr [지원·사업] > [건설적산기준] > [건설임금]", + cycle: "해마다 두 번", + latest_published: "2026-09-01", + our_edition: "2026-01-01", + checked_at: "2026-09-15", + outdated: true, + }, + { + source_id: "labor_mfg", + name: "중소제조업 직종별 임금", + publisher: "중소기업중앙회", + where: "", + latest_published: null, + our_edition: "2026-07-01", + outdated: null, + }, +]; const banners = [ - outdatedBanner(laborSource), - outdatedBanner({ ...laborSource, outdated: false }), + outdatedBanner(laborSources), + outdatedBanner([{ ...laborSources[0], outdated: false }]), outdatedBanner(undefined), ]; @@ -225,10 +239,12 @@ def test_칸_글자와_숨김_열(tmp_path: Path) -> None: assert [row[1] for row in got["editions"]] == ["2026-01-01", "판 모름", "판 모름"] # 단추 글자·칸 설명의 값 — 쉼표 + 단위(이름표) · 단위 없으면 수만 assert got["withUnit"] == ["17 원", "41,500,000 원", "0.17", "12,345.678", "", "5억 미만"] - # 날짜가 뒤처져 보여도 서버가 outdated=false 면 띠 없음 · 출처가 안 오면 띠 없음 + # 뒤처진 원본만 한 줄씩 · 판정을 모르는 것(outdated null)은 안 세움 · 없으면 빈 목록 assert got["banners"] == [ - "이 자료는 2026-01-01 판 · 최신은 2026-09-01 공표 · " - "대한건설협회 cak.or.kr [지원·사업]>[건설적산기준]>[건설임금]", - None, - None, + [ + "건설업 시중노임단가 — 이 자료는 2026-01-01 판 · 최신은 2026-09-01 공표 · " + "대한건설협회 cak.or.kr [지원·사업] > [건설적산기준] > [건설임금]" + ], + [], + [], ] diff --git a/ui_template/ui_template_locale_b3.ts b/ui_template/ui_template_locale_b3.ts index 02460588..5b7bfdc9 100644 --- a/ui_template/ui_template_locale_b3.ts +++ b/ui_template/ui_template_locale_b3.ts @@ -148,10 +148,6 @@ export const ui_locales_b3 = { Z01_MasterData_Base_oil: ["유가", "Fuel"], Z01_MasterData_Base_rate: ["요율", "Rates"], Z01_MasterData_Overrides: ["고친 것 모아 보기", "Edited values"], - Z01_MasterData_Rate_Notice: [ - "법이 정한 값 — 고치면 갱신 때 주인을 잃기 쉬움", - "Values set by law — edits may lose their row when the law is updated", - ], Z01_MasterData_Edit_Can: [ "고칠 수 있는 칸 — 눌러 고치고 Enter 로 저장 (Esc 취소)", "Editable — click, type, Enter to save (Esc cancels)",