Untitled
"use client" import { useState, useEffect } from "react" export interface MetricsData { // ... (keep existing interface properties) fill_predictions: { [binId: string]: { current_level: number fill_rate: number predicted_full_time: string updated_at: string } } } export function useMetrics() { const [metrics, setMetrics] = useState<MetricsData | null>(null) const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null) useEffect(() => { async function fetchMetrics() { try { const response = await fetch("/api/metrics") if (!response.ok) throw new Error("Failed to fetch metrics") const data = await response.json() setMetrics(data) } catch (err) { setError(err instanceof Error ? err.message : "Failed to load metrics") } finally { setLoading(false) } } fetchMetrics() }, []) return { metrics, loading, error } } "use client" import { useEffect, useState, useMemo } from "react" import { useTheme } from "next-themes" import { Label, PolarGrid, PolarRadiusAxis, RadialBar, RadialBarChart } from "recharts" import { Card, CardContent } from "@/components/ui/card" import { ChartContainer, type ChartConfig } from "@/components/ui/chart" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { useSettings } from "@/hooks/useSettings" import { useTranslation, type SupportedLanguages } from "@/utils/translations" import { useMetrics } from "@/hooks/useMetrics" import { Clock } from "lucide-react" interface CircularProgressProps { binId: string fillLevel: number color: string darkColor: string name: string } const DEFAULT_BINS = ["Biomüll", "Gelber Sack", "Papier", "Restmüll"] export function CircularProgress({ binId, fillLevel, color, darkColor, name }: CircularProgressProps) { const { theme } = useTheme() const [progress, setProgress] = useState(0) const { settings } = useSettings() const { t } = useTranslation((settings?.language as SupportedLanguages) || "EN") const { metrics } = useMetrics() useEffect(() => { const timer = setTimeout(() => setProgress(fillLevel), 100) return () => clearTimeout(timer) }, [fillLevel]) const isDarkMode = theme === "dark" const bgColor = isDarkMode ? darkColor : color const progressColor = getProgressColor(fillLevel, isDarkMode) const displayName = useMemo(() => (DEFAULT_BINS.includes(name) ? t(`bins.${name}`) : name), [name, t]) const chartConfig: ChartConfig = useMemo( () => ({ [binId]: { label: displayName, color: progressColor, }, }), [binId, displayName, progressColor], ) const chartData = useMemo( () => [ { name: `${binId}-progress`, value: progress, fill: progressColor, }, ], [binId, progress, progressColor], ) const predictionData = metrics?.fill_predictions[binId] const predictionDate = predictionData ? new Date(predictionData.predicted_full_time) : null const timeUntilFull = predictionDate ? getTimeUntilFull(predictionDate) : null return ( <TooltipProvider> <Tooltip> <TooltipTrigger asChild> <Card className="border-0 bg-transparent shadow-none w-[460px] min-w-[460px]"> <CardContent className="p-0"> <ChartContainer config={chartConfig} className="relative w-full aspect-square"> <div className="absolute inset-0 flex items-center justify-center"> <div className={cn("rounded-full", isDarkMode ? "opacity-[0.15]" : "opacity-[0.55]")} style={{ backgroundColor: bgColor, width: "57%", height: "57%", position: "absolute", left: "50%", top: "50%", transform: "translate(-50%, -50%)", }} /> </div> <RadialBarChart width="100%" height="100%" data={chartData} startAngle={90} endAngle={90 - progress * 3.6} innerRadius="65%" outerRadius="90%" barSize={12} > <PolarRadiusAxis tick={false} axisLine={false} tickLine={false} stroke="none"> <Label content={({ viewBox }) => { if (viewBox && "cx" in viewBox && "cy" in viewBox) { return ( <text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle" className="fill-foreground text-2xl font-medium" > {displayName} </text> ) } }} /> </PolarRadiusAxis> <RadialBar background={false} dataKey="value" cornerRadius={15} className="stroke-none" /> </RadialBarChart> </ChartContainer> </CardContent> </Card> </TooltipTrigger> <TooltipContent> <div className="flex items-center space-x-2"> <Clock className="h-4 w-4" /> <span>{timeUntilFull ? `Full in ${timeUntilFull}` : "Prediction unavailable"}</span> </div> </TooltipContent> </Tooltip> </TooltipProvider> ) } function getProgressColor(level: number, isDark: boolean) { if (isDark) { if (level >= 80) return "#ff3b30" if (level >= 60) return "#ff9f0a" if (level >= 40) return "#ffd60a" return "#30d158" } else { if (level >= 80) return "#ff453a" if (level >= 60) return "#ff9f0a" if (level >= 40) return "#ffd60a" return "#34c759" } } function getTimeUntilFull(predictionDate: Date): string { const now = new Date() const diffInMinutes = Math.floor((predictionDate.getTime() - now.getTime()) / (1000 * 60)) if (diffInMinutes < 60) { return `${diffInMinutes} minutes` } else if (diffInMinutes < 1440) { const hours = Math.floor(diffInMinutes / 60) return `${hours} hour${hours > 1 ? "s" : ""}` } else { const days = Math.floor(diffInMinutes / 1440) return `${days} day${days > 1 ? "s" : ""}` } }
Leave a Comment