Files
HMDesign/packages/org-chart/src/core/position-summary.ts
T

83 lines
4.7 KiB
TypeScript

import { selectOrganizationPositionCounts } from '../data/content-selectors.js';
import type { RenderContext } from './render.js';
function text(document: Document, tag: string, className: string, value: string): HTMLElement {
const element = document.createElement(tag);
element.className = className;
element.textContent = value;
return element;
}
/** Independent, passive content component; composition controls its visibility and placement. */
export function createPositionSummary(document: Document, context: RenderContext): HTMLElement {
const section = document.createElement('section');
section.className = 'hm-orgchart-summary hm-orgchart-position-summary';
section.setAttribute('aria-label', '조직 및 직위별 원본 인원 현황');
section.dataset.contentSection = 'position-summary';
section.append(text(document, 'h3', 'hm-orgchart-summary-title', '조직 · 직위별 인원 현황'));
section.append(text(document, 'p', 'hm-orgchart-raw-total', `Raw Count · 원본 직원 전체 ${context.summary.raw.total}명`));
section.append(text(document, 'p', 'hm-orgchart-policy', '원본 직원 ID를 직접 소속 조직에서 한 번 집계합니다. 조직장 · 파견 · 휴직을 포함하며 업무 총원 정책을 뜻하지 않습니다.'));
section.append(text(document, 'p', 'hm-orgchart-policy', context.summary.aggregation.status === 'ready'
? `승인 정책 ${context.summary.aggregation.policyVersion}: 총원 ${context.summary.aggregation.counts.total}명`
: '정책 총원 미확정: 집계 정책 승인이 필요합니다.'));
const scroll = document.createElement('div');
scroll.className = 'hm-orgchart-summary-scroll';
scroll.tabIndex = 0;
scroll.setAttribute('role', 'region');
scroll.setAttribute('aria-label', '조직 및 직위별 인원 표, 가로 스크롤 가능');
const table = document.createElement('table');
table.className = 'hm-orgchart-position-table';
const caption = document.createElement('caption');
caption.textContent = '직위별 교차 인원과 소계는 직접 소속 기준입니다. 하위 포함 수는 서로 합산하지 않습니다.';
const positions = Object.keys(context.summary.raw.byPosition);
const matrix = selectOrganizationPositionCounts(context.data);
const head = document.createElement('thead');
const headerRow = document.createElement('tr');
for (const label of ['조직', ...positions, '소계 (직접)', '하위 포함']) {
const cell = document.createElement('th');
cell.scope = 'col';
cell.textContent = label;
headerRow.append(cell);
}
head.append(headerRow);
const body = document.createElement('tbody');
for (const organization of context.orderedOrganizations) {
const row = document.createElement('tr');
row.dataset.organizationId = organization.id;
const name = document.createElement('th');
name.scope = 'row';
name.textContent = organization.name;
row.append(name);
for (const position of positions) row.append(text(document, 'td', 'hm-orgchart-count-cell', String(matrix.get(organization.id)?.get(position) ?? 0)));
const counts = context.summary.raw.byOrganization[organization.id];
row.append(text(document, 'td', 'hm-orgchart-count-cell hm-orgchart-subtotal', String(counts?.direct ?? 0)));
row.append(text(document, 'td', 'hm-orgchart-count-cell hm-orgchart-inclusive', String(counts?.inclusive ?? 0)));
body.append(row);
}
const foot = document.createElement('tfoot');
const totalRow = document.createElement('tr');
const totalLabel = document.createElement('th');
totalLabel.scope = 'row';
totalLabel.textContent = '전체 합계';
totalRow.append(totalLabel);
for (const position of positions) totalRow.append(text(document, 'td', 'hm-orgchart-count-cell', String(context.summary.raw.byPosition[position] ?? 0)));
totalRow.append(text(document, 'td', 'hm-orgchart-count-cell', String(context.summary.raw.total)));
const inclusiveTotal = text(document, 'td', 'hm-orgchart-inclusive', '—');
inclusiveTotal.setAttribute('aria-label', '하위 포함 수는 중복되어 합산하지 않음');
totalRow.append(inclusiveTotal);
foot.append(totalRow);
table.append(caption, head, body, foot);
scroll.append(table);
section.append(scroll);
section.append(text(document, 'h4', 'hm-orgchart-group-title', '근무 상태별 원본 직원'));
const statuses = document.createElement('dl');
statuses.className = 'hm-orgchart-summary-group hm-orgchart-status-totals';
for (const [key, label] of [['active', '재직'], ['dispatched', '파견'], ['onLeave', '휴직']] as const) {
statuses.append(text(document, 'dt', '', label), text(document, 'dd', '', `${context.summary.raw.byStatus[key]}명`));
}
section.append(statuses);
return section;
}