feat(716-3): M02 왼쪽 칸 차례 · 이름 · 구분선 — 일위대가 › 구조물 조합 › 구분선 › 불러오기 · 옵션 줄 뺌
- 일위대가(옛 사전조합) 제목 locale 키 M02_PrecomboTitle · 나무 안 글 · 마우스 오른쪽 메뉴 · 빈 글을 일위대가로 - 구조물 옵션 정의 읽기 줄 뺌(M01 에 입구 있음) · open=옵션 은 M01 로 그대로 돌려보냄 - 시험: PRECOMBO_TITLE 상수 기대 → locale 키 기대로 고침 · 칸 차례 시험 더함 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -101,7 +101,7 @@ const TEXT = {
|
||||
Labor_LineApplied: ["반영(수량 곱함)", "Applied (× quantity)"],
|
||||
Labor_LineBase: ["기준", "Base"],
|
||||
Labor_FromLogic: ["원 로직", "Source logic"],
|
||||
Lib_Precombo: ["사전조합", "Pre-combination"],
|
||||
Lib_Precombo: ["일위대가", "Unit-cost item"],
|
||||
Lib_System: ["마스터", "Master"],
|
||||
Lib_Company: ["회사", "Company"],
|
||||
Lib_Personal: ["개인", "Personal"],
|
||||
@@ -156,13 +156,13 @@ const TEXT = {
|
||||
In_Close: ["닫기", "Close"],
|
||||
Lib_Name: ["이름", "Name"],
|
||||
// 서고 목록표(51-4)
|
||||
Lib_PrecomboAll: ["일위대가 사전조합", "Pre-combination"],
|
||||
Lib_None: ["사전조합 없음", "No pre-combination"],
|
||||
Lib_PrecomboAll: ["일위대가", "Unit-cost items"],
|
||||
Lib_None: ["일위대가 없음", "No unit-cost items"],
|
||||
Lib_Unsorted: ["미분류", "Unsorted"],
|
||||
Lib_NewHere: ["여기에 새 사전조합", "New pre-combination here"],
|
||||
Lib_NewHere: ["여기에 새 일위대가", "New unit-cost item here"],
|
||||
Lib_FolderFull: [
|
||||
"폴더 안에 사전조합 {n}개 — 먼저 옮기거나 지운 뒤 지움",
|
||||
"Folder holds {n} pre-combination(s) — move or delete them first",
|
||||
"폴더 안에 일위대가 {n}개 — 먼저 옮기거나 지운 뒤 지움",
|
||||
"Folder holds {n} unit-cost item(s) — move or delete them first",
|
||||
],
|
||||
Lib_FolderDelete: ["「{value}」 빈 폴더를 지움", "Delete empty folder “{value}”"],
|
||||
Lib_Create: ["생성", "Create"],
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_UI_Side.ts
|
||||
* 마스터 템플릿 좌측 패널 — 「구조물 조합」(층 단추 · 나무) · 「일위대가 사전조합」(층 단추 따로 · 2단 폴더 나무 ·
|
||||
* `UI_Side_Precombo` · 50-3 · 134-3) · 「일위대가 실무자료」(구조물 조합 층 · `Hopyo_Side` · 324-4) ·
|
||||
* 「도면 양식」(새로 · 본떠 · 지우기).
|
||||
* 마스터 템플릿 좌측 패널 차례(716-3) — 「일위대가」(층 단추 따로 · 2단 폴더 나무 · `UI_Side_Precombo` ·
|
||||
* 50-3 · 134-3) › 「구조물 조합」(층 단추 · 나무) › 수평 구분선 › 「일위대가 불러오기」(구조물 조합 층 ·
|
||||
* `Hopyo_Side` · 324-4) › 「도면 양식」(새로 · 본떠 · 지우기) › 실무문서. 「단가산출」 칸은 맨 위(717-2a).
|
||||
* 구조물 옵션 정의는 M01 로 옮김(716-1).
|
||||
* 서버 길은 계약 `6_계약.md`.
|
||||
* 구조물 도면 · 일위대가 · 면적산출 상세는 좌측 줄이 아니라 항목 카드 [수정]으로(PLAN 45-2).
|
||||
* 화면 이름 「구조물 조합」(PLAN 26-7a) — 파일 이름 · 양식 키 · API 길은 「구조물집계표」 그대로.
|
||||
@@ -30,17 +31,13 @@ import {
|
||||
type TemplateInfo,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { STRUCTURE_TABLE } from "./M02_MasterTemplete_Structure";
|
||||
import { buildOptionsSection } from "./StructOptions/M02_SO_Options";
|
||||
import { buildPracticeSection } from "./M02_MasterTemplete_Practice";
|
||||
import { HOPYO_BUILT } from "./M02_MasterTemplete_Hopyo_Check";
|
||||
import { buildHopyoSection } from "./M02_MasterTemplete_Hopyo_Side";
|
||||
import { canEdit, type Selection } from "./M02_MasterTemplete_UI_Main";
|
||||
import {
|
||||
buildPrecomboSection,
|
||||
PRECOMBO_TITLE,
|
||||
type PrecomboOptions,
|
||||
} from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
import { buildPrecomboSection, type PrecomboOptions } from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
import { createDivider } from "@ui/ui_template_divider";
|
||||
|
||||
const KINDS: { kind: Kind; label: () => string }[] = [
|
||||
{ kind: "table", label: () => L("M02_KindTable") },
|
||||
@@ -68,7 +65,7 @@ export interface SideOptions {
|
||||
showView: (label: string, node: HTMLElement) => (dirty: boolean) => void;
|
||||
/** 「구조물 조합」 줄 아래 — 층 단추 · 고른 층의 구조물 라이브러리 나무(PLAN 29-2 · 페이지가 만듦) */
|
||||
tree?: HTMLElement[];
|
||||
/** 「일위대가 사전조합」 층(134-3 · 페이지 층과 따로 · 층 단추는 페이지가 만듦) */
|
||||
/** 「일위대가」 층(134-3 · 페이지 층과 따로 · 층 단추는 페이지가 만듦) */
|
||||
precombo: Pick<PrecomboOptions, "scope" | "bar" | "more">;
|
||||
}
|
||||
|
||||
@@ -77,7 +74,7 @@ export interface SideHandle {
|
||||
refresh: () => Promise<void>;
|
||||
/** 고름 표시를 지금 열린 것 한 줄로 — 편집 칸이 무엇을 열든(카드 [도면 수정] 등) 부름 */
|
||||
paint: () => void;
|
||||
/** 진입 해시 `open`(PLAN 27-1d) — 그 컨테이너를 펼침(옵션은 열기까지) · 옵션 · 도면은 시스템 층에만 */
|
||||
/** 진입 해시 `open`(PLAN 27-1d) — 그 컨테이너를 펼침 · 도면은 시스템 층에만 · 옵션은 M01 로 옮겨 아무 일 없음 */
|
||||
reveal: (target: "옵션" | "도면" | "일위대가조합") => void;
|
||||
/** 사전조합 층이 바뀜(134-3) — 그 컨테이너만 다시 그림 */
|
||||
refreshPrecombo: () => Promise<void>;
|
||||
@@ -120,13 +117,6 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
del.addEventListener("click", () => void deleteCurrent());
|
||||
return [el("div", { className: "m02-side__row", children: [btnNew, copy, del] })];
|
||||
};
|
||||
// 구조물 옵션 보이기 — 구조물 집계 양식 아래 별도 줄 하나(누르면 메인 칸에 종류별 옵션 체크)
|
||||
const options = buildOptionsSection({
|
||||
isAdmin: canEdit(layer, opt.isAdmin),
|
||||
confirmLeave: opt.confirmLeave,
|
||||
show: opt.showView,
|
||||
onOpen: () => (practice?.clear(), paint()),
|
||||
});
|
||||
/** 컨테이너 제목을 펼치면 그 화면을 엶 — 같은 이름 줄 단추를 갈음(PLAN 29-6) */
|
||||
const openOnExpand = (sec: HTMLElement, run: () => void): HTMLElement => {
|
||||
sec.querySelector(".ui-collapsible__title")?.addEventListener("click", () => {
|
||||
@@ -143,27 +133,24 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
openOnExpand(summary, async () => {
|
||||
if (!isPicked("table", STRUCTURE_TABLE) && (await opt.confirmLeave())) opt.onOpen(null);
|
||||
});
|
||||
// 일위대가 사전조합 서고(50-3 · 134-3) — 구조물 조합 바로 아래 · 층은 페이지 층과 따로
|
||||
// 일위대가 서고(50-3 · 134-3) — 맨 위 칸 · 층은 페이지 층과 따로
|
||||
const precombo = buildPrecomboSection({
|
||||
...opt.precombo,
|
||||
confirmLeave: opt.confirmLeave,
|
||||
onOpen: opt.onOpen,
|
||||
getOpen: opt.getOpen,
|
||||
});
|
||||
const precomboSec = section(PRECOMBO_TITLE, precombo.body);
|
||||
// 실무자료로 사전조합을 만들었음(확인 창 · 엑셀 보기 창) → 목록 다시 · 이 패널이 내려갔으면 듣기 끝
|
||||
const precomboSec = section(L("M02_PrecomboTitle"), precombo.body);
|
||||
// 실무자료로 일위대가를 만들었음(확인 창 · 엑셀 보기 창) → 목록 다시 · 이 패널이 내려갔으면 듣기 끝
|
||||
const onBuilt = (): void =>
|
||||
precombo.body.isConnected
|
||||
? void precombo.refresh()
|
||||
: document.removeEventListener(HOPYO_BUILT, onBuilt);
|
||||
document.addEventListener(HOPYO_BUILT, onBuilt);
|
||||
// 구조물 옵션 정의 · 도면 양식은 시스템 층(마스터 템플릿)에만 · 정의 고쳐 씀은 시스템 관리자만 · 다른 역할은 읽기만(PLAN 48-2)
|
||||
const extras =
|
||||
// 도면 양식은 시스템 층(마스터 템플릿)에만
|
||||
const drawing =
|
||||
layer === "system"
|
||||
? {
|
||||
옵션: section(options.title, options.body),
|
||||
도면: section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing")),
|
||||
}
|
||||
? section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing"))
|
||||
: null;
|
||||
// 실무문서(옛 마스터 일위대가 조합 · PLAN 39-2) — 구조물 조합 밖 · 도면 양식 아래 · 마스터 층 · 시스템 관리자만
|
||||
const practice =
|
||||
@@ -171,23 +158,24 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
? buildPracticeSection({
|
||||
confirmLeave: opt.confirmLeave,
|
||||
show: opt.showView,
|
||||
onOpen: () => (options.clear(), paint()),
|
||||
onOpen: paint,
|
||||
})
|
||||
: null;
|
||||
// 일위대가 실무자료(324-4) — 사전조합 아래 · 층 = 구조물 조합 층 · 쓰기 권한 없으면 올리기 꺼짐
|
||||
// 일위대가 불러오기(324-4 · 제목은 718-1) · 층 = 구조물 조합 층 · 쓰기 권한 없으면 올리기 꺼짐
|
||||
const hopyo = buildHopyoSection({
|
||||
canWrite: canEdit(layer, opt.isAdmin),
|
||||
confirmLeave: opt.confirmLeave,
|
||||
show: opt.showView,
|
||||
onOpen: () => (options.clear(), practice?.clear(), paint()),
|
||||
onOpen: () => (practice?.clear(), paint()),
|
||||
});
|
||||
const listHost = el("div", {
|
||||
className: "m02-side__lists ui-sidebar-panel",
|
||||
children: [
|
||||
summary,
|
||||
precomboSec,
|
||||
summary,
|
||||
createDivider(),
|
||||
section(hopyo.title, hopyo.body),
|
||||
...(extras ? [extras.옵션, extras.도면].filter((s): s is HTMLElement => !!s) : []),
|
||||
...(drawing ? [drawing] : []),
|
||||
...(practice ? [section(practice.title, practice.body)] : []),
|
||||
],
|
||||
});
|
||||
@@ -237,14 +225,13 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
for (const [kind, b] of editButtons) b.copy.disabled = b.del.disabled = cur?.종류 !== kind;
|
||||
}
|
||||
|
||||
/** 고름 표시는 여기서만 — 목록 · 옵션 줄을 함께 그려야 두 줄이 동시에 안 남음
|
||||
* (양식이 열려 있으면 옵션 · 일위대가 조합 고름은 지움) */
|
||||
/** 고름 표시는 여기서만 — 목록 · 실무문서 줄을 함께 그려야 두 줄이 동시에 안 남음
|
||||
* (양식이 열려 있으면 실무문서 고름은 지움) */
|
||||
function paint(): void {
|
||||
paintList();
|
||||
precombo.paint();
|
||||
hopyo.paint();
|
||||
paintButtons();
|
||||
if (opt.getOpen()) options.clear();
|
||||
if (opt.getOpen()) practice?.clear();
|
||||
}
|
||||
|
||||
@@ -347,10 +334,8 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
}
|
||||
|
||||
const reveal = (target: "옵션" | "도면" | "일위대가조합"): void => {
|
||||
const sec = target === "일위대가조합" ? precomboSec : extras?.[target];
|
||||
if (!sec) return; // 옵션 보이기는 시스템 관리자만
|
||||
sec.classList.remove("is-collapsed");
|
||||
if (target === "옵션") options.body.querySelector<HTMLElement>("button")?.click();
|
||||
const sec = target === "일위대가조합" ? precomboSec : target === "도면" ? drawing : null;
|
||||
sec?.classList.remove("is-collapsed");
|
||||
};
|
||||
|
||||
void refresh();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_UI_Side_Precombo.ts
|
||||
* 좌측 「일위대가 사전조합」 컨테이너(50-3 ③ · 51-4 · 134-3) — 구조물 조합 아래 · 층 단추(페이지가 만듦 ·
|
||||
* 페이지 층과 따로 · 마스터 · 회사 · 내 개인) + 그 층 사전조합 나무.
|
||||
* 좌측 「일위대가」 컨테이너(50-3 ③ · 51-4 · 134-3) — 구조물 조합 아래 · 층 단추(페이지가 만듦 ·
|
||||
* 페이지 층과 따로 · 마스터 · 회사 · 내 개인) + 그 층 일위대가 나무.
|
||||
* 나무 = 자유 폴더(158-1 · 구조물 조합 탐색기와 같은 꼴 · 깊이 자유 · 층마다 따로 · 자리 없는 문서 = 뿌리) —
|
||||
* 폴더 오른쪽 클릭 [새 폴더] · [여기에 새 사전조합] · [이름 바꾸기] · [지우기](빈 폴더만) · 빈 자리 오른쪽
|
||||
* 폴더 오른쪽 클릭 [새 폴더] · [여기에 새 일위대가] · [이름 바꾸기] · [지우기](빈 폴더만) · 빈 자리 오른쪽
|
||||
* 클릭 = 뿌리 메뉴 · 문서 · 폴더 끌어 옮기기. 폴더 자리만 서버(`precombo-tree`)에 적음 — 문서 파일은 안 옮김.
|
||||
* 구분 · 상세구분은 문서 칸(상세 머리 · 고르기 창 거름 · `precomboClasses`) — 나무 폴더와 무관.
|
||||
* 문서 누름 = 편집 칸(UI_Main 종류 precombo = 일위대가 상세 모드) · 켜진 층 단추 다시 누름 = 목록표
|
||||
@@ -42,7 +42,6 @@ import { createEmpty } from "@ui/ui_template_empty";
|
||||
|
||||
export type PrecomboKind = Extract<Kind, "precombo">;
|
||||
|
||||
export const PRECOMBO_TITLE = "일위대가 사전조합";
|
||||
export const isPrecombo = (kind: Kind): kind is PrecomboKind => kind === "precombo";
|
||||
|
||||
/** 여러 구조물에 쓰는 묶음(콘크리트 · 거푸집 등) — 군마다 첫 상세구분(134-5) */
|
||||
@@ -183,7 +182,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle {
|
||||
}
|
||||
};
|
||||
|
||||
/** [여기에 새 사전조합] — 뿌리에 생성 → 폴더면 그 자리로 옮김 → 나무 다시 · 상세 엶 */
|
||||
/** [여기에 새 일위대가] — 뿌리에 생성 → 폴더면 그 자리로 옮김 → 나무 다시 · 상세 엶 */
|
||||
const make = (path: string[]): void => {
|
||||
const at = opt.scope();
|
||||
nameDialog(tc("Lib_NewHere"), "", async (name) => {
|
||||
@@ -303,7 +302,7 @@ export function buildPrecomboSection(opt: PrecomboOptions): PrecomboHandle {
|
||||
paint();
|
||||
};
|
||||
|
||||
// 빈 자리 오른쪽 클릭 = 뿌리 메뉴(새 폴더 · 새 사전조합)
|
||||
// 빈 자리 오른쪽 클릭 = 뿌리 메뉴(새 폴더 · 새 일위대가)
|
||||
treeHost.addEventListener("contextmenu", (event) => {
|
||||
if (!view || (event.target as HTMLElement).closest("[data-node]")) return;
|
||||
event.preventDefault();
|
||||
|
||||
@@ -82,7 +82,7 @@ def test_사전조합_층_단추는_페이지를_다시_안_세움():
|
||||
|
||||
def test_고름에_owner_권한_서고_길에_명시_owner():
|
||||
side = _text("M02_MasterTemplete_UI_Side_Precombo.ts")
|
||||
assert 'export const PRECOMBO_TITLE = "일위대가 사전조합";' in side
|
||||
assert "PRECOMBO_TITLE" not in side # 716-3 — 제목은 locale 키 「일위대가」
|
||||
assert "owner: scope.owner," in side and "canWrite: scope.canWrite," in side
|
||||
assert "listTemplates(at.layer, null, at.owner)" in side
|
||||
main = _text("M02_MasterTemplete_UI_Main.ts")
|
||||
@@ -133,3 +133,20 @@ def test_목록표_빈_구분_미분류_고르기_창_새_분류_거름():
|
||||
# 거름은 칸 값 그대로 — 품셈 구분을 고르면 서고 줄 빠지고 새 분류를 고르면 로직 줄 빠짐
|
||||
assert "(!sub.select.value || x.row.구분 === sub.select.value)" in pick
|
||||
assert "(!sub.select.value || x.구분 === sub.select.value)" in pick
|
||||
|
||||
|
||||
def test_왼쪽_칸_차례_일위대가_구조물조합_구분선_불러오기_716_3():
|
||||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||||
order = side[side.index("m02-side__lists") :]
|
||||
marks = [
|
||||
"precomboSec,",
|
||||
"summary,",
|
||||
"createDivider()",
|
||||
"hopyo.title",
|
||||
"drawing ?",
|
||||
"practice ?",
|
||||
]
|
||||
at = [order.index(m) for m in marks]
|
||||
assert at == sorted(at)
|
||||
|
||||
assert 'L("M02_PrecomboTitle")' in side and "buildOptionsSection" not in side
|
||||
|
||||
@@ -189,4 +189,5 @@ export const ui_locales_m2 = {
|
||||
"Practice documents changed — reloaded",
|
||||
],
|
||||
B01_Dashboard_CardPrecombo: ["일위대가", "Unit-cost items"],
|
||||
M02_PrecomboTitle: ["일위대가", "Unit-cost items"],
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user