Untitled

 avatar
unknown
plain_text
13 days ago
6.9 kB
3
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" : ""}`
  }
}

Leave a Comment