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; }