Complete personal finance management application with: - Dashboard with financial metrics and alerts - Credit card management and payments - Fixed and variable debt tracking - Monthly budget planning - Intelligent alert system - Responsive design with Tailwind CSS Tech stack: Next.js 14, TypeScript, Zustand, Recharts 🤖 Generated with [Claude Code](https://claude.com/claude-code)
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { cn, formatCurrency } from '@/lib/utils'
|
|
|
|
interface BudgetRingProps {
|
|
spent: number
|
|
total: number
|
|
label: string
|
|
}
|
|
|
|
export function BudgetRing({ spent, total, label }: BudgetRingProps) {
|
|
const percentage = total > 0 ? Math.min((spent / total) * 100, 100) : 0
|
|
const remaining = Math.max(total - spent, 0)
|
|
|
|
const getColor = () => {
|
|
if (percentage < 70) return { stroke: '#10b981', bg: 'text-emerald-400' }
|
|
if (percentage < 90) return { stroke: '#f59e0b', bg: 'text-amber-400' }
|
|
return { stroke: '#ef4444', bg: 'text-red-400' }
|
|
}
|
|
|
|
const colors = getColor()
|
|
|
|
const radius = 80
|
|
const strokeWidth = 12
|
|
const normalizedRadius = radius - strokeWidth / 2
|
|
const circumference = normalizedRadius * 2 * Math.PI
|
|
const strokeDashoffset = circumference - (percentage / 100) * circumference
|
|
|
|
return (
|
|
<div className="flex flex-col items-center">
|
|
<div className="relative">
|
|
<svg
|
|
width={radius * 2}
|
|
height={radius * 2}
|
|
className="transform -rotate-90"
|
|
>
|
|
{/* Background circle */}
|
|
<circle
|
|
stroke="#334155"
|
|
strokeWidth={strokeWidth}
|
|
fill="transparent"
|
|
r={normalizedRadius}
|
|
cx={radius}
|
|
cy={radius}
|
|
/>
|
|
{/* Progress circle */}
|
|
<circle
|
|
stroke={colors.stroke}
|
|
strokeWidth={strokeWidth}
|
|
strokeLinecap="round"
|
|
fill="transparent"
|
|
r={normalizedRadius}
|
|
cx={radius}
|
|
cy={radius}
|
|
style={{
|
|
strokeDasharray: `${circumference} ${circumference}`,
|
|
strokeDashoffset,
|
|
transition: 'stroke-dashoffset 0.5s ease-in-out',
|
|
}}
|
|
/>
|
|
</svg>
|
|
{/* Center content */}
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
|
<span className={cn('text-3xl font-bold', colors.bg)}>
|
|
{percentage.toFixed(0)}%
|
|
</span>
|
|
<span className="text-slate-400 text-sm mt-1">usado</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats below */}
|
|
<div className="mt-4 text-center">
|
|
<p className="text-slate-400 text-sm">{label}</p>
|
|
<p className="text-lg font-semibold text-white mt-1">
|
|
{formatCurrency(spent)} <span className="text-slate-500">/ {formatCurrency(total)}</span>
|
|
</p>
|
|
<p className="text-sm text-slate-500 mt-1">
|
|
{formatCurrency(remaining)} disponible
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|