refactor(b09): 옛 탭 옮기기 ① 설계서 구성 — 새 틀 탭 파일로 · 옛 표 도우미(head·note·표·근거 호버)를 UI_Table 로
- 화면 확인: 설계서 구성 13줄 · 요약·법 문구 그대로 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -195,6 +195,11 @@ export function injectSheetStyles(): void {
|
||||
.b09s-title { font-weight:700; font-size:14px; }
|
||||
.b09s-formula { white-space:pre-wrap; font-size:12px; color:var(--ui-text, #1f2430); }
|
||||
.b09s-legacy .b09-tabs { display:none; }
|
||||
.b09s-head { font-weight:600; margin-top:6px; }
|
||||
.b09s-info td { text-align:right; font-variant-numeric:tabular-nums; }
|
||||
.b09s-info td.b09s-left, .b09s-info th.b09s-left { text-align:left; white-space:normal; }
|
||||
.b09s-group { display:flex; flex-direction:column; gap:4px; border-top:1px solid var(--ui-border, #d0d4dc); padding-top:6px; }
|
||||
.b09s-inline { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
|
||||
.b09s-table td.b09s-adopted { font-weight:700; color:var(--ui-accent, #2f6fed); background:rgba(47,111,237,0.08); }
|
||||
.b09s-min { display:inline-block; margin-left:4px; font-size:10px; color:#1e8e3e; border:1px solid #1e8e3e; border-radius:8px; padding:0 4px; }
|
||||
`;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { machineTab } from "./B09_Estimation_UI_Tab_Machine";
|
||||
import { priceCompareTab } from "./B09_Estimation_UI_Tab_PriceCompare";
|
||||
import { summaryTab } from "./B09_Estimation_UI_Tab_Summary";
|
||||
import { listsTab } from "./B09_Estimation_UI_Tab_Lists";
|
||||
import { designDocTab } from "./B09_Estimation_UI_Tab_DesignDoc";
|
||||
|
||||
/** 탭 등록 — 한 줄에 탭 하나. 옛 탭(`legacyTab`)은 새 탭 파일이 서면 그 줄만 바꿈. */
|
||||
const TABS: B09Tab[] = [
|
||||
@@ -37,7 +38,7 @@ const TABS: B09Tab[] = [
|
||||
listsTab,
|
||||
legacyTab("supply", "B09_Estimation_Tab_Supply"),
|
||||
legacyTab("base_data", "B09_Estimation_Tab_BaseData"),
|
||||
legacyTab("design_doc", "B09_Estimation_Tab_DesignDoc"),
|
||||
designDocTab,
|
||||
legacyTab("basis_sheet", "B09_Estimation_Tab_BasisSheet"),
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Tab_DesignDoc.ts
|
||||
* B09 설계서 구성 탭 — 법이 정한 목차와 우리가 내는 것을 맞대 봄(별표2 (5)(가))
|
||||
*
|
||||
* - 옛 `B09_Estimation_UI_BaseData.ts` 의 설계서 구성표를 그대로 옮김(PLAN 12장 옛 탭 옮기기).
|
||||
* - ⚠ 「없음」과 「우리 몫 아님」을 갈라 보임 — 갈라야 다음에 할 일이 달라짐.
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import type { B09Tab } from "./B09_Estimation_UI_Shell_Types";
|
||||
import { L, hint } from "./B09_Estimation_UI_Sheet";
|
||||
import { head, infoTable, note } from "./B09_Estimation_UI_Table";
|
||||
|
||||
interface DesignDocDto {
|
||||
law: string;
|
||||
summary: string;
|
||||
items: Array<{
|
||||
order: number;
|
||||
name: string;
|
||||
status: string;
|
||||
owner: string;
|
||||
where: string;
|
||||
note: string;
|
||||
}>;
|
||||
notes: string[];
|
||||
}
|
||||
|
||||
let cached: DesignDocDto | null = null;
|
||||
|
||||
function draw(body: HTMLElement, data: DesignDocDto): void {
|
||||
body.append(head(`설계서 구성 (법이 정한 ${data.items.length})`));
|
||||
body.append(note(data.summary));
|
||||
body.append(note(data.law));
|
||||
body.append(
|
||||
infoTable(
|
||||
["차례", "이 름", "상 태", "누가 만드나", "어디서 나오나", "비 고"],
|
||||
data.items.map((row) => [
|
||||
String(row.order),
|
||||
row.name,
|
||||
row.status,
|
||||
row.owner,
|
||||
row.where || "—",
|
||||
row.note,
|
||||
]),
|
||||
[0, 1, 2, 3, 4, 5],
|
||||
),
|
||||
);
|
||||
for (const line of data.notes) body.append(note(line));
|
||||
}
|
||||
|
||||
export const designDocTab: B09Tab = {
|
||||
key: "design_doc",
|
||||
label: () => L("B09_Estimation_Tab_DesignDoc"),
|
||||
render(ctx) {
|
||||
if (!ctx.projectId) {
|
||||
ctx.body.append(hint(L("B09_Sheet_NoProject")));
|
||||
return;
|
||||
}
|
||||
// 프로젝트 값이 아니라 **우리가 무엇을 내는가**의 표 — 한 번 받으면 그대로 씀.
|
||||
if (cached) {
|
||||
draw(ctx.body, cached);
|
||||
return;
|
||||
}
|
||||
ctx.body.append(hint(L("B09_Sheet_Loading")));
|
||||
fetch(
|
||||
`${API_BASE_URL}/projects/${encodeURIComponent(ctx.projectId)}/estimation/design-doc-index`,
|
||||
{ credentials: "include" },
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error(String(response.status));
|
||||
return response.json() as Promise<DesignDocDto>;
|
||||
})
|
||||
.then((data) => {
|
||||
cached = data;
|
||||
ctx.body.replaceChildren();
|
||||
draw(ctx.body, data);
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
ctx.body.replaceChildren(hint(`${L("B09_Sheet_LoadFailed")} ${error.message}`, true));
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Table.ts
|
||||
* 옛 B09 화면에서 옮겨 온 **설명 표** 공용 — 머리글 한 줄 · 제목 · 안내 글 · 근거 호버 (PLAN 12장)
|
||||
*
|
||||
* - 옛 `B09_Estimation_UI_BaseData.ts` 의 `head`·`note`·`table` 을 그대로 옮김(옛 파일을 지우려고).
|
||||
* - 칸마다 근거 호버(`keys`·`sheet`) — 사전은 **개발환경에서만** 오고, 없으면 아무 일도 안 함.
|
||||
* - 숫자 칸은 오른쪽, `leftCols` 칸은 왼쪽(옛 `.b09-left` 와 같음).
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
attachProvenance,
|
||||
markProvenanceCell,
|
||||
type ProvenanceSheet,
|
||||
} from "@ui/ui_template_provenance";
|
||||
import { el } from "./B09_Estimation_UI_Sheet";
|
||||
|
||||
/** 금액 글 → 천 단위 쉼표(옛 `money` 그대로 — 소수부를 자르지 않음). */
|
||||
export function money(value: string | null | undefined): string {
|
||||
if (value === null || value === undefined || value === "") return "";
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed.toLocaleString("ko-KR") : value;
|
||||
}
|
||||
|
||||
/** 구역 제목. */
|
||||
export function head(text: string): HTMLElement {
|
||||
return el("div", "b09s-hint b09s-head", text);
|
||||
}
|
||||
|
||||
/** 안내 한 줄. */
|
||||
export function note(text: string): HTMLElement {
|
||||
return el("div", "b09s-hint", text);
|
||||
}
|
||||
|
||||
/** 설명 표 한 장 — 가로로 넘치면 제 칸 안에서만 밀림. */
|
||||
export function infoTable(
|
||||
headers: string[],
|
||||
rows: string[][],
|
||||
leftCols: number[],
|
||||
keys?: string[],
|
||||
sheet?: ProvenanceSheet,
|
||||
): HTMLElement {
|
||||
const wrap = el("div", "b09s-wrap");
|
||||
const table = el("table", "b09s-table b09s-info");
|
||||
const thead = el("thead");
|
||||
const headRow = el("tr");
|
||||
headers.forEach((text, index) => {
|
||||
headRow.append(el("th", leftCols.includes(index) ? "b09s-left" : "", text));
|
||||
});
|
||||
thead.append(headRow);
|
||||
const tbody = el("tbody");
|
||||
for (const cells of rows) {
|
||||
const tr = el("tr");
|
||||
cells.forEach((text, index) => {
|
||||
const td = el("td", leftCols.includes(index) ? "b09s-left" : "", text);
|
||||
const key = keys?.[index];
|
||||
const column = key ? sheet?.columns[key] : undefined;
|
||||
if (key && column) markProvenanceCell(td, key, column.tier);
|
||||
tr.append(td);
|
||||
});
|
||||
tbody.append(tr);
|
||||
}
|
||||
table.append(thead, tbody);
|
||||
attachProvenance(table, sheet);
|
||||
wrap.append(table);
|
||||
return wrap;
|
||||
}
|
||||
Reference in New Issue
Block a user