import React, { useEffect, useState } from 'react'; import { ScanStatus } from '../../types/business'; export interface ScanStatusBadgeProps { scanStatus: ScanStatus; showTimestamp?: boolean; showTicketInfo?: boolean; animated?: boolean; size?: 'sm' | 'md' | 'lg'; className?: string; onStatusChange?: (status: ScanStatus) => void; } const ScanStatusBadge: React.FC = ({ scanStatus, showTimestamp = true, showTicketInfo = false, animated = true, size = 'md', className = '', onStatusChange }) => { const [isAnimating, setIsAnimating] = useState(false); const [announceText, setAnnounceText] = useState(''); // Trigger animation on status change useEffect(() => { if (animated) { setIsAnimating(true); const timer = setTimeout(() => setIsAnimating(false), 600); return () => clearTimeout(timer); } }, [scanStatus, animated]); // Handle accessibility announcements useEffect(() => { const getAnnouncementText = () => { switch (scanStatus.status) { case 'valid': return `Valid ticket scanned. ${scanStatus.ticketInfo?.eventTitle || 'Event'} ticket accepted.`; case 'used': return `Ticket already used. This ticket was previously scanned.`; case 'expired': return `Expired ticket. This ticket is no longer valid.`; case 'invalid': return `Invalid ticket. ${scanStatus.errorMessage || 'Please check the QR code.'}`; case 'not_found': return `Ticket not found. Please verify the QR code.`; default: return 'Ticket status unknown.'; } }; setAnnounceText(getAnnouncementText()); onStatusChange?.(scanStatus); }, [scanStatus, onStatusChange]); // Format timestamp const formatTimestamp = (timestamp?: string) => { if (!timestamp) return null; const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.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 'Just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); }; // Get status configuration const getStatusConfig = () => { switch (scanStatus.status) { case 'valid': return { variant: 'success' as const, icon: ( ), label: 'Valid', bgColor: 'bg-success/10', borderColor: 'border-success/20', textColor: 'text-success' }; case 'used': return { variant: 'warning' as const, icon: ( ), label: 'Used', bgColor: 'bg-warning/10', borderColor: 'border-warning/20', textColor: 'text-warning' }; case 'expired': return { variant: 'secondary' as const, icon: ( ), label: 'Expired', bgColor: 'bg-secondary/10', borderColor: 'border-secondary/20', textColor: 'text-secondary' }; case 'invalid': return { variant: 'destructive' as const, icon: ( ), label: 'Invalid', bgColor: 'bg-destructive/10', borderColor: 'border-destructive/20', textColor: 'text-destructive' }; case 'not_found': return { variant: 'destructive' as const, icon: ( ), label: 'Not Found', bgColor: 'bg-destructive/10', borderColor: 'border-destructive/20', textColor: 'text-destructive' }; default: return { variant: 'secondary' as const, icon: ( ), label: 'Unknown', bgColor: 'bg-secondary/10', borderColor: 'border-secondary/20', textColor: 'text-secondary' }; } }; const config = getStatusConfig(); // Size configurations const sizeConfig = { sm: { badge: 'text-xs px-2 py-1', icon: 'w-3 h-3', text: 'text-xs', container: 'space-y-1' }, md: { badge: 'text-sm px-3 py-1.5', icon: 'w-4 h-4', text: 'text-sm', container: 'space-y-2' }, lg: { badge: 'text-base px-4 py-2', icon: 'w-5 h-5', text: 'text-base', container: 'space-y-3' } }; const sizeClasses = sizeConfig[size]; return (
{/* Screen reader announcement */}
{announceText}
{/* Status Badge with Animation */}
{/* Status Icon */}
{config.icon}
{/* Status Label */} {config.label} {/* Animation pulse effect */} {isAnimating && scanStatus.status === 'valid' && (
)}
{/* Timestamp */} {showTimestamp && scanStatus.timestamp && ( {formatTimestamp(scanStatus.timestamp)} )}
{/* Error Message */} {scanStatus.errorMessage && (
{scanStatus.errorMessage}
)} {/* Ticket Information */} {showTicketInfo && scanStatus.ticketInfo && (
{scanStatus.ticketInfo.eventTitle}
{scanStatus.ticketInfo.ticketTypeName}
{scanStatus.ticketInfo.customerEmail}
{scanStatus.ticketInfo.seatNumber && (
Seat: {scanStatus.ticketInfo.seatNumber}
)}
)} {/* Success/Error animations */}
); }; export default ScanStatusBadge;