import { notFound } from "next/navigation"; import { EmployeeDetail } from "@/components/employees/EmployeeDetail"; import { currentUserId } from "@/lib/auth/session"; import { withUser } from "@/lib/db"; import { todayIso } from "@/lib/format"; import { breadcrumbLabel, loadOrgMaps } from "@/lib/org"; import { loadPlacements, loadReportingLines, orgAsOf } from "@/lib/placement"; import { loadOpenPositions } from "@/lib/positions"; type PageProps = { params: Promise<{ id: string }> }; export default async function EmployeeDetailPage({ params }: PageProps) { const { id } = await params; const today = todayIso(); const data = await withUser(await currentUserId(), async (tx) => { // Die Person zuerst, allein: erst aus Eintritt und Austritt ergibt sich // der Stichtag, zu dem ihre Organisation überhaupt eine Antwort hat. // Eine Person, die am 01.09. anfängt, hat heute keine laufende Besetzung // — zu heute gefragt lieferte om_reporting_lines() nichts, und die Akte // behauptete „Keine Führungskraft", obwohl das Team eine hat. const employee = await tx.selectFrom("employees").selectAll().where("id", "=", id).executeTakeFirst(); if (!employee) return null; const asOf = orgAsOf(employee, today); // Vorgesetzte und direkte Berichte stehen nirgends als Spalte — sie // kommen aus om_reporting_lines(). Beide Abfragen schränken *in* der // Funktion ein, es wandern also neun Zeilen über die Leitung und nicht // achthundert. const [ownLines, reports, history, dependents, notes, orgMaps, placements, openPositions] = await Promise.all([ loadReportingLines(tx, asOf, { employeeId: id }), loadReportingLines(tx, asOf, { actingManagerId: id }), tx .selectFrom("employee_history") .selectAll() .where("employee_id", "=", id) .orderBy("event_date", "desc") .orderBy("created_at", "desc") .execute(), tx.selectFrom("employee_dependents").selectAll().where("employee_id", "=", id).orderBy("created_at").execute(), tx.selectFrom("employee_notes").selectAll().where("employee_id", "=", id).orderBy("created_at", "desc").execute(), loadOrgMaps(tx), loadPlacements(tx, { asOf, employeeIds: [id] }), // Die offenen Planstellen bleiben bei heute: sie gehören zur // Organisation, nicht zu dieser Person. loadOpenPositions(tx), ]); const line = ownLines[0] ?? null; // Namen für die beteiligten Personen in einem Zug: die Vertretung, die // formal zuständige Leitung und die direkten Berichte. const relatedIds = Array.from( new Set( [line?.acting_manager_id, line?.formal_manager_id, ...reports.map((r) => r.employee_id)].filter( (x): x is string => Boolean(x) ) ) ); const relatedRows = relatedIds.length ? await tx .selectFrom("employees") .select(["id", "first_name", "last_name", "job_title", "status"]) .where("id", "in", relatedIds) .execute() : []; return { employee, line, reports, history, dependents, notes, orgMaps, placement: placements.get(id) ?? null, openPositions, byId: new Map(relatedRows.map((e) => [e.id, e])), }; }); if (!data) notFound(); const { employee, line, reports, history, dependents, notes, orgMaps, placement, openPositions, byId } = data; return ( { const e = byId.get(r.employee_id); return e ? [e] : []; })} history={history ?? []} dependents={dependents ?? []} notes={notes ?? []} locations={orgMaps.locationList} openPositions={openPositions} /> ); }