Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_TreePanel_Roots.ts
T
eomsangdonandClaude Opus 5.5 cd2ee5664d feat(M02): 좌측 층 단추 셋 · 나무를 좌측 패널로 · 탐색기 모양 나무 · 사람 고르기 · 해시 company (PLAN 29-2)
- 「일위대가 라이브러리」 · 「구조물 조합」 줄 아래 층 단추 셋(마스터 · 회사 · 개인 · 권한 없으면 「읽기」) → 고른 층의 나무를 좌측 패널에 · 상세는 표 · 편집만
- 찾기 칸은 상세 위쪽(구조물 조합일 때 · 일위대가 화면 맨 위)
- 회사 → 사람 펼침 없앰 · 「사람 고르기」 select(같은 줄 · 회사 관리자 · 시스템 관리자) → 해시 owner
- 시스템 관리자 볼 회사 = 해시 company · sessionStorage aislo.adminCompany · 자기 회사(viewCompany)
- 공용 나무(ui_template_tree) — 들여쓰기 안내선 · 폴더 · 항목 아이콘 · 폴더 굵게(M01 · 고르기 모달도 같이)
- 시험 — combo_screen · labor_tree · layer_screen 새 배치 · viewCompany Node 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 17:37:46 +09:00

117 lines
5.0 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_TreePanel_Roots.ts
* 라이브러리 층 단추 셋(마스터 · 회사 · 개인 · PLAN 29-2) — 좌측 패널 「일위대가 라이브러리」 ·
* 「구조물 조합」 아래 한 줄 · 나무 종류와 무관한 공용 부품. 고른 층의 나무는 부르는 쪽이 이 줄 아래에 붙임.
* 권한 없는 층 = 단추 글 끝에 「읽기」(권한 = `accessFor` · 서버 `M02_Layer_Access` 와 짝)
* 회사 = 볼 회사(`viewCompany` — 시스템 관리자는 해시 `company` · `aislo.adminCompany` · 자기 회사)
* 「사람 고르기」(같은 줄 select · 회사 관리자 · 시스템 관리자) = 그 회사 사람의 개인 층 · 고르면 해시 `owner`
* 누르면 `go`(부르는 쪽이 해시로 다시 엶).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { t as L } from "@ui/ui_template_locale";
import type { SessionUser } from "../A06_Login/A06_Login_Api_Fetch";
import { fetchOwners } from "./M02_MasterTemplete_Api_Fetch";
import {
accessFor,
buildNeedCompany,
layerOfRoot,
ownOwner,
type Root,
} from "./M02_MasterTemplete_Layer";
export interface LayerBarOptions {
user: SessionUser | null;
/** 지금 고른 층 — `owner` null = 자기 층 */
active: { root: Root; owner: string | null };
/** 볼 회사 id — 없으면 회사 · 개인 단추 없음 */
company: string | null;
/** 다른 층을 누름 — `owner` null = 자기 층 */
go: (root: Root, owner: string | null) => void;
}
/** 회사 단추의 주인 — 볼 회사가 자기 회사면 null(자기 층) */
export const companyOwner = (company: string | null, user: SessionUser | null): string | null =>
company && company !== ownOwner("company", user) ? company : null;
export function createLayerBar(opt: LayerBarOptions): HTMLElement {
const { user, active } = opt;
const isSystem = user?.role === "SYSTEM_ADMIN";
const companyAdmin = isSystem || user?.role === "ADMIN" || !!user?.is_master;
const ownPersonal = ownOwner("personal", user);
if (!opt.company && !isSystem) {
return el("div", {
className: "m02-layers",
children: [button("master", null), buildNeedCompany()],
});
}
function button(root: Root, owner: string | null): HTMLButtonElement {
const readOnly = !accessFor(layerOfRoot(root), owner, user).canWrite;
const name = L(
root === "master"
? "M02_PickerSystem"
: root === "company"
? "M02_PickerCompany"
: "M02_PickerPersonal",
);
const current = root === active.root && (root !== "personal" || !active.owner);
const b = el("button", {
className: `m02-layers__btn${current ? " is-active" : ""}`,
text: readOnly ? `${name} · ${L("M02_RootReadOnly")}` : name,
attrs: { type: "button", "data-root": root, "aria-pressed": String(current) },
});
b.addEventListener("click", () => {
if (!current) opt.go(root, owner);
});
return b;
}
const buttons = [
button("master", null),
...(opt.company ? [button("company", companyOwner(opt.company, user))] : []),
...(ownPersonal ? [button("personal", null)] : []),
];
const row = el("div", { className: "m02-layers__row", children: buttons });
const bar = el("div", { className: "m02-layers", children: [row] });
if (companyAdmin && opt.company) row.append(buildPeople(opt)); // 같은 줄
return bar;
}
/** 「사람 고르기」 — 그 회사 사람(본인 뺌 · 층 폴더 있는 사람)의 개인 층 · 지금 고른 남의 층은 목록에 없어도 보임 */
function buildPeople(opt: LayerBarOptions): HTMLElement {
const { active } = opt;
const picked = active.root === "personal" ? active.owner : null;
const select = el("select", {
className: "m02-layers__people",
attrs: { "aria-label": L("M02_PeoplePick") },
}) as HTMLSelectElement;
const option = (value: string, text: string): HTMLOptionElement => {
const one = el("option", { text, attrs: { value } }) as HTMLOptionElement;
one.selected = value === (picked ?? "");
return one;
};
select.append(option("", "—"), ...(picked ? [option(picked, picked)] : []));
select.disabled = true;
void fetchOwners("personal", opt.company)
.then((list) => {
const rows =
picked && !list.some((one) => one.owner === picked)
? [...list, { owner: picked, name: picked }]
: list;
select.replaceChildren(option("", "—"), ...rows.map((one) => option(one.owner, one.name)));
select.disabled = rows.length === 0;
select.title = rows.length ? "" : L("M02_RootNone");
})
.catch((error: unknown) => {
select.title = error instanceof Error ? error.message : "";
});
select.addEventListener("change", () => {
if (select.value) opt.go("personal", select.value);
});
return el("label", {
className: "m02-layers__pick",
children: [el("span", { text: L("M02_PeoplePick") }), select],
});
}