feat(org-chart): finalize chart features and AI generation rules
This commit is contained in:
1 parent
7614507583
commit
d0828a7ff4
80 files changed
+8152
-221
No files matched your search
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
import {
|
||||
Avatar, AvatarFallback, AvatarImage, Button, Dialog, DialogBody, DialogClose,
|
||||
DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
|
||||
} from '@hm-design/ui';
|
||||
import type { Employee, OrgChartEmployeeClickContext } from '@hm-design/org-chart';
|
||||
|
||||
export interface EmployeeDialogSelection {
|
||||
employee: Employee;
|
||||
context: OrgChartEmployeeClickContext;
|
||||
}
|
||||
|
||||
const statusNames = { active: '재직', dispatched: '파견', onLeave: '휴직' } as const;
|
||||
|
||||
/** Lazy React-only adapter. The Core and Vanilla remain independent of HM UI. */
|
||||
export default function HMEmployeeDialog({ selection, onClose }: {
|
||||
selection: EmployeeDialogSelection | null;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const returnTarget = useRef<EmployeeDialogSelection | null>(null);
|
||||
const returnRoot = useRef<ShadowRoot | null>(null);
|
||||
const returnContainer = useRef<HTMLElement | null>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (!selection) return;
|
||||
returnTarget.current = selection;
|
||||
const root = selection.context.trigger.getRootNode();
|
||||
const Shadow = selection.context.trigger.ownerDocument.defaultView?.ShadowRoot;
|
||||
returnRoot.current = Shadow && root instanceof Shadow ? root : null;
|
||||
returnContainer.current = returnRoot.current?.host.parentElement ?? null;
|
||||
}, [selection]);
|
||||
|
||||
function restoreFocus(event: Event) {
|
||||
event.preventDefault();
|
||||
const previous = returnTarget.current;
|
||||
returnTarget.current = null;
|
||||
if (!previous) return;
|
||||
const { trigger } = previous.context;
|
||||
// A mode/selection render can replace the card while the Portal is open.
|
||||
// Retain its ShadowRoot before replacement and resolve the same employee.
|
||||
const shadow = returnRoot.current;
|
||||
const currentRoot = shadow?.host.isConnected ? shadow : returnContainer.current?.firstElementChild?.shadowRoot;
|
||||
const replacement = [...currentRoot?.querySelectorAll<HTMLElement>('.hm-orgchart-employee') ?? []]
|
||||
.find(card => card.dataset.employeeId === previous.employee.id);
|
||||
const target = trigger.isConnected ? trigger : replacement
|
||||
?? currentRoot?.querySelector<HTMLElement>('.hm-orgchart-shell');
|
||||
target?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
const displayed = selection ?? returnTarget.current;
|
||||
const employee = displayed?.employee;
|
||||
const context = displayed?.context;
|
||||
const image = employee?.profileImage
|
||||
?? context?.trigger.querySelector<HTMLImageElement>('.hm-orgchart-avatar img')?.src;
|
||||
const fields: [string, string | null | undefined][] = employee ? [
|
||||
['이름', employee.name], ['직위', employee.position],
|
||||
['소속 회사', context?.affiliation?.name], ['소속 조직', context?.organization?.name],
|
||||
['직책', employee.jobTitle], ['근무 상태', statusNames[employee.workStatus]],
|
||||
] : [];
|
||||
|
||||
return <Dialog open={selection !== null} onOpenChange={open => { if (!open) onClose(); }}>
|
||||
<DialogContent onCloseAutoFocus={restoreFocus} data-component="HMOrgChartEmployeeDialog" data-employee-id={employee?.id}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>직원 정보</DialogTitle>
|
||||
<DialogDescription>조직도에서 선택한 직원의 정보를 확인합니다.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogBody>
|
||||
<Avatar size="lg">
|
||||
{image ? <AvatarImage src={image} alt="" referrerPolicy="no-referrer" /> : null}
|
||||
<AvatarFallback>{employee?.name.slice(0, 1)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<dl style={{ display: 'grid', gap: '0.75rem', margin: '1rem 0 0' }}>{fields.filter(([, value]) => Boolean(value)).map(([label, value]) => <div key={label} style={{ display: 'grid', gridTemplateColumns: '5rem minmax(0, 1fr)', gap: '0.75rem' }}>
|
||||
<dt style={{ fontWeight: 600 }}>{label}</dt><dd style={{ margin: 0, minWidth: 0, overflowWrap: 'anywhere' }}>{value}</dd>
|
||||
</div>)}</dl>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild><Button variant="cancel" appearance="solid">닫기</Button></DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>;
|
||||
}
|
||||
Reference in new issue
Block a user