"use client"; import { ChevronDown, ChevronRight, Search } from "lucide-react"; import Link from "next/link"; import { useMemo, useState } from "react"; import { Avatar } from "@/components/ui/Avatar"; import type { OrgEmployee } from "./types"; export function EmployeeTree({ employees }: { employees: OrgEmployee[] }) { const [expanded, setExpanded] = useState>(new Set()); const [query, setQuery] = useState(""); const { childrenByManager, totalReportsById, root } = useMemo(() => { const byManager = new Map(); for (const e of employees) { const key = e.manager_id ?? "__root__"; if (!byManager.has(key)) byManager.set(key, []); byManager.get(key)!.push(e); } for (const list of byManager.values()) list.sort((a, b) => a.last_name.localeCompare(b.last_name)); const totals = new Map(); function countTotal(id: string): number { if (totals.has(id)) return totals.get(id)!; const direct = byManager.get(id) ?? []; let total = direct.length; for (const child of direct) total += countTotal(child.id); totals.set(id, total); return total; } for (const e of employees) countTotal(e.id); return { childrenByManager: byManager, totalReportsById: totals, root: byManager.get("__root__") ?? [] }; }, [employees]); const matchIds = useMemo(() => { if (query.trim().length < 2) return null; const q = query.trim().toLowerCase(); const matches = new Set(); for (const e of employees) { if ( `${e.first_name} ${e.last_name}`.toLowerCase().includes(q) || e.job_title.toLowerCase().includes(q) || String(e.personnel_number).includes(q) ) { matches.add(e.id); } } return matches; }, [query, employees]); const ancestorExpandIds = useMemo(() => { if (!matchIds) return null; const byId = new Map(employees.map((e) => [e.id, e])); const toExpand = new Set(); for (const id of matchIds) { let current = byId.get(id); while (current?.manager_id) { toExpand.add(current.manager_id); current = byId.get(current.manager_id); } } return toExpand; }, [matchIds, employees]); function toggle(id: string) { setExpanded((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); } function isExpanded(id: string): boolean { return expanded.has(id) || (ancestorExpandIds?.has(id) ?? false); } function renderNode(e: OrgEmployee, depth: number) { const children = childrenByManager.get(e.id) ?? []; const hasChildren = children.length > 0; const expandedNow = isExpanded(e.id); const isMatch = matchIds?.has(e.id) ?? false; const totalReports = totalReportsById.get(e.id) ?? 0; return (
{hasChildren ? ( ) : ( )} {e.first_name} {e.last_name} {e.job_title} {hasChildren && ( {children.length} direkt · {totalReports} gesamt )}
{hasChildren && expandedNow &&
{children.map((c) => renderNode(c, depth + 1))}
}
); } return (
setQuery(e.target.value)} placeholder="Name, Pers.-Nr., Titel…" className="w-full text-sm text-ink outline-none placeholder:text-ink-muted" />
{root.map((r) => renderNode(r, 0))}
); }