bc4e25f132
- Rewrote AI chatbot context system with deep role-aware data injection for all 7 roles (Owner, Admin, Field Agent, Contractor, Subcontractor, Vendor, Customer) plus guest fallback - Added mobile-responsive bottom-sheet modals across all roles (ChangeOrderDrawer, InvoiceDetailModal, FinancialSummaryModal, VendorFinancialSummaryModal, FinancialDetailsModal) - Converted Owner Project List and Vendor Orders tables to mobile card views with touch-friendly layouts - Optimized Vendor Management and People Directory with mobile list/detail toggle and back navigation - Fixed Document Control mobile view: horizontally scrollable filter tabs, proper overflow chain for document visibility - Added responsive padding, text scaling, and flex-wrap across StatCard, TaskDetailsModal, OwnerSnapshot, OwnerProjectDetail, VendorDashboard, and DocumentManagement - Expanded mock data store with richer vendor invoices, orders, documents, and compliance records - Enhanced Owner Snapshot with financial KPI cards, urgent items panel, and Action Center modal - Built Contractor Dashboard with task management, financial summary, and performance metrics - Built Subcontractor Dashboard with clock-in tracking, task assignments, and invoice management - Enhanced Vendor Dashboard with earnings summary, active orders, compliance status, and performance rating - Added icon-only tab navigation on mobile for OwnerProjectDetail - Extended attribution signatures across all platform pages
142 lines
7.4 KiB
React
142 lines
7.4 KiB
React
import React from 'react';
|
|
import { AlertCircle, Clock, FileWarning, Wallet, CheckCircle, AlertTriangle, ChevronRight } from 'lucide-react';
|
|
import { SpotlightCard } from '../SpotlightCard';
|
|
import { useMockStore } from '../../data/mockStore';
|
|
|
|
const UrgentItemsPanel = ({ onActionClick, ownerId }) => {
|
|
const { documents, vendors, projects } = useMockStore();
|
|
|
|
// Filter projects by owner when provided
|
|
const ownerProjects = ownerId ? projects.filter(p => p.ownerId === ownerId) : projects;
|
|
|
|
// Logic to find urgent items
|
|
const expiringDocs = documents.filter(d => {
|
|
if (!d.expirationDate) return false;
|
|
const expDate = new Date(d.expirationDate);
|
|
const today = new Date();
|
|
const diffTime = Math.abs(expDate - today);
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
return diffDays <= 30 && d.status !== 'expired';
|
|
});
|
|
|
|
const nonCompliantVendors = vendors.filter(v =>
|
|
v.compliance.w9.status !== 'approved' ||
|
|
v.compliance.coi.status === 'expired'
|
|
);
|
|
|
|
const pendingInvoices = ownerProjects.flatMap(p =>
|
|
(p.invoices || []).filter(i => i.status === 'pending')
|
|
);
|
|
|
|
const urgentItems = [
|
|
{
|
|
id: 'docs',
|
|
title: 'Expiring Documents',
|
|
count: expiringDocs.length,
|
|
icon: Clock,
|
|
color: 'text-amber-400',
|
|
bg: 'bg-amber-400/10',
|
|
border: 'border-amber-400/20',
|
|
message: `${expiringDocs.length} compliance documents expiring soon.`,
|
|
},
|
|
{
|
|
id: 'vendors',
|
|
title: 'Non-Compliant Vendors',
|
|
count: nonCompliantVendors.length,
|
|
icon: FileWarning,
|
|
color: 'text-red-400',
|
|
bg: 'bg-red-400/10',
|
|
border: 'border-red-400/20',
|
|
message: `${nonCompliantVendors.length} vendors missing critical paperwork.`,
|
|
},
|
|
{
|
|
id: 'invoices',
|
|
title: 'Pending Invoices',
|
|
count: pendingInvoices.length,
|
|
icon: Wallet,
|
|
color: 'text-blue-400',
|
|
bg: 'bg-blue-400/10',
|
|
border: 'border-blue-400/20',
|
|
message: `${pendingInvoices.length} invoices awaiting approval.`,
|
|
}
|
|
];
|
|
|
|
if (urgentItems.every(item => item.count === 0)) {
|
|
return (
|
|
<SpotlightCard className="p-8 text-center border-dashed border-zinc-300 dark:border-white/10 bg-zinc-50/50 dark:bg-white/5 h-full flex flex-col justify-center items-center">
|
|
<div className="p-4 bg-emerald-100 dark:bg-emerald-500/20 rounded-full text-emerald-600 dark:text-emerald-400 ring-4 ring-emerald-50 dark:ring-emerald-500/10 mb-4">
|
|
<CheckCircle size={32} />
|
|
</div>
|
|
<h3 className="text-xl font-bold text-zinc-900 dark:text-white">All Caught Up!</h3>
|
|
<p className="text-zinc-500 dark:text-zinc-400 text-sm mt-1">No urgent items requiring attention right now.</p>
|
|
</SpotlightCard>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SpotlightCard className="h-full flex flex-col">
|
|
<div className="p-6 border-b border-zinc-200 dark:border-white/5 flex justify-between items-center bg-zinc-50/50 dark:bg-white/5">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-amber-100 dark:bg-amber-500/10 rounded-lg text-amber-600 dark:text-amber-400">
|
|
<AlertTriangle size={20} />
|
|
</div>
|
|
<div>
|
|
<h2 className="text-lg font-bold text-zinc-900 dark:text-white">Action Center</h2>
|
|
<p className="text-xs text-zinc-500 dark:text-zinc-400">Items requiring your immediate attention</p>
|
|
</div>
|
|
</div>
|
|
<span className="text-xs font-bold px-2.5 py-1 rounded-full bg-amber-100 dark:bg-amber-500/10 text-amber-700 dark:text-amber-300 border border-amber-200 dark:border-amber-500/20">
|
|
{urgentItems.reduce((acc, item) => acc + item.count, 0)} Pending
|
|
</span>
|
|
</div>
|
|
|
|
<div className="p-6 space-y-4 flex-1 overflow-y-auto custom-scrollbar">
|
|
{urgentItems.map((item) => (
|
|
item.count > 0 && (
|
|
<div key={item.id} className="group flex flex-col sm:flex-row sm:items-center justify-between p-4 rounded-xl bg-zinc-50 dark:bg-black/20 border border-zinc-200 dark:border-white/5 hover:border-amber-500/30 hover:bg-zinc-100 dark:hover:bg-white/5 transition-all duration-300">
|
|
<div className="flex items-start gap-4 mb-3 sm:mb-0">
|
|
<div className={`p-3 rounded-xl shrink-0 ${item.id === 'docs' ? 'bg-blue-100/50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400' :
|
|
item.id === 'vendors' ? 'bg-red-100/50 text-red-600 dark:bg-red-500/10 dark:text-red-400' :
|
|
'bg-amber-100/50 text-amber-600 dark:bg-amber-500/10 dark:text-amber-400'
|
|
}`}>
|
|
<item.icon size={20} />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-bold text-zinc-900 dark:text-white text-sm flex items-center gap-2">
|
|
{item.title}
|
|
<span className="text-[10px] px-1.5 py-0.5 rounded-md bg-zinc-200 dark:bg-white/10 text-zinc-600 dark:text-zinc-300 font-mono">
|
|
{item.count}
|
|
</span>
|
|
</h3>
|
|
<p className="text-xs text-zinc-500 dark:text-zinc-400 mt-1 max-w-xs leading-relaxed">
|
|
{item.message}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => onActionClick && onActionClick(item.id)}
|
|
className="whitespace-nowrap px-4 py-2 text-xs font-bold uppercase tracking-wider rounded-lg bg-white dark:bg-white/5 text-zinc-700 dark:text-zinc-300 border border-zinc-200 dark:border-white/10 hover:bg-zinc-50 dark:hover:bg-white/10 hover:text-amber-600 dark:hover:text-amber-400 hover:border-amber-200 dark:hover:border-amber-500/30 transition-all shadow-sm"
|
|
>
|
|
Review Items
|
|
</button>
|
|
</div>
|
|
)
|
|
))}
|
|
</div>
|
|
|
|
{/* Footer Action */}
|
|
<div className="p-4 border-t border-zinc-200 dark:border-white/5 bg-zinc-50/50 dark:bg-white/5">
|
|
<button
|
|
onClick={() => onActionClick && onActionClick('all')}
|
|
className="w-full py-2.5 rounded-xl text-xs font-bold uppercase tracking-widest text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white transition-colors flex items-center justify-center gap-2 hover:bg-zinc-100 dark:hover:bg-white/5"
|
|
>
|
|
View All Compliance Tasks <ChevronRight size={14} />
|
|
</button>
|
|
</div>
|
|
</SpotlightCard>
|
|
);
|
|
};
|
|
|
|
export default UrgentItemsPanel;
|