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
This commit is contained in:
@@ -138,9 +138,37 @@ const FinancialSummaryModal = ({ isOpen, onClose, role, data }) => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div className="flex-1 overflow-y-auto overflow-x-auto custom-scrollbar bg-white dark:bg-[#121214]">
|
||||
<table className="w-full text-left border-collapse min-w-[600px]">
|
||||
{/* Mobile Card View */}
|
||||
<div className="flex-1 overflow-y-auto sm:hidden custom-scrollbar bg-white dark:bg-[#121214]">
|
||||
{sortedData.length > 0 ? (
|
||||
<div className="divide-y divide-zinc-100 dark:divide-white/5">
|
||||
{sortedData.map((item, idx) => (
|
||||
<div key={idx} className="px-4 py-3">
|
||||
<div className="flex justify-between items-start mb-1.5">
|
||||
<div className="flex-1 min-w-0 mr-3">
|
||||
<p className="font-semibold text-sm text-zinc-900 dark:text-white truncate">{item.description || item.project}</p>
|
||||
{item.project && <p className="text-xs text-zinc-500 truncate">{item.project}</p>}
|
||||
</div>
|
||||
<span className="font-mono font-medium text-sm text-zinc-900 dark:text-white shrink-0">{formatCurrency(item.amount)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-zinc-500 font-mono">{item.date}</span>
|
||||
<span className={`px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wide ${item.status === 'paid' || item.status === 'completed'
|
||||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
||||
}`}>{item.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-20 text-center text-zinc-500">No transactions found.</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Desktop Table View */}
|
||||
<div className="flex-1 overflow-y-auto overflow-x-auto hidden sm:block custom-scrollbar bg-white dark:bg-[#121214]">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead className="sticky top-0 z-10 bg-zinc-50 dark:bg-[#18181b] border-b border-zinc-200 dark:border-white/10 shadow-sm">
|
||||
<tr>
|
||||
{[
|
||||
@@ -152,7 +180,7 @@ const FinancialSummaryModal = ({ isOpen, onClose, role, data }) => {
|
||||
<th
|
||||
key={col.key}
|
||||
onClick={() => handleSort(col.key)}
|
||||
className={`px-3 sm:px-6 py-3 sm:py-4 text-[10px] sm:text-xs font-bold uppercase tracking-wider text-zinc-500 cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors ${col.align === 'right' ? 'text-right' : ''}`}
|
||||
className={`px-6 py-4 text-xs font-bold uppercase tracking-wider text-zinc-500 cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors ${col.align === 'right' ? 'text-right' : ''}`}
|
||||
>
|
||||
{col.label}
|
||||
</th>
|
||||
@@ -162,22 +190,22 @@ const FinancialSummaryModal = ({ isOpen, onClose, role, data }) => {
|
||||
<tbody className="divide-y divide-zinc-100 dark:divide-white/5">
|
||||
{sortedData.length > 0 ? sortedData.map((item, idx) => (
|
||||
<tr key={idx} className="hover:bg-zinc-50 dark:hover:bg-white/5 transition-colors">
|
||||
<td className="px-3 sm:px-6 py-3 sm:py-4 text-xs sm:text-sm text-zinc-600 dark:text-zinc-400 font-mono whitespace-nowrap">
|
||||
<td className="px-6 py-4 text-sm text-zinc-600 dark:text-zinc-400 font-mono whitespace-nowrap">
|
||||
{item.date}
|
||||
</td>
|
||||
<td className="px-3 sm:px-6 py-3 sm:py-4">
|
||||
<div className="font-semibold text-sm sm:text-base text-zinc-900 dark:text-white">{item.description || item.project}</div>
|
||||
<td className="px-6 py-4">
|
||||
<div className="font-semibold text-base text-zinc-900 dark:text-white">{item.description || item.project}</div>
|
||||
{item.project && <div className="text-xs text-zinc-500">{item.project}</div>}
|
||||
</td>
|
||||
<td className="px-3 sm:px-6 py-3 sm:py-4">
|
||||
<span className={`px-2 sm:px-2.5 py-0.5 rounded-full text-[10px] sm:text-xs font-bold uppercase tracking-wide whitespace-nowrap ${item.status === 'paid' || item.status === 'completed'
|
||||
<td className="px-6 py-4">
|
||||
<span className={`px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wide whitespace-nowrap ${item.status === 'paid' || item.status === 'completed'
|
||||
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400'
|
||||
: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400'
|
||||
}`}>
|
||||
{item.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 sm:px-6 py-3 sm:py-4 text-right font-mono font-medium text-sm sm:text-base text-zinc-900 dark:text-white whitespace-nowrap">
|
||||
<td className="px-6 py-4 text-right font-mono font-medium text-base text-zinc-900 dark:text-white whitespace-nowrap">
|
||||
{formatCurrency(item.amount)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -91,7 +91,7 @@ const TaskDetailsModal = ({ isOpen, onClose, task, onUpdate }) => {
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="p-6 overflow-y-auto flex-1 space-y-6">
|
||||
<div className="p-4 sm:p-6 overflow-y-auto flex-1 space-y-5 sm:space-y-6">
|
||||
{/* Task Info Card */}
|
||||
<div className="p-5 rounded-xl bg-blue-50 dark:bg-blue-500/5 border border-blue-100 dark:border-blue-500/20">
|
||||
<h3 className="text-xl font-bold text-zinc-900 dark:text-white mb-3">{task.name}</h3>
|
||||
@@ -153,7 +153,7 @@ const TaskDetailsModal = ({ isOpen, onClose, task, onUpdate }) => {
|
||||
</div>
|
||||
|
||||
{/* Footer Actions */}
|
||||
<div className="p-6 border-t border-zinc-200 dark:border-white/10 bg-zinc-50 dark:bg-white/5 flex gap-4">
|
||||
<div className="p-4 sm:p-6 border-t border-zinc-200 dark:border-white/10 bg-zinc-50 dark:bg-white/5 flex gap-3 sm:gap-4">
|
||||
{task.status !== 'completed' ? (
|
||||
<>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user