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:
Satyam
2026-02-11 18:33:04 +05:30
parent bfc90b668b
commit be828b205d
7 changed files with 676 additions and 12 deletions
@@ -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>
);
};