Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
# Conflicts: # B08_Quantity/B08_Quantity_UI_Page.ts
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
/* B08 2탭 구조물도(PLAN 61-2) — 보조 탭 · 카드 둘(도면 · 면적산출). 색은 테마 변수만. */
|
||||
|
||||
.b08-drawing__tabs .is-missing {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* M02 카드 칸은 셋(도면 · 일위대가 · 면적산출) — 여기는 둘 · 도면을 넓게 */
|
||||
.b08-drawing__cards .m02-struct {
|
||||
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.b08-drawing__cards .m02-struct {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/* =============================================================================
|
||||
* B08_Quantity_UI_Drawing.ts
|
||||
* 2탭 구조물도(PLAN 61-2) — 집계표 열(저장된 구조물 조합)마다 보조 탭 · 한 장 = 그 조합의
|
||||
* M02 도면(도각 포함 · 보기 전용 CAD) + 면적산출 — M02 항목 카드(`M02_MasterTemplete_ItemCards`) 그대로.
|
||||
*
|
||||
* 조합 목록 · 차례 = 집계표와 같음(`B08_Quantity_Summary_Data`)
|
||||
* 읽기만 — 조합 원본 층(`readTemplate`)을 읽고 카드는 [저장] · [수정] 없음 · B05/B06 자료도 읽기만
|
||||
* 카드 · CAD · 스프레드시트는 이 탭을 열 때만 `import()`
|
||||
* ========================================================================== */
|
||||
|
||||
import "./B08_Quantity_UI_Drawing.css";
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
import { loadRows as loadPickerRows } from "@ui/ui_template_item_picker";
|
||||
import { layerOf, pathOf, rowId, type PickerRow } from "@ui/ui_template_item_picker_data";
|
||||
import { readTemplate } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
|
||||
import type { ItemSource } from "../M02_MasterTemplete/M02_MasterTemplete_ItemSource";
|
||||
import { fetchStructures } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import { summaryTooltip } from "../common_util/common_util_summary_item";
|
||||
import {
|
||||
comboId,
|
||||
summaryColumns,
|
||||
type ComboColumn,
|
||||
type ComboLabel,
|
||||
} from "./B08_Quantity_Summary_Data";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
function note(text: string): HTMLElement {
|
||||
const node = document.createElement("p");
|
||||
node.className = "ui-general-block__empty";
|
||||
node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
/** 집계표와 같은 열 + 열마다 M02 줄(없으면 M02 에 없는 조합). */
|
||||
async function loadColumns(
|
||||
projectId: string,
|
||||
): Promise<{ columns: ComboColumn[]; rows: Map<string, PickerRow> }> {
|
||||
const [structures, pickerRows, pipes] = await Promise.all([
|
||||
fetchStructures(projectId),
|
||||
loadPickerRows(),
|
||||
// 배수 분석 전이면 관이 없음 — 구조물만으로
|
||||
fetchDetailPipePoints(projectId).then(
|
||||
(response) => response.pipe_points,
|
||||
() => [],
|
||||
),
|
||||
]);
|
||||
const rows = new Map(pickerRows.map((row) => [rowId(row), row]));
|
||||
const describe = (item: Parameters<typeof comboId>[0]): ComboLabel | null => {
|
||||
const row = rows.get(comboId(item));
|
||||
if (!row) return null;
|
||||
const [group = "", ...rest] = pathOf(row);
|
||||
return { group, name: rest.join(" ") || group || row.key };
|
||||
};
|
||||
return { columns: summaryColumns([...structures.structures, ...pipes], describe), rows };
|
||||
}
|
||||
|
||||
/** 탭 몸통을 그림 — 불러오는 동안 한 줄 · 실패하면 까닭 한 줄. */
|
||||
export async function renderStructureSheets(host: HTMLElement, projectId: string): Promise<void> {
|
||||
host.replaceChildren(note(L("B08_Quantity_Drawing_Loading")));
|
||||
try {
|
||||
const [{ columns, rows }, { createItemCards }] = await Promise.all([
|
||||
loadColumns(projectId),
|
||||
import("../M02_MasterTemplete/M02_MasterTemplete_ItemCards"),
|
||||
]);
|
||||
if (!host.isConnected) return; // 그새 다른 탭으로 옮김
|
||||
if (!columns.length) return void host.replaceChildren(note(L("B08_Quantity_Summary_NoCombo")));
|
||||
|
||||
let row: PickerRow | null = null;
|
||||
const source = (): ItemSource => {
|
||||
const layer = row ? layerOf(row) : "system";
|
||||
return {
|
||||
layer,
|
||||
projectId: null,
|
||||
read: (part, column) => readTemplate(layer, part, column, null),
|
||||
};
|
||||
};
|
||||
const cards = createItemCards({
|
||||
source,
|
||||
editable: () => false,
|
||||
onEdit: () => undefined,
|
||||
warnOnFail: () => false, // 못 읽으면 카드에 「없음」
|
||||
});
|
||||
|
||||
const tabs = document.createElement("div");
|
||||
tabs.className = "b08-quantity__tabs b08-drawing__tabs";
|
||||
tabs.setAttribute("role", "tablist");
|
||||
const notice = document.createElement("div");
|
||||
const sheet = document.createElement("div");
|
||||
sheet.className = "b08-drawing__cards";
|
||||
sheet.append(cards.root);
|
||||
|
||||
const select = (picked: ComboColumn): void => {
|
||||
tabs.replaceChildren(
|
||||
...columns.map((column) => {
|
||||
const button = createButton({
|
||||
label: column.group ? `${column.group} ${column.name}` : column.name,
|
||||
variant: column === picked ? "filled" : "ghost",
|
||||
size: "sm",
|
||||
onClick: () => column !== picked && select(column),
|
||||
});
|
||||
button.setAttribute("role", "tab");
|
||||
button.setAttribute("aria-selected", String(column === picked));
|
||||
button.dataset.b08Combo = column.id;
|
||||
button.title = summaryTooltip(column.item);
|
||||
if (column.missing) button.classList.add("is-missing");
|
||||
return button;
|
||||
}),
|
||||
);
|
||||
row = rows.get(picked.id) ?? null;
|
||||
if (!row?.열) {
|
||||
notice.replaceChildren(note(L("B08_Quantity_Drawing_Missing")));
|
||||
return void cards.load(null);
|
||||
}
|
||||
notice.replaceChildren(...(row.도번 ? [] : [note(L("B08_Quantity_Drawing_NoDrawing"))]));
|
||||
void cards.load({ id: row.열, label: picked.name });
|
||||
};
|
||||
|
||||
host.replaceChildren(tabs, notice, sheet);
|
||||
select(columns[0]);
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
host.replaceChildren(note(`${L("B08_Quantity_Drawing_Failed")} — ${reason}`));
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav";
|
||||
import { renderStructureSummary } from "./B08_Quantity_UI_Summary";
|
||||
import { renderHydro } from "./B08_Quantity_UI_Hydro";
|
||||
import { renderStructureSheets } from "./B08_Quantity_UI_Drawing";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
@@ -43,6 +44,7 @@ function renderTab(host: HTMLElement, tab: TabId, projectId: string | null): voi
|
||||
void renderHydro(view, projectId);
|
||||
return;
|
||||
}
|
||||
if (tab === "sheet" && projectId) return void renderStructureSheets(view, projectId);
|
||||
const pending = document.createElement("p");
|
||||
pending.className = "ui-general-block__empty";
|
||||
pending.textContent = L("B08_Quantity_Tab_Pending");
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""61-2 B08 2탭 구조물도 — 탭에 붙음 · 읽기만 · 집계표와 같은 조합 목록 · 안내 글 키."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
DRAWING = Path("B08_Quantity/B08_Quantity_UI_Drawing.ts").read_text(encoding="utf-8")
|
||||
PAGE = Path("B08_Quantity/B08_Quantity_UI_Page.ts").read_text(encoding="utf-8")
|
||||
LOCALE = Path("ui_template/ui_template_locale_b4.ts").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_sheet_tab_wired():
|
||||
assert 'tab === "sheet"' in PAGE and "renderStructureSheets(view, projectId)" in PAGE
|
||||
|
||||
|
||||
def test_read_only():
|
||||
# 쓰는 길이 없음 — 저장 · 고침 · PUT/POST 없음 · 카드는 고칠 수 없음
|
||||
for word in ("saveTemplate", "write:", "method:", "sheetEdit", "onImport", "onClear"):
|
||||
assert word not in DRAWING, word
|
||||
assert "editable: () => false" in DRAWING
|
||||
|
||||
|
||||
def test_same_columns_as_summary():
|
||||
assert "summaryColumns(" in DRAWING and "./B08_Quantity_Summary_Data" in DRAWING
|
||||
|
||||
|
||||
def test_locale_keys_exist():
|
||||
for key in set(re.findall(r'L\("([A-Za-z0-9_]+)"\)', DRAWING)):
|
||||
assert f"{key}:" in LOCALE or key.startswith("B08_Quantity_Summary_"), key
|
||||
@@ -19,6 +19,19 @@ export const ui_locales_b4 = {
|
||||
],
|
||||
B08_Quantity_Tab_Hydrology: ["수리집수면적표", "Hydraulic Catchment Table"],
|
||||
B08_Quantity_Tab_Pending: ["차례대로 만들 탭입니다.", "This tab comes later, in order."],
|
||||
B08_Quantity_Drawing_Loading: ["구조물도를 불러오는 중…", "Loading structure sheets…"],
|
||||
B08_Quantity_Drawing_Failed: [
|
||||
"구조물도를 불러오지 못했습니다",
|
||||
"Failed to load structure sheets",
|
||||
],
|
||||
B08_Quantity_Drawing_Missing: [
|
||||
"M02 에 없는 조합입니다 — 도면 · 면적산출을 보일 수 없습니다.",
|
||||
"This combination is not in M02 — no drawing or area sheet to show.",
|
||||
],
|
||||
B08_Quantity_Drawing_NoDrawing: [
|
||||
"이 조합은 도면이 없습니다 — M02 구조물 조합에서 그립니다.",
|
||||
"This combination has no drawing — draw it in M02 structure combinations.",
|
||||
],
|
||||
B08_Quantity_Summary_Station: ["측점", "Station"],
|
||||
B08_Quantity_Summary_MissingGroup: ["M02 에 없음", "Not in M02"],
|
||||
B08_Quantity_Summary_Loading: ["구조물 집계표를 불러오는 중…", "Loading the structure summary…"],
|
||||
|
||||
Reference in New Issue
Block a user