Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import Link from 'next/link'
|
|
import { db } from '@/lib/db'
|
|
import { requirePermission } from '@/lib/auth/current'
|
|
import { listPersonnelFiles, listStatusTags } from '@/lib/personnel/service'
|
|
import { de } from '@/lib/i18n/de'
|
|
import { StampMark, classificationEdgeStyle } from '@/components/StampMark'
|
|
import { StatusTags } from '@/components/StatusTags'
|
|
|
|
export const metadata: Metadata = { title: de.personnel.listHeading }
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
/**
|
|
* Liste der Personalakten.
|
|
*
|
|
* Wie bei den Fallakten: Suche, Sichtbarkeit und Zählung laufen über dieselbe
|
|
* Abfrage. Eine Akte über der Stufe des Lesers taucht weder auf noch in der
|
|
* Zahl daneben.
|
|
*/
|
|
export default async function PersonnelListPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ q?: string; status?: string }>
|
|
}) {
|
|
// Ohne das Recht gibt es die Seite nicht - 404, nicht 403.
|
|
const user = await requirePermission('personnel.read')
|
|
const { q, status } = await searchParams
|
|
|
|
const [{ files, total }, levels, statusTags] = await Promise.all([
|
|
listPersonnelFiles(user.permissions, { query: q, statusTag: status }),
|
|
db.clearanceLevelConfig.findMany({ orderBy: { level: 'asc' } }),
|
|
listStatusTags(),
|
|
])
|
|
const levelName = new Map(levels.map((l) => [l.level, l.name]))
|
|
const darfAnlegen =
|
|
user.permissions.isAdmin || user.permissions.permissions.has('personnel.edit')
|
|
|
|
return (
|
|
<main className="mx-auto max-w-5xl px-6 py-10">
|
|
<div className="flex flex-wrap items-baseline justify-between gap-4">
|
|
<h1 className="font-condensed">{de.personnel.listHeading}</h1>
|
|
<span className="font-mono text-xs text-text-dim">{de.personnel.countLabel(total)}</span>
|
|
</div>
|
|
|
|
<form method="get" className="mt-4 flex flex-wrap items-center gap-3">
|
|
<input
|
|
type="search"
|
|
name="q"
|
|
defaultValue={q ?? ''}
|
|
placeholder={de.personnel.search}
|
|
aria-label={de.personnel.search}
|
|
className="w-full max-w-sm field"
|
|
/>
|
|
{status ? <input type="hidden" name="status" value={status} /> : null}
|
|
{darfAnlegen ? (
|
|
<Link href="/personalakten/neu" className="ml-auto btn btn-sm btn-primary">
|
|
{de.personnel.create}
|
|
</Link>
|
|
) : null}
|
|
</form>
|
|
|
|
<nav className="mt-4 flex flex-wrap gap-2">
|
|
<Link
|
|
href={q ? `/personalakten?q=${encodeURIComponent(q)}` : '/personalakten'}
|
|
aria-current={status ? undefined : 'page'}
|
|
className="btn btn-sm"
|
|
>
|
|
{de.personnel.filterAll}
|
|
</Link>
|
|
{statusTags.map((tag) => (
|
|
<Link
|
|
key={tag.key}
|
|
href={`/personalakten?${new URLSearchParams({ ...(q ? { q } : {}), status: tag.key })}`}
|
|
aria-current={tag.key === status ? 'page' : undefined}
|
|
className="btn btn-sm"
|
|
>
|
|
{tag.name}
|
|
</Link>
|
|
))}
|
|
</nav>
|
|
|
|
{files.length === 0 ? (
|
|
<p className="mt-10 text-text-dim">
|
|
{q ? de.personnel.emptySearch : de.personnel.empty}
|
|
</p>
|
|
) : (
|
|
<ul className="mt-6 border-t border-line">
|
|
{files.map((akte) => (
|
|
<li key={akte.id} className="border-b border-line">
|
|
<Link
|
|
href={`/personalakten/${akte.id}`}
|
|
prefetch
|
|
className="record-row py-3 pl-3 text-text no-underline"
|
|
style={classificationEdgeStyle(akte.classification)}
|
|
>
|
|
<span className="record-rank">{akte.rank ? `[${akte.rank}]` : ''}</span>
|
|
<span className="record-title">{akte.displayName}</span>
|
|
<StatusTags tags={akte.statusTags} known={statusTags} />
|
|
<StampMark
|
|
level={akte.classification}
|
|
name={levelName.get(akte.classification) ?? de.common.unknown}
|
|
/>
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</main>
|
|
)
|
|
}
|