Untitled

 avatar
unknown
plain_text
a month ago
4.5 kB
2
Indexable
'use client'

import { useEffect, useState, useRef } 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"

interface CircularProgressProps {
  binId: string
  fillLevel: number
  color: string
  darkColor: string
  name: string
}

export function CircularProgress({ binId, fillLevel, color, darkColor, name }: CircularProgressProps) {
  const { theme } = useTheme()
  const [progress, setProgress] = useState(0)
  const containerRef = useRef<HTMLDivElement>(null)
  const [containerSize, setContainerSize] = useState({ width: 0, height: 0 })

  useEffect(() => {
    const timer = setTimeout(() => setProgress(fillLevel), 100)
    return () => clearTimeout(timer)
  }, [fillLevel])

  useEffect(() => {
    const updateSize = () => {
      if (containerRef.current) {
        const { width, height } = containerRef.current.getBoundingClientRect()
        setContainerSize({ width, height })
      }
    }

    updateSize()
    window.addEventListener('resize', updateSize)
    return () => window.removeEventListener('resize', updateSize)
  }, [])

  const isDarkMode = theme === 'dark'
  const bgColor = isDarkMode ? darkColor : color
  const textColor = getTextColor(bgColor)
  const progressColor = getProgressColor(fillLevel, isDarkMode)

  const chartConfig: ChartConfig = {
    [binId]: {
      label: name,
      color: progressColor,
    },
  }

  const chartData = [
    { name: `${binId}-progress`, value: progress, fill: progressColor }
  ]

  const innerCircleSize = Math.min(containerSize.width, containerSize.height) * 0.65 * 0.95

  return (
    <Card className="w-full border-0 bg-transparent shadow-none">
      <CardContent className="relative p-1" ref={containerRef}>
        <div 
          className={cn(
            "absolute rounded-full",
            isDarkMode ? "opacity-[0.15]" : "opacity-[0.35]"
          )}
          style={{
            backgroundColor: bgColor,
            width: `${innerCircleSize}px`,
            height: `${innerCircleSize}px`,
            left: '50%',
            top: '50%',
            transform: 'translate(-50%, -50%)',
          }}
        />
        <ChartContainer config={chartConfig} className="mx-auto aspect-square h-[400px]">
          <RadialBarChart
            data={chartData}
            startAngle={90}
            endAngle={90 - (progress * 3.6)}
            innerRadius="65%"
            outerRadius="95%"
            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"
                      >
                        {name}
                      </text>
                    )
                  }
                }}
              />
            </PolarRadiusAxis>
            <RadialBar
              background={false}
              dataKey="value"
              cornerRadius={15}
              className="stroke-none"
            />
          </RadialBarChart>
        </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'
  }
}

function getTextColor(backgroundColor: string) {
  const rgb = parseInt(backgroundColor.slice(1), 16)
  const r = (rgb >> 16) & 0xff
  const g = (rgb >> 8) & 0xff
  const b = (rgb >> 0) & 0xff
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
  return luminance > 0.5 ? '#000000' : '#ffffff'
}

Leave a Comment