feat(z01): 표 머리 알림을 서버 목록 그대로 — 줄마다 한 줄(첫 줄 「새로 만드는 프로젝트부터」 · 요율은 법정값 경고까지) · 글자 속 **굵게** 를 굵은 조각으로 폄(별표 안 보임) · 보라 굵은 칸으로 또렷이
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -85,14 +85,29 @@ export function outdatedBanner(sources?: TableSource[]): string[] {
|
||||
});
|
||||
}
|
||||
|
||||
/** 알림은 한 줄로도 목록으로도 옴 — 받은 대로 줄 목록으로. */
|
||||
export function noticeLines(notice?: string | string[]): string[] {
|
||||
if (!notice) return [];
|
||||
return Array.isArray(notice) ? notice.filter(Boolean) : [notice];
|
||||
}
|
||||
|
||||
/** 서버 글자의 `**굵게**` 를 조각으로 — 별표가 화면에 글자로 남으면 안 됨. */
|
||||
export function boldSegments(text: string): { text: string; bold: boolean }[] {
|
||||
return text
|
||||
.split(/\*\*(.+?)\*\*/g)
|
||||
.map((part, index) => ({ text: part, bold: index % 2 === 1 }))
|
||||
.filter((part) => part.text !== "");
|
||||
}
|
||||
|
||||
/** 기초단가 표 머리 — 열 하나에 한 문장이라 표에 한 번만 옴(2026-09-15 브레인 ④). */
|
||||
export interface TableMeta {
|
||||
/** 한 표가 모은 원본마다 한 줄 */
|
||||
source?: TableSource[];
|
||||
/** 서버가 주는 알림 한 줄(요율 — 법이 정한 값) — 화면이 사전을 가지면 두 벌이 됨 */
|
||||
notice?: string;
|
||||
/** 고친 값이 어디에 닿는지 — 새로 만드는 프로젝트부터 쓰임(서버가 줌) */
|
||||
scope_notice?: string;
|
||||
/**
|
||||
* 표 머리 알림 — 서버가 한 벌로 줌(첫 줄 「새로 만드는 프로젝트부터」 · 요율은 법정값 경고까지).
|
||||
* 화면이 사전을 가지면 두 벌이 됨.
|
||||
*/
|
||||
notice?: string | string[];
|
||||
editable?: string[];
|
||||
/** 열key → 왜 못 고치나 */
|
||||
locked?: Record<string, string>;
|
||||
|
||||
@@ -11,10 +11,12 @@ import { createButton, createInputField, el, showToast } from "@ui/ui_template_e
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import type { MasterRows, RowsQuery } from "./Z01_MasterData_Api_Fetch";
|
||||
import {
|
||||
boldSegments,
|
||||
cellInfo,
|
||||
clampPage,
|
||||
columnTitle,
|
||||
latestOnly,
|
||||
noticeLines,
|
||||
outdatedBanner,
|
||||
pageCount,
|
||||
parseCellInput,
|
||||
@@ -97,8 +99,7 @@ export function buildRowsView(): RowsView {
|
||||
className: "z01-master__check",
|
||||
children: [hiddenToggle, L("Z01_MasterData_ShowHidden")],
|
||||
});
|
||||
const notice = el("p", { className: "z01-master__notice" });
|
||||
const scope = el("p", { className: "z01-master__scope" });
|
||||
const notice = el("div", { className: "z01-master__notice" });
|
||||
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" });
|
||||
@@ -127,9 +128,8 @@ export function buildRowsView(): RowsView {
|
||||
className: "z01-master__panel",
|
||||
children: [
|
||||
el("div", { children: [title, titleKey] }),
|
||||
scope,
|
||||
banner,
|
||||
notice,
|
||||
banner,
|
||||
toolbar,
|
||||
detail,
|
||||
grid,
|
||||
@@ -141,7 +141,6 @@ 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);
|
||||
@@ -191,15 +190,22 @@ export function buildRowsView(): RowsView {
|
||||
function draw(): void {
|
||||
if (!last) return;
|
||||
const meta = last;
|
||||
// 표 머리 세 줄 — 모두 서버가 주는 글자 그대로(화면이 사전을 가지면 두 벌이 됨).
|
||||
// ① 고친 값이 닿는 자리 ② 뒤처진 원본(서버 outdated 만 · 날짜 비교 안 함) ③ 그 밖 알림
|
||||
scope.textContent = meta.scope_notice ?? "";
|
||||
scope.hidden = !meta.scope_notice;
|
||||
// 표 머리 — 모두 서버 글자 그대로(화면이 사전을 가지면 두 벌이 됨).
|
||||
// ① 알림(첫 줄 「새로 만드는 프로젝트부터」 · 요율은 법정값 경고까지) ② 뒤처진 원본(서버 outdated 만)
|
||||
const notices = noticeLines(meta.notice);
|
||||
notice.replaceChildren(
|
||||
...notices.map((text) =>
|
||||
el("p", {
|
||||
children: boldSegments(text).map((part) =>
|
||||
part.bold ? el("strong", { text: part.text }) : el("span", { text: part.text }),
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
notice.hidden = notices.length === 0;
|
||||
const outdated = outdatedBanner(meta.source);
|
||||
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");
|
||||
@@ -366,7 +372,6 @@ export function buildRowsView(): RowsView {
|
||||
titleKey.textContent = picked.key;
|
||||
notice.hidden = true;
|
||||
banner.hidden = true;
|
||||
scope.hidden = true;
|
||||
search.root.hidden = picked.searchable === false;
|
||||
toolbar.hidden = false;
|
||||
pager.hidden = false;
|
||||
|
||||
@@ -125,17 +125,6 @@
|
||||
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;
|
||||
@@ -151,16 +140,23 @@
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
/* 요율 알림 — 막지 않고 알리기만 */
|
||||
/* 표 머리 알림 — 고친 값이 닿는 자리·법정값 경고. 흐리면 「저장이 안 됐다」로 읽힘(2026-09-16 브레인) */
|
||||
.z01-master__notice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
margin: 0;
|
||||
padding: var(--spacing-8) var(--spacing-12);
|
||||
border-left: 4px solid var(--color-warning);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-text-body);
|
||||
border-left: 4px solid var(--color-accent);
|
||||
background: var(--color-mist-violet);
|
||||
color: var(--color-accent);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.z01-master__notice p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.z01-master__toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -25,9 +25,20 @@ _RUNNER = """
|
||||
import { writeFileSync } from "node:fs";
|
||||
import {
|
||||
cellInfo, cellText, clampPage, columnTitle, latestOnly, pageCount, parseCellInput, rowCells,
|
||||
outdatedBanner, shownColumns, valueWithUnit,
|
||||
boldSegments, noticeLines, outdatedBanner, shownColumns, valueWithUnit,
|
||||
} from "./Z01_MasterData_UI_Cells.js";
|
||||
|
||||
// 표 수준 알림 — 서버가 한 벌로 목록을 줌(첫 줄 「새로 만드는 프로젝트부터」) · 글자에 **굵게** 가 섞임
|
||||
const notices = [
|
||||
noticeLines(["여기서 고친 값은 **새로 만드는 프로젝트부터** 쓰임", "법이 정한 값 — 고치면…"]),
|
||||
noticeLines("한 줄짜리"),
|
||||
noticeLines(undefined),
|
||||
];
|
||||
const bolds = [
|
||||
boldSegments("여기서 고친 값은 **새로 만드는 프로젝트부터** 쓰임"),
|
||||
boldSegments("굵게 없음"),
|
||||
];
|
||||
|
||||
// 뒤처짐 띠 — 판정은 서버 outdated 만(화면이 날짜를 안 견줌) · 받는 자리를 띠에 적음(2026-09-15 브레인)
|
||||
// ⚠ 한 표가 원본 여럿을 모은 것이라 출처는 목록으로 옴(노임 = 건설업 + 제조업).
|
||||
const laborSources = [
|
||||
@@ -155,6 +166,8 @@ writeFileSync(process.argv[2], JSON.stringify({
|
||||
valueWithUnit("5억 미만", ""),
|
||||
],
|
||||
banners,
|
||||
notices,
|
||||
bolds,
|
||||
}));
|
||||
"""
|
||||
|
||||
@@ -248,3 +261,18 @@ def test_칸_글자와_숨김_열(tmp_path: Path) -> None:
|
||||
[],
|
||||
[],
|
||||
]
|
||||
# 알림은 목록도 한 줄도 받음 · 없으면 빈 목록
|
||||
assert got["notices"] == [
|
||||
["여기서 고친 값은 **새로 만드는 프로젝트부터** 쓰임", "법이 정한 값 — 고치면…"],
|
||||
["한 줄짜리"],
|
||||
[],
|
||||
]
|
||||
# **…** 는 굵게 조각으로 갈라 보임 — 별표가 글자로 남으면 안 됨
|
||||
assert got["bolds"] == [
|
||||
[
|
||||
{"text": "여기서 고친 값은 ", "bold": False},
|
||||
{"text": "새로 만드는 프로젝트부터", "bold": True},
|
||||
{"text": " 쓰임", "bold": False},
|
||||
],
|
||||
[{"text": "굵게 없음", "bold": False}],
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user