Files
planer/src/lib/aufgabe-anzeige.ts
T
Florian WildeandClaude Opus 5 63c3c15a3c feat(aufgaben): Datenmodell und Regeln für Aufgaben und Projekte
Aufgabe mit Typ, Status, Priorität, Aufwand, Fälligkeit und Herkunft;
Projekt als eigene Klammer darum.

Zwei Entwurfsentscheidungen:
- Die Inbox ist kein eigener Zustand, sondern schlicht eine Aufgabe ohne
  Bereich. Einsortieren heißt: einen Bereich setzen. Ein zusätzliches
  Feld könnte mit der Zuordnung auseinanderlaufen.
- Projekt ist ein eigenes Modell, kein Aufgabentyp. Ein Projekt hat
  selbst weder Aufwand noch Fälligkeit, sondern leitet beides aus dem
  ab, was drinsteckt. ARBEITSPAKET und MEILENSTEIN sind Typen der
  Aufgaben *innerhalb* eines Projekts.

@@unique([quelle, quelleRef]) ist die Grundlage für die Integrationen ab
M6: sie upserten darüber, statt zu doppeln. Von Hand erfasste Aufgaben
tragen quelleRef = null; PostgreSQL sieht NULL-Werte als verschieden an,
sie behindern sich also nicht.

Verworfene Aufgaben zählen bewusst gar nicht in den Fortschritt eines
Projekts — sonst sähe es fertig aus, weil die Hälfte gestrichen wurde.

Fälligkeit wird in Kalendertagen in Europe/Berlin gerechnet, nicht in
24-Stunden-Schritten: was heute um 23:00 fällig ist, ist heute fällig.

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

97 lines
2.9 KiB
TypeScript

import type { AufgabenStatus, AufgabenTyp, Quelle } from '@/generated/prisma/enums'
import {
faelligkeitEinordnen,
faelligkeitText,
istUeberfaellig,
type Faelligkeit,
} from '@/lib/aufgabe'
import { alsDatum, alsTagesschluessel } from '@/lib/zeit'
/**
* Bereitet eine Aufgabe für die Anzeige auf.
*
* Reine Funktion mit dem Zeitpunkt als Parameter: Formatierung und
* Fälligkeitsrechnung passieren serverseitig in Europe/Berlin, damit im
* Browser nichts nachgerechnet wird und alle Listen gleich aussehen.
*/
export type AufgabeRoh = {
id: string
titel: string
notiz: string | null
typ: AufgabenTyp
status: AufgabenStatus
prioritaet: number
aufwandMinuten: number | null
faelligAm: Date | null
quelle: Quelle
quelleUrl: string | null
bereich: { id: string; name: string; farbe: string } | null
projekt: { id: string; name: string } | null
}
export type AufgabeAnzeige = {
id: string
titel: string
notiz: string | null
typ: AufgabenTyp
status: AufgabenStatus
prioritaet: number
aufwandMinuten: number | null
/** ISO-Datum für das Formularfeld, sonst leer. */
faelligWert: string
/** "heute fällig", "seit 3 Tagen überfällig", "ohne Termin". */
faelligText: string
/** Ausgeschriebenes Datum für den Titelhinweis, sonst null. */
faelligDatum: string | null
faelligkeit: Faelligkeit['art']
ueberfaellig: boolean
quelle: Quelle
quelleUrl: string | null
bereich: { id: string; name: string; farbe: string } | null
projekt: { id: string; name: string } | null
}
/**
* ISO-Datum in Europe/Berlin, passend für ein <input type="date">.
* Der Wert des Feldes ist ein reines Datum ohne Zeitzone, also der Berliner
* Kalendertag — dieselbe Rechnung wie überall sonst im Planer.
*/
function alsDatumswert(zeitpunkt: Date | null): string {
return zeitpunkt ? alsTagesschluessel(zeitpunkt) : ''
}
export function fuerAnzeige(aufgabe: AufgabeRoh, jetzt: Date): AufgabeAnzeige {
const faelligkeit = faelligkeitEinordnen(aufgabe.faelligAm, jetzt)
return {
id: aufgabe.id,
titel: aufgabe.titel,
notiz: aufgabe.notiz,
typ: aufgabe.typ,
status: aufgabe.status,
prioritaet: aufgabe.prioritaet,
aufwandMinuten: aufgabe.aufwandMinuten,
faelligWert: alsDatumswert(aufgabe.faelligAm),
faelligText: faelligkeitText(faelligkeit),
faelligDatum: aufgabe.faelligAm ? alsDatum(aufgabe.faelligAm) : null,
faelligkeit: faelligkeit.art,
ueberfaellig: istUeberfaellig(aufgabe.faelligAm, aufgabe.status, jetzt),
quelle: aufgabe.quelle,
quelleUrl: aufgabe.quelleUrl,
bereich: aufgabe.bereich,
projekt: aufgabe.projekt,
}
}
/** Wie dringend die Fälligkeit aussieht — steuert die Farbe des Kennzeichens. */
export function faelligkeitFarbe(
anzeige: AufgabeAnzeige,
): 'neutral' | 'warnung' | 'gefahr' {
if (anzeige.ueberfaellig) return 'gefahr'
if (anzeige.faelligkeit === 'heute' || anzeige.faelligkeit === 'morgen') {
return 'warnung'
}
return 'neutral'
}