Files
finanzas/components/alerts/AlertItem.tsx
renato97 712b06f118 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)
2026-01-29 00:00:32 +00:00

149 lines
3.8 KiB
TypeScript

'use client'
import { useState } from 'react'
import { Check, Trash2 } from 'lucide-react'
import { Alert } from '@/lib/types'
import { cn } from '@/lib/utils'
import { AlertIcon } from './AlertIcon'
interface AlertItemProps {
alert: Alert
onMarkRead: () => void
onDelete: () => void
}
const severityStyles = {
info: 'text-blue-400',
warning: 'text-amber-400',
danger: 'text-red-400',
}
function getRelativeTime(date: string): string {
const now = new Date()
const alertDate = new Date(date)
const diffMs = now.getTime() - alertDate.getTime()
const diffMins = Math.floor(diffMs / (1000 * 60))
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
if (diffMins < 1) {
return 'ahora'
}
if (diffMins < 60) {
return `hace ${diffMins} min`
}
if (diffHours < 24) {
return `hace ${diffHours} hora${diffHours > 1 ? 's' : ''}`
}
if (diffDays === 1) {
return 'ayer'
}
if (diffDays < 7) {
return `hace ${diffDays} días`
}
return alertDate.toLocaleDateString('es-AR', {
day: 'numeric',
month: 'short',
})
}
export function AlertItem({ alert, onMarkRead, onDelete }: AlertItemProps) {
const [showActions, setShowActions] = useState(false)
const [isExiting, setIsExiting] = useState(false)
const handleMarkRead = () => {
setIsExiting(true)
setTimeout(() => {
onMarkRead()
}, 200)
}
const handleDelete = () => {
setIsExiting(true)
setTimeout(() => {
onDelete()
}, 200)
}
return (
<div
className={cn(
'group relative flex items-center gap-3 p-3 rounded-lg',
'transition-all duration-200',
'hover:bg-white/5',
isExiting && 'opacity-0 -translate-x-4',
!alert.isRead && 'bg-white/[0.02]'
)}
onMouseEnter={() => setShowActions(true)}
onMouseLeave={() => setShowActions(false)}
role="listitem"
>
{/* Unread indicator */}
{!alert.isRead && (
<span className="absolute left-1 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full bg-blue-500" />
)}
{/* Icon */}
<div
className={cn(
'flex-shrink-0',
severityStyles[alert.severity],
!alert.isRead && 'ml-3'
)}
>
<AlertIcon type={alert.type} className="h-4 w-4" />
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<p
className={cn(
'text-sm truncate',
alert.isRead ? 'text-gray-400' : 'text-white font-medium'
)}
>
{alert.title}
</p>
<p className="text-xs text-gray-500 mt-0.5">{getRelativeTime(alert.date)}</p>
</div>
{/* Actions */}
<div
className={cn(
'flex items-center gap-1 transition-opacity duration-200',
showActions ? 'opacity-100' : 'opacity-0'
)}
>
{!alert.isRead && (
<button
onClick={handleMarkRead}
className={cn(
'p-1.5 rounded-md transition-colors',
'text-gray-500 hover:text-green-400 hover:bg-green-400/10',
'focus:outline-none focus:ring-2 focus:ring-green-400/20'
)}
title="Marcar como leída"
aria-label="Marcar como leída"
>
<Check className="h-3.5 w-3.5" />
</button>
)}
<button
onClick={handleDelete}
className={cn(
'p-1.5 rounded-md transition-colors',
'text-gray-500 hover:text-red-400 hover:bg-red-400/10',
'focus:outline-none focus:ring-2 focus:ring-red-400/20'
)}
title="Eliminar"
aria-label="Eliminar alerta"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
</div>
)
}