Untitled

 avatar
unknown
plain_text
10 days ago
6.9 kB
3
Indexable
// /components/circular-progress.tsx
'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, ChartConfig } from "@/components/ui/chart"
import { cn } from "@/lib/utils"
import { useSettings } from "@/hooks/useSettings"
import { useTranslation, type SupportedLanguages } from "@/utils/translations"
import { Timer, AlertTriangle, Clock } from 'lucide-react'
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"
import { useMetrics } from '@/hooks/useMetrics'

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()
  const [isHovered, setIsHovered] = useState(false)

  // Get prediction for this bin
  const prediction = metrics?.fill_predictions?.[binId]

  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 getPredictionInfo = () => {
    if (!prediction) return null

    const predictedTime = new Date(prediction.predicted_full_time)
    const now = new Date()
    const hoursUntilFull = (predictedTime.getTime() - now.getTime()) / (1000 * 60 * 60)

    const color = hoursUntilFull < 1 ? 'text-red-500' 
                 : hoursUntilFull < 12 ? 'text-orange-500'
                 : hoursUntilFull < 24 ? 'text-yellow-500'
                 : 'text-green-500'

    const icon = hoursUntilFull < 1 ? AlertTriangle 
                : hoursUntilFull < 24 ? Timer
                : Clock

    const formattedTime = new Intl.DateTimeFormat(
      settings?.language === 'DE' ? 'de-DE' : 'en-US',
      { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }
    ).format(predictedTime)

    return { color, Icon: icon, formattedTime, fillRate: prediction.fill_rate }
  }

  const predictionInfo = getPredictionInfo()

  return (
    <TooltipProvider>
      <Tooltip open={isHovered}>
        <TooltipTrigger asChild>
          <div 
            onMouseEnter={() => setIsHovered(true)}
            onMouseLeave={() => setIsHovered(false)}
          >
            <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">
                  {/* ... rest of your chart code ... */}
                  <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>
          </div>
        </TooltipTrigger>
        {predictionInfo && (
          <TooltipContent 
            side="top"
            align="center"
            className="bg-card p-3"
          >
            <div className="flex items-center gap-3">
              <div className={cn("flex items-center gap-2", predictionInfo.color)}>
                <predictionInfo.Icon className="h-4 w-4" />
                <span className="font-medium">
                  {t('predictions.fullBy', { time: predictionInfo.formattedTime })}
                </span>
              </div>
              <span className="text-sm text-muted-foreground">
                ({Math.round(predictionInfo.fillRate * 10) / 10}%/h)
              </span>
            </div>
          </TooltipContent>
        )}
      </Tooltip>
    </TooltipProvider>
  )
}
Leave a Comment