feat: Multi-role platform expansion, mobile nav redesign, and UI polish
- Add Owner, Contractor, Vendor, Subcontractor dashboards and role-based routing - Owner now has superuser access to all Admin pages (dashboard, schedule, leaderboard) - Redesign landing page mobile menu as slide-in sidebar (replaces broken Framer Motion overlay) - Add body scroll lock to app sidebar for mobile consistency - Fix Team Schedule to match Admin Dashboard design language (ambient glows, gradient header, SpotlightCard, zinc palette) - Fix login page tab overflow — use abbreviated labels and grid layout for 6 role tabs - Fix Recharts ResponsiveContainer warnings — replace height="100%" with fixed pixel heights - Fix lightbox image viewer — unified nav bar, touch swipe support, no more overlapping text - Add AI Assistant page, People/Vendor/Document management for Owner role - Expand mock data store with contractor, vendor, and subcontractor data
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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 }) => {
|
||||
const { documents, vendors, projects } = useMockStore();
|
||||
// Removed direct navigate
|
||||
|
||||
// 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 = projects.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;
|
||||
Reference in New Issue
Block a user