Files
planer/src/app/(app)/aufgaben/page.tsx
T
Florian WildeandClaude Opus 5 144457b9f0 fix(aufgaben): Ideen aus Aufgabenliste und Inbox heraushalten
Der Fehler fiel erst in den aufgenommenen Bildern auf: Ideen standen
mitten in der Aufgabenliste, obwohl der ganze Sinn der getrennten
Ideenseite ist, dass sie das nicht tun. Ohne Termin hätten sie dort
entweder unsichtbar unten gestanden oder mit erfundenem Termin oben.

Der Filter kennt jetzt ohneIdeen; ein ausdrücklich gewählter Typ gewinnt,
wer nach Ideen filtert, sieht sie also weiterhin.

Die Startseite filterte Ideen bisher von Hand nach dem Laden — das
erledigt jetzt dieselbe Abfrage wie überall sonst.

Ein E2E-Test hält den Fall fest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:33:33 +02:00

113 lines
3.5 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'
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 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>
<AufgabenListe
aufgaben={aufgaben}
bereiche={bereiche}
projekte={projekte}
leerText="Keine Aufgabe passt zu diesen Filtern."
/>
</div>
)
}