feat(org-chart): add core cards and fixed-layout integration
This commit is contained in:
1 parent
d18e3c3380
commit
61c6f8d1f1
161 files changed
+13044
-1
No files matched your search
@@ -0,0 +1,56 @@
|
||||
import { normalizeOrgChartDataset } from './normalize.js';
|
||||
import type { ApprovedAggregationPolicy, Employee, OrgChartDataset, OrgChartSummary, SummaryCounts, WorkStatus } from './types.js';
|
||||
|
||||
const statuses: WorkStatus[] = ['active', 'dispatched', 'onLeave'];
|
||||
function supportedPolicy(policy: ApprovedAggregationPolicy): boolean {
|
||||
const validStatuses = (values: unknown): values is WorkStatus[] => Array.isArray(values)
|
||||
&& values.every(value => statuses.includes(value)) && new Set(values).size === values.length;
|
||||
return !!policy && typeof policy.policyVersion === 'string' && !!policy.policyVersion.trim()
|
||||
&& !/[\u0000-\u001f<>]/.test(policy.policyVersion)
|
||||
&& validStatuses(policy.includedStatuses) && validStatuses(policy.activeStatuses)
|
||||
&& policy.activeStatuses.every(status => policy.includedStatuses.includes(status))
|
||||
&& policy.includeLeaders === true && policy.membershipMode === 'primaryOnly';
|
||||
}
|
||||
function count(data: OrgChartDataset, employees: readonly Employee[]): SummaryCounts {
|
||||
const counts: SummaryCounts = {
|
||||
total: 0,
|
||||
byOrganization: Object.create(null) as SummaryCounts['byOrganization'],
|
||||
byPosition: Object.create(null) as SummaryCounts['byPosition'],
|
||||
byAffiliation: Object.create(null) as SummaryCounts['byAffiliation'],
|
||||
byStatus: { active: 0, dispatched: 0, onLeave: 0 },
|
||||
};
|
||||
for (const org of data.organizations) counts.byOrganization[org.id] = { direct: 0, inclusive: 0 };
|
||||
for (const affiliation of data.affiliations) counts.byAffiliation[affiliation.id] = 0;
|
||||
const seen = new Set<string>();
|
||||
for (const employee of employees) {
|
||||
if (seen.has(employee.id)) continue;
|
||||
seen.add(employee.id);
|
||||
counts.total += 1;
|
||||
counts.byOrganization[employee.organizationId]!.direct += 1;
|
||||
counts.byPosition[employee.position] = (counts.byPosition[employee.position] ?? 0) + 1;
|
||||
counts.byAffiliation[employee.affiliationId] = (counts.byAffiliation[employee.affiliationId] ?? 0) + 1;
|
||||
counts.byStatus[employee.workStatus] += 1;
|
||||
}
|
||||
// Single primary membership makes child totals disjoint; compute a postorder iteratively.
|
||||
const stack = [...normalizeOrgChartDataset(data).roots];
|
||||
const traversal = [];
|
||||
while (stack.length) {
|
||||
const node = stack.pop()!;
|
||||
traversal.push(node);
|
||||
stack.push(...node.children);
|
||||
}
|
||||
for (let index = traversal.length - 1; index >= 0; index--) {
|
||||
const node = traversal[index]!;
|
||||
counts.byOrganization[node.organization.id]!.inclusive = counts.byOrganization[node.organization.id]!.direct
|
||||
+ node.children.reduce((sum, child) => sum + counts.byOrganization[child.organization.id]!.inclusive, 0);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
/** Raw counts are literal unique employee IDs, independent of presentation or business totals. */
|
||||
export function calculateSummary(data: OrgChartDataset, policy?: ApprovedAggregationPolicy): OrgChartSummary {
|
||||
const raw = count(data, data.employees);
|
||||
if (!policy) return { raw, aggregation: { status: 'unavailable', reason: 'policyRequired' } };
|
||||
if (!supportedPolicy(policy)) return { raw, aggregation: { status: 'unavailable', reason: 'unsupportedPolicy' } };
|
||||
const selected = data.employees.filter(employee => policy.includedStatuses.includes(employee.workStatus));
|
||||
return { raw, aggregation: { status: 'ready', policyVersion: policy.policyVersion, counts: count(data, selected) } };
|
||||
}
|
||||
Reference in new issue
Block a user