Der Schalter erscheint nur, wo auch gespiegelt wird. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { Suspense } from 'react'
|
|
import { Filterleiste } from '@/app/(app)/aufgaben/filterleiste'
|
|
import { NeueAufgabe } from '@/app/(app)/aufgaben/neue-aufgabe'
|
|
import { AufgabenListe } from '@/components/aufgaben/aufgaben-liste'
|
|
import { Card, CardInhalt } from '@/components/ui/card'
|
|
import type { AufgabenStatus, AufgabenTyp } from '@/generated/prisma/enums'
|
|
import { aufwandSumme } from '@/lib/aufgabe'
|
|
import { alsDauer } from '@/lib/zeit'
|
|
import {
|
|
aufgabenLaden,
|
|
bereicheLaden,
|
|
projekteZurAuswahlLaden,
|
|
type AufgabenFilter,
|
|
} from '@/server/abfragen/aufgaben'
|
|
import { verlangeAnmeldung } from '@/server/berechtigung'
|
|
import { umgebung } from '@/server/umgebung'
|
|
|
|
export const metadata: Metadata = { title: 'Aufgaben' }
|
|
|
|
const STATUS_WERTE: readonly string[] = [
|
|
'OFFEN',
|
|
'GEPLANT',
|
|
'WARTET',
|
|
'ERLEDIGT',
|
|
'VERWORFEN',
|
|
]
|
|
const TYP_WERTE: readonly string[] = [
|
|
'AUFGABE',
|
|
'IDEE',
|
|
'ARBEITSPAKET',
|
|
'MEILENSTEIN',
|
|
'SPIEGEL',
|
|
]
|
|
|
|
/** Übersetzt die Adresszeile in einen Filter; Unbekanntes wird ignoriert. */
|
|
function filterAus(
|
|
parameter: Record<string, string | string[] | undefined>,
|
|
): AufgabenFilter {
|
|
const einer = (name: string): string | undefined => {
|
|
const wert = parameter[name]
|
|
return typeof wert === 'string' && wert.length > 0 ? wert : undefined
|
|
}
|
|
|
|
const bereich = einer('bereich')
|
|
const status = einer('status')
|
|
const typ = einer('typ')
|
|
|
|
return {
|
|
...(bereich === 'inbox' ? { nurInbox: true } : bereich ? { bereichId: bereich } : {}),
|
|
...(einer('projekt') ? { projektId: einer('projekt') } : {}),
|
|
// Ohne Angabe zeigt die Liste nur, was noch Arbeit bedeutet.
|
|
...(status === 'alle'
|
|
? {}
|
|
: status && STATUS_WERTE.includes(status)
|
|
? { status: status as AufgabenStatus }
|
|
: { nurOffen: true }),
|
|
...(typ && TYP_WERTE.includes(typ) ? { typ: typ as AufgabenTyp } : {}),
|
|
...(einer('suche') ? { suche: einer('suche') } : {}),
|
|
// Ideen haben ihre eigene Seite; wer sie hier sehen will, filtert danach.
|
|
ohneIdeen: true,
|
|
}
|
|
}
|
|
|
|
export default async function AufgabenSeite({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
}) {
|
|
await verlangeAnmeldung('/aufgaben')
|
|
const spiegelt = umgebung().spiegelSendenAktiv
|
|
const parameter = await searchParams
|
|
|
|
const [aufgaben, bereiche, projekte] = await Promise.all([
|
|
aufgabenLaden(filterAus(parameter)),
|
|
bereicheLaden(),
|
|
projekteZurAuswahlLaden(),
|
|
])
|
|
|
|
const geschaetzt = aufwandSumme(aufgaben)
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
<header className="flex flex-wrap items-start justify-between gap-4">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight">Aufgaben</h1>
|
|
<p className="text-gedaempft-vordergrund mt-1 text-sm">
|
|
{aufgaben.length === 0
|
|
? 'Nichts gefunden.'
|
|
: `${aufgaben.length} Aufgabe(n)${geschaetzt > 0 ? `, zusammen ${alsDauer(geschaetzt)} geschätzt` : ''}.`}
|
|
</p>
|
|
</div>
|
|
<NeueAufgabe bereiche={bereiche} projekte={projekte} />
|
|
</header>
|
|
|
|
<Card>
|
|
<CardInhalt className="py-4">
|
|
<Suspense
|
|
fallback={<p className="text-gedaempft-vordergrund text-sm">Filter …</p>}
|
|
>
|
|
<Filterleiste bereiche={bereiche} projekte={projekte} />
|
|
</Suspense>
|
|
</CardInhalt>
|
|
</Card>
|
|
|
|
{/* Nur wo auch gespiegelt wird, hat der Schalter einen Sinn. */}
|
|
<AufgabenListe
|
|
aufgaben={aufgaben}
|
|
bereiche={bereiche}
|
|
projekte={projekte}
|
|
leerText="Keine Aufgabe passt zu diesen Filtern."
|
|
mitSpiegeln={spiegelt}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|