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,79 @@
|
||||
import { normalizeOrgChartDataset } from './normalize.js';
|
||||
import type { Employee, OrgChartDataset, OrgChartSummary, Organization, WorkStatus } from './types.js';
|
||||
|
||||
/** Internal content selectors: these do not expand the package's public data API. */
|
||||
function uniqueEmployees(data: OrgChartDataset): Employee[] {
|
||||
const seen = new Set<string>();
|
||||
return data.employees.filter(employee => {
|
||||
if (seen.has(employee.id)) return false;
|
||||
seen.add(employee.id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/** Literal unique IDs in their primary organization, including leaders and all statuses.
|
||||
* Inclusive organization counts stay in Summary; they must never be summed as a subtotal.
|
||||
* Map keys safely retain opaque organization IDs and position names such as __proto__.
|
||||
*/
|
||||
export function selectOrganizationPositionCounts(data: OrgChartDataset): ReadonlyMap<string, ReadonlyMap<string, number>> {
|
||||
const counts = new Map<string, Map<string, number>>();
|
||||
for (const organization of data.organizations) counts.set(organization.id, new Map());
|
||||
for (const employee of uniqueEmployees(data)) {
|
||||
const row = counts.get(employee.organizationId);
|
||||
if (row) row.set(employee.position, (row.get(employee.position) ?? 0) + 1);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
export function selectAffiliationSummary(data: OrgChartDataset, summary: OrgChartSummary) {
|
||||
// Include all declared affiliations, including zero, without recounting employees.
|
||||
return data.affiliations.map(affiliation => ({
|
||||
affiliation,
|
||||
count: summary.raw.byAffiliation[affiliation.id] ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
export interface MajorOrganizationTotal {
|
||||
organization: Organization;
|
||||
/** 'direct' for a root's own members (e.g. its leader); 'inclusive' for a major organization. */
|
||||
scope: 'direct' | 'inclusive';
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** Compact totals for the read-only composition: each root's direct members, then
|
||||
* each major (root child) organization including its cells/teams. Primary
|
||||
* membership is single, so the rows are disjoint and add up to the raw total. */
|
||||
export function selectMajorOrganizationTotals(data: OrgChartDataset, summary: OrgChartSummary): MajorOrganizationTotal[] {
|
||||
const rows: MajorOrganizationTotal[] = [];
|
||||
for (const root of normalizeOrgChartDataset(data).roots) {
|
||||
const counts = summary.raw.byOrganization[root.organization.id];
|
||||
if (!root.children.length) {
|
||||
rows.push({ organization: root.organization, scope: 'inclusive', count: counts?.inclusive ?? 0 });
|
||||
continue;
|
||||
}
|
||||
if (counts?.direct) rows.push({ organization: root.organization, scope: 'direct', count: counts.direct });
|
||||
for (const child of root.children) {
|
||||
rows.push({ organization: child.organization, scope: 'inclusive', count: summary.raw.byOrganization[child.organization.id]?.inclusive ?? 0 });
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
export interface StatusContentGroup {
|
||||
status: Extract<WorkStatus, 'dispatched' | 'onLeave'>;
|
||||
employees: readonly Employee[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export function selectStatusGroups(data: OrgChartDataset, summary: OrgChartSummary): StatusContentGroup[] {
|
||||
const employees = uniqueEmployees(data);
|
||||
const groups: StatusContentGroup[] = [];
|
||||
if (data.presentation?.groups.showDispatched !== false) {
|
||||
groups.push({ status: 'dispatched', employees: employees.filter(employee => employee.workStatus === 'dispatched'), count: summary.raw.byStatus.dispatched });
|
||||
}
|
||||
if (data.presentation?.groups.showOnLeave !== false) {
|
||||
groups.push({ status: 'onLeave', employees: employees.filter(employee => employee.workStatus === 'onLeave'), count: summary.raw.byStatus.onLeave });
|
||||
}
|
||||
// Cards retain original Employee references. A second display never changes Summary.
|
||||
return groups;
|
||||
}
|
||||
Reference in new issue
Block a user