fix(공용): 근거 호버 배지를 칸 등급으로 그리고 빠진 등급은 열에서 채움

같은 열이라도 줄마다 성격이 갈리는 자리가 있음 — 원가계산서 「금액」은 중간줄
(간접노무비 따위)이 계산인데 마지막줄(총원가·도급금액·총계)은 최종임(데스크탑 보조
B09 배선에서 나옴). `markProvenanceCell` 은 이미 칸마다 등급을 받고 있었으나
카드 배지가 열 등급으로만 그려져 **띠는 최종인데 배지는 「계산」**으로 어긋났음.

- `fill()` 이 칸을 받아 배지를 `cell.dataset.provTier || column.tier` 로 그림
- 등급을 안 심은 칸은 `attachProvenance` 가 열 등급으로 채움 — 안 그러면 배지는
  뜨는데 띠가 안 붙는 「색 없는 칸」이 생김. 부르는 쪽이 등급을 빼먹기 쉬운 자리라
  여기서 맞춤. 줄마다 다른 칸에만 세 번째 인자를 주면 됨.

자체검증(ORCA 5173) — 같은 열 `amount_krw` 에서 등급 안 심은 칸은 배지 「계산」·띠
초록 rgb(31,138,76), `final` 심은 칸은 배지 「최종」·띠 금색 rgb(184,134,11) 3px inset.
배지와 띠가 같은 말을 함. `tsc --noEmit` 통과 · 병합 뒤 `pytest -q` 1313 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RANEBHns1S4tkmsYwewtk
This commit is contained in:
2026-09-12 16:00:19 +09:00
co-authored by Claude Opus 5
parent de3f95b532
commit c94e77734e
+29 -4
View File
@@ -52,7 +52,14 @@ const TINT_KEY = "aislo.provenance.tint";
const STYLE_ID = "ui-provenance-style";
const CARD_ID = "ui-provenance-card";
/** 칸에 심는 표시 — 열 키와 등급. 표를 그리는 쪽이 칸마다 한 번 부른다. */
/**
* 칸에 심는 표시 — 열 키와 등급. 표를 그리는 쪽이 칸마다 한 번 부른다.
*
* `tier` 를 주면 **그 칸만 열 등급을 이긴다.** 같은 열이라도 줄마다 성격이 갈리는
* 자리가 있기 때문이다 — 원가계산서 「금액」은 중간줄(간접노무비 따위)이 `calc` 인데
* 마지막줄(총원가·도급금액·총계)은 `final` 이다(2026-09-12 데스크탑 보조 B09 배선).
* 칸 등급을 심지 않으면 열 등급이 그대로 선다.
*/
export function markProvenanceCell(cell: HTMLElement, columnKey: string, tier?: string): void {
cell.dataset.provCol = columnKey;
if (tier) cell.dataset.provTier = tier;
@@ -137,7 +144,15 @@ function place(element: HTMLElement, x: number, y: number): void {
}
/** 카드 한 장을 채운다. 값은 **화면에 적힌 글자 그대로** 보인다 — 자리수까지 같은 것이 요점. */
function fill(target: HTMLElement, column: ProvenanceColumn, value: string, extra: string[]): void {
/** 카드 한 장을 채운다. 배지는 **칸 등급**을 먼저 본다 — 띄는 띄었는데 배지가 다른 말을
* 하면 읽는 사람이 둘 중 어느 것을 믿을지 모른다. */
function fill(
target: HTMLElement,
cell: HTMLElement,
column: ProvenanceColumn,
value: string,
extra: string[],
): void {
target.replaceChildren();
const head = document.createElement("div");
head.className = "ui-prov-card__head";
@@ -145,8 +160,9 @@ function fill(target: HTMLElement, column: ProvenanceColumn, value: string, extr
title.textContent = column.label;
const badge = document.createElement("span");
badge.className = "ui-prov-card__badge";
badge.dataset.provTier = column.tier;
badge.textContent = TIER_LABELS[column.tier] ?? column.tier;
const tier = cell.dataset.provTier || column.tier;
badge.dataset.provTier = tier;
badge.textContent = TIER_LABELS[tier] ?? tier;
head.append(title, badge);
target.append(head);
@@ -171,6 +187,14 @@ export function attachProvenance(
): void {
if (!sheet) return;
injectProvenanceStyles();
// 등급을 안 심은 칸은 **여기서 열 등급으로 채운다.**
// 색칠은 CSS 가 `data-prov-tier` 를 보고 하므로, 그 칸은 배지만 띄고 띄는 안 붙어
// 「색이 안 붙는 칸」이 생긴다. 부르는 쪽이 등급을 빼먹는 것은 흔한 일이라 여기서 맞춘다.
for (const cell of root.querySelectorAll<HTMLElement>("[data-prov-col]")) {
if (cell.dataset.provTier) continue;
const tier = sheet.columns[cell.dataset.provCol ?? ""]?.tier;
if (tier) cell.dataset.provTier = tier;
}
const hide = (): void => {
const element = document.getElementById(CARD_ID);
if (element) element.style.display = "none";
@@ -183,6 +207,7 @@ export function attachProvenance(
const target = card();
fill(
target,
cell,
column,
cell.textContent?.trim() ?? "",
resolveExtra?.(cell, cell.dataset.provCol ?? "") ?? [],