import { useState, useEffect, useCallback } from 'react';
import { motion } from 'framer-motion';
import { HiSearch } from 'react-icons/hi';
import { HiArrowPath } from 'react-icons/hi2';
import api from '../../../services/api';
import { formatDate, formatCurrency } from '../../../utils/helpers';
import toast from 'react-hot-toast';
const STATUSES = ['', 'submitted', 'under_review', 'approved', 'rejected', 'fulfilled'];
const STATUS_STYLE = {
submitted: { bg: '#eff6ff', color: '#2563EB', label: 'New' },
under_review: { bg: '#fffbeb', color: '#D97706', label: 'Reviewing' },
approved: { bg: '#ecfdf5', color: '#059669', label: 'Approved' },
rejected: { bg: '#fef2f2', color: '#EF4444', label: 'Rejected' },
fulfilled: { bg: '#f5f3ff', color: '#7C3AED', label: 'Fulfilled' },
};
const URGENCY_STYLE = {
critical: '#EF4444', high: '#F59E0B', medium: '#3B82F6', low: '#10B981',
};
const NEXT_STATUS = {
submitted: 'under_review', under_review: 'approved', approved: 'fulfilled',
};
function ReviewModal({ request, onClose, onUpdate }) {
const [status, setStatus] = useState(request.status);
const [notes, setNotes] = useState(request.reviewNotes || '');
const [saving, setSaving] = useState(false);
const handle = async () => {
setSaving(true);
try {
await api.put(`/help-requests/${request.id}/status`, { status, reviewNotes: notes });
toast.success('Request updated!');
onUpdate();
onClose();
} catch (e) { toast.error(e.message || 'Update failed.'); }
finally { setSaving(false); }
};
return (
e.stopPropagation()}>
๐ Review: {request.title}
Submitted {formatDate(request.createdAt)} ยท Est. {formatCurrency(request.estimatedAmount || 0)}
{request.description}
);
}
export default function HelpRequestTriage() {
const [requests, setRequests] = useState([]);
const [stats, setStats] = useState(null);
const [statusFilter, setStatusFilter] = useState('');
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(true);
const [reviewing, setReviewing] = useState(null);
const [quickActioning, setQuickActioning] = useState(null);
const load = useCallback(async () => {
setLoading(true);
try {
const [reqs, st] = await Promise.allSettled([
api.get(`/help-requests/all?limit=100${statusFilter ? '&status=' + statusFilter : ''}`),
api.get('/help-requests/stats'),
]);
if (reqs.status === 'fulfilled') setRequests(reqs.value.requests || []);
if (st.status === 'fulfilled') setStats(st.value);
} finally { setLoading(false); }
}, [statusFilter]);
useEffect(() => { load(); }, [load]);
const handleQuickAction = async (req, nextStatus) => {
setQuickActioning(req.id);
try {
await api.put(`/help-requests/${req.id}/status`, { status: nextStatus });
toast.success(`Moved to ${STATUS_STYLE[nextStatus]?.label}`);
await load();
} catch (e) { toast.error(e.message || 'Action failed.'); }
finally { setQuickActioning(null); }
};
const filtered = requests.filter(r =>
!search || r.title?.toLowerCase().includes(search.toLowerCase()) ||
r.requestType?.toLowerCase().includes(search.toLowerCase())
);
const statsItems = [
{ label: 'Total', value: stats?.total || 0, color: '#374151' },
{ label: 'New', value: stats?.submitted || 0, color: '#2563EB' },
{ label: 'Reviewing', value: stats?.under_review || 0, color: '#D97706' },
{ label: 'Approved', value: stats?.approved || 0, color: '#059669' },
{ label: 'Fulfilled', value: stats?.fulfilled || 0, color: '#7C3AED' },
];
return (
{reviewing &&
setReviewing(null)} onUpdate={load} />}
๐ Help Request Triage
Review, assign, and resolve citizen help requests.
{/* Stats */}
{statsItems.map(s => (
setStatusFilter(s.label === 'Total' ? '' : s.label.toLowerCase().replace(' ', '_'))}>
))}
{/* Filters */}
{/* Requests */}
{loading ? (
) : filtered.length === 0 ? (
โ
No help requests found.
) : (
{filtered.map((req, i) => {
const sty = STATUS_STYLE[req.status] || STATUS_STYLE.submitted;
const urgColor = URGENCY_STYLE[req.urgency] || URGENCY_STYLE.medium;
const nextStatus = NEXT_STATUS[req.status];
return (
{req.title}
{req.urgency}
{sty.label}
{req.description?.slice(0, 120)}{req.description?.length > 120 ? '...' : ''}
๐ {req.requestType}
๐ฐ Est. {formatCurrency(req.estimatedAmount || 0)}
{req.location?.city && ๐ {req.location.city}}
๐ {formatDate(req.createdAt)}
{req.reviewNotes && (
๐ {req.reviewNotes}
)}
{nextStatus && (
)}
{req.status !== 'rejected' && req.status !== 'fulfilled' && (
)}
);
})}
)}
);
}