import type { TenantSummary, UserSummary } from "../../lib/adminApi"; import { type TenantNode, buildTenantFullTree } from "../../lib/tenantTree"; import type { OrgPickerTreeNode } from "./pickerTypes"; import { filterTenantsByVisibility } from "./tenantVisibility"; 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 isHanmacFamilyCompanyGroup(tenant: TenantSummary) { return ( tenant.type.toUpperCase() === "COMPANY_GROUP" && tenant.slug.toLowerCase() === "hanmac-family" ); } function findTenantByRef(tenants: TenantSummary[], ref?: string) { const normalizedRef = ref?.trim().toLowerCase(); if (!normalizedRef) return undefined; return ( tenants.find((tenant) => tenant.slug.toLowerCase() === normalizedRef) ?? tenants.find((tenant) => tenant.id === ref) ); } function tenantToPickerNode( tenant: TenantNode, usersBySlug: Map, ): 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[], tenantRef: string, ): TenantNode | undefined { const findBySlug = (node: TenantNode): TenantNode | undefined => { if (node.slug.toLowerCase() === tenantRef.trim().toLowerCase()) { return node; } for (const child of node.children) { const match = findBySlug(child); if (match) return match; } return undefined; }; const findById = (node: TenantNode): TenantNode | undefined => { if (node.id === tenantRef) return node; for (const child of node.children) { const match = findById(child); if (match) return match; } return undefined; }; for (const root of roots) { const slugMatch = findBySlug(root); if (slugMatch) return slugMatch; } for (const root of roots) { const idMatch = findById(root); if (idMatch) return idMatch; } return undefined; } export function buildOrgPickerTree({ tenants, users, rootTenantId, tenantId, }: { tenants: TenantSummary[]; users: UserSummary[]; rootTenantId?: string; tenantId?: string; }) { const visibleTenants = filterTenantsByVisibility( tenants.filter(isOrgFrontTenantType), "internal", ); const usersBySlug = new Map(); 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 = findTenantByRef(visibleTenants, rootTenantId) ?? visibleTenants.find(isHanmacFamilyCompanyGroup) ?? 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; }