feat(720-4): 구조물 조합 층 단추 = 제자리 바꿈(해시로 페이지 통째 다시 세움 없음) · 도면 양식 · 실무문서 칸 = 시스템 관리자면 늘
- 페이지: 층 고름(resolve) · 층 맞춤(settleLayer) · 띠 · 나무 · 층 줄 갈아 끼움(build) 한 길 · 주소 replaceState · 새 층 구조물 표 - 편집 칸 권한 = 함수(UI_Main isAdmin) · 「새 항목」 = 고른 것 권한으로 가림 · 불러오기 장부 = 열 때 층 · 권한(Hopyo_Side) - 왼쪽 칸 펼침 · 단가산출 · 일위대가 층 그대로 - 낡은 시험(층 단추 해시 이동 · 사람 나무 · 도면 칸 층 묶음 · 새 항목 · 머리 고침 · 불러오기 권한 · 실무문서 차례 · home) 새 꼴로 — test_m02_practice 도면 차례 글은 이미 낡아 실패하던 것 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,8 @@ import { buildHopyoLedger, LEDGER_HINT, LEDGER_TITLE } from "./M02_MasterTemplet
|
||||
import { buildHopyoWizard } from "./M02_MasterTemplete_Hopyo_Wizard";
|
||||
|
||||
export interface HopyoSectionOptions {
|
||||
/** 이 층 쓰기 권한 — 별칭 장부 고침 */
|
||||
canWrite: boolean;
|
||||
/** 이 층 쓰기 권한 — 별칭 장부 고침(구조물 조합 층이 제자리에서 바뀌어 열 때 읽음 · 720-4) */
|
||||
canWrite: () => boolean;
|
||||
/** 저장 안 한 고침을 버릴지 확인 — 취소면 false */
|
||||
confirmLeave: () => Promise<boolean>;
|
||||
/** 메인 칸에 화면을 띄움 */
|
||||
@@ -34,7 +34,6 @@ const WIZARD = "불러오기";
|
||||
|
||||
export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
|
||||
const title = L("M02_HopyoImport");
|
||||
const scope = pageScope();
|
||||
const start = createButton({
|
||||
label: "불러오기",
|
||||
variant: "filled",
|
||||
@@ -63,7 +62,9 @@ export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
|
||||
if (open?.key === key && open.view.isConnected) return;
|
||||
if (!(await opt.confirmLeave())) return;
|
||||
const view =
|
||||
key === WIZARD ? buildHopyoWizard() : buildHopyoLedger({ scope, canWrite: opt.canWrite });
|
||||
key === WIZARD
|
||||
? buildHopyoWizard()
|
||||
: buildHopyoLedger({ scope: pageScope(), canWrite: opt.canWrite() });
|
||||
opt.show(key === WIZARD ? title : `${title} — ${LEDGER_TITLE}`, view);
|
||||
opt.onOpen();
|
||||
open = { key, view };
|
||||
|
||||
@@ -130,9 +130,10 @@ export function newItemButton(run: () => void, label = L("M02_TreeNewItem")): HT
|
||||
return button;
|
||||
}
|
||||
|
||||
/** `onOpened` — 무엇을 열든(좌측 · 카드 [도면 수정] · 다시 읽기) 연 바로 뒤 부름(좌측 고름 표시) */
|
||||
/** `onOpened` — 무엇을 열든(좌측 · 카드 [도면 수정] · 다시 읽기) 연 바로 뒤 부름(좌측 고름 표시) ·
|
||||
* `isAdmin` = 지금 구조물 조합 층 고칠 권한(층을 제자리에서 바꾸므로 함수 · 720-4) */
|
||||
export function createMain(
|
||||
isAdmin: boolean,
|
||||
isAdmin: () => boolean,
|
||||
onSaved?: () => void,
|
||||
onOpened?: () => void,
|
||||
extras: MainExtras = {},
|
||||
@@ -185,7 +186,7 @@ export function createMain(
|
||||
// 「새 항목」 · 표 접기(제목)는 「구조물 조합」일 때만
|
||||
const showAside = (): void => {
|
||||
const combo = isCombo(sel);
|
||||
if (extras.newItem) extras.newItem.hidden = !combo;
|
||||
if (extras.newItem) extras.newItem.hidden = !combo || !sel || !writable(sel);
|
||||
title.classList.toggle("m02-main__title--fold", combo);
|
||||
title.title = combo ? L("M02_TableFold") : "";
|
||||
if (!combo) {
|
||||
@@ -251,7 +252,7 @@ export function createMain(
|
||||
host.replaceChildren(createEmpty({ text }));
|
||||
};
|
||||
/** 고를 것마다 고칠 권한 — 사전조합 층은 고름에 실림(134-3) · 그 밖 페이지 층 */
|
||||
const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin);
|
||||
const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin());
|
||||
const bar = (): void => {
|
||||
reload.hidden = !sel;
|
||||
save.hidden = !sel || !writable(sel);
|
||||
@@ -282,7 +283,7 @@ export function createMain(
|
||||
/** 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 카드 */
|
||||
function buildPanel(layer: Layer): StructurePanelHandle {
|
||||
const made = createStructurePanel({
|
||||
isAdmin,
|
||||
isAdmin: isAdmin(),
|
||||
confirmLeave,
|
||||
keepColumn: (colId) => sheet?.selectColumn(colId), // 카드 고침을 안 버림 — 고른 열 되돌림
|
||||
onNumbers: () => sheet?.recalc(), // 카드 [저장] 뒤 번호 · 일위대가 합계 줄을 새로(86-3)
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* 우측 편집 칸(표·도면 부품 · 「구조물 조합」이면 나무는 좌측 패널 · PLAN 29-2)
|
||||
*
|
||||
* 한 화면에 층 셋(마스터 · 회사 · 개인 · PLAN 27-1) — 고른 층 = 진입 해시 `?open=&root=&owner=&company=`
|
||||
* (`M02_MasterTemplete_Layer.ts`) · 층 단추 · 「사람 고르기」를 누르면 해시를 바꿔 이 화면을 다시 세움.
|
||||
* (`M02_MasterTemplete_Layer.ts`) · 구조물 조합 층 단추 · 사람 나무를 누르면 제자리에서 그 층으로 갈아 끼움
|
||||
* (720-4 · 해시는 replaceState · 왼쪽 칸 펼침 · 단가산출 · 일위대가 층 그대로).
|
||||
* 화면은 로그인만 확인 — 고치기는 층별 권한일 때만 켬(서버도 막음 · `accessFor`).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -20,7 +21,7 @@ import { el } from "@ui/ui_template_elements";
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
import { createWorkflowOverlays } from "@ui/ui_template_overlay";
|
||||
import { mountSettingsDrawer } from "../A00_Common/m_settings_nav";
|
||||
import { navigateTo, type MountedPage } from "../A00_Common/router";
|
||||
import type { MountedPage } from "../A00_Common/router";
|
||||
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
||||
import { fetchOwners, setActiveLayer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import {
|
||||
@@ -33,7 +34,7 @@ import {
|
||||
ownOwner,
|
||||
redirectOldOptions,
|
||||
viewCompany,
|
||||
type OpenTarget,
|
||||
type ManageLayer,
|
||||
type Root,
|
||||
} from "./M02_MasterTemplete_Layer";
|
||||
import {
|
||||
@@ -67,100 +68,141 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
if (redirectOldOptions(user)) return { covers: () => false, show: () => {}, dispose: () => {} };
|
||||
const entry = entryFromHash(user); // 134-4 — 해시에 층 없음 = 개인(회사 없으면 마스터)
|
||||
const company = viewCompany(entry, user); // 볼 회사(시스템 관리자는 고른 회사)
|
||||
let layer = layerOfRoot(entry.root);
|
||||
// 회사 층 주인 = 볼 회사 · 자기 층 주인을 적어도 자기 층 · 회사 없는 사람의 회사 · 개인 = 마스터(단추 자리에 안내)
|
||||
let owner = entry.owner ?? (entry.root === "company" ? companyOwner(company, user) : null);
|
||||
if (owner === ownOwner(layer, user)) owner = null;
|
||||
// 시스템 관리자가 남의 회사 「개인」 — 그 회사 관리자처럼 사람 나무(PLAN 29-6) · 사람을 안 골랐으면 첫 사람 ·
|
||||
// 사람이 없으면 마스터
|
||||
const people = entry.root === "personal" && seesOthers(company, user);
|
||||
if (people && !owner) {
|
||||
owner = (await fetchOwners("personal", company).catch(() => []))[0]?.owner ?? null;
|
||||
entry.owner = owner;
|
||||
if (!owner) [entry.root, layer] = ["master", "system"];
|
||||
}
|
||||
if (accessFor(layer, owner, user).needCompany) {
|
||||
[entry.root, layer, owner] = ["master", "system", null];
|
||||
}
|
||||
setActiveLayer(layer, owner);
|
||||
const isAdmin = accessFor(layer, owner, user).canWrite; // 이 층을 고칠 수 있나(하위 부품의 「관리자」)
|
||||
// 자기 회사용을 읽기만 볼 때 — 안내 + [개인용 복사]
|
||||
const readOnlyBar = layer === "company" && !owner && !isAdmin ? buildReadOnlyBar(user) : null;
|
||||
// 고른 층의 나무 — 고르기 모달과 같은 폴더 나무 · 좌측 「구조물 조합」 줄 아래
|
||||
// 구조물 조합 층(720-4) — 층 단추 · 사람 나무 · 공유 승인 띠가 바꿈 · 페이지는 그대로 두고 제자리에서 갈아 끼움
|
||||
// (`switchStructure` · 해시는 replaceState) · 단가산출 · 일위대가 층 · 왼쪽 칸 펼침은 그대로
|
||||
let layer: ManageLayer = "system";
|
||||
let owner: string | null = null;
|
||||
let people = false;
|
||||
let isAdmin = false; // 이 층을 고칠 수 있나(하위 부품의 「관리자」)
|
||||
let readOnlyBar: ReturnType<typeof buildReadOnlyBar> | null = null;
|
||||
let share: ReturnType<typeof createShare>;
|
||||
let tree: ReturnType<typeof createTreePanel>;
|
||||
let turn = 0;
|
||||
/** 고른 층 → 실제 층 — 회사 층 주인 = 볼 회사 · 자기 층 주인을 적어도 자기 층 ·
|
||||
* 시스템 관리자가 남의 회사 「개인」 = 사람 나무(29-6 · 안 골랐으면 첫 사람 · 사람이 없으면 마스터) ·
|
||||
* 회사 없는 사람의 회사 · 개인 = 마스터(단추 자리에 안내) */
|
||||
const resolve = async (want: Root, wantOwner: string | null) => {
|
||||
let [at, lay] = [want, layerOfRoot(want)];
|
||||
let who = wantOwner ?? (want === "company" ? companyOwner(company, user) : null);
|
||||
if (who === ownOwner(lay, user)) who = null;
|
||||
const many = want === "personal" && seesOthers(company, user);
|
||||
if (many && !who) {
|
||||
who = (await fetchOwners("personal", company).catch(() => []))[0]?.owner ?? null;
|
||||
if (!who) [at, lay] = ["master", "system"];
|
||||
}
|
||||
if (accessFor(lay, who, user).needCompany) [at, lay, who] = ["master", "system", null];
|
||||
return { root: at, layer: lay, owner: who, people: many && at === "personal" };
|
||||
};
|
||||
// 「〇〇로 복사…」(30-2 · 39-6) — 쓸 수 있는 층 전부(마스터 · 볼 회사 · 내 개인 · 지금 보는 층은 뺌) ·
|
||||
// 대상 폴더는 고르는 창
|
||||
const here = owner === ownOwner(layer, user) ? null : owner;
|
||||
const copyTo = (["master", "company", "personal"] as Root[]).flatMap((one) => {
|
||||
const to = layerOfRoot(one);
|
||||
const toOwner = one === "company" ? companyOwner(company, user) : null;
|
||||
if (!accessFor(to, toOwner, user).canWrite || (to === layer && toOwner === here)) return [];
|
||||
const key = one === "master" ? "Master" : one === "company" ? "Company" : "Personal";
|
||||
return [{ layer: to, owner: toOwner, label: L(`M02_TreeCopyTo${key}`) }];
|
||||
});
|
||||
// 회사에 올리기 요청 · 승인(706-1) — 요청 = 개인 층 나무 덧메뉴 · 승인 = 회사 층 위 띠 · 결과 띠
|
||||
const share = createShare({
|
||||
user,
|
||||
company,
|
||||
layer,
|
||||
owner,
|
||||
showView: (label, node) =>
|
||||
void main.confirmLeave().then((ok) => ok && main.showView(label, node)),
|
||||
openPrecombo: (r) =>
|
||||
void main.open({
|
||||
layer: "personal",
|
||||
owner: r.주인,
|
||||
canWrite: false,
|
||||
projectId: null,
|
||||
kind: "precombo",
|
||||
name: r.키,
|
||||
label: r.이름,
|
||||
}),
|
||||
openItem: (r) => void go("personal", r.주인, "구조물조합"),
|
||||
changed: () => void Promise.all([side.refresh(), tree.refresh()]),
|
||||
});
|
||||
const tree = createTreePanel({
|
||||
layer,
|
||||
owner,
|
||||
copyTo,
|
||||
more: share.itemMenu,
|
||||
canEdit: canEdit(layer, isAdmin),
|
||||
onPick: (item) => void main.openTable(layer, item.열),
|
||||
settle: () => main.settle(),
|
||||
// 나무가 바뀜(새 항목 · 복사 생성 · 붙여넣기 · 지우기 · 이름 · 옮기기) — 집계표
|
||||
onChanged: async (colId) => {
|
||||
await main.openTable(layer, colId, true);
|
||||
},
|
||||
});
|
||||
const go = async (next: Root, nextOwner: string | null, open: OpenTarget): Promise<void> => {
|
||||
// 같은 주소 안 이동은 머리 가드가 안 물음 — 저장 안 한 고침은 여기서
|
||||
if (!(await main.confirmLeave())) return;
|
||||
navigateTo(
|
||||
ROUTES.M02_MASTER_TEMPLATE,
|
||||
entryQuery({ ...entry, open, root: next, owner: nextOwner }),
|
||||
const copyTargets = () => {
|
||||
const here = owner === ownOwner(layer, user) ? null : owner;
|
||||
return (["master", "company", "personal"] as Root[]).flatMap((one) => {
|
||||
const to = layerOfRoot(one);
|
||||
const toOwner = one === "company" ? companyOwner(company, user) : null;
|
||||
if (!accessFor(to, toOwner, user).canWrite || (to === layer && toOwner === here)) return [];
|
||||
const key = one === "master" ? "Master" : one === "company" ? "Company" : "Personal";
|
||||
return [{ layer: to, owner: toOwner, label: L(`M02_TreeCopyTo${key}`) }];
|
||||
});
|
||||
};
|
||||
/** 지금 층의 띠 · 나무 · 층 줄을 새로 만들어 감싼 칸에 끼움(처음 · 층 바꿈 한 길) */
|
||||
const build = (): void => {
|
||||
// 자기 회사용을 읽기만 볼 때 — 안내 + [개인용 복사]
|
||||
readOnlyBar = layer === "company" && !owner && !isAdmin ? buildReadOnlyBar(user) : null;
|
||||
// 회사에 올리기 요청 · 승인(706-1) — 요청 = 개인 층 나무 덧메뉴 · 승인 = 회사 층 위 띠 · 결과 띠
|
||||
share = createShare({
|
||||
user,
|
||||
company,
|
||||
layer,
|
||||
owner,
|
||||
showView: (label, node) =>
|
||||
void main.confirmLeave().then((ok) => ok && main.showView(label, node)),
|
||||
openPrecombo: (r) =>
|
||||
void main.open({
|
||||
layer: "personal",
|
||||
owner: r.주인,
|
||||
canWrite: false,
|
||||
projectId: null,
|
||||
kind: "precombo",
|
||||
name: r.키,
|
||||
label: r.이름,
|
||||
}),
|
||||
openItem: (r) => void go("personal", r.주인),
|
||||
changed: () => void Promise.all([side.refresh(), tree.refresh()]),
|
||||
});
|
||||
// 고른 층의 나무 — 고르기 모달과 같은 폴더 나무 · 좌측 「구조물 조합」 줄 아래
|
||||
tree = createTreePanel({
|
||||
layer,
|
||||
owner,
|
||||
copyTo: copyTargets(),
|
||||
more: share.itemMenu,
|
||||
canEdit: canEdit(layer, isAdmin),
|
||||
onPick: (item) => void main.openTable(layer, item.열),
|
||||
settle: () => main.settle(),
|
||||
// 나무가 바뀜(새 항목 · 복사 생성 · 붙여넣기 · 지우기 · 이름 · 옮기기) — 집계표
|
||||
onChanged: async (colId) => {
|
||||
await main.openTable(layer, colId, true);
|
||||
},
|
||||
});
|
||||
topHost.replaceChildren(...(readOnlyBar ? [readOnlyBar.root] : []), share.top);
|
||||
treeHost.replaceChildren(
|
||||
layerBar(tree.add, () => void main.openTable(layer)),
|
||||
peopleTree(tree.root),
|
||||
);
|
||||
void tree.refresh();
|
||||
};
|
||||
/** 층을 정해 페이지 층 길 · 권한을 맞춤 */
|
||||
const settleLayer = (got: Awaited<ReturnType<typeof resolve>>): void => {
|
||||
[entry.root, entry.owner, layer, owner, people] = [
|
||||
got.root,
|
||||
got.owner,
|
||||
got.layer,
|
||||
got.owner,
|
||||
got.people,
|
||||
];
|
||||
setActiveLayer(layer, owner);
|
||||
isAdmin = accessFor(layer, owner, user).canWrite;
|
||||
};
|
||||
/** 구조물 조합 층 바꿈(720-4) — 제자리 · 주소 replaceState · 새 층 구조물 표 */
|
||||
const switchStructure = async (want: Root, wantOwner: string | null): Promise<void> => {
|
||||
const mine = ++turn;
|
||||
const got = await resolve(want, wantOwner);
|
||||
if (mine !== turn) return;
|
||||
settleLayer(got);
|
||||
const query = new URLSearchParams(entryQuery({ ...entry, open: "구조물조합" }));
|
||||
history.replaceState(null, "", `#/${ROUTES.M02_MASTER_TEMPLATE}?${query}`);
|
||||
build();
|
||||
await main.openTable(layer, null, true);
|
||||
};
|
||||
const go = async (next: Root, nextOwner: string | null): Promise<void> => {
|
||||
// 저장 안 한 고침은 먼저 물음(취소 = 층 그대로)
|
||||
if (!(await main.confirmLeave())) return;
|
||||
await switchStructure(next, nextOwner);
|
||||
};
|
||||
// 층 단추 셋(PLAN 29-2) — 「구조물 조합」 줄 아래 한 벌
|
||||
// 단추 옆 「+」 = 그 나무 맨 위 더하기(뿌리 줄을 갈음 · 29-6)
|
||||
// 켜진 단추를 다시 누르면 그 층 목록 화면(30-4 · 집계표)
|
||||
const layerBar = (open: OpenTarget, add: HTMLElement | null, reselect: () => void): HTMLElement =>
|
||||
const layerBar = (add: HTMLElement, reselect: () => void): HTMLElement =>
|
||||
createLayerBar({
|
||||
user,
|
||||
active: { root: entry.root, owner },
|
||||
company,
|
||||
go: (next, nextOwner) => void go(next, nextOwner, open),
|
||||
...(add ? { add } : {}),
|
||||
go: (next, nextOwner) => void go(next, nextOwner),
|
||||
add,
|
||||
reselect,
|
||||
});
|
||||
// 남의 회사 「개인」 — 고른 사람 줄 아래에 그 사람 나무(사람 줄 → 라이브러리 · 29-6)
|
||||
const peopleTree = (open: OpenTarget, panel: HTMLElement): HTMLElement =>
|
||||
const peopleTree = (panel: HTMLElement): HTMLElement =>
|
||||
people && owner && company
|
||||
? createPeopleTree({
|
||||
company,
|
||||
active: owner,
|
||||
panel,
|
||||
go: (next) => void go("personal", next, open),
|
||||
go: (next) => void go("personal", next),
|
||||
})
|
||||
: panel;
|
||||
const topHost = el("div");
|
||||
const treeHost = el("div");
|
||||
// 704-4 [구조물 조합으로] 만든 항목 → 나무 · 옆 목록 · 열린 표(행렬)를 다시(페이지가 내려가면 듣기 끝)
|
||||
const afterItem = async (): Promise<void> => {
|
||||
await Promise.all([side.refresh(), tree.refresh()]);
|
||||
@@ -169,29 +211,27 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
if (shown && (await main.settle()) !== false) await main.openTable(layer, null, true);
|
||||
};
|
||||
const onItem = (): void =>
|
||||
tree.root.isConnected ? void afterItem() : document.removeEventListener(ITEM_BUILT, onItem);
|
||||
main.root.isConnected ? void afterItem() : document.removeEventListener(ITEM_BUILT, onItem);
|
||||
document.addEventListener(ITEM_BUILT, onItem);
|
||||
const main = createMain(
|
||||
isAdmin,
|
||||
() => isAdmin,
|
||||
() => void Promise.all([side.refresh(), tree.refresh()]),
|
||||
() => side.paint(),
|
||||
{
|
||||
top: el("div", { children: [...(readOnlyBar ? [readOnlyBar.root] : []), share.top] }),
|
||||
// 「새 항목」(30-5) — 제목 줄 오른쪽 끝 · 쓸 수 있는 층만 · 상세 칸에 [새로 만들기] [복사 생성](39-5)
|
||||
...(canEdit(layer, isAdmin) ? { newItem: newItemButton(() => void openNew()) } : {}),
|
||||
top: topHost,
|
||||
// 「새 항목」(30-5) — 제목 줄 오른쪽 끝 · 쓸 수 있는 층만(편집 칸이 가림) · [새로 만들기] [복사 생성](39-5)
|
||||
newItem: newItemButton(() => void openNew()),
|
||||
onColumn: (column) => {
|
||||
readOnlyBar?.setColumn(column);
|
||||
tree.select(column);
|
||||
},
|
||||
// 표 머리 이름 칸 고침 = 나무 이름 바꾸기(31-1) · 쓸 수 있는 층만
|
||||
...(canEdit(layer, isAdmin)
|
||||
? {
|
||||
onHeadRename: (col: StructureColumn, level: number, name: string) =>
|
||||
void tree.editFromTable(headRenameEdit(col, level, name), col.id),
|
||||
}
|
||||
: {}),
|
||||
// 표 머리 이름 칸 고침 = 나무 이름 바꾸기(31-1) · 쓸 수 있는 층만(나무가 가림)
|
||||
onHeadRename: (col: StructureColumn, level: number, name: string) =>
|
||||
void tree.editFromTable(headRenameEdit(col, level, name), col.id),
|
||||
},
|
||||
);
|
||||
settleLayer(await resolve(entry.root, entry.owner ?? null));
|
||||
build();
|
||||
/** 새 구조물 조합(39-5 · 답 2) — 새로 만들기 = 나무 「항목 추가」 창 · 복사 생성 = 탭 + 목록 →
|
||||
* 지금 보는 층 · 나무에서 고른 폴더(없으면 맨 위) · 원래 이름(겹치면 「(2)」) · 새 키 · 새 번호 */
|
||||
const openNew = async (): Promise<void> => {
|
||||
@@ -214,12 +254,7 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
);
|
||||
};
|
||||
// 첫 화면 · 닫은 뒤(양식 지움 · 자재 조합 상세 닫음) = 구조물집계표 — 빈 화면 없음
|
||||
const home: Selection = {
|
||||
layer,
|
||||
projectId: null,
|
||||
kind: "table",
|
||||
name: STRUCTURE_TABLE,
|
||||
};
|
||||
const home = (): Selection => ({ layer, projectId: null, kind: "table", name: STRUCTURE_TABLE });
|
||||
// 일위대가 사전조합 층(134-3) — 페이지 층(해시 root)과 따로 · 마스터 · 볼 회사 · 내 개인(사람 나무 없음) ·
|
||||
// 못 가는 층(회사 없음) = 마스터 · 고른 층 = 주소 `pre`(페이지를 다시 세우지 않음 · replaceState)
|
||||
const preScope = (want: Root): PrecomboScope => {
|
||||
@@ -273,16 +308,13 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
reselect: () => void main.confirmLeave().then((ok) => ok && void main.open(sangeunList(sk))),
|
||||
});
|
||||
const side = buildSide({
|
||||
layer,
|
||||
isAdmin,
|
||||
onOpen: (sel) => void main.open(sel ?? home),
|
||||
isSystem: user?.role === "SYSTEM_ADMIN",
|
||||
canWrite: () => canEdit(layer, isAdmin),
|
||||
onOpen: (sel) => void main.open(sel ?? home()),
|
||||
getOpen: main.current,
|
||||
confirmLeave: main.confirmLeave,
|
||||
showView: main.showView,
|
||||
tree: [
|
||||
layerBar("구조물조합", tree.add, () => void main.openTable(layer)),
|
||||
peopleTree("구조물조합", tree.root),
|
||||
],
|
||||
tree: [treeHost],
|
||||
precombo: {
|
||||
scope: () => pre,
|
||||
bar: preBar,
|
||||
@@ -307,12 +339,11 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
|
||||
);
|
||||
root.innerHTML = "";
|
||||
root.append(layout);
|
||||
void main.open(home);
|
||||
void main.open(home());
|
||||
// 진입 해시 `open` — 옵션 · 도면 컨테이너는 마스터에만 ·
|
||||
// 옵션 화면이 뒤에 열리면 편집 칸 차례(`seq`)가 늦게 온 집계표를 버림
|
||||
if (entry.open !== "구조물조합") side.reveal(entry.open);
|
||||
mountSettingsDrawer(user ?? {}, "m02", entry.company); // ☰ 시스템 설정 바로가기(334)
|
||||
void tree.refresh();
|
||||
// 이 화면을 떠나면 층 길을 시스템 · 자기 층으로 되돌림(다른 화면의 항목 고르기가 남의 층을 안 부르게)
|
||||
return { covers: () => false, show: () => {}, dispose: () => setActiveLayer("system") };
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* 50-3 · 134-3) › 「구조물 조합」(층 단추 · 나무) › 수평 구분선 › 「일위대가 불러오기」(구조물 조합 층 ·
|
||||
* `Hopyo_Side` · 324-4) › 「도면 양식」(새로 · 본떠 · 지우기) › 실무문서.
|
||||
* 구조물 옵션 정의는 M01 로 옮김(716-1).
|
||||
* 도면 양식 · 실무문서 = 시스템 관리자면 늘(마스터 층 것 · 구조물 조합 층과 무관 · 720-4).
|
||||
* 서버 길은 계약 `6_계약.md`.
|
||||
* 구조물 도면 · 일위대가 · 면적산출 상세는 좌측 줄이 아니라 항목 카드 [수정]으로(PLAN 45-2).
|
||||
* 화면 이름 「구조물 조합」(PLAN 26-7a) — 파일 이름 · 양식 키 · API 길은 「구조물집계표」 그대로.
|
||||
@@ -34,7 +35,7 @@ import { STRUCTURE_TABLE } from "./M02_MasterTemplete_Structure";
|
||||
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 type { Selection } from "./M02_MasterTemplete_UI_Main";
|
||||
import { buildPrecomboSection, type PrecomboOptions } from "./M02_MasterTemplete_UI_Side_Precombo";
|
||||
import { buildSangeunSection, type SangeunOptions } from "./M02_MasterTemplete_UI_Side_Sangeun";
|
||||
import { createEmpty } from "@ui/ui_template_empty";
|
||||
@@ -54,10 +55,10 @@ const STUB_DRAWING = {
|
||||
};
|
||||
|
||||
export interface SideOptions {
|
||||
/** 이 화면의 층 — 도면 양식 목록은 시스템 층에만 있음 */
|
||||
layer: Layer;
|
||||
/** 이 층을 고칠 수 있나 */
|
||||
isAdmin: boolean;
|
||||
/** 시스템 관리자 — 도면 양식(시스템 층에만 있음) · 실무문서 칸을 늘 보임(720-4) */
|
||||
isSystem: boolean;
|
||||
/** 지금 구조물 조합 층을 고칠 수 있나(층이 제자리에서 바뀜) — 일위대가 불러오기 장부 */
|
||||
canWrite: () => boolean;
|
||||
onOpen: (sel: Selection | null) => void;
|
||||
getOpen: () => Selection | null;
|
||||
/** 저장 안 한 고침을 버릴지 확인 — 취소면 false */
|
||||
@@ -97,7 +98,7 @@ const failed = (error: unknown): void =>
|
||||
showToast(L("M02_ActionFailed").replace("{value}", why(error)), "error");
|
||||
|
||||
export function buildSide(opt: SideOptions): SideHandle {
|
||||
const layer = opt.layer;
|
||||
const layer: Layer = "system"; // 도면 양식 목록 · 고름의 층
|
||||
let items: TemplateInfo[] = [];
|
||||
let seq = 0;
|
||||
|
||||
@@ -112,7 +113,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
/** 새로 · 본떠 만들기 · 지우기 — 도면 양식 컨테이너 안(구조물 집계 양식은 집계표 하나라 없음) · 관리자만 */
|
||||
const editButtons = new Map<Kind, { copy: HTMLButtonElement; del: HTMLButtonElement }>();
|
||||
const actionRow = (kind: Kind): HTMLElement[] => {
|
||||
if (!canEdit(layer, opt.isAdmin)) return [];
|
||||
if (!opt.isSystem) return [];
|
||||
const btnNew = createButton({ label: L("M02_New"), variant: "filled", size: "sm" });
|
||||
const copy = createButton({ label: L("M02_Copy"), variant: "ghost", size: "sm" });
|
||||
const del = createButton({ label: L("M02_Delete"), variant: "danger", size: "sm" });
|
||||
@@ -160,23 +161,21 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
? void Promise.all([precombo.refresh(), sangeun.refresh()])
|
||||
: document.removeEventListener(HOPYO_BUILT, onBuilt);
|
||||
document.addEventListener(HOPYO_BUILT, onBuilt);
|
||||
// 도면 양식은 시스템 층(마스터 템플릿)에만
|
||||
const drawing =
|
||||
layer === "system"
|
||||
? section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing"))
|
||||
: null;
|
||||
// 실무문서(옛 마스터 일위대가 조합 · PLAN 39-2) — 구조물 조합 밖 · 도면 양식 아래 · 마스터 층 · 시스템 관리자만
|
||||
const practice =
|
||||
layer === "system" && opt.isAdmin
|
||||
? buildPracticeSection({
|
||||
confirmLeave: opt.confirmLeave,
|
||||
show: opt.showView,
|
||||
onOpen: paint,
|
||||
})
|
||||
: null;
|
||||
// 도면 양식은 시스템 층(마스터 템플릿)에만 — 시스템 관리자면 늘(720-4)
|
||||
const drawing = opt.isSystem
|
||||
? section(L("M02_KindDrawing"), bodies.get("drawing")!, ...actionRow("drawing"))
|
||||
: null;
|
||||
// 실무문서(옛 마스터 일위대가 조합 · PLAN 39-2) — 구조물 조합 밖 · 도면 양식 아래 · 시스템 관리자만
|
||||
const practice = opt.isSystem
|
||||
? buildPracticeSection({
|
||||
confirmLeave: opt.confirmLeave,
|
||||
show: opt.showView,
|
||||
onOpen: paint,
|
||||
})
|
||||
: null;
|
||||
// 일위대가 불러오기(324-4 · 제목은 718-1) · 층 = 구조물 조합 층 · 쓰기 권한 없으면 올리기 꺼짐
|
||||
const hopyo = buildHopyoSection({
|
||||
canWrite: canEdit(layer, opt.isAdmin),
|
||||
canWrite: opt.canWrite,
|
||||
confirmLeave: opt.confirmLeave,
|
||||
show: opt.showView,
|
||||
onOpen: () => (practice?.clear(), paint()),
|
||||
@@ -197,10 +196,10 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
|
||||
const root = el("div", { className: "m02-side ui-sidebar-panel", children: [listHost] });
|
||||
|
||||
/** 고름 표시 — 편집 칸에 지금 열린 것 하나뿐 */
|
||||
/** 고름 표시 — 편집 칸에 지금 열린 것 하나뿐(도면 양식 = 시스템 층뿐 · 구조물 조합 표 = 어느 층이든) */
|
||||
const isPicked = (kind: Kind, name: string): boolean => {
|
||||
const cur = opt.getOpen();
|
||||
return !!cur && cur.layer === layer && cur.kind === kind && cur.name === name;
|
||||
return !!cur && cur.kind === kind && cur.name === name;
|
||||
};
|
||||
|
||||
const isOpen = (info: TemplateInfo): boolean => isPicked(info.종류, info.이름);
|
||||
@@ -260,7 +259,7 @@ export function buildSide(opt: SideOptions): SideHandle {
|
||||
const mine = ++seq;
|
||||
paintButtons();
|
||||
try {
|
||||
items = await listTemplates(layer, null);
|
||||
items = opt.isSystem ? await listTemplates(layer, null) : []; // 도면 양식 목록 — 시스템 관리자만
|
||||
} catch (error) {
|
||||
items = [];
|
||||
showToast(L("M02_LoadFailed").replace("{value}", why(error)), "error");
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_화면_층_단추_최상위_폴더_이름_바꾸기():
|
||||
assert "setPre(next), void main.open(precomboList(pre))" in page
|
||||
assert "void main.open(sangeunList(sk))" in page.split("const skBar")[1].split("reselect:")[0]
|
||||
# 720-2 — 층 줄 「+」 = 서고 칸 맨 위 메뉴 · 메뉴 줄 = 같은 addFolder([])
|
||||
assert page.count(" add,\n") == 2
|
||||
assert page.count(" add,\n") == 3 # 구조물 조합 · 일위대가 · 단가산출
|
||||
assert "onClick: (event) => openTreeMenu(event, menuAt([]))" in lib
|
||||
assert "add.hidden = !at.canWrite;" in lib and "opt.bar(add)" in lib
|
||||
assert lib.count('L("M02_TreeAddTopFolder"), () => void addFolder([])') == 1 # 폴더 줄
|
||||
@@ -74,3 +74,23 @@ def test_화면_층_단추_최상위_폴더_이름_바꾸기():
|
||||
assert "이름: name }" in lib and "saveTemplate(at.layer, kind, row.이름, null" in lib
|
||||
main = _text("M02_MasterTemplete_UI_Main.ts")
|
||||
assert "title.textContent = at.label = named ? `${at.name} ${named}` : at.name;" in main
|
||||
|
||||
|
||||
def test_구조물_조합_층_제자리_바꿈():
|
||||
"""720-4 — 해시 이동 없음 · replaceState · 나무 · 층 줄 · 띠를 감싼 칸에 갈아 끼움 · 권한 = 함수 ·
|
||||
도면 양식 · 실무문서 = 시스템 관리자면 늘 · 불러오기 장부 = 열 때 층 · 권한."""
|
||||
page = _text("M02_MasterTemplete_UI_Page.ts")
|
||||
assert "navigateTo" not in page
|
||||
switch = page.split("const switchStructure = async")[1].split("const go =")[0]
|
||||
assert "history.replaceState(" in switch and "build();" in switch
|
||||
assert "await main.openTable(layer, null, true);" in switch
|
||||
assert "if (mine !== turn) return;" in switch # 늦게 온 옛 고름 버림
|
||||
build = page.split("const build = (): void => {")[1].split("const settleLayer")[0]
|
||||
assert "topHost.replaceChildren(" in build and "treeHost.replaceChildren(" in build
|
||||
assert "tree: [treeHost]," in page and "top: topHost," in page
|
||||
assert "() => isAdmin," in page and 'isSystem: user?.role === "SYSTEM_ADMIN",' in page
|
||||
assert "main.root.isConnected ? void afterItem()" in page # 나무를 갈아도 듣기 그대로
|
||||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||||
assert "const drawing = opt.isSystem" in side and "const practice = opt.isSystem" in side
|
||||
hopyo = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||||
assert "buildHopyoLedger({ scope: pageScope(), canWrite: opt.canWrite() })" in hopyo
|
||||
|
||||
@@ -106,7 +106,7 @@ def test_나무는_좌측_패널_찾기_칸_없음():
|
||||
assert 'info.이름 === STRUCTURE_TABLE ? L("M02_KindTable")' in side # 좌측 줄 이름
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert "createTreePanel({" in page and "tree.select(column)" in page
|
||||
assert 'layerBar("구조물조합", tree.add, () => void main.openTable(layer)),' in page
|
||||
assert "layerBar(tree.add, () => void main.openTable(layer))," in page
|
||||
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
|
||||
assert 'at.kind === "table" && at.name === STRUCTURE_TABLE && at.layer !== "project"' in main
|
||||
assert 'isCombo(next) ? L("M02_KindTable")' in main # 상세 제목
|
||||
|
||||
@@ -87,7 +87,9 @@ def test_머리_고침은_쓸_수_있는_층만_나무로():
|
||||
assert "readOnly || !extras.onHeadRename" in main
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert "tree.editFromTable(headRenameEdit(col, level, name), col.id)" in page
|
||||
assert page.index("...(canEdit(layer, isAdmin)\n ? {\n onHeadRename") > 0
|
||||
assert (
|
||||
"onHeadRename: (col: StructureColumn" in page
|
||||
) # 720-4 — 가림은 편집 칸 readOnly · 나무 canEdit
|
||||
tree = _text(M02 / "M02_MasterTemplete_TreePanel.ts")
|
||||
assert "if (opt.canEdit && (await ready())) await edit(body, colId);" in tree
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ def test_좌측_판_자리와_권한():
|
||||
order = ("precomboSec,", "summary,", "createDivider(),", "section(hopyo.title, hopyo.body),")
|
||||
assert [host.index(k) for k in order] == sorted(host.index(k) for k in order)
|
||||
assert host.index("section(hopyo.title, hopyo.body),") < host.index("drawing ?")
|
||||
assert "canWrite: canEdit(layer, opt.isAdmin)," in side # 구조물 조합 층 쓰기 권한
|
||||
assert "canWrite: opt.canWrite," in side # 구조물 조합 층 쓰기 권한(720-4 함수)
|
||||
assert "hopyo.paint();" in side.split("function paint(): void {")[1]
|
||||
hopyo_side = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||||
# 718-1 — 제목 = 화면 글 키 · [불러오기] = 구조물 조합 층 단추 꼴 → 본 칸 마법사 · 옛 파일 목록 없음
|
||||
@@ -351,7 +351,9 @@ def test_좌측_판_자리와_권한():
|
||||
assert 'label: "불러오기"' in start and 'start.classList.add("m02-layers__btn");' in start
|
||||
assert "buildHopyoWizard()" in hopyo_side and "listHopyo" not in hopyo_side
|
||||
assert "uploadHopyo" not in hopyo_side and "openHopyoSheet" not in hopyo_side
|
||||
assert "buildHopyoLedger({ scope, canWrite: opt.canWrite })" in hopyo_side # [별칭 장부]
|
||||
assert (
|
||||
"buildHopyoLedger({ scope: pageScope(), canWrite: opt.canWrite() })" in hopyo_side
|
||||
) # [별칭 장부]
|
||||
wizard = _text("M02_MasterTemplete_Hopyo_Wizard.ts")
|
||||
assert 'accept: ".xls,.xlsx"' in wizard and "input.hidden = true;" in wizard
|
||||
assert "accessFor(layer, null, user).canWrite" in wizard # 결과 층 = 쓸 수 있는 층만
|
||||
|
||||
@@ -181,7 +181,10 @@ def test_층_단추_셋_남의_회사_사람_나무():
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert "createLayerBar({" in page and "setActiveLayer(layer, owner)" in page
|
||||
assert "const company = viewCompany(entry, user);" in page
|
||||
assert "await main.confirmLeave()" in page.split("const go =")[1].split("navigateTo(")[0]
|
||||
# 720-4 — 다른 층 = 저장 안 한 고침 물음 뒤 제자리 바꿈(해시 이동 · 페이지 다시 세움 없음)
|
||||
go = page.split("const go =")[1].split("};")[0]
|
||||
assert "await main.confirmLeave()" in go and "await switchStructure(next, nextOwner);" in go
|
||||
assert "navigateTo" not in page
|
||||
assert 'dispose: () => setActiveLayer("system")' in page
|
||||
assert "side.reveal(entry.open)" in page
|
||||
roots = _text(M02 / "M02_MasterTemplete_TreePanel_Roots.ts")
|
||||
@@ -192,9 +195,9 @@ def test_층_단추_셋_남의_회사_사람_나무():
|
||||
assert "manages(company, user);" in roots.split("export const seesOthers")[1] # 맡은 회사(39-6)
|
||||
assert "const readOnly = !people && !accessFor(" in roots # 사람 나무 = 고침
|
||||
assert 'L("M02_PeopleEmpty")' in roots
|
||||
assert 'const people = entry.root === "personal" && seesOthers(company, user);' in page
|
||||
assert 'const many = want === "personal" && seesOthers(company, user);' in page
|
||||
assert '(await fetchOwners("personal", company).catch(() => []))[0]?.owner' in page
|
||||
assert 'peopleTree("구조물조합", tree.root)' in page
|
||||
assert "peopleTree(tree.root)" in page
|
||||
assert 'peopleTree("일위대가"' not in page # 일위대가 사람 나무 벌은 39-2 에서 지움
|
||||
panel = _text(M02 / "M02_MasterTemplete_TreePanel.ts")
|
||||
assert (
|
||||
@@ -223,8 +226,8 @@ def test_층_길_접두어와_고정_풀림():
|
||||
in labor
|
||||
)
|
||||
side = _text(M02 / "M02_MasterTemplete_UI_Side.ts")
|
||||
assert "const layer = opt.layer;" in side
|
||||
assert 'layer === "system"' in side # 도면 양식은 시스템 층에만
|
||||
assert 'const layer: Layer = "system";' in side # 도면 양식은 시스템 층에만
|
||||
assert "const drawing = opt.isSystem" in side # 720-4 — 시스템 관리자면 늘
|
||||
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
|
||||
assert 'sel.layer !== "system"' not in main
|
||||
assert 'return layer === "project" || isAdmin;' in main
|
||||
@@ -236,7 +239,7 @@ def test_읽기만_회사용_개인용으로_복사_막대():
|
||||
assert (
|
||||
"readOnlyBar?.setColumn(column)" in page
|
||||
) # 열 고름 → 막대 · 구조물 라이브러리가 함께 받음
|
||||
assert "accessFor(layer, owner, user).needCompany" in page # 회사 없음 = 마스터로
|
||||
assert "if (accessFor(lay, who, user).needCompany)" in page # 회사 없음 = 마스터로
|
||||
assert "buildNeedCompany()" in _text(M02 / "M02_MasterTemplete_TreePanel_Roots.ts")
|
||||
assert 'title: L("M02_Title")' in page # 한 화면 한 제목
|
||||
layer = _text(LAYER)
|
||||
@@ -290,7 +293,7 @@ def test_켜진_층_단추는_목록으로_복사_끌기():
|
||||
roots = _text(M02 / "M02_MasterTemplete_TreePanel_Roots.ts")
|
||||
assert "if (!current) opt.go(root, owner);\n else opt.reselect?.();" in roots
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert 'layerBar("구조물조합", tree.add, () => void main.openTable(layer))' in page
|
||||
assert "layerBar(tree.add, () => void main.openTable(layer))" in page
|
||||
# 39-6 — 쓸 수 있는 층 전부(볼 회사 주인까지 · 지금 층 뺌) · 대상 폴더 고르는 창
|
||||
assert 'const toOwner = one === "company" ? companyOwner(company, user) : null;' in page
|
||||
assert "(to === layer && toOwner === here)) return [];" in page
|
||||
@@ -316,12 +319,10 @@ def test_구조물_조합_제목_줄_새_항목():
|
||||
r"\s*\.\.\.\(extras\.newItem \? \[extras\.newItem\] : \[\]\),?\s*\]",
|
||||
main,
|
||||
)
|
||||
assert "if (extras.newItem) extras.newItem.hidden = !combo;" in main
|
||||
# 720-4 — 쓸 수 있는 층만 = 편집 칸이 고른 것 권한으로 가림(층이 제자리에서 바뀜)
|
||||
assert "extras.newItem.hidden = !combo || !sel || !writable(sel);" in main
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert (
|
||||
"...(canEdit(layer, isAdmin) ? { newItem: newItemButton(() => void openNew()) } : {})"
|
||||
in page
|
||||
)
|
||||
assert "newItem: newItemButton(() => void openNew())," in page
|
||||
panel = _text(M02 / "M02_MasterTemplete_TreePanel.ts")
|
||||
assert 'return at?.kind === "folder" ? at.path : [];' in panel # 고른 폴더(39-5 folder())
|
||||
assert "const newItem = (): void => void addItem(folder());" in panel
|
||||
|
||||
@@ -51,9 +51,9 @@ def test_통째_지우기_시스템_관리자만_판_쓰는_곳(world, master_co
|
||||
|
||||
def test_화면_시스템_관리자_마스터만_물음_두_번_읽기만():
|
||||
side = (M02 / "M02_MasterTemplete_UI_Side.ts").read_text("utf-8")
|
||||
assert 'layer === "system" && opt.isAdmin' in side
|
||||
assert "const practice = opt.isSystem" in side # 720-4 — 시스템 관리자면 늘
|
||||
order = side.split('className: "m02-side__lists ui-sidebar-panel",')[1].split("});")[0]
|
||||
assert order.index("extras.도면") < order.index("practice") # 도면 양식 아래
|
||||
assert order.index("drawing ?") < order.index("practice ?") # 도면 양식 아래
|
||||
view = (M02 / "M02_MasterTemplete_Practice.ts").read_text("utf-8")
|
||||
assert view.count("showConfirmDialog(") == 2
|
||||
assert "dropPractice(" in view and "createInputField" not in view # 고칠 칸 없음
|
||||
|
||||
@@ -72,15 +72,17 @@ def test_사전조합_층_단추는_페이지를_다시_안_세움():
|
||||
assert "history.replaceState(" in pre and "side.refreshPrecombo()" in pre
|
||||
assert "navigateTo" not in pre and "setActiveLayer" not in pre # 페이지 층 · 해시 이동 없음
|
||||
assert "entry.pre = pre.root;" in pre # 구조물 조합 층 이동(go) 때도 주소에 남음
|
||||
bar = page.split("const preBar = (add: HTMLElement): HTMLElement =>")[1].split("const side = buildSide(")[0]
|
||||
bar = page.split("const preBar = (add: HTMLElement): HTMLElement =>")[1].split(
|
||||
"const side = buildSide("
|
||||
)[0]
|
||||
# 720-1 — 다른 층 = 저장 안 한 고침 물음 → 그 층 나무 + 목록표
|
||||
go = bar.split("go: (next) =>")[1].split("reselect:")[0]
|
||||
assert ".confirmLeave()" in go and "setPre(next), void main.open(precomboList(pre))" in go
|
||||
sk = page.split("const skBar = (")[1].split("const side = buildSide(")[0]
|
||||
assert ".confirmLeave()" in sk and "void main.open(sangeunList(sk))" in sk.split("reselect:")[0]
|
||||
# 구조물 조합 층 단추는 그대로 해시 이동 한 벌
|
||||
assert page.count('layerBar("') == 1
|
||||
assert 'layerBar("구조물조합", tree.add, () => void main.openTable(layer)),' in page
|
||||
# 구조물 조합 층 단추 한 벌(720-4 제자리 바꿈)
|
||||
assert page.count("layerBar(") == 1
|
||||
assert "layerBar(tree.add, () => void main.openTable(layer))," in page
|
||||
assert "precombo: {\n scope: () => pre,\n bar: preBar," in page # 706-1 more 덧붙음
|
||||
|
||||
|
||||
@@ -92,7 +94,7 @@ def test_고름에_owner_권한_서고_길에_명시_owner():
|
||||
assert "listTemplates(at.layer, null, at.owner)" in lib_side
|
||||
main = _text("M02_MasterTemplete_UI_Main.ts")
|
||||
assert (
|
||||
"const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin);"
|
||||
"const writable = (at: Selection): boolean => at.canWrite ?? canEdit(at.layer, isAdmin());"
|
||||
in main
|
||||
)
|
||||
assert "readTemplate(next.layer, next.kind, next.name, next.projectId, next.owner)" in main
|
||||
|
||||
@@ -180,7 +180,7 @@ def test_면적_오른쪽_일위대가_패널은_공용_틀_손잡이():
|
||||
def test_첫_화면과_닫은_뒤는_구조물집계표():
|
||||
page = _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
assert re.search(r'kind: "table",\s*name: STRUCTURE_TABLE', page)
|
||||
assert "main.open(sel ?? home)" in page and "void main.open(home);" in page
|
||||
assert "main.open(sel ?? home())" in page and "void main.open(home());" in page
|
||||
|
||||
|
||||
def test_좌측_고름_표시는_열린_것_한_줄뿐():
|
||||
|
||||
Reference in New Issue
Block a user