Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
} from "@ui/ui_template_elements";
|
||||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||||
import { createDataTable, type DataTable } from "@ui/ui_template_data_table";
|
||||
import { attachHoverInfo } from "@ui/ui_template_hover_info";
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
@@ -227,16 +228,16 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
const box = cell.getBoundingClientRect();
|
||||
openMenuAt(box.left, box.bottom, items);
|
||||
});
|
||||
// 157-3 칸 풍선 — 공용 표는 그대로 · 올린 칸에 title
|
||||
// 157-3 칸 풍선 — 공용 표는 그대로 · 올린 칸에 공용 호버 정보(185-3)
|
||||
if (spec.tip)
|
||||
made.root.addEventListener("mouseover", (event) => {
|
||||
const cell = (event.target as Element).closest<HTMLTableCellElement>("tr[data-i] td");
|
||||
const row = cell?.parentElement;
|
||||
const column = cell ? spec.columns[cell.cellIndex] : undefined;
|
||||
if (!cell || !row || !column) return;
|
||||
const tip = spec.tip?.(Number(row.dataset.i), column.key);
|
||||
if (tip) cell.title = tip;
|
||||
});
|
||||
attachHoverInfo(
|
||||
made.root,
|
||||
(cell) => {
|
||||
const column = spec.columns[(cell as HTMLTableCellElement).cellIndex];
|
||||
return column && spec.tip?.(Number(cell.parentElement?.dataset.i), column.key);
|
||||
},
|
||||
"tr[data-i] td",
|
||||
);
|
||||
return made;
|
||||
};
|
||||
const hint = spec.hint ? note(spec.hint) : null;
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
* `pick` · 옛 수기 `applied` 지움) · 고른 출처 단가 칸 강조(`is-picked` · 기본 = 최저 출처) ·
|
||||
* 관급 / 별도 칸 누름 = 표시 바꿈 · 견적 단가 손입력
|
||||
* · 「견적서」 칸(견적 묶음 아래 · 183-2) 누름 = 파일 모달(`B09_Estimation_UI_Tabs_Base_Quote`)(166-4)
|
||||
* · 출처 묶음 둘째 칸 = 「출처」(M01 면수 · 유가 기준일 값 그대로 · 183-2 · 견적엔 없음)
|
||||
* 견적서 글자 = 견적 단가 수기 줄만(185-5 · 나머지 빈칸 · 빈칸 누름 = 안 엶)
|
||||
* · 출처 묶음 = 단가 칸 하나 · 출처 뜻(M01 면수 · 유가 기준일)은 단가 칸 호버(185-4 `sourceInfo`)
|
||||
* 이동 = 재료비목록표 줄 → 대비표 · 중기목록표 줄 → 중기사용료 블록 · 블록 줄 → 경비 · 기초 · 대비표
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -125,10 +126,33 @@ function appliedChoices(r: Row, pick: (s: string | null) => void): MapContextMen
|
||||
];
|
||||
}
|
||||
|
||||
/** 출처 단가 칸 호버 글(185-4) — M01 면수 「670(26.7)」 · 유가 기준일 「2026-08-14」 를 뜻으로 풂 ·
|
||||
* 값 없는(0 · 빈칸) 칸 = 없음 · 값 · 저장 꼴은 그대로 */
|
||||
export function sourceInfo(r: Row, s: string, files = 0): string | null {
|
||||
if (!(Number(r.prices?.[s]) > 0)) return null;
|
||||
const page = String(r.pages?.[s] ?? "").trim();
|
||||
if (s === "관급") return "관급 · 조달청 계약 단가";
|
||||
if (s === "견적") return `견적 · 수기 입력${files ? ` · 견적서 ${files} 파일` : ""}`;
|
||||
if (s === "유가") {
|
||||
const from = String(r.source ?? "");
|
||||
if (!page) return "유가";
|
||||
if (from.includes("오피넷")) return `유가 · 오피넷 · ${page} 수집`;
|
||||
if (from.includes("한전")) return `전기요금 · 한전 · ${page} 시행`;
|
||||
return `유가 · ${page} 기준`;
|
||||
}
|
||||
// 면수 = [부(부록) · 하(하권) · n권] 쪽(연.월) — 예 「부536(26.7)」 = 2026-07 호 부록 536쪽
|
||||
const m = /^(부|하|\d+권)?(\d+)\((\d{2})\.(\d{1,2})\)$/.exec(page);
|
||||
if (!m) return page ? `${s} · ${page}` : `${s} · 면수 없음`;
|
||||
const book = m[1] === "부" ? "부록 " : m[1] === "하" ? "하권 " : m[1] ? `${m[1]} ` : "";
|
||||
return `${s} · 20${m[3]}-${m[4].padStart(2, "0")} · ${book}${m[2]}쪽`;
|
||||
}
|
||||
|
||||
export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => {
|
||||
const rows: Row[] = sheets.compare || [];
|
||||
const out: DataRow[] = rows.map((r) => {
|
||||
const files: string[] = (user.materials || {})[r.key]?.files || [];
|
||||
// 185-5 견적서 글자 = 견적 단가 수기 줄만(올린 파일이 남은 줄도 · 나머지 빈칸)
|
||||
const quoted = r.prices["견적"] != null || files.length > 0;
|
||||
const row: DataRow = {
|
||||
no: r.no,
|
||||
key: r.key,
|
||||
@@ -139,12 +163,9 @@ export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => {
|
||||
applied: r.applied,
|
||||
applied_from: r.pick ? `${r.pick} 고름` : (r.applied_from ?? ""),
|
||||
supply: r.supply ?? "",
|
||||
files: files.length ? `${files.length} 파일` : "첨부",
|
||||
files: !quoted ? "" : files.length ? `${files.length} 파일` : "첨부",
|
||||
};
|
||||
for (const s of SOURCES) {
|
||||
row[`p:${s}`] = r.prices[s];
|
||||
row[`g:${s}`] = r.pages[s] ?? "";
|
||||
}
|
||||
for (const s of SOURCES) row[`p:${s}`] = r.prices[s];
|
||||
return row;
|
||||
});
|
||||
const mine = (key: string): Row => ({ ...((user.materials || {})[key] || {}) });
|
||||
@@ -160,11 +181,12 @@ export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => {
|
||||
text("name", "품명", 180),
|
||||
text("spec", "규격", 140),
|
||||
text("unit", "단위", 56),
|
||||
// 183-2 — 견적 = 단가 · 견적서 · 그 밖 = 단가 · 출처(면수 값)
|
||||
...SOURCES.flatMap((s) => [
|
||||
money(`p:${s}`, "단가", [s]),
|
||||
s === "견적" ? text("files", "견적서", 72, [s]) : text(`g:${s}`, "출처", 64, [s]),
|
||||
]),
|
||||
// 185-4 — 출처 = 단가 하나(출처 뜻은 단가 칸 호버) · 견적 = 단가 · 견적서
|
||||
...SOURCES.flatMap((s) =>
|
||||
s === "견적"
|
||||
? [money(`p:${s}`, "단가", [s]), text("files", "견적서", 72, [s])]
|
||||
: [money(`p:${s}`, "단가", [s])],
|
||||
),
|
||||
money("lowest", "최저"),
|
||||
money("applied", "적용 단가", ["적용"]),
|
||||
text("applied_from", "출처", 80, ["적용"]),
|
||||
@@ -172,7 +194,15 @@ export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => {
|
||||
],
|
||||
rows: out,
|
||||
frozen: 2,
|
||||
hint: "적용 단가 = 출처 중 최저(0 · 빈칸 뺌 · 견적 포함) 자동 · 「적용 단가」 칸 누름 = 고르기(값 있는 출처 · 견적 · 최저(자동) · 견적이 높아도 고름) · 고른 출처 단가 칸 강조 · 견적 단가 = 수기 입력(출처 없는 B08 줄은 견적으로 단가) · 「관급 · 별도」 칸 누름 = 없음 → 관급 → 별도 · 「견적서」 칸 누름 = 파일 올리기 · 보기",
|
||||
hint: "적용 단가 = 출처 중 최저(0 · 빈칸 뺌 · 견적 포함) 자동 · 「적용 단가」 칸 누름 = 고르기(값 있는 출처 · 견적 · 최저(자동) · 견적이 높아도 고름) · 고른 출처 단가 칸 강조 · 견적 단가 = 수기 입력(출처 없는 B08 줄은 견적으로 단가) · 「관급 · 별도」 칸 누름 = 없음 → 관급 → 별도 · 「견적서」 칸(견적 단가를 넣은 줄만) 누름 = 파일 올리기 · 보기 · 출처 단가 칸에 마우스 = 출처 뜻(책 · 호 · 쪽 · 기준일)",
|
||||
tip: (i, key) =>
|
||||
key.startsWith("p:") && rows[i]
|
||||
? sourceInfo(
|
||||
rows[i],
|
||||
key.slice(2),
|
||||
((user.materials || {})[rows[i].key]?.files || []).length,
|
||||
)
|
||||
: null,
|
||||
editable: (_i, key) => key === "p:견적",
|
||||
onEdit: (i, key, value) => {
|
||||
const row = out[i];
|
||||
@@ -201,7 +231,7 @@ export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => {
|
||||
const r = rows[i];
|
||||
if (!row || !r) return;
|
||||
const cell = mine(String(row.key));
|
||||
if (key === "files") {
|
||||
if (key === "files" && row.files) {
|
||||
const label = `${r.name} ${r.spec}`.trim();
|
||||
return openQuotes(r.key, label, (names) => {
|
||||
const now = mine(r.key);
|
||||
|
||||
Reference in New Issue
Block a user