export type OrgChartTenantSelection = { id: string; name: string; }; export type TenantFilterTarget = { id?: string; slug?: string; type?: string; parentId?: string | null; name?: string; }; export type HanmacFamilyUserTarget = { companyCode?: string; tenantSlug?: string; tenant?: TenantFilterTarget; joinedTenants?: TenantFilterTarget[]; metadata?: Record; }; type OrgChartPickerMessage = { type?: unknown; payload?: { selections?: Array<{ type?: unknown; id?: unknown; name?: unknown; }>; }; }; type OrgChartTenantPickerOptions = { tenantId?: string; }; type OrgChartLoginOptions = { returnTo?: string; }; function isSystemTenant(tenant: TenantFilterTarget) { const slug = tenant.slug?.trim().toLowerCase(); const type = tenant.type?.trim().toUpperCase(); return ( !tenant.id?.trim() || !tenant.slug?.trim() || type === "SYSTEM" || slug === "system" || slug === "global" ); } function isInTenantSubtree( tenant: T, rootTenantId: string, tenantById: Map, ) { if (!rootTenantId) { return false; } if (tenant.id === rootTenantId) { return true; } const visitedTenantIds = new Set(); let parentId = tenant.parentId ?? ""; while (parentId) { if (parentId === rootTenantId) { return true; } if (visitedTenantIds.has(parentId)) { return false; } visitedTenantIds.add(parentId); parentId = tenantById.get(parentId)?.parentId ?? ""; } return false; } function resolveHanmacFamilyTenantId( tenants: T[], hanmacFamilyTenantId?: string, ) { const envTenantId = hanmacFamilyTenantId?.trim(); if (envTenantId) return envTenantId; return ( tenants.find((tenant) => tenant.slug?.toLowerCase() === "hanmac-family") ?.id ?? "" ); } export function isHanmacFamilyTenant( tenant: T | undefined, tenants: T[], hanmacFamilyTenantId?: string, ) { if (!tenant || !tenant.id) return false; const rootTenantId = resolveHanmacFamilyTenantId( tenants, hanmacFamilyTenantId, ); if (!rootTenantId) return false; const tenantById = new Map( tenants .filter((item) => item.id?.trim()) .map((item) => [item.id as string, item]), ); const target = tenantById.get(tenant.id) ?? tenant; return isInTenantSubtree(target, rootTenantId, tenantById); } export function isHanmacFamilyUser( user: HanmacFamilyUserTarget, tenants: T[], hanmacFamilyTenantId?: string, ) { const metadata = user.metadata ?? {}; if (metadata.hanmacFamily === true || metadata.userType === "hanmac") { return true; } const tenantBySlug = new Map( tenants .filter((tenant) => tenant.slug?.trim()) .map((tenant) => [tenant.slug?.toLowerCase() as string, tenant]), ); const tenantCandidates = [ user.tenant, ...(user.joinedTenants ?? []), tenantBySlug.get(user.companyCode?.toLowerCase() ?? ""), tenantBySlug.get(user.tenantSlug?.toLowerCase() ?? ""), ]; return tenantCandidates.some((tenant) => isHanmacFamilyTenant(tenant, tenants, hanmacFamilyTenantId), ); } export function filterNonHanmacFamilyTenants( tenants: T[], hanmacFamilyTenantId?: string, ) { const rootTenantId = resolveHanmacFamilyTenantId( tenants, hanmacFamilyTenantId, ); const tenantById = new Map( tenants .filter((tenant) => tenant.id?.trim()) .map((tenant) => [tenant.id as string, tenant]), ); return tenants.filter( (tenant) => !isSystemTenant(tenant) && !isInTenantSubtree(tenant, rootTenantId, tenantById), ); } export function buildOrgChartTenantPickerUrl( baseUrl?: string, options: OrgChartTenantPickerOptions = {}, ) { const normalizedBase = (baseUrl ?? "").replace(/\/+$/, ""); const params = new URLSearchParams({ mode: "single", select: "tenant", width: "400", height: "600", }); const tenantId = options.tenantId?.trim(); if (tenantId) { params.set("tenantId", tenantId); } return `${normalizedBase}/embed/picker?${params.toString()}`; } export function buildAuthenticatedOrgChartTenantPickerUrl( baseUrl?: string, options: OrgChartTenantPickerOptions = {}, ) { const pickerUrl = buildOrgChartTenantPickerUrl("", options); return buildAuthenticatedOrgChartUrl(baseUrl, { returnTo: pickerUrl }); } export function buildAuthenticatedOrgChartUrl( baseUrl?: string, options: OrgChartLoginOptions = {}, ) { const normalizedBase = (baseUrl ?? "").replace(/\/+$/, ""); const returnTo = options.returnTo?.trim() || "/chart"; const params = new URLSearchParams({ auto: "1", returnTo, }); return `${normalizedBase}/login?${params.toString()}`; } export function parseOrgChartTenantSelection( message: unknown, ): OrgChartTenantSelection | null { const data = message as OrgChartPickerMessage; if (data?.type !== "orgfront:picker:confirm") { return null; } const selection = data.payload?.selections?.[0]; if ( selection?.type !== "tenant" || typeof selection.id !== "string" || typeof selection.name !== "string" || selection.id.trim() === "" ) { return null; } return { id: selection.id, name: selection.name, }; }