84 lines
2.4 KiB
TypeScript
84 lines
2.4 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>
|
|
);
|
|
});
|