Untitled

 avatar
unknown
plain_text
15 days ago
7.8 kB
4
Indexable
'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 { Button } from "@/components/ui/button"
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 { useRouter } from 'next/navigation'
import { Clock } from 'lucide-react'
import { useMetrics } from '@/hooks/useMetrics'

interface CircularProgressProps {
  binId: string
  fillLevel: number
  color: string
  darkColor: string
  name: string
  backContent?: {
    text: string
    buttonText: string
  }
}

export function CircularProgress({ 
  binId, 
  fillLevel, 
  color, 
  darkColor, 
  name,
  backContent = {
    text: "Click to manage this bin",
    buttonText: "Details"
  }
}: CircularProgressProps) {
  const { theme } = useTheme()
  const [progress, setProgress] = useState(0)
  const [isFlipped, setIsFlipped] = useState(false)
  const { settings } = useSettings()
  const { t } = useTranslation(settings?.language as SupportedLanguages || 'EN')
  const router = useRouter()
  const { metrics } = useMetrics()

  // Extract bin number and match to prediction
  const getBinNumber = (id: string) => {
    const match = id.match(/\d+/)
    return match ? `bin${match[0]}` : null
  }

  const binNumber = getBinNumber(binId)
  const prediction = binNumber ? metrics?.fill_predictions?.[binNumber] : null

  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 getPredictionColor = (predictedDate: string) => {
    const timeUntilFull = (new Date(predictedDate).getTime() - new Date().getTime()) / (1000 * 60 * 60) // hours
    if (timeUntilFull <= 0.5) return 'text-red-500'  // Less than 30 minutes
    if (timeUntilFull <= 2) return 'text-orange-500' // Less than 2 hours
    if (timeUntilFull <= 24) return 'text-yellow-500' // Less than 24 hours
    return 'text-green-500' // More than 24 hours
  }

  const formatPredictionTime = (dateString: string) => {
    const date = new Date(dateString)
    return date.toLocaleString('en-US', {
      day: 'numeric',
      month: 'short',
      hour: '2-digit',
      minute: '2-digit',
      hour12: false
    })
  }

  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 handleClick = () => {
    setIsFlipped(!isFlipped)
  }

  const handleRedirect = (e: React.MouseEvent) => {
    e.stopPropagation()
    router.push('/bins')
  }

  return (
    <Card className="relative border-0 bg-transparent shadow-none w-[460px] min-w-[460px]">
      <CardContent className="p-0">
        <ChartContainer config={chartConfig} className="relative w-full aspect-square">
          <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" />
            <RadialBar
              background={false}
              dataKey="value"
              cornerRadius={15}
              className="stroke-none"
            />
          </RadialBarChart>

          <div 
            className="absolute inset-0 flex items-center justify-center cursor-pointer"
            onClick={handleClick}
          >
            <div className="relative perspective-1000 w-[57%] h-[57%]">
              <div className={cn(
                "absolute w-full h-full rounded-full transition-transform duration-700 transform-style-preserve-3d",
                isFlipped && "rotate-y-180"
              )}>
                <div className="absolute w-full h-full rounded-full backface-hidden">
                  <div
                    className={cn(
                      "absolute inset-0 rounded-full",
                      isDarkMode ? "opacity-[0.15]" : "opacity-[0.55]"
                    )}
                    style={{ backgroundColor: bgColor }}
                  />
                  <div className="absolute inset-0 flex items-center justify-center">
                    <span className="text-2xl font-medium text-foreground">
                      {displayName}
                    </span>
                  </div>
                </div>

                <div
                  className={cn(
                    "absolute w-full h-full rounded-full backface-hidden rotate-y-180 flex flex-col items-center justify-center p-4",
                    isDarkMode 
                      ? "bg-zinc-800/90 text-zinc-100" 
                      : "bg-zinc-100/90 text-zinc-800"
                  )}
                >
                  {prediction ? (
                    <div className="flex flex-col items-center space-y-2">
                      <Clock className={cn(
                        "w-6 h-6",
                        getPredictionColor(prediction.predicted_full_time)
                      )} />
                      <p className={cn(
                        "text-sm font-medium text-center",
                        getPredictionColor(prediction.predicted_full_time)
                      )}>
                        Full by {formatPredictionTime(prediction.predicted_full_time)}
                      </p>
                      <Button 
                        onClick={handleRedirect}
                        size="sm"
                        className="w-32 text-xs mt-2"
                        variant="outline"
                      >
                        {backContent.buttonText}
                      </Button>
                    </div>
                  ) : (
                    <div className="flex flex-col items-center space-y-2">
                      <p className="text-sm text-center mb-2">No prediction available</p>
                      <Button 
                        onClick={handleRedirect}
                        size="sm"
                        className="w-32 text-xs"
                        variant="outline"
                      >
                        {backContent.buttonText}
                      </Button>
                    </div>
                  )}
                </div>
              </div>
            </div>
          </div>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}

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'
  }
}

const DEFAULT_BINS = ['Biomüll', 'Gelber Sack', 'Papier', 'Restmüll']
Leave a Comment