feat(aufgaben): Inbox, Aufgabenliste, Ideen und Projekte
Die Schnellerfassung braucht ein Feld und einen Knopf; wer beim Erfassen erst ein Formular ausfüllen muss, erfasst nichts. Der Fokus bleibt danach im Feld, damit mehrere Sachen hintereinander hineingehen. Die Filter der Aufgabenliste stehen in der Adresse, nicht im Zustand der Komponente — so lässt sich eine gefilterte Liste als Lesezeichen ablegen, und der Zurück-Knopf tut, was er soll. Die Startseite zeigt jetzt, was überfällig, heute oder morgen fällig ist, statt nur die Bereiche aufzuzählen. Für die Auswahlfelder bewusst das native <select>: es bedient sich auf dem Handy mit dem Systemdialog und ist ohne Zusatzcode tastaturbedienbar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b3ca1c9ce7
commit
a4a889fcb8
@@ -0,0 +1,110 @@
|
||||
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') } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user