Untitled
unknown
plain_text
a year ago
6.9 kB
6
Indexable
"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" : ""}`
}
}
Editor is loading...
Leave a Comment