"use client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useParams } from "next/navigation"; import { api } from "@/lib/api"; import type { TimeEntry } from "@/lib/types"; import { format } from "date-fns"; import { de } from "date-fns/locale"; import { Timer, Loader2, Plus, Trash2 } from "lucide-react"; import { useState } from "react"; function formatDuration(minutes: number): string { const h = Math.floor(minutes / 60); const m = minutes % 60; if (h === 0) return `${m}min`; if (m === 0) return `${h}h`; return `${h}h ${m}min`; } function formatAmount(minutes: number, rate?: number): string { if (!rate) return "-"; return `${((minutes / 60) * rate).toFixed(2)} EUR`; } const ACTIVITIES = [ { value: "", label: "Keine Kategorie" }, { value: "research", label: "Recherche" }, { value: "drafting", label: "Entwurf" }, { value: "hearing", label: "Verhandlung" }, { value: "call", label: "Telefonat" }, { value: "review", label: "Prüfung" }, { value: "travel", label: "Reise" }, { value: "meeting", label: "Besprechung" }, ]; export default function ZeiterfassungPage() { const { id } = useParams<{ id: string }>(); const queryClient = useQueryClient(); const [showForm, setShowForm] = useState(false); const [desc, setDesc] = useState(""); const [hours, setHours] = useState(""); const [minutes, setMinutes] = useState(""); const [date, setDate] = useState(format(new Date(), "yyyy-MM-dd")); const [activity, setActivity] = useState(""); const [billable, setBillable] = useState(true); const { data, isLoading } = useQuery({ queryKey: ["case-time-entries", id], queryFn: () => api.get<{ time_entries: TimeEntry[] }>(`/cases/${id}/time-entries`), }); const createMutation = useMutation({ mutationFn: (input: { description: string; duration_minutes: number; date: string; activity?: string; billable: boolean; }) => api.post(`/cases/${id}/time-entries`, input), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["case-time-entries", id] }); setShowForm(false); setDesc(""); setHours(""); setMinutes(""); setActivity(""); setBillable(true); }, }); const deleteMutation = useMutation({ mutationFn: (entryId: string) => api.delete(`/time-entries/${entryId}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["case-time-entries", id] }); }, }); function handleSubmit(e: React.FormEvent) { e.preventDefault(); const totalMinutes = (parseInt(hours || "0") * 60) + parseInt(minutes || "0"); if (totalMinutes <= 0 || !desc.trim()) return; createMutation.mutate({ description: desc.trim(), duration_minutes: totalMinutes, date, activity: activity || undefined, billable, }); } if (isLoading) { return (
); } const entries = data?.time_entries ?? []; const totalMinutes = entries.reduce((s, e) => s + e.duration_minutes, 0); const billableMinutes = entries .filter((e) => e.billable) .reduce((s, e) => s + e.duration_minutes, 0); return (
{/* Summary bar */}
Gesamt: {formatDuration(totalMinutes)} Abrechenbar: {formatDuration(billableMinutes)}
{/* Quick add form */} {showForm && (
setDesc(e.target.value)} placeholder="Was wurde getan?" className="w-full rounded-md border border-neutral-300 px-3 py-1.5 text-sm focus:border-neutral-500 focus:outline-none" required />
setHours(e.target.value)} placeholder="0" className="w-16 rounded-md border border-neutral-300 px-2 py-1.5 text-sm focus:border-neutral-500 focus:outline-none" /> h
setMinutes(e.target.value)} placeholder="0" className="w-16 rounded-md border border-neutral-300 px-2 py-1.5 text-sm focus:border-neutral-500 focus:outline-none" /> min
setDate(e.target.value)} className="w-full rounded-md border border-neutral-300 px-3 py-1.5 text-sm focus:border-neutral-500 focus:outline-none" />
{createMutation.isError && (

Fehler beim Speichern.

)}
)} {/* Entries list */} {entries.length === 0 ? (

Keine Zeiteintraege vorhanden.

) : (
{entries.map((entry) => (

{entry.description}

{entry.activity && ( {ACTIVITIES.find((a) => a.value === entry.activity)?.label ?? entry.activity} )} {!entry.billable && ( nicht abrechenbar )} {entry.billed && ( abgerechnet )}
{format(new Date(entry.date), "d. MMM yyyy", { locale: de })} {entry.hourly_rate && ( {formatAmount(entry.duration_minutes, entry.hourly_rate)} )}
{formatDuration(entry.duration_minutes)} {!entry.billed && ( )}
))}
)}
); }