import React, { useState, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; import { useMockStore } from '../../data/mockStore'; import { SpotlightCard } from '../../components/SpotlightCard'; import { AnimatedCounter } from '../../components/AnimatedCounter'; import { motion } from 'framer-motion'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'; import { ArrowLeft, CheckCircle, Clock, AlertTriangle, PauseCircle, ShieldAlert, FileText, DollarSign, GitPullRequest, AlertCircle, Activity, Milestone, Shield, ChevronRight, Phone, MessageSquare, Mail, AlertOctagon, TrendingUp, Key, Zap, Camera, Plus, Map, User, Crosshair, Image as ImageIcon, MapPin } from 'lucide-react'; import ChangeOrderDrawer from '../../components/owner/ChangeOrderDrawer'; import InvoiceDetailModal from '../../components/owner/InvoiceDetailModal'; import CreateItemModal from '../../components/owner/CreateItemModal'; // --- NEOMORPHIC STYLING CONSTANTS --- const NEO_PANEL_CLASS = "bg-zinc-900/60 backdrop-blur-3xl border border-white/5 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-[#39ff14] drop-shadow-[0_0_8px_rgba(57,255,20,0.4)]"; const NEON_GOLD = "text-[#fda913] drop-shadow-[0_0_8px_rgba(253,169,19,0.4)]"; const NEON_ORANGE = "text-[#ff4500] drop-shadow-[0_0_8px_rgba(255,69,0,0.4)]"; const NEON_RED = "text-[#ff003c] drop-shadow-[0_0_8px_rgba(255,0,60,0.4)]"; const NEON_BLUE = "text-[#00f0ff] drop-shadow-[0_0_8px_rgba(0,240,255,0.4)]"; // ----------------------------------------------------------------- // SHARED UI COMPONENTS // ----------------------------------------------------------------- const NeoCard = ({ children, className = "", innerClassName = "", spotlightColor = "rgba(255, 255, 255, 0.05)" }) => (
{children}
); const ProgressBar = ({ progress, goal = 100, colorClass = "bg-[#39ff14]", shadowClass = "shadow-[0_0_10px_#39ff14]", height = "h-1.5" }) => { const pct = Math.min((progress / goal) * 100, 100); return (
); }; const statusConfig = { active: { label: 'Active', color: 'text-emerald-600 bg-emerald-100 dark:text-emerald-400 dark:bg-emerald-500/10', icon: Clock }, completed: { label: 'Completed', color: 'text-blue-600 bg-blue-100 dark:text-blue-400 dark:bg-blue-500/10', icon: CheckCircle }, delayed: { label: 'Delayed', color: 'text-amber-600 bg-amber-100 dark:text-amber-400 dark:bg-amber-500/10', icon: AlertTriangle }, on_hold: { label: 'On Hold', color: 'text-purple-600 bg-purple-100 dark:text-purple-400 dark:bg-purple-500/10', icon: PauseCircle }, disputed: { label: 'Disputed', color: 'text-red-600 bg-red-100 dark:text-red-400 dark:bg-red-500/10', icon: ShieldAlert }, }; const tabs = [ { id: 'overview', label: 'Overview', icon: Activity }, { id: 'budget', label: 'Budget & Costs', icon: DollarSign }, { id: 'changeOrders', label: 'Change Orders', icon: GitPullRequest }, { id: 'rfis', label: 'RFIs', icon: FileText }, { id: 'milestones', label: 'Milestones', icon: Milestone }, { id: 'invoices', label: 'Invoices', icon: DollarSign }, { id: 'risks', label: 'Risk & Issues', icon: AlertCircle }, { id: 'activity', label: 'Activity', icon: Clock }, ]; const OwnerProjectDetail = () => { const { projectId } = useParams(); const { user } = useAuth(); const { projects, vendors } = useMockStore(); const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('overview'); const [selectedCO, setSelectedCO] = useState(null); const [selectedInvoice, setSelectedInvoice] = useState(null); const [createModalConfig, setCreateModalConfig] = useState(null); const handleCreateSubmit = (data) => { console.log("Mock submitted data:", data); alert("Action successful! (Mock interface)"); }; const project = useMemo(() => projects.find(p => p.id === projectId && p.ownerId === user?.id) , [projects, projectId, user]); if (!project) { return (

Project Not Found

This project doesn't exist or you don't have access.

); } const cfg = statusConfig[project.status] || statusConfig.active; const StatusIcon = cfg.icon; const contractor = vendors.find(v => v.id === project.contractorId) || null; const formatCurrency = (amt) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(amt); const getHealthColor = (s) => s >= 70 ? 'text-emerald-500' : s >= 40 ? 'text-amber-500' : 'text-red-500'; const getMilestoneColor = (status) => { if (status === 'completed') return 'bg-emerald-500'; if (status === 'in_progress') return 'bg-blue-500'; return 'bg-zinc-300 dark:bg-zinc-700'; }; const getInvoiceStatusColor = (status) => { if (status === 'paid') return 'text-emerald-600 bg-emerald-100 dark:text-emerald-400 dark:bg-emerald-500/10'; if (status === 'pending') return 'text-amber-600 bg-amber-100 dark:text-amber-400 dark:bg-amber-500/10'; return 'text-red-600 bg-red-100 dark:text-red-400 dark:bg-red-500/10'; }; const CustomTooltip = ({ active, payload, label }) => { if (active && payload?.length) { return (

{label}

{payload.map((p, i) => (

{p.name}: {formatCurrency(p.value * 1000)}

))}
); } return null; }; // Budget chart data const budgetChartData = (project.budgetBreakdown || []).map(b => ({ name: b.category.length > 12 ? b.category.slice(0, 12) + '...' : b.category, allocated: b.allocated / 1000, committed: b.committed / 1000, actual: b.actual / 1000, })); return (
{/* Ambient Background */}
{/* Back & Title */}

{project.projectType}

{project.address}

{cfg.label}
{/* Key Metrics Row */}
{[ { label: 'Budget', value: formatCurrency(project.approvedBudget || project.budget) }, { label: 'Spent', value: formatCurrency(project.actualCost || project.spent) }, { label: 'Variance', value: `${(project.variancePercent || 0) > 0 ? '+' : ''}${(project.variancePercent || 0).toFixed(1)}%` }, { label: 'Completion', value: `${project.completionPercentage || 0}%` }, { label: 'Health', value: project.healthScore != null ? project.healthScore : '—', color: getHealthColor(project.healthScore || 0) }, { label: 'Phase', value: project.phase || '—' }, ].map((m, i) => (
{m.value}
{m.label}
))}
{/* Tab Navigation */}
{tabs.map(tab => { const TabIcon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
{/* Tab Content */}
{/* OVERVIEW TAB - COMMAND CENTER */} {activeTab === 'overview' && (
{/* Row 1: The Macro View */}
{/* Project Progression */}

Project Progression

{/* Stepper Line */}
{/* Stepper Nodes */} {["Lead", "Inspection Scheduled", "Damage Verified", "Scope Approved", "Project Completed"].map((step, i) => { const isActive = (i * 25) <= (project.completionPercentage || 0); return (
{isActive ? : }
{step}
); })}
{project.completionPercentage || 0}% CMP
{/* Relationship Intelligence */}

Relationship Intel

{project.customerName?.[0] || 'C'}

{project.customerName || 'Customer Records'}

{contractor?.vendorName || project.contractorId}

PREFERENTIAL ROUTING

Responds fastest to SMS in the evenings. Critical approvals required via email.

Last Contact: 2 hours ago

{/* Row 2: The Assessment */}
{/* Performance Snapshot */}

Performance Snapshot

96%

Inspection Coverage

Damage Verification Complete

{/* Embedded Map/Evidence Thumbnails */}
{['Hail_Damaged_Shingles.jpg', 'Cracked_Storm_Shingles.jpg', 'Broken_Roof_Surface.jpg', 'Curled_Aging_Shingles.jpg', 'Storm_Worn_Roof.jpg', 'Red_Shingle_Cottage.jpg'].map((img, i) => (
Evidence
))}
{/* Evidence Summary */}

Evidence Summary

Hail Impacts Detected 178
Gutters Denting Moderate
Window Screens 4 / 12
AI Confidence: 92%
{/* Financial Overview */}

Financial Overview

Estimated Scope {formatCurrency(project.approvedBudget || 34190)}
Supplements Potential + {formatCurrency(4800)}
Insurance Probability 82%
Net Profit Projection {formatCurrency(project.budget - project.spent || 14200)}
{/* Row 3: Action & Log */}
{/* Action Needed & Timeline */}

Action Needed

{/* Pulled from Project Risks / Mocks */}
Missing downspout photos for final supplement push.
Adjuster meeting not scheduled within SLA.

Project Timeline Highlights

Inspection Completed
Feb 20 Pending QC
Claim Filed
Not Scheduled
{/* Quick Actions Palette */}

Quick Actions

)} {/* BUDGET TAB */} {activeTab === 'budget' && (
{budgetChartData.length > 0 ? ( <>

Budget Breakdown

Allocated vs Committed vs Actual ($k)

} />
{['Category', 'Allocated', 'Committed', 'Actual', 'Variance'].map(h => ( ))} {(project.budgetBreakdown || []).map((b, i) => { const variance = b.actual - b.allocated; return ( ); })}
{h}
{b.category} {formatCurrency(b.allocated)} {formatCurrency(b.committed)} {formatCurrency(b.actual)} 0 ? 'text-[#ff4500]' : 'text-[#39ff14]'}`}> {variance > 0 ? '+' : ''}{formatCurrency(variance)}
) : (

No detailed budget breakdown available.

Budget: {formatCurrency(project.approvedBudget || project.budget)}  ·  Spent: {formatCurrency(project.actualCost || project.spent)}

)}
)} {/* CHANGE ORDERS TAB */} {activeTab === 'changeOrders' && (

Change Orders

{(project.changeOrders || []).length > 0 ? ( {['ID', 'Title', 'Amount', 'Status', 'Date'].map(h => ( ))} {project.changeOrders.map((co, i) => ( setSelectedCO(co)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedCO(co); } }}> ))}
{h}
{co.id}
{co.title}
{co.description &&
{co.description}
}
{formatCurrency(co.amount)} {co.status} {co.dateSubmitted}
) : (

No change orders found.

)}
)} {/* RFIs TAB */} {activeTab === 'rfis' && (

Requests for Information (RFI)

{(project.rfis || []).length > 0 ? ( {['ID', 'Subject', 'Status', 'Submitted By', 'Opened', 'Closed'].map(h => ( ))} {project.rfis.map((rfi, i) => ( ))}
{h}
{rfi.id} {rfi.subject} {rfi.status} {rfi.submittedBy} {rfi.dateOpened} {rfi.dateClosed || '—'}
) : (

No RFIs for this project.

)}
)} {/* MILESTONES TAB */} {activeTab === 'milestones' && (

Milestone Timeline

{/* Timeline line */}
{(project.milestones || []).map((ms, i) => (
{ms.name}
Due: {ms.dueDate} · Assigned to: {ms.assignedTo}
{ms.status.replace('_', ' ')}
))}
)} {/* INVOICES TAB */} {activeTab === 'invoices' && (

Invoices

{(project.invoices || []).length > 0 ? ( {['Invoice ID', 'Amount', 'Submitted By', 'Status', 'Due Date', 'Paid Date'].map(h => ( ))} {project.invoices.map((inv, i) => ( setSelectedInvoice(inv)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedInvoice(inv); } }}> ))}
{h}
{inv.id} {formatCurrency(inv.amount)} {inv.submittedBy} {inv.status} {inv.dueDate} {inv.datePaid || '—'}
) : (

No invoices for this project.

)}
)} {/* RISK & ISSUES TAB */} {activeTab === 'risks' && (
{/* Risk Log */}

Risk Log

{(project.riskLog || []).length > 0 ? ( {['ID', 'Description', 'Severity', 'Likelihood', 'Status', 'Mitigation'].map(h => ( ))} {project.riskLog.map((r, i) => ( ))}
{h}
{r.id} {r.description} {r.severity} {r.likelihood} {r.status} {r.mitigation}
) : (

No risk entries for this project.

)}
{/* Issue Log */}

Issue Log

{(project.issueLog || []).length > 0 ? ( {['ID', 'Title', 'Priority', 'Status', 'Assigned To', 'Reported'].map(h => ( ))} {project.issueLog.map((issue, i) => ( ))}
{h}
{issue.id} {issue.title} {issue.priority} {issue.status} {issue.assignedTo} {issue.dateReported}
) : (

No issues logged for this project.

)}
)} {/* ACTIVITY TAB */} {activeTab === 'activity' && (

Activity Timeline

{(project.activityTimeline || []).length > 0 ? (
{project.activityTimeline.map((a, i) => (
{a.action}
{a.details &&
{a.details}
}
{a.date}
{a.user &&
{a.user}
}
))}
) : (

No activity logged yet.

)} )}
{/* Modals & Drawers */} setSelectedCO(null)} changeOrder={selectedCO} /> setSelectedInvoice(null)} invoice={selectedInvoice} /> {createModalConfig && ( setCreateModalConfig(null)} {...createModalConfig} /> )} igotsar.matyas | LynkedUpPro - Turns out roofing CRMs don't build themselves. Who knew?
); }; export default OwnerProjectDetail;