139 lines
5.6 KiB
TypeScript
139 lines
5.6 KiB
TypeScript
import Link from "next/link";
|
||
import { notFound } from "next/navigation";
|
||
import { ArrowLeft, Laptop } from "lucide-react";
|
||
import { MitarbeiterAktiv } from "@/components/mitarbeiter-aktiv";
|
||
import { MitarbeiterStammdaten } from "@/components/mitarbeiter-stammdaten";
|
||
import { MitarbeiterVorgesetzter } from "@/components/mitarbeiter-vorgesetzter";
|
||
import { Herkunft } from "@/generated/prisma/enums";
|
||
import { darf } from "@/server/auth/rollen";
|
||
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
|
||
import { kundenEbeneSichtbar } from "@/server/mandant";
|
||
import {
|
||
geraeteDesMitarbeiters,
|
||
mitarbeiterHolen,
|
||
moeglicheVorgesetzte,
|
||
vorgesetzterVon,
|
||
} from "@/server/queries/mitarbeiter";
|
||
|
||
/**
|
||
* Die Seite einer Person (#011).
|
||
*
|
||
* Sie beantwortet drei Fragen: wer ist das, welche Geraete gehoeren ihr, und was
|
||
* ist mit ihr passiert. Die Laufhistorie kommt mit #013 dazu — bis dahin steht
|
||
* hier, dass sie kommt, statt eine leere Ueberschrift zu zeigen.
|
||
*/
|
||
|
||
export default async function MitarbeiterSeite({ params }: { params: Promise<{ id: string }> }) {
|
||
const { id } = await params;
|
||
const { benutzer } = await sitzungErforderlich("lesen", `/mitarbeiter/${id}`);
|
||
const person = await mitarbeiterHolen(id, benutzer);
|
||
// Nicht vorhanden und nicht sichtbar sind dieselbe Antwort – die
|
||
// Unterscheidung waere schon eine Auskunft ueber fremde Kunden (#009).
|
||
if (!person) notFound();
|
||
|
||
const darfBearbeiten = darf(benutzer.rolle, "mitarbeiterBearbeiten");
|
||
const [geraete, kundenEbene, vorgesetzter, auswahl] = await Promise.all([
|
||
geraeteDesMitarbeiters(person.id, benutzer),
|
||
kundenEbeneSichtbar(),
|
||
vorgesetzterVon(person.id, benutzer),
|
||
// Nur holen, wer sie auch braucht: fuer einen Leser ist die Liste aller
|
||
// Kolleginnen und Kollegen eine Abfrage ohne Zweck.
|
||
darfBearbeiten ? moeglicheVorgesetzte(person.id, benutzer) : [],
|
||
]);
|
||
|
||
return (
|
||
<>
|
||
<Link
|
||
href="/mitarbeiter"
|
||
className="inline-flex min-h-11 items-center gap-2 text-sm text-nebentext hover:text-text"
|
||
>
|
||
<ArrowLeft className="size-4" strokeWidth={1.75} aria-hidden="true" />
|
||
Alle Personen
|
||
</Link>
|
||
|
||
<h1 className="mt-2 text-2xl font-bold">{person.anzeigename}</h1>
|
||
<div className="mt-2 flex flex-wrap items-center gap-2 text-sm text-nebentext">
|
||
{person.titel ? <span>{person.titel}</span> : null}
|
||
{person.abteilung ? <span>· {person.abteilung}</span> : null}
|
||
{kundenEbene ? <span>· {person.kundeName}</span> : null}
|
||
{person.aktiv ? null : <span className="text-faellig">· deaktiviert</span>}
|
||
{person.herkunft === Herkunft.ABGLEICH ? <span>· aus dem Verzeichnis</span> : null}
|
||
</div>
|
||
{vorgesetzter ? (
|
||
<p className="mt-1 text-sm text-nebentext">
|
||
Vorgesetzter:{" "}
|
||
<Link href={`/mitarbeiter/${vorgesetzter.id}`} className="text-akzent underline underline-offset-2">
|
||
{vorgesetzter.anzeigename}
|
||
</Link>
|
||
</p>
|
||
) : null}
|
||
|
||
{darfBearbeiten ? (
|
||
<div className="mt-6 flex flex-col gap-6">
|
||
<MitarbeiterAktiv mitarbeiterId={person.id} aktiv={person.aktiv} />
|
||
<MitarbeiterVorgesetzter
|
||
mitarbeiterId={person.id}
|
||
vorgesetzterId={vorgesetzter?.id ?? null}
|
||
auswahl={auswahl}
|
||
ausDemVerzeichnis={person.herkunft === Herkunft.ABGLEICH}
|
||
/>
|
||
</div>
|
||
) : null}
|
||
|
||
<section className="mt-8 rounded-karte border border-linie bg-flaeche p-5">
|
||
<h2 className="flex items-center gap-2 text-lg font-semibold">
|
||
<Laptop className="size-5 text-nebentext" strokeWidth={1.75} aria-hidden="true" />
|
||
Geräte
|
||
</h2>
|
||
{geraete.length === 0 ? (
|
||
<p className="mt-2 text-sm text-nebentext">Dieser Person ist kein Gerät zugeordnet.</p>
|
||
) : (
|
||
<ul className="mt-4 flex flex-col gap-2">
|
||
{geraete.map((geraet) => (
|
||
<li
|
||
key={geraet.id}
|
||
className="flex flex-wrap items-center gap-2 border-t border-linie py-3 first:border-t-0"
|
||
>
|
||
<span className="mono rounded-pille bg-flaeche-hoch px-2.5 py-1 text-xs text-nebentext">
|
||
{geraet.tagCode}
|
||
</span>
|
||
<span className={geraet.archiviert ? "text-nebentext line-through" : ""}>{geraet.name}</span>
|
||
<span className="text-xs text-nebentext">{geraet.typ}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</section>
|
||
|
||
<section className="mt-4 rounded-karte border border-linie bg-flaeche p-5">
|
||
<h2 className="text-lg font-semibold">Stammdaten</h2>
|
||
<div className="mt-4">
|
||
<MitarbeiterStammdaten
|
||
person={{
|
||
id: person.id,
|
||
vorname: person.vorname,
|
||
nachname: person.nachname,
|
||
anzeigename: person.anzeigename,
|
||
mail: person.mail,
|
||
upn: person.upn,
|
||
sam: person.sam,
|
||
abteilung: person.abteilung,
|
||
titel: person.titel,
|
||
ausVerzeichnis: person.herkunft === Herkunft.ABGLEICH,
|
||
}}
|
||
darfBearbeiten={darfBearbeiten}
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="mt-4 rounded-karte border border-linie bg-flaeche p-5">
|
||
<h2 className="text-lg font-semibold">Läufe</h2>
|
||
<p className="mt-2 max-w-prose text-sm text-nebentext">
|
||
Onboarding, Übergaben und Offboarding erscheinen hier chronologisch, sobald es Läufe gibt — sie
|
||
kommen mit dem nächsten Meilenstein.
|
||
</p>
|
||
</section>
|
||
</>
|
||
);
|
||
}
|