1
0
forked from baron/baron-sso
Files
baron-sso/orgfront/src/features/orgchart/pickerTree.ts
2026-05-07 11:05:07 +09:00

143 lines
3.9 KiB
TypeScript

import type { TenantSummary, UserSummary } from "../../lib/adminApi";
import { type TenantNode, buildTenantFullTree } from "../../lib/tenantTree";
import type { OrgPickerTreeNode } from "./pickerTypes";
import { getOrgChartUserDisplayName } from "./userDisplay";
function getUserTenantSlug(user: UserSummary) {
return (
user.companyCode?.toLowerCase() || user.tenantSlug?.toLowerCase() || ""
);
}
function isOrgFrontTenantType(tenant: TenantSummary) {
return ["COMPANY_GROUP", "COMPANY", "ORGANIZATION", "USER_GROUP"].includes(
tenant.type.toUpperCase(),
);
}
function getCompanyGroupId(node: TenantNode, allTenants: TenantSummary[]) {
let cursor: TenantSummary | undefined = node;
const byId = new Map(allTenants.map((tenant) => [tenant.id, tenant]));
while (cursor?.parentId) {
const parent = byId.get(cursor.parentId);
if (!parent) break;
cursor = parent;
}
return cursor?.type === "COMPANY_GROUP" ? cursor.id : node.id;
}
function tenantToPickerNode(
tenant: TenantNode,
usersBySlug: Map<string, UserSummary[]>,
): OrgPickerTreeNode {
const tenantChildren = tenant.children.map((child) =>
tenantToPickerNode(child, usersBySlug),
);
const userChildren = (usersBySlug.get(tenant.slug.toLowerCase()) || []).map(
(user) => ({
type: "user" as const,
id: user.id,
name: getOrgChartUserDisplayName(user, tenant),
parentId: tenant.id,
user,
children: [],
}),
);
return {
type: "tenant",
id: tenant.id,
name: tenant.name,
parentId: tenant.parentId ?? null,
tenant,
children: [...userChildren, ...tenantChildren],
};
}
function findTenantNode(
roots: TenantNode[],
tenantId: string,
): TenantNode | undefined {
for (const root of roots) {
if (root.id === tenantId) return root;
const child = findTenantNode(root.children, tenantId);
if (child) return child;
}
return undefined;
}
export function buildOrgPickerTree({
tenants,
users,
rootTenantId,
tenantId,
}: {
tenants: TenantSummary[];
users: UserSummary[];
rootTenantId?: string;
tenantId?: string;
}) {
const visibleTenants = tenants.filter(isOrgFrontTenantType);
const usersBySlug = new Map<string, UserSummary[]>();
for (const user of users) {
if (user.status !== "active") continue;
const slug = getUserTenantSlug(user);
if (!slug) continue;
const list = usersBySlug.get(slug) || [];
list.push(user);
usersBySlug.set(slug, list);
}
const companyGroup =
visibleTenants.find((tenant) => tenant.id === rootTenantId) ??
visibleTenants.find((tenant) => tenant.type === "COMPANY_GROUP") ??
visibleTenants.find((tenant) => !tenant.parentId);
if (!companyGroup) return { roots: [], companies: [], companyGroupId: "" };
const { currentBase } = buildTenantFullTree(visibleTenants, companyGroup.id);
const groupNode =
currentBase ??
buildTenantFullTree(visibleTenants).subTree.find(
(node) => node.id === companyGroup.id,
);
if (!groupNode) return { roots: [], companies: [], companyGroupId: "" };
const companies = groupNode.children.filter(
(node) => node.type === "COMPANY",
);
const scopedRoot = tenantId
? findTenantNode([groupNode], tenantId)
: groupNode;
const filteredRoots = scopedRoot ? [scopedRoot] : [];
const roots = filteredRoots.map((node) =>
tenantToPickerNode(node, usersBySlug),
);
return {
roots,
companies: companies.map((company) => ({
id: company.id,
name: company.name,
companyGroupTenantId: getCompanyGroupId(company, tenants),
})),
companyGroupId: companyGroup.id,
};
}
export function flattenDescendants(node: OrgPickerTreeNode) {
const descendants: OrgPickerTreeNode[] = [];
const walk = (current: OrgPickerTreeNode) => {
for (const child of current.children) {
descendants.push(child);
walk(child);
}
};
walk(node);
return descendants;
}