BillTracker/client/components/tracker/StatusBadge.tsx

69 lines
2.3 KiB
TypeScript

import React, { useMemo } from 'react';
import { AlertCircle, CheckCircle2, Circle, Clock3, Loader2, SkipForward } from 'lucide-react';
import { cn } from '@/lib/utils';
import { STATUS_META, isPaidStatus } from '@/lib/trackerUtils';
import type { TrackerStatus } from '@/types';
interface StatusBadgeProps {
status: TrackerStatus;
clickable?: boolean;
onClick?: () => void;
loading?: boolean;
}
export const StatusBadge = React.memo(function StatusBadge({ status, clickable, onClick, loading }: StatusBadgeProps) {
const meta = useMemo(() => STATUS_META[status] || STATUS_META.upcoming, [status]);
const StatusIcon = useMemo(() => {
switch (status) {
case 'paid':
case 'autodraft':
return CheckCircle2;
case 'late':
case 'missed':
return AlertCircle;
case 'due_soon':
return Clock3;
case 'skipped':
return SkipForward;
default:
return Circle;
}
}, [status]);
const isSkipped = status === 'skipped';
const canClick = clickable && !isSkipped && !loading;
return (
<button
type="button"
disabled={!canClick || loading}
onClick={onClick}
className={cn(
'inline-flex items-center gap-1.5 px-2.5 py-0.5 text-[11px] rounded-full font-semibold',
'uppercase tracking-wide whitespace-nowrap',
'transition-colors duration-150',
(status === 'late' || status === 'missed') && 'px-2.5 py-1 text-xs',
canClick && 'cursor-pointer hover:shadow-sm',
canClick && status === 'paid' && 'hover:bg-red-500/20 hover:text-red-600 hover:border-red-500/40',
canClick && status !== 'paid' && 'hover:bg-emerald-500/20 hover:text-emerald-600 hover:border-emerald-500/40',
loading && 'opacity-60 cursor-wait',
meta.cls,
)}
title={canClick ? (isPaidStatus(status) ? 'Click to mark unpaid' : 'Click to mark paid') : undefined}
aria-label={`${meta.label}${canClick ? (isPaidStatus(status) ? ', click to mark unpaid' : ', click to mark paid') : ''}`}
>
{loading ? (
<>
<Loader2 className="h-3 w-3 animate-spin" aria-hidden="true" />
{meta.label}
</>
) : (
<>
<StatusIcon className="h-3.5 w-3.5" aria-hidden="true" />
{meta.label}
</>
)}
</button>
);
});