import React, { useState, useMemo, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import { useAuth } from '../context/AuthContext';
import { useMockStore } from '../data/mockStore';
import { useHailHistory, hailSeverityColor, daysAgo } from '../hooks/useHailRecon';
import { SpotlightCard } from '../components/SpotlightCard';
import { AnimatedCounter } from '../components/AnimatedCounter';
import { toast } from 'sonner';
import {
BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer
} from 'recharts';
import {
ArrowLeft, Phone, Mail, MapPin, User, Briefcase, Shield, Clock,
FileText, ChevronRight, ArrowUpRight, ArrowDownLeft, ArrowRight,
MessageSquare, CheckCircle, ChevronDown, Activity, AlertTriangle,
TrendingUp, Calendar, DollarSign, GitPullRequest, AlertCircle,
Milestone, FolderOpen, Upload, Trash2, Eye, Pencil, X as XIcon,
File, StickyNote, Download, Image as ImageIcon, CloudLightning
} from 'lucide-react';
// ── Neomorphic constants (mirrors OwnerProjectDetail) ─────────────────────────
const NEO_PANEL_CLASS = "dark:bg-zinc-900/60 dark:backdrop-blur-3xl border border-zinc-200 dark:border-white/5 dark:shadow-[8px_8px_16px_rgba(0,0,0,0.6),-8px_-8px_16px_rgba(255,255,255,0.02)] rounded-3xl relative overflow-hidden transition-all duration-300";
const NEON_GREEN = "text-green-600 dark:text-[#39ff14] dark:drop-shadow-[0_0_8px_rgba(57,255,20,0.4)]";
const NEON_GOLD = "text-amber-600 dark:text-[#fda913] dark:drop-shadow-[0_0_8px_rgba(253,169,19,0.4)]";
const NEON_ORANGE = "text-orange-600 dark:text-[#ff4500] dark:drop-shadow-[0_0_8px_rgba(255,69,0,0.4)]";
const NEON_BLUE = "text-sky-600 dark:text-[#00f0ff] dark:drop-shadow-[0_0_8px_rgba(0,240,255,0.4)]";
function formatCurrency(n) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(n || 0);
}
function formatDate(d) {
if (!d) return '—';
return new Date(d + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
function getInitials(name) {
return (name || '').split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
}
function getHealthColor(s) {
return s >= 70 ? 'text-emerald-500' : s >= 40 ? 'text-amber-500' : 'text-red-500';
}
function getVarianceColor(v) {
if (!v) return 'text-zinc-400';
return v < 0 ? NEON_GREEN : NEON_ORANGE;
}
function getMilestoneColor(status) {
if (status === 'completed') return 'bg-emerald-500';
if (status === 'in_progress') return 'bg-blue-500';
return 'bg-zinc-700';
}
// ── NeoCard ───────────────────────────────────────────────────────────────────
function NeoCard({ children, className = '', spotlightColor = 'rgba(255,255,255,0.05)' }) {
return (
{children}
);
}
// ── Animated progress bar ─────────────────────────────────────────────────────
function NeoProgress({ pct, color = '#3B82F6', height = 'h-1.5' }) {
return (
);
}
// ── Stage mover dropdown ──────────────────────────────────────────────────────
function StageMoverDropdown({ lead, columns, onStageChange }) {
const [open, setOpen] = useState(false);
const stageColumns = columns.filter(c => c.type === 'stage').sort((a, b) => a.order - b.order);
const bucketColumns = columns.filter(c => c.type === 'bucket');
return (
{open && (
Stages
{stageColumns.map(col => (
))}
Buckets
{bucketColumns.map(col => (
))}
{lead.bucketId && (
<>
>
)}
)}
);
}
// ── Activity entry (stage movement) ──────────────────────────────────────────
function StageActivityItem({ entry, isLast }) {
const isFirst = !entry.from;
const isBucket = entry.to === 'Stuck' || entry.to === 'Follow-up';
const isRegress = entry.activity_type === 'regression';
const dotColor = isFirst ? '#3B82F6' : isBucket ? '#F59E0B' : isRegress ? '#F97316' : '#10B981';
const bgClass = isFirst ? 'bg-blue-500/15' : isBucket ? 'bg-amber-500/15' : isRegress ? 'bg-orange-500/15' : 'bg-emerald-500/15';
return (
{isFirst ?
:
isBucket ?
:
isRegress ?
:
}
{!isLast &&
}
{entry.from ? (
{entry.from}
{entry.to}
) : (
Lead Created
)}
by {entry.by} · {formatDate(entry.date)}
{entry.note &&
{entry.note}
}
);
}
// ── Docs helpers ──────────────────────────────────────────────────────────────
const DOC_TYPE_CONFIG = {
pdf: { color: '#ef4444', bg: 'rgba(239,68,68,0.15)', icon: FileText, label: 'PDF' },
docx: { color: '#3b82f6', bg: 'rgba(59,130,246,0.15)', icon: FileText, label: 'DOCX' },
doc: { color: '#3b82f6', bg: 'rgba(59,130,246,0.15)', icon: FileText, label: 'DOC' },
md: { color: '#a855f7', bg: 'rgba(168,85,247,0.15)', icon: File, label: 'MD' },
txt: { color: '#6b7280', bg: 'rgba(107,114,128,0.15)',icon: File, label: 'TXT' },
jpg: { color: '#10b981', bg: 'rgba(16,185,129,0.15)', icon: ImageIcon,label: 'JPG' },
jpeg: { color: '#10b981', bg: 'rgba(16,185,129,0.15)', icon: ImageIcon,label: 'JPG' },
png: { color: '#06b6d4', bg: 'rgba(6,182,212,0.15)', icon: ImageIcon,label: 'PNG' },
webp: { color: '#06b6d4', bg: 'rgba(6,182,212,0.15)', icon: ImageIcon,label: 'IMG' },
};
function getDocType(filename) {
const ext = (filename || '').split('.').pop().toLowerCase();
return DOC_TYPE_CONFIG[ext] || { color: '#9ca3af', bg: 'rgba(156,163,175,0.15)', icon: File, label: ext.toUpperCase() || 'FILE' };
}
function seedLeadDocs(leadId) {
const base = [
{ id: 'ld1', name: 'Insurance Claim Form.pdf', uploadedBy: 'Owner', uploadedDate: '2026-02-01', size: '245 KB', notes: 'Original claim. Reference for all supplements.' },
{ id: 'ld2', name: 'Signed Contract.pdf', uploadedBy: 'Frank Agent',uploadedDate: '2026-02-05', size: '512 KB', notes: 'Fully executed. See payment schedule p.4.' },
{ id: 'ld3', name: 'Inspection Report.pdf', uploadedBy: 'Frank Agent',uploadedDate: '2026-01-28', size: '1.1 MB', notes: 'Hail damage confirmed. Photos on p.7-12.' },
{ id: 'ld4', name: 'Before Photo - Front.jpg', uploadedBy: 'Frank Agent',uploadedDate: '2026-02-06', size: '2.8 MB', notes: '' },
{ id: 'ld5', name: 'Material Spec Sheet.md', uploadedBy: 'Owner', uploadedDate: '2026-02-07', size: '11 KB', notes: 'GAF Timberline HDZ — Class 4 IR.' },
{ id: 'ld6', name: 'Adjuster Notes.docx', uploadedBy: 'Owner', uploadedDate: '2026-02-10', size: '42 KB', notes: '' },
];
const n = 3 + (leadId.charCodeAt(leadId.length - 1) % 3);
return base.slice(0, n);
}
// ── Tabs ──────────────────────────────────────────────────────────────────────
const TABS_FULL = [
{ id: 'overview', label: 'Overview', icon: Activity },
{ id: 'budget', label: 'Budget & Costs', icon: DollarSign },
{ id: 'milestones', label: 'Milestones', icon: Milestone },
{ id: 'changeOrders', label: 'Change Orders', icon: GitPullRequest },
{ id: 'rfis', label: 'RFIs', icon: FileText },
{ id: 'invoices', label: 'Invoices', icon: DollarSign },
{ id: 'docs', label: 'Docs', icon: FolderOpen },
{ id: 'activity', label: 'Activity', icon: Clock },
];
const TABS_SIMPLE = [
{ id: 'overview', label: 'Overview', icon: Activity },
{ id: 'contact', label: 'Contact', icon: User },
{ id: 'docs', label: 'Docs', icon: FolderOpen},
{ id: 'activity', label: 'Activity', icon: Clock },
];
// ── Main component ────────────────────────────────────────────────────────────
// ── Hail History Section ──────────────────────────────────────────────────────
function HailHistorySection({ leadId, lat, lng }) {
const { history, loading, hitCount, lastHit, maxSize } = useHailHistory(leadId, lat, lng, 12);
const summaryColor = maxSize >= 2.0 ? 'text-red-600 dark:text-red-400' :
maxSize >= 1.5 ? 'text-orange-600 dark:text-orange-400' :
maxSize >= 1.0 ? 'text-yellow-600 dark:text-yellow-400' :
'text-zinc-400';
return (
{/* Header */}
Hail Impact History
Last 12 months · Powered by Hail Recon
{!loading && hitCount > 0 && (
{hitCount}x
{lastHit ? `Last: ${daysAgo(lastHit.date)}d ago` : ''}
)}
{/* Body */}
{loading ? (
) : hitCount === 0 ? (
No hail impacts recorded in the last 12 months.
) : (
{history.map((event, i) => {
const textCls = hailSeverityColor(event.hailSize, 'text');
const bgCls = hailSeverityColor(event.hailSize, 'bg');
const brdCls = hailSeverityColor(event.hailSize, 'border');
const days = daysAgo(event.date);
const dateObj = new Date(event.date);
const dateLabel = dateObj.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
return (
{/* Date */}
{dateLabel}
{days === 0 ? 'Today' : `${days}d ago`}
{/* Size badge */}
{event.hailSize?.toFixed(2)}"
{/* Severity label */}
{event.hailSize >= 2.0 ? 'Severe' :
event.hailSize >= 1.5 ? 'Significant' :
event.hailSize >= 1.0 ? 'Moderate' : 'Trace'}
{i === 0 && (
Most Recent
)}
);
})}
)}
);
}
export default function LeadProjectPage() {
const { leadId } = useParams();
const navigate = useNavigate();
const { user } = useAuth();
const { kanbanLeads, kanbanColumns, kanbanProjectData, moveKanbanLead } = useMockStore();
const basePath = user?.role === 'OWNER' ? '/owner' : user?.role === 'ADMIN' ? '/admin' : '/emp/fa';
const lead = kanbanLeads.find(l => l.id === leadId);
const proj = kanbanProjectData?.[leadId] ?? null;
const tabs = proj ? TABS_FULL : TABS_SIMPLE;
const [activeTab, setActiveTab] = useState('overview');
// ── Docs state ────────────────────────────────────────────────────────────
const [docs, setDocs] = useState(() => seedLeadDocs(leadId));
const [viewDoc, setViewDoc] = useState(null);
const [editNoteDoc, setEditNoteDoc] = useState(null);
const [deleteDocId, setDeleteDocId] = useState(null);
const [uploadOpen, setUploadOpen] = useState(false);
const [uploadForm, setUploadForm] = useState({ name: '', notes: '' });
const fileInputRef = useRef(null);
const handleDocUpload = () => {
if (!uploadForm.name.trim()) return;
setDocs(prev => [{
id: `ld_${Date.now()}`, name: uploadForm.name.trim(),
uploadedBy: user?.name || 'Owner', uploadedDate: '2026-04-08', size: '—', notes: uploadForm.notes.trim(),
}, ...prev]);
setUploadOpen(false); setUploadForm({ name: '', notes: '' });
};
const handleFileSelect = (e) => { const f = e.target.files?.[0]; if (f) setUploadForm(p => ({ ...p, name: f.name })); };
const handleSaveNote = () => { setDocs(prev => prev.map(d => d.id === editNoteDoc.doc.id ? { ...d, notes: editNoteDoc.noteText } : d)); setEditNoteDoc(null); };
const handleDeleteDoc = () => { setDocs(prev => prev.filter(d => d.id !== deleteDocId)); setDeleteDocId(null); };
const handleStageChange = (newColumnId, newBucketId) => {
if (!lead) return;
if (newColumnId === lead.columnId && newBucketId === lead.bucketId) return;
moveKanbanLead(lead.id, newColumnId, newBucketId);
toast.success('Stage updated');
};
if (!lead) {
return (
Lead not found
);
}
const stageColumns = [...kanbanColumns].filter(c => c.type === 'stage').sort((a, b) => a.order - b.order);
const currentStage = stageColumns.find(c => c.id === lead.columnId);
const bucketCol = lead.bucketId ? kanbanColumns.find(c => c.id === lead.bucketId) : null;
const stageIdx = stageColumns.findIndex(c => c.id === lead.columnId);
const stagePct = stageColumns.length > 0 ? Math.round(((stageIdx + 1) / stageColumns.length) * 100) : 0;
const completionPct = proj?.completionPct ?? stagePct;
// Budget chart
const budgetChartData = (proj?.budgetBreakdown || []).map(b => ({
name: b.category.length > 12 ? b.category.slice(0, 10) + '…' : b.category,
allocated: b.allocated / 1000, committed: b.committed / 1000, actual: b.actual / 1000,
}));
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload?.length) return (
{label}
{payload.map((p, i) => (
{p.name}: {formatCurrency(p.value * 1000)}
))}
);
return null;
};
const invoiceTotalPaid = (proj?.invoices || []).filter(i => i.status === 'paid').reduce((s, i) => s + i.amount, 0);
const invoiceTotalPending = (proj?.invoices || []).filter(i => i.status !== 'paid').reduce((s, i) => s + i.amount, 0);
return (
{/* Ambient glow */}
{/* ── Header ── */}
{/* ── Key Metrics Row ── */}
{proj ? [
{ label: 'Estimated', value: formatCurrency(proj.estimatedAmount) },
{ label: 'Actual Cost', value: formatCurrency(proj.actualCost) },
{ label: 'Variance', value: `${(proj.variancePercent || 0) > 0 ? '+' : ''}${(proj.variancePercent || 0).toFixed(1)}%`, color: getVarianceColor(proj.variancePercent) },
{ label: 'Completion', value: `${proj.completionPct}%` },
{ label: 'Health', value: proj.healthScore, color: getHealthColor(proj.healthScore) },
{ label: 'Stage', value: currentStage?.name || '—' },
].map((m, i) => (
{m.value}
{m.label}
)) : [
{ label: 'Stage', value: currentStage?.name || '—' },
{ label: 'Job Type', value: lead.jobType },
{ label: 'Category', value: lead.insuranceType },
{ label: 'Assigned To', value: lead.assignedAgentName || 'Unassigned' },
{ label: 'Created', value: formatDate(lead.createdDate) },
{ label: 'Stage Moves', value: lead.activity?.length ?? 0 },
].map((m, i) => (
{m.value}
{m.label}
))}
{/* ── Tabs ── */}
{tabs.map(tab => {
const TabIcon = tab.icon;
const isActive = activeTab === tab.id;
return (
);
})}
{/* ── Tab Content ── */}
{/* ── OVERVIEW ── */}
{activeTab === 'overview' && proj && (
{/* Project Progression */}
Project Progression
{['Signed', 'Materials', 'On Site', 'Install', 'Complete'].map((step, i) => {
const isActive = (i * 25) <= completionPct;
return (
);
})}
{/* Row: Contractor + Summary */}
Contact & Contractor
{[
{ icon: User, label: 'Client', value: lead.name },
{ icon: Phone, label: 'Phone', value: lead.phone },
{ icon: Mail, label: 'Email', value: lead.email },
{ icon: MapPin, label: 'Address', value: lead.address },
{ icon: Briefcase, label: 'Contractor', value: proj.contractorName },
{ icon: Phone, label: 'Crew Phone', value: proj.contractorPhone },
{ icon: Shield, label: 'Assigned Agent', value: lead.assignedAgentName },
].map(({ icon: Icon, label, value }) => value ? (
) : null)}
{[
{ label: 'Change Orders', count: proj.changeOrders?.length || 0, color: '#fda913' },
{ label: 'Open RFIs', count: (proj.rfis || []).filter(r => r.status === 'open').length, color: '#ff4500' },
{ label: 'Pending Invoices', count: (proj.invoices || []).filter(i => i.status === 'pending').length, color: '#00f0ff' },
{ label: 'Milestones Done', count: (proj.milestones || []).filter(m => m.status === 'completed').length + '/' + (proj.milestones?.length || 0), color: '#39ff14' },
].map(item => (
{item.count}
{item.label}
))}
{lead.notes && (
)}
)}
{/* Overview for simple (no project data) */}
{activeTab === 'overview' && !proj && (
Contact
{[
{ icon: User, label: 'Name', value: lead.name },
{ icon: Phone, label: 'Phone', value: lead.phone },
{ icon: Mail, label: 'Email', value: lead.email },
{ icon: MapPin, label: 'Address', value: lead.address },
{ icon: Briefcase, label: 'Job Type', value: lead.jobType },
{ icon: Shield, label: 'Category', value: lead.insuranceType },
{ icon: User, label: 'Assigned Agent', value: lead.assignedAgentName },
].map(({ icon: Icon, label, value }) => value ? (
) : null)}
{stageColumns.map((col, i) => (
{col.name}
{i < stageColumns.length - 1 && }
))}
{lead.notes && (
)}
Full project details (budget, milestones, invoices) become available once the lead is Signed.
)}
{/* ── CONTACT (simple only) ── */}
{activeTab === 'contact' && !proj && (
Contact Information
{[
{ icon: User, label: 'Full Name', value: lead.name },
{ icon: Phone, label: 'Phone', value: lead.phone },
{ icon: Mail, label: 'Email', value: lead.email },
{ icon: MapPin, label: 'Address', value: lead.address },
].map(({ icon: Icon, label, value }) => value ? (
) : null)}
Quick Actions
{lead.phone && (
Call {lead.phone}
)}
{lead.email && (
Email {lead.email}
)}
{lead.address && (
View on Maps
)}
)}
{/* ── BUDGET & COSTS ── */}
{activeTab === 'budget' && proj && (
{/* Summary cards */}
{[
{ label: 'Estimated', value: formatCurrency(proj.estimatedAmount), color: NEON_BLUE },
{ label: 'Committed', value: formatCurrency(proj.budgetBreakdown?.reduce((s, b) => s + b.committed, 0)), color: NEON_GOLD },
{ label: 'Actual Spent', value: formatCurrency(proj.actualCost), color: proj.actualCost > proj.estimatedAmount ? NEON_ORANGE : NEON_GREEN },
{ label: 'Variance', value: `${(proj.variancePercent || 0) > 0 ? '+' : ''}${(proj.variancePercent || 0).toFixed(1)}%`, color: getVarianceColor(proj.variancePercent) },
].map((m, i) => (
{m.value}
{m.label}
))}
{/* Chart */}
{budgetChartData.length > 0 && (
Budget Breakdown
`$${v}k`} />
} />
{[['#3b82f6','Allocated'],['#fda913','Committed'],['#39ff14','Actual']].map(([c, l]) => (
))}
)}
{/* Table */}
Line Item Breakdown
| Category |
Allocated |
Committed |
Actual |
Remaining |
{(proj.budgetBreakdown || []).map((b, i) => {
const rem = b.allocated - b.actual;
return (
| {b.category} |
{formatCurrency(b.allocated)} |
{formatCurrency(b.committed)} |
{formatCurrency(b.actual)} |
= 0 ? 'text-emerald-400' : 'text-red-400'}`}>{formatCurrency(rem)} |
);
})}
| Total |
{formatCurrency(proj.estimatedAmount)} |
{formatCurrency(proj.budgetBreakdown?.reduce((s, b) => s + b.committed, 0))} |
{formatCurrency(proj.actualCost)} |
= 0 ? 'text-emerald-400' : 'text-red-400'}`}>
{formatCurrency(proj.estimatedAmount - proj.actualCost)}
|
)}
{/* ── MILESTONES ── */}
{activeTab === 'milestones' && proj && (
Project Milestones
{(proj.milestones || []).filter(m => m.status === 'completed').length} / {proj.milestones?.length || 0} complete
{(proj.milestones || []).map((m, i) => {
const dotColor = m.status === 'completed' ? '#39ff14' : m.status === 'in_progress' ? '#00f0ff' : '#3f3f46';
return (
{m.name}
{m.status.replace('_', ' ')}
{m.date &&
{formatDate(m.date)}
}
{m.notes &&
{m.notes}
}
);
})}
)}
{/* ── CHANGE ORDERS ── */}
{activeTab === 'changeOrders' && proj && (
Change Orders
{proj.changeOrders?.length || 0}
{(proj.changeOrders || []).length === 0 ? (
) : (
{proj.changeOrders.map(co => (
{co.title}
{co.status}
+{formatCurrency(co.amount)}
{co.description}
Requested by {co.requestedBy} · {formatDate(co.date)}
))}
)}
)}
{/* ── RFIs ── */}
{activeTab === 'rfis' && proj && (
Requests for Information
{proj.rfis?.length || 0}
{(proj.rfis || []).length === 0 ? (
) : (
| ID |
Subject |
Status |
Submitted By |
Opened |
Closed |
{proj.rfis.map((r, i) => (
| {r.id.toUpperCase()} |
{r.subject} |
{r.status}
|
{r.submittedBy} |
{r.openedDate} |
{r.closedDate || '—'} |
))}
)}
)}
{/* ── INVOICES ── */}
{activeTab === 'invoices' && proj && (
{[
{ label: 'Total Contract', value: formatCurrency(proj.estimatedAmount), color: NEON_BLUE },
{ label: 'Collected', value: formatCurrency(invoiceTotalPaid), color: NEON_GREEN },
{ label: 'Outstanding', value: formatCurrency(invoiceTotalPending), color: invoiceTotalPending > 0 ? NEON_GOLD : 'text-zinc-400' },
].map((m, i) => (
{m.value}
{m.label}
))}
Invoice Schedule
| Description |
Amount |
Status |
Due |
Paid |
{(proj.invoices || []).map(inv => (
| {inv.description} |
{formatCurrency(inv.amount)} |
{inv.status}
|
{inv.dueDate || '—'} |
{inv.paidDate || '—'} |
))}
)}
{/* ── DOCS (shared across simple + full) ── */}
{activeTab === 'docs' && (
Project Documents
{docs.length}
{docs.length === 0 ? (
) : (
{docs.map(doc => {
const cfg = getDocType(doc.name);
const DocIcon = cfg.icon;
return (
{doc.size} · {doc.uploadedBy} · {doc.uploadedDate}
{doc.notes ? (
{doc.notes}
) : (
)}
);
})}
)}
)}
{/* ── ACTIVITY ── */}
{activeTab === 'activity' && (
{/* Work timeline (project-level) */}
{proj && (proj.workTimeline || []).length > 0 && (
{proj.workTimeline.map((a, i) => (
{a.action}
{a.details &&
{a.details}
}
{a.date}
{a.user &&
{a.user}
}
))}
)}
{/* Stage movement log */}
Stage Movement Log
{lead.activity?.length ?? 0} entries
{(lead.activity || []).length > 0 ? (
(lead.activity || []).map((entry, i) => (
))
) : (
No stage movements recorded.
)}
)}
{/* ── Upload Modal ── */}
{uploadOpen && (
e.stopPropagation()}>
Upload Document
Supports PDF, DOCX, MD, TXT, JPG, PNG, WEBP
fileInputRef.current?.click()}
className="border-2 border-dashed border-white/10 hover:border-blue-500/50 rounded-2xl p-8 text-center cursor-pointer transition-colors mb-4 bg-white/[0.02] hover:bg-blue-500/5">
Click to select a file
{uploadForm.name &&
{uploadForm.name}
}
{!uploadForm.name && (
setUploadForm(p => ({ ...p, name: e.target.value }))}
className="w-full px-4 py-2.5 rounded-xl bg-black/40 border border-white/10 text-zinc-200 text-sm placeholder-zinc-600 outline-none focus:ring-2 focus:ring-blue-500/30 mb-4" />
)}
)}
{/* ── View Doc Modal ── */}
{viewDoc && (() => {
const cfg = getDocType(viewDoc.name); const DocIcon = cfg.icon;
return (
setViewDoc(null)}>
e.stopPropagation()}>
{viewDoc.name}
{viewDoc.size} · {viewDoc.uploadedDate}
{viewDoc.name}
Uploaded by {viewDoc.uploadedBy}
{viewDoc.notes && (
)}
);
})()}
{/* ── Edit Note Modal ── */}
{editNoteDoc && (
setEditNoteDoc(null)}>
e.stopPropagation()}>
Edit Note
{editNoteDoc.doc.name}
)}
{/* ── Delete Confirm ── */}
{deleteDocId && (
setDeleteDocId(null)}>
e.stopPropagation()}>
Delete Document?
{docs.find(d => d.id === deleteDocId)?.name} will be permanently removed.
)}
);
}