import React, { useState, useEffect, useMemo } from 'react'; import { Phone, CheckCircle2, Clock, AlertCircle, Plus, Filter, Search, X, ChevronDown, User, FileText, Mail, RotateCcw } from 'lucide-react'; // ============================================================ // CONFIGURATION — Edit these lists to update the dropdowns // ============================================================ const ADJUSTERS = [ { name: 'Leanna Flint', email: '[email protected]' }, { name: 'Monica Jackson', email: '[email protected]' }, { name: 'John Fischer', email: '[email protected]' }, { name: 'Amanda Armendariz', email: '[email protected]' }, { name: 'Becky Cedillo', email: '[email protected]' }, { name: 'Mario Yanez', email: '[email protected]' }, { name: 'Emaus Gutierrez', email: '[email protected]' }, { name: 'Alex Rodriguez', email: '[email protected]' }, { name: 'Laura Rendon', email: '[email protected]' }, { name: 'Samantha Foltz', email: '[email protected]' }, { name: 'Evelyn Lopez', email: '[email protected]' }, { name: 'Trae Hall', email: '[email protected]' }, { name: 'Mercedes Baker', email: '[email protected]' }, { name: 'Dan Merritt', email: '[email protected]' }, { name: 'Erika Epps', email: '[email protected]' }, { name: 'Dillon Emery', email: '[email protected]' }, { name: 'Edgar Cerda', email: '[email protected]' }, { name: 'Demarcus Smith', email: '[email protected]' }, { name: 'Sergio Torres', email: '[email protected]' }, ]; const TOPICS = [ 'Status update on claim', 'Settlement discussion', 'Repair authorization', 'Rental car', 'Medical bills or treatment', 'Total loss', 'Subrogation', 'Document request', 'Complaint', 'Other', ]; const STORAGE_KEY = 'callbacks:all'; // ============================================================ // HELPERS // ============================================================ function formatPhone(value) { const digits = value.replace(/\D/g, '').slice(0, 10); if (digits.length < 4) return digits; if (digits.length < 7) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`; return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`; } function ageInMinutes(timestamp) { return Math.floor((Date.now() - timestamp) / 60000); } function formatAge(minutes) { if (minutes < 1) return 'just now'; if (minutes < 60) return `${minutes}m`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ${minutes % 60}m`; const days = Math.floor(hours / 24); return `${days}d ${hours % 24}h`; } function ageColor(minutes) { if (minutes < 60) return 'fresh'; // under 1h if (minutes < 240) return 'normal'; // under 4h if (minutes < 1440) return 'warning'; // under 24h return 'critical'; // 24h+ } function formatTimestamp(ts) { return new Date(ts).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } function uid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 8); } // ============================================================ // MAIN COMPONENT // ============================================================ export default function ClaimsCallbacksDashboard() { const [callbacks, setCallbacks] = useState([]); const [loading, setLoading] = useState(true); const [view, setView] = useState('pending'); // 'pending' | 'completed' const [adjusterFilter, setAdjusterFilter] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); const [showForm, setShowForm] = useState(false); const [showEmailPreview, setShowEmailPreview] = useState(null); const [tick, setTick] = useState(0); // forces re-render for age updates // Load data on mount useEffect(() => { loadCallbacks(); }, []); // Re-render every 30s so ages stay current useEffect(() => { const interval = setInterval(() => setTick(t => t + 1), 30000); return () => clearInterval(interval); }, []); async function loadCallbacks() { setLoading(true); try { const result = await window.storage.get(STORAGE_KEY, true); if (result && result.value) { setCallbacks(JSON.parse(result.value)); } else { setCallbacks([]); } } catch (e) { // Key doesn't exist yet — that's fine, start with empty array setCallbacks([]); } setLoading(false); } async function saveCallbacks(updated) { setCallbacks(updated); try { await window.storage.set(STORAGE_KEY, JSON.stringify(updated), true); } catch (e) { console.error('Save failed:', e); } } async function addCallback(data) { const newCallback = { id: uid(), ...data, status: 'pending', createdAt: Date.now(), completedAt: null, completedBy: null, }; const updated = [newCallback, ...callbacks]; await saveCallbacks(updated); setShowForm(false); setShowEmailPreview(newCallback); } async function markComplete(id, completedBy) { const updated = callbacks.map(cb => cb.id === id ? { ...cb, status: 'completed', completedAt: Date.now(), completedBy } : cb ); await saveCallbacks(updated); } async function reopenCallback(id) { const updated = callbacks.map(cb => cb.id === id ? { ...cb, status: 'pending', completedAt: null, completedBy: null } : cb ); await saveCallbacks(updated); } // Filtering const filtered = useMemo(() => { return callbacks .filter(cb => cb.status === view) .filter(cb => adjusterFilter === 'all' || cb.adjuster === adjusterFilter) .filter(cb => { if (!searchQuery) return true; const q = searchQuery.toLowerCase(); return ( cb.callerName.toLowerCase().includes(q) || cb.claimNumber.toLowerCase().includes(q) || cb.phone.includes(q) || cb.topic.toLowerCase().includes(q) ); }); }, [callbacks, view, adjusterFilter, searchQuery, tick]); // Counts for tabs const pendingCount = callbacks.filter(cb => cb.status === 'pending').length; const completedCount = callbacks.filter(cb => cb.status === 'completed').length; const overdueCount = callbacks.filter( cb => cb.status === 'pending' && ageInMinutes(cb.createdAt) >= 240 ).length; return (
{/* Header */}

