- 오른쪽 클릭 「마스터 · 회사 · 개인으로 복사」 — 쓸 수 있는 자기 층(지금 층 뺌) 맨 위에 바로 · 새 키 · 같은 이름 「(2)」 - 끌어다 옮기기 — 같은 층 · 폴더 줄 위 = 그 안 · 빈 자리 = 맨 위 · 고른 것 전부 · 제 안으로 막음 · 읽기 층은 끌기 없음 - 켜진 층 단추를 다시 누르면 그 층 목록(구조물 = 집계표 · 일위대가 = 조합 목록 · 저장 안 한 고침은 물음) - 시험 맞춤 · 30-4 시험 한 건 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
134 lines
5.9 KiB
TypeScript
134 lines
5.9 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_TreePanel_Roots.ts
|
|
* 라이브러리 층 단추 셋(마스터 · 회사 · 개인 · PLAN 29-2) — 좌측 패널 「일위대가 라이브러리」 ·
|
|
* 「구조물 조합」 아래 한 줄 · 나무 종류와 무관한 공용 부품. 고른 층의 나무는 부르는 쪽이 이 줄 아래에 붙임.
|
|
* 권한 없는 층 = 단추 글 끝에 「읽기」(권한 = `accessFor` · 서버 `M02_Layer_Access` 와 짝)
|
|
* 회사 = 볼 회사(`viewCompany` — 시스템 관리자는 해시 `company` · `aislo.adminCompany` · 자기 회사)
|
|
* 시스템 관리자가 남의 회사를 보면 그 회사 관리자처럼(PLAN 29-6) — 「개인」 = 그 회사 사람 나무
|
|
* (`createPeopleTree` · 사람 줄 → 그 사람 라이브러리 · 읽기) · 자기 개인 층은 자기 회사를 고를 때만
|
|
* 누르면 `go`(부르는 쪽이 해시로 다시 엶).
|
|
* ========================================================================== */
|
|
|
|
import { el } from "@ui/ui_template_elements";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import { createTreeRow } from "@ui/ui_template_tree";
|
|
import type { SessionUser } from "../A06_Login/A06_Login_Api_Fetch";
|
|
import { fetchOwners, type LayerOwner } 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;
|
|
/** 단추 옆 「+」(고른 층 나무 맨 위 폴더 · 항목 더하기 · PLAN 29-6) */
|
|
add?: HTMLElement;
|
|
/** 켜진 단추를 다시 누름 — 그 층 목록 화면으로(열어 둔 항목 · 조합에서 · PLAN 30-4) */
|
|
reselect?: () => void;
|
|
}
|
|
|
|
/** 회사 단추의 주인 — 볼 회사가 자기 회사면 null(자기 층) */
|
|
export const companyOwner = (company: string | null, user: SessionUser | null): string | null =>
|
|
company && company !== ownOwner("company", user) ? company : null;
|
|
|
|
/** 시스템 관리자가 남의 회사를 봄 — 「개인」 단추 = 그 회사 사람 나무(읽기) */
|
|
export const seesOthers = (company: string | null, user: SessionUser | null): boolean =>
|
|
user?.role === "SYSTEM_ADMIN" && !!companyOwner(company, user);
|
|
|
|
export function createLayerBar(opt: LayerBarOptions): HTMLElement {
|
|
const { user, active } = opt;
|
|
const isSystem = user?.role === "SYSTEM_ADMIN";
|
|
const ownPersonal = ownOwner("personal", user);
|
|
const others = seesOthers(opt.company, user);
|
|
if (!opt.company && !isSystem) {
|
|
return el("div", {
|
|
className: "m02-layers",
|
|
children: [button("master", null), buildNeedCompany()],
|
|
});
|
|
}
|
|
|
|
function button(root: Root, owner: string | null, people = false): HTMLButtonElement {
|
|
const readOnly = people || !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" || people || !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);
|
|
else opt.reselect?.();
|
|
});
|
|
return b;
|
|
}
|
|
|
|
const buttons = [
|
|
button("master", null),
|
|
...(opt.company ? [button("company", companyOwner(opt.company, user))] : []),
|
|
...(others ? [button("personal", null, true)] : ownPersonal ? [button("personal", null)] : []),
|
|
];
|
|
const row = el("div", {
|
|
className: "m02-layers__row",
|
|
children: [...buttons, ...(opt.add ? [opt.add] : [])],
|
|
});
|
|
return el("div", { className: "m02-layers", children: [row] });
|
|
}
|
|
|
|
export interface PeopleTreeOptions {
|
|
/** 볼 회사 id */
|
|
company: string;
|
|
/** 지금 고른 사람의 개인 층 주인 `{C}/{U}` */
|
|
active: string;
|
|
/** 고른 사람 줄 아래에 붙일 그 사람 라이브러리 나무 */
|
|
panel: HTMLElement;
|
|
/** 다른 사람을 누름 */
|
|
go: (owner: string) => void;
|
|
}
|
|
|
|
/** 그 회사 사람 나무 — 사람 줄(층 폴더 있는 사람) → 고른 사람 줄 아래 그 사람 라이브러리(읽기) ·
|
|
* 지금 고른 사람은 목록에 없어도 줄을 세움 */
|
|
export function createPeopleTree(opt: PeopleTreeOptions): HTMLElement {
|
|
const box = el("div", { className: "m02-people" });
|
|
const paint = (list: LayerOwner[]): void => {
|
|
const rows = list.some((one) => one.owner === opt.active)
|
|
? list
|
|
: [...list, { owner: opt.active, name: opt.active }];
|
|
const wrap = el("div", { className: "m02-people__panel", children: [opt.panel] });
|
|
box.replaceChildren(
|
|
...rows.flatMap((one) => {
|
|
const current = one.owner === opt.active;
|
|
const node = { id: one.owner, label: one.name, count: null, folder: true };
|
|
const row = createTreeRow(node, current ? "▾" : "▸", 0, () => {
|
|
if (!current) return opt.go(one.owner);
|
|
wrap.hidden = !wrap.hidden;
|
|
const caret = row.querySelector(".ui-tree__caret");
|
|
if (caret) caret.textContent = wrap.hidden ? "▸" : "▾";
|
|
});
|
|
row.classList.toggle("is-active", current);
|
|
return current ? [row, wrap] : [row];
|
|
}),
|
|
);
|
|
};
|
|
paint([]);
|
|
void fetchOwners("personal", opt.company)
|
|
.then(paint)
|
|
.catch(() => undefined); // 목록을 못 받아도 고른 사람 나무는 보임
|
|
return box;
|
|
}
|