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 (
);
})}
{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 Summary */}
Evidence Summary
Hail Impacts Detected
178
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 */}
{/* 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 */}
)}
{/* BUDGET TAB */}
{activeTab === 'budget' && (
{budgetChartData.length > 0 ? (
<>
Budget Breakdown
Allocated vs Committed vs Actual ($k)
} />
{['Category', 'Allocated', 'Committed', 'Actual', 'Variance'].map(h => (
| {h} |
))}
{(project.budgetBreakdown || []).map((b, i) => {
const variance = b.actual - b.allocated;
return (
| {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 => (
| {h} |
))}
{project.changeOrders.map((co, i) => (
setSelectedCO(co)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedCO(co); } }}>
| {co.id} |
{co.title}
{co.description && {co.description} }
|
{formatCurrency(co.amount)} |
{co.status}
|
{co.dateSubmitted} |
))}
) : (
)}
)}
{/* RFIs TAB */}
{activeTab === 'rfis' && (
Requests for Information (RFI)
{(project.rfis || []).length > 0 ? (
{['ID', 'Subject', 'Status', 'Submitted By', 'Opened', 'Closed'].map(h => (
| {h} |
))}
{project.rfis.map((rfi, i) => (
| {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 => (
| {h} |
))}
{project.invoices.map((inv, i) => (
setSelectedInvoice(inv)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setSelectedInvoice(inv); } }}>
| {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 => (
| {h} |
))}
{project.riskLog.map((r, i) => (
| {r.id} |
{r.description} |
{r.severity}
|
{r.likelihood} |
{r.status} |
{r.mitigation} |
))}
) : (
No risk entries for this project.
)}
{/* Issue Log */}
{(project.issueLog || []).length > 0 ? (
{['ID', 'Title', 'Priority', 'Status', 'Assigned To', 'Reported'].map(h => (
| {h} |
))}
{project.issueLog.map((issue, i) => (
| {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}
}
))}
) : (
)}
)}
{/* 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;