Claims Call Backs

Titan Claim Services

{/* Stat row */}
0 ? 'red' : 'neutral'} />
{/* Tabs + filters */}
setView('pending')}> Pending ({pendingCount}) setView('completed')}> Completed ({completedCount})
setSearchQuery(e.target.value)} className="pl-9 pr-3 py-2 text-sm border border-stone-200 rounded-lg bg-white text-slate-900 placeholder-stone-400 focus:outline-none focus:ring-2 focus:ring-slate-900/10 focus:border-stone-300 w-64" />
{/* Table */} {loading ? (
Loading...
) : filtered.length === 0 ? ( ) : ( )}
{/* Footer info */}
{callbacks.length} total records · Data shared across all users Ages update every 30 seconds
{/* Form modal */} {showForm && ( setShowForm(false)} /> )} {/* Email preview modal */} {showEmailPreview && ( setShowEmailPreview(null)} /> )}
); } // ============================================================ // SUB-COMPONENTS // ============================================================ function StatCard({ label, value, icon: Icon, accent }) { const accents = { amber: 'bg-amber-50 text-amber-700 border-amber-100', red: 'bg-red-50 text-red-700 border-red-100', green: 'bg-emerald-50 text-emerald-700 border-emerald-100', neutral: 'bg-stone-50 text-stone-600 border-stone-100', }; return (
{label}
{value}
); } function TabButton({ active, onClick, children }) { return ( ); } function EmptyState({ view, hasFilters }) { return (
{view === 'pending' ? ( ) : ( )}

{hasFilters ? 'No matching call backs' : view === 'pending' ? 'No pending call backs' : 'No completed call backs yet'}

{hasFilters ? 'Try clearing filters or search' : view === 'pending' ? 'All caught up. Click "New call back" to add one.' : 'Completed call backs will appear here.'}

); } function CallbackTable({ callbacks, view, onComplete, onReopen, onViewEmail }) { return (
{callbacks.map(cb => ( ))}
Caller Claim # Phone Topic Adjuster {view === 'pending' ? 'Age' : 'Completed'}
); } function CallbackRow({ callback: cb, view, onComplete, onReopen, onViewEmail }) { const [showCompleteDialog, setShowCompleteDialog] = useState(false); const minutes = ageInMinutes(cb.createdAt); const ageBadge = ageColor(minutes); const ageBadgeClass = { fresh: 'bg-stone-100 text-stone-600', normal: 'bg-stone-100 text-stone-600', warning: 'bg-amber-50 text-amber-700 border border-amber-200', critical: 'bg-red-50 text-red-700 border border-red-200', }[ageBadge]; return ( <> {view === 'pending' ? ( ) : (
)}
{cb.callerName}
{cb.notes && (
{cb.notes}
)} {cb.claimNumber} {cb.phone} {cb.topic} {cb.adjuster} {view === 'pending' ? ( {formatAge(minutes)} ) : (
{formatTimestamp(cb.completedAt)}
by {cb.completedBy}
)}
{view === 'completed' && ( )}
{showCompleteDialog && ( { onComplete(cb.id, name); setShowCompleteDialog(false); }} onCancel={() => setShowCompleteDialog(false)} /> )} ); } function CompleteDialog({ callback: cb, onConfirm, onCancel }) { const [completedBy, setCompletedBy] = useState(cb.adjuster); return (

Mark call back complete

Confirm you called {cb.callerName} regarding claim {cb.claimNumber}.

); } function CallbackForm({ onSubmit, onCancel }) { const [callerName, setCallerName] = useState(''); const [claimNumber, setClaimNumber] = useState(''); const [phone, setPhone] = useState(''); const [adjuster, setAdjuster] = useState(''); const [topic, setTopic] = useState(''); const [notes, setNotes] = useState(''); const [submittedBy, setSubmittedBy] = useState(''); const [errors, setErrors] = useState({}); function validate() { const e = {}; if (!callerName.trim()) e.callerName = 'Required'; if (!claimNumber.trim()) e.claimNumber = 'Required'; if (!phone.trim()) e.phone = 'Required'; else if (phone.replace(/\D/g, '').length !== 10) e.phone = '10 digits required'; if (!adjuster) e.adjuster = 'Required'; if (!topic) e.topic = 'Required'; if (!submittedBy.trim()) e.submittedBy = 'Required'; return e; } function handleSubmit() { const validationErrors = validate(); if (Object.keys(validationErrors).length > 0) { setErrors(validationErrors); return; } onSubmit({ callerName: callerName.trim(), claimNumber: claimNumber.trim(), phone: phone.trim(), adjuster, topic, notes: notes.trim(), submittedBy: submittedBy.trim(), }); } return (

New call back request

Fill out the details from the call. Submitting adds it to the dashboard and sends notifications.

setCallerName(e.target.value)} placeholder="First and last" className="form-input" />
setClaimNumber(e.target.value)} placeholder="e.g. 2024-0142" className="form-input font-mono" /> setPhone(formatPhone(e.target.value))} placeholder="(555) 123-4567" className="form-input tabular-nums" />
({ value: a.name, label: a.name })) ]} /> ({ value: t, label: t })) ]} />