feat(M01): 인력 한 표 · 왼쪽 「전체」 + 구분 → 상세구분 두 단 거름 · 준용 모든 줄 고르기

- 인력 열 순서 = 키 … 옛이름 · 비고 · 준용 · 상태 읽기 전용
- 구분·상세구분 목록은 서버 /subs · 고른 거름은 sessionStorage 기억

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-20 15:18:26 +09:00
co-authored by Claude Sonnet 5
parent ef040a5749
commit 02b9c8be92
4 changed files with 105 additions and 10 deletions
+11 -8
View File
@@ -34,7 +34,7 @@ const SIZE = 50;
const MARKET = "재료_시중물가.json";
const LINKED = "재료_품셈재료.json";
const JOB = ["인력.json"];
const NOTE = "비고";
const NOTE = "비고"; // 끝 열(인력은 준용 앞) · 줄에 없으면 빈 칸 · 눌러 적음
const SUPPLY = ["값", "조달"];
const SOURCE = [...SUPPLY, "출처"];
const NARA = "나라장터:";
@@ -68,7 +68,7 @@ function hidden(file: string, col: string): boolean {
/** 키(한 번 주면 안 바뀜) · 빌더가 원문에서 채운 칸(구분 · 상세구분 · 옛이름) — 읽기 전용. */
const fixed = (file: string, col: string): boolean =>
col === "키" ||
(JOB.includes(file) && ["구분", "상세구분", "옛이름"].includes(col.split(SEP)[0]));
(JOB.includes(file) && ["구분", "상세구분", "상태", "옛이름"].includes(col.split(SEP)[0]));
const HEADS: Record<string, string[]> = {
[MARKET]: ["조달 값", "조달 기준", "M01_ProcureCol"],
@@ -115,14 +115,16 @@ export function renderRows(
for (const k of Object.keys(flatten(row)))
if (!hidden(file, k) && k !== NOTE && !cols.includes(k)) cols.push(k);
}
const heads = (HEADS[file] ?? []).map((h) =>
el("th", { text: h.startsWith("M01_") ? L(h as "M01_PriceCol") : h }),
);
const head = el("tr", {
children: [
el("th"),
...cols.map((c) => el("th", { text: c })),
...(HEADS[file] ?? []).map((h) =>
el("th", { text: h.startsWith("M01_") ? L(h as "M01_PriceCol") : h }),
),
el("th", { text: NOTE }), // 비고 — 항상 끝 열(줄에 없으면 빈 칸 · 눌러 적음)
...(JOB.includes(file) ? [] : heads),
el("th", { text: NOTE }),
...(JOB.includes(file) ? heads : []),
],
});
const body = el("tbody");
@@ -145,6 +147,7 @@ export function renderRows(
onEdit: (text) => setAdd(file, i, withNote(row, text)),
}),
);
if (JOB.includes(file)) tr.append(el("td"));
body.append(tr);
});
@@ -178,7 +181,6 @@ export function renderRows(
: (next: Row): void => editRow(file, d.version, key, row, next);
if (file === MARKET) tr.append(...marketCells(row, cur, put));
if (file === LINKED) tr.append(linkedCell(row, cur, put));
if (JOB.includes(file)) tr.append(wageCell(row, cur, put));
tr.append(
buildCell(show(cur[NOTE]), {
changed: !same(cur[NOTE], row[NOTE]),
@@ -188,6 +190,7 @@ export function renderRows(
: (text) => editRow(file, d.version, key, row, withNote(cur, text)),
}),
);
if (JOB.includes(file)) tr.append(wageCell(row, cur, put));
body.append(tr);
}
@@ -328,7 +331,7 @@ function linkedCell(row: Row, cur: Row, put?: Put): HTMLTableCellElement {
function wageCell(row: Row, cur: Row, put?: Put): HTMLTableCellElement {
const text = show(cur["준용"]);
const td = buildCell(named(text), { changed: text !== show(row["준용"]) });
if (put && row["값"] === null) {
if (put) {
const btn = rowBtn(L("M01_JobFind"));
btn.addEventListener("click", () =>
openPickModal({