Untitled
unknown
plain_text
9 months ago
7.8 kB
6
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']Editor is loading...
Leave a Comment