import { useState } from 'react'; import { useNavigate } from 'react-router'; import { trpc } from '../lib/trpc'; import { voice } from '../lib/voice'; import { Card } from '../components/ui/Card'; import { Button } from '../components/ui/Button'; import { SkeletonCard } from '../components/ui/SkeletonCard'; import { toast } from 'sonner'; import { clsx } from 'clsx'; import { Check, X, Minus, TrendingUp, TrendingDown, ArrowRight, Pencil } from 'lucide-react'; type Ampel = 'green' | 'amber' | 'red'; type Trend = 'besser' | 'gleich' | 'schlechter'; type TodoStatus = 'open' | 'done' | 'dropped'; export function ReflexionRoute() { const today = trpc.dayEntry.today.useQuery(); const navigate = useNavigate(); if (today.isPending) return ; if (!today.data?.entry) { return (

Du bist heute noch nicht locked in. {voice.hint.notCheckedInToday}

); } return ( navigate('/dashboard')} /> ); } function ReflectForm({ entry, todos, onDone, }: { entry: { id: string }; todos: Array<{ id: string; title: string; status: TodoStatus; bezugKind: 'project' | 'deal' | 'campaign' | 'allgemein' | null; unplanned?: boolean; }>; onDone: () => void; }) { const [todoStates, setTodoStates] = useState< Record >(Object.fromEntries(todos.map((t) => [t.id, { status: t.status }]))); const [reflectionText, setReflectionText] = useState(''); const [ampel, setAmpel] = useState(null); const [trend, setTrend] = useState(null); const [blockerEnabled, setBlockerEnabled] = useState(false); const [blockerTitle, setBlockerTitle] = useState(''); const [blockerDescription, setBlockerDescription] = useState(''); const [unplannedTitle, setUnplannedTitle] = useState(''); const hasProjectBezug = todos.some((t) => t.bezugKind === 'project'); const utils = trpc.useUtils(); const addUnplanned = trpc.dayEntry.addUnplanned.useMutation({ onSuccess: async () => { setUnplannedTitle(''); toast('Ungeplant erledigt eingetragen.'); await utils.dayEntry.today.invalidate(); }, }); const mutation = trpc.dayEntry.submitReflection.useMutation({ onSuccess: async () => { toast(voice.toast.reflected); await utils.dayEntry.today.invalidate(); onDone(); }, }); function setTodoStatus(id: string, status: TodoStatus) { setTodoStates((prev) => ({ ...prev, [id]: { ...prev[id], status } })); } function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!ampel) { toast('Bitte Ampel setzen.'); return; } if (hasProjectBezug && !trend) { toast('Bei Projektbezug ist der Trend Pflicht.'); return; } if (blockerEnabled && !blockerTitle.trim()) { toast('Blocker braucht einen Titel.'); return; } mutation.mutate({ dayEntryId: entry.id, // Über die aktuelle ToDo-Liste mappen (nicht nur den initialen Snapshot), // damit mitten am Tag ergänzte ToDos ihren Status behalten. todos: todos.map((t) => ({ id: t.id, status: todoStates[t.id]?.status ?? t.status, dropReason: todoStates[t.id]?.reason, })), reflectionText, ampel, trend: trend ?? null, ...(blockerEnabled ? { blocker: { title: blockerTitle, description: blockerDescription || undefined } } : {}), }); } return (

Reflexion

{voice.hint.howIsItGoing}

Was ist heute passiert?

Deine ToDos vom Morgen

    {todos.map((t) => { const status = todoStates[t.id]?.status ?? t.status; return (
  • {t.unplanned && ( ungeplant )}
    setTodoStatus(t.id, 'done')} label="Erledigt" > setTodoStatus(t.id, 'open')} label="Offen" > setTodoStatus(t.id, 'dropped')} label="Verworfen" >
  • ); })}
setUnplannedTitle(e.target.value)} placeholder="Ungeplant erledigt? Hier nachtragen …" maxLength={200} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); const v = unplannedTitle.trim(); if (v) addUnplanned.mutate({ title: v }); } }} className="flex-1 rounded-[var(--radius-md)] border border-dashed border-[var(--color-border)] bg-white px-3 py-2 text-sm focus:border-[var(--color-ink)] focus:outline-none" />