feat: initial commit - finanzas app
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)
This commit is contained in:
83
components/budget/BudgetRing.tsx
Normal file
83
components/budget/BudgetRing.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user