Files
LynkedUpPro_CRM/src/components/owner/UrgentItemsPanel.jsx
T
Satyam bc4e25f132 feat: comprehensive mobile optimization, role-based AI chatbot, and multi-role dashboard enhancements
- 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
2026-02-18 12:34:55 +05:30

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;