"use client"; import { BoltIcon, CalendarDaysIcon, FireIcon, PlusIcon, SparklesIcon, TrashIcon, } from "@heroicons/react/24/outline"; import { useMemo, useState } from "react"; import { trpc } from "@/lib/trpc"; type TemplateCategory = "mobility" | "cardio" | "strength" | "habit" | string; type ActivitySource = "manual" | "fitbit"; function todayKey() { return new Date().toISOString().slice(0, 10); } function startOfWeek(dateKey: string) { const date = new Date(`${dateKey}T12:00:00`); const day = date.getDay() === 0 ? 7 : date.getDay(); date.setDate(date.getDate() - day + 1); return date.toISOString().slice(0, 10); } function pointsLabel(points: number) { if (points >= 500) return "Starke Woche"; if (points >= 250) return "Guter Lauf"; if (points >= 100) return "In Bewegung"; return "Startklar"; } function categoryColor(category: TemplateCategory) { if (category.includes("cardio")) return "bg-orange-50 text-orange-700"; if (category.includes("strength")) return "bg-blue-50 text-blue-700"; if (category.includes("mobility")) return "bg-emerald-50 text-emerald-700"; if (category.includes("sport")) return "bg-amber-50 text-amber-700"; return "bg-purple-50 text-purple-700"; } function categoryLabel(category: TemplateCategory) { if (category.includes("cardio")) return "Ausdauer"; if (category.includes("strength")) return "Kraft"; if (category.includes("mobility")) return "Beweglichkeit"; if (category.includes("sport")) return "Sport"; return "Routine"; } function sourceLabel(source: string) { if (source === "fitbit") return "Fitbit"; if (source === "health_connect") return "Health Connect"; return "manuell"; } function estimatePoints(minutes: number, calories: number) { return Math.max(5, Math.round(minutes * 1.2 + calories / 6)); } export default function AktivitätenPage() { const utils = trpc.useUtils(); const [selectedDate, setSelectedDate] = useState(todayKey()); const [templateQuery, setTemplateQuery] = useState(""); const [templateCategory, setTemplateCategory] = useState<"all" | "fitbit" | "manual">("all"); const [customName, setCustomName] = useState(""); const [customMinutes, setCustomMinutes] = useState("15"); const [customPoints, setCustomPoints] = useState("30"); const [customCalories, setCustomCalories] = useState("60"); const [customNote, setCustomNote] = useState(""); const [customSource, setCustomSource] = useState("manual"); const templatesQuery = trpc.health.listActivityTemplates.useQuery(); const activityWeekQuery = trpc.health.activityWeek.useQuery({ date: selectedDate }); const addLogMutation = trpc.health.addActivityLog.useMutation({ onSuccess: async () => { await utils.health.activityWeek.invalidate(); await utils.health.overview.invalidate(); }, }); const saveTemplateMutation = trpc.health.saveActivityTemplate.useMutation({ onSuccess: async () => { await utils.health.listActivityTemplates.invalidate(); setCustomName(""); }, }); const deleteLogMutation = trpc.health.deleteActivityLog.useMutation({ onSuccess: async () => { await utils.health.activityWeek.invalidate(); await utils.health.overview.invalidate(); }, }); const logs = activityWeekQuery.data?.logs ?? []; const templates = templatesQuery.data ?? []; const filteredTemplates = useMemo(() => { const needle = templateQuery.trim().toLowerCase(); return templates.filter((template) => { const isFitbit = template.id.startsWith("fitbit-") || template.category.startsWith("fitbit_"); if (templateCategory === "fitbit" && !isFitbit) return false; if (templateCategory === "manual" && isFitbit) return false; if (!needle) return true; return [template.name, template.category].some((value) => value.toLowerCase().includes(needle)); }); }, [templateCategory, templateQuery, templates]); const todayLogs = logs.filter((log) => log.date === selectedDate); const dayPoints = todayLogs.reduce((sum, log) => sum + log.points, 0); const weekPoints = logs.reduce((sum, log) => sum + log.points, 0); const weekMinutes = logs.reduce((sum, log) => sum + log.minutes, 0); const weekCalories = logs.reduce((sum, log) => sum + (log.calories ?? 0), 0); const weeklyDays = useMemo(() => { const monday = new Date(`${startOfWeek(selectedDate)}T12:00:00`); return Array.from({ length: 7 }, (_, index) => { const date = new Date(monday); date.setDate(monday.getDate() + index); const key = date.toISOString().slice(0, 10); const points = logs.filter((log) => log.date === key).reduce((sum, log) => sum + log.points, 0); return { key, label: date.toLocaleDateString("de-DE", { weekday: "narrow" }), day: date.getDate(), points }; }); }, [logs, selectedDate]); function logTemplate(template: (typeof templates)[number]) { const isFitbit = template.id.startsWith("fitbit-") || template.category.startsWith("fitbit_"); addLogMutation.mutate({ date: selectedDate, name: template.name, templateId: template.id, minutes: template.defaultMinutes, points: template.defaultPoints, calories: template.defaultCalories, source: isFitbit ? "fitbit" : "manual", }); } function saveCustomTemplate() { if (!customName.trim()) return; saveTemplateMutation.mutate({ name: customName.trim(), defaultMinutes: Number(customMinutes) || 5, defaultPoints: Number(customPoints) || 10, defaultCalories: Number(customCalories) || 0, category: "habit", icon: "custom", }); } function addCustomLog() { if (!customName.trim()) return; const minutes = Number(customMinutes) || 5; const calories = Number(customCalories) || 0; const points = Number(customPoints) || estimatePoints(minutes, calories); addLogMutation.mutate({ date: selectedDate, name: customName.trim(), minutes, points, calories, source: customSource, note: customNote.trim() || null, }); } return (

Belohnungssystem

Aktivitätspunkte

Fitbit-kompatibler Katalog, verbrannte kcal und manuelle Nacherfassung für Training ohne Uhr.

{dayPoints}

heute

{weekPoints}

Punkte diese Woche

{weekMinutes}

aktive Minuten

{weekCalories}

kcal Sportbonus

{pointsLabel(weekPoints)}

aktueller Status

Woche

setSelectedDate(event.target.value || todayKey())} className="rounded-full border border-gray-200 px-4 py-3 text-sm font-bold" />
{weeklyDays.map((day) => { const active = day.key === selectedDate; return ( ); })}

Fitness-Katalog

{(["all", "fitbit", "manual"] as const).map((key) => ( ))}
setTemplateQuery(event.target.value)} placeholder="Aktivität suchen" className="mt-4 w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm font-semibold" />
{filteredTemplates.map((template) => ( ))} {!filteredTemplates.length && !templatesQuery.isLoading ? (

Keine passende Vorlage gefunden.

) : null}