feat(crm): Complete Admin Action Center & Reliability Overhaul
- Added ActionCenterWidget, DetailDrawer, AgentSelectionModal - Implemented global ErrorBoundary and Chatbot resilience - Updated documentation (README, Structure, Status, Overview) - Refined responsive design and modal portals
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useMockStore } from '../../data/mockStore';
|
||||
import {
|
||||
UserPlus, Flame, CalendarX, FileSignature,
|
||||
AlertCircle, ArrowRight
|
||||
} from 'lucide-react';
|
||||
import { ActionDetailDrawer } from './ActionDetailDrawer';
|
||||
|
||||
export const ActionCenterWidget = () => {
|
||||
const { properties, meetings } = useMockStore();
|
||||
const [selectedView, setSelectedView] = useState(null); // 'UNASSIGNED', 'HOT', 'RESCHEDULE', 'SIGNATURE', or null
|
||||
|
||||
// Helper: Logic to filter counts
|
||||
const unassignedLeads = properties.filter(p => !p.assignedAgentId && p.canvassingStatus === 'Lead');
|
||||
|
||||
// Last contact check: older than 24h or null
|
||||
const timeThreshold = new Date(Date.now() - 86400000);
|
||||
const hotLeads = properties.filter(p =>
|
||||
p.canvassingStatus === 'Hot Lead' &&
|
||||
(!p.lastContactDate || new Date(p.lastContactDate) < timeThreshold)
|
||||
);
|
||||
|
||||
const rescheduleMeets = meetings.filter(m =>
|
||||
m.status === 'Cancelled' || m.status === 'Missed'
|
||||
);
|
||||
|
||||
const pendingSignatures = properties.filter(p => p.pendingSignature);
|
||||
|
||||
const totalUrgent = unassignedLeads.length + hotLeads.length + rescheduleMeets.length + pendingSignatures.length;
|
||||
|
||||
const cards = [
|
||||
{
|
||||
id: 'UNASSIGNED',
|
||||
title: 'Unassigned Leads',
|
||||
count: unassignedLeads.length,
|
||||
icon: UserPlus,
|
||||
color: 'text-blue-600 dark:text-blue-400',
|
||||
bg: 'bg-blue-50 dark:bg-blue-900/20',
|
||||
border: 'border-blue-200 dark:border-blue-800'
|
||||
},
|
||||
{
|
||||
id: 'HOT',
|
||||
title: 'Hot Leads Inactive',
|
||||
count: hotLeads.length,
|
||||
icon: Flame,
|
||||
color: 'text-orange-600 dark:text-orange-400',
|
||||
bg: 'bg-orange-50 dark:bg-orange-900/20',
|
||||
border: 'border-orange-200 dark:border-orange-800'
|
||||
},
|
||||
{
|
||||
id: 'RESCHEDULE',
|
||||
title: 'Need Reschedule',
|
||||
count: rescheduleMeets.length,
|
||||
icon: CalendarX,
|
||||
color: 'text-rose-600 dark:text-rose-400',
|
||||
bg: 'bg-rose-50 dark:bg-rose-900/20',
|
||||
border: 'border-rose-200 dark:border-rose-800'
|
||||
},
|
||||
{
|
||||
id: 'SIGNATURE',
|
||||
title: 'Pending Signatures',
|
||||
count: pendingSignatures.length,
|
||||
icon: FileSignature,
|
||||
color: 'text-purple-600 dark:text-purple-400',
|
||||
bg: 'bg-purple-50 dark:bg-purple-900/20',
|
||||
border: 'border-purple-200 dark:border-purple-800'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mb-8 select-none">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
Action Center
|
||||
{totalUrgent > 0 && (
|
||||
<span className="flex items-center gap-1 px-2.5 py-0.5 rounded-full bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 text-xs font-bold border border-red-200 dark:border-red-800 animate-pulse">
|
||||
<AlertCircle size={12} />
|
||||
{totalUrgent} URGENT
|
||||
</span>
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-3 md:gap-4">
|
||||
{cards.map((card) => {
|
||||
const Icon = card.icon;
|
||||
return (
|
||||
<div
|
||||
key={card.id}
|
||||
onClick={() => setSelectedView(card.id)}
|
||||
className={`
|
||||
group relative overflow-hidden cursor-pointer
|
||||
p-4 md:p-5 rounded-xl border transition-all duration-200
|
||||
${card.bg} ${card.border}
|
||||
hover:shadow-md hover:scale-[1.02] hover:border-opacity-100
|
||||
active:scale-95 touch-manipulation
|
||||
`}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div className={`p-2 rounded-lg bg-white dark:bg-white/5 shadow-sm ${card.color}`}>
|
||||
<Icon size={20} />
|
||||
</div>
|
||||
<span className={`text-3xl font-black ${card.color}`}>
|
||||
{card.count}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
<span className="text-xs font-bold text-gray-600 dark:text-gray-400 uppercase tracking-wider truncate mr-2">
|
||||
{card.title}
|
||||
</span>
|
||||
<ArrowRight
|
||||
size={14}
|
||||
className={`opacity-0 group-hover:opacity-100 transform group-hover:translate-x-1 transition-all duration-300 ${card.color} hidden sm:block`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Details Drawer */}
|
||||
{selectedView && (
|
||||
<ActionDetailDrawer
|
||||
isOpen={!!selectedView}
|
||||
onClose={() => setSelectedView(null)}
|
||||
viewType={selectedView}
|
||||
data={{ unassignedLeads, hotLeads, rescheduleMeets, pendingSignatures }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user