be828b205d
- Added ActionCenterWidget, DetailDrawer, AgentSelectionModal - Implemented global ErrorBoundary and Chatbot resilience - Updated documentation (README, Structure, Status, Overview) - Refined responsive design and modal portals
134 lines
5.6 KiB
React
134 lines
5.6 KiB
React
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>
|
|
);
|
|
};
|