diff --git a/src/components/owner/CreateItemModal.jsx b/src/components/owner/CreateItemModal.jsx
new file mode 100644
index 0000000..02f4a3a
--- /dev/null
+++ b/src/components/owner/CreateItemModal.jsx
@@ -0,0 +1,103 @@
+import React, { useEffect, useState } from 'react';
+import { createPortal } from 'react-dom';
+import { X } from 'lucide-react';
+
+const CreateItemModal = ({ isOpen, onClose, title, icon: Icon, iconColor = "text-[#00f0ff]", iconBg = "bg-blue-500/10", fields, onSubmit, submitLabel = "Submit", submitColor = "bg-blue-500/20 text-[#00f0ff] border-blue-500/30" }) => {
+ const [formData, setFormData] = useState({});
+
+ useEffect(() => {
+ const handleEsc = (e) => { if (e.key === 'Escape') onClose(); };
+ if (isOpen) window.addEventListener('keydown', handleEsc);
+ return () => window.removeEventListener('keydown', handleEsc);
+ }, [isOpen, onClose]);
+
+ if (!isOpen) return null;
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ onSubmit(formData);
+ setFormData({});
+ onClose();
+ };
+
+ return createPortal(
+
+
+
+ {/* Header */}
+
+
+ {Icon && (
+
+
+
+ )}
+
{title}
+
+
+
+
+ {/* Content */}
+
+
+
,
+ document.body
+ );
+};
+
+export default CreateItemModal;
diff --git a/src/data/mockStore.jsx b/src/data/mockStore.jsx
index 73f684f..649f16a 100644
--- a/src/data/mockStore.jsx
+++ b/src/data/mockStore.jsx
@@ -2132,7 +2132,7 @@ const MOCK_PROJECTS = [
completionPercentage: 42,
healthScore: 58,
openRFIs: 1,
- changeOrderCount: 0,
+ changeOrderCount: 2,
pendingInvoiceCount: 2,
// TODO: cross-reference — Contractor: Texas Builders LLC, Vendors: North Texas HVAC Solutions, ABC Supply Co.
milestones: [
@@ -2143,11 +2143,37 @@ const MOCK_PROJECTS = [
{ id: 'ms_003_5', name: 'Electrical Hookup', dueDate: '2026-02-14', status: 'pending', assignedTo: 'sub_001' },
{ id: 'ms_003_6', name: 'System Test and Commissioning', dueDate: '2026-02-25', status: 'pending', assignedTo: 'v6' }
],
+ budgetBreakdown: [
+ { category: 'Equipment & Materials', allocated: 15000, committed: 15000, actual: 12500 },
+ { category: 'Labor - HVAC', allocated: 8000, committed: 8000, actual: 4000 },
+ { category: 'Labor - Electrical', allocated: 3000, committed: 3000, actual: 0 },
+ { category: 'Permits & Fees', allocated: 1000, committed: 1000, actual: 950 },
+ { category: 'Contingency', allocated: 5000, committed: 1200, actual: 950 }
+ ],
invoices: [
{ id: 'inv_003_1', amount: 8200, submittedBy: 'con_001', status: 'paid', dueDate: '2026-01-15', datePaid: '2026-01-16' },
{ id: 'inv_003_2', amount: 10500.25, submittedBy: 'v6', status: 'pending', dueDate: '2026-02-19' },
{ id: 'inv_003_3', amount: 3400, submittedBy: 'v2', status: 'pending', dueDate: '2026-02-22' }
],
+ changeOrders: [
+ { id: 'CO-003-01', title: 'Upsize Return Ducts', amount: 1200, status: 'approved', dateSubmitted: '2026-01-22', description: 'Increase return duct size for better airflow in master bedroom.' },
+ { id: 'CO-003-02', title: 'Smart Thermostat Upgrade', amount: 450, status: 'pending', dateSubmitted: '2026-02-18', description: 'Upgrade to Ecobee Premium thermostats per owner request.' }
+ ],
+ rfis: [
+ { id: 'RFI-003-01', subject: 'Condenser Pad Location', status: 'closed', submittedBy: 'North Texas HVAC', dateOpened: '2026-01-12', dateClosed: '2026-01-14' },
+ { id: 'RFI-003-02', subject: 'Duct Routing Conflict in Attic', status: 'open', submittedBy: 'Texas Builders LLC', dateOpened: '2026-02-15' }
+ ],
+ riskLog: [
+ { id: 'risk_003_1', description: 'Equipment backorder from manufacturer', severity: 'High', likelihood: 'Likely', mitigation: 'Source equivalent unit from secondary distributor', status: 'open' }
+ ],
+ issueLog: [
+ { id: 'issue_003_1', title: 'Failed framing inspection for duct clearance', priority: 'High', status: 'open', assignedTo: 'Texas Builders LLC', dateReported: '2026-02-14' }
+ ],
+ activityTimeline: [
+ { date: '2026-02-15', action: 'RFI Submitted', user: 'Site Supervisor', details: 'Duct routing conflict in attic' },
+ { date: '2026-02-14', action: 'Inspection Failed', user: 'City Inspector', details: 'Framing clearance insufficient for 12" ducts' },
+ { date: '2026-01-30', action: 'Milestone Completed', user: 'North Texas HVAC', details: 'Equipment Delivery phase finished' }
+ ],
documents: [
{ name: 'HVAC Design Plans', url: '#', type: 'plans' },
{ name: 'Mechanical Permit #26-0187', url: '#', type: 'permit' },
@@ -2177,7 +2203,7 @@ const MOCK_PROJECTS = [
endDate: '2026-02-28',
completionPercentage: 78,
healthScore: 35,
- openRFIs: 0,
+ openRFIs: 1,
changeOrderCount: 2,
pendingInvoiceCount: 2,
// TODO: cross-reference — Contractor: Texas Builders LLC, Vendors: ABC Supply Co., Texas Roofing Crew A, Sparky Electric
@@ -2190,6 +2216,37 @@ const MOCK_PROJECTS = [
{ id: 'ms_004_6', name: 'Flooring and Trim', dueDate: '2026-02-18', status: 'in_progress', assignedTo: 'con_001' },
{ id: 'ms_004_7', name: 'Final Punch List', dueDate: '2026-02-25', status: 'pending', assignedTo: 'con_001' }
],
+ budgetBreakdown: [
+ { category: 'Demolition', allocated: 5000, committed: 5500, actual: 5500 },
+ { category: 'Framing & Structural', allocated: 8000, committed: 8500, actual: 8500 },
+ { category: 'Electrical', allocated: 7000, committed: 7000, actual: 4800 },
+ { category: 'Plumbing', allocated: 6000, committed: 6500, actual: 6500 },
+ { category: 'Drywall & Paint', allocated: 9000, committed: 10000, actual: 10000 },
+ { category: 'Flooring & Trim', allocated: 12000, committed: 15500, actual: 15500 },
+ { category: 'Contingency', allocated: 8000, committed: 9300, actual: 11500 }
+ ],
+ changeOrders: [
+ { id: 'CO-004-01', title: 'Scope Expansion - Kitchen Island', amount: 4500, status: 'approved', dateSubmitted: '2026-01-10', description: 'Add a custom 6ft island with quartz countertop and built-in microwave.' },
+ { id: 'CO-004-02', title: 'Hardwood Flooring Upgrade', amount: 2800, status: 'approved', dateSubmitted: '2026-01-25', description: 'Upgrade from LVP to engineered hardwood in living areas.' }
+ ],
+ rfis: [
+ { id: 'RFI-004-01', subject: 'Load-bearing wall clarification', status: 'closed', submittedBy: 'Texas Builders LLC', dateOpened: '2025-12-15', dateClosed: '2025-12-16' },
+ { id: 'RFI-004-02', subject: 'Paint finish in bathrooms', status: 'open', submittedBy: 'Texas Builders LLC', dateOpened: '2026-02-02' }
+ ],
+ riskLog: [
+ { id: 'risk_004_1', description: 'Flooring shipment delayed at port', severity: 'High', likelihood: 'Likely', mitigation: 'Source alternative matching material from local distributor', status: 'open' },
+ { id: 'risk_004_2', description: 'Paint color mismatch between batches', severity: 'Medium', likelihood: 'Possible', mitigation: 'Verify batch numbers before final coat', status: 'mitigated' }
+ ],
+ issueLog: [
+ { id: 'issue_004_1', title: 'Drywall texturing inconsistent in hallway', priority: 'Medium', status: 'open', assignedTo: 'Texas Builders LLC', dateReported: '2026-02-16' },
+ { id: 'issue_004_2', title: 'Plumbing rough-in failed initial inspection', priority: 'High', status: 'resolved', assignedTo: 'Lone Star Plumbing', dateReported: '2026-01-16' }
+ ],
+ activityTimeline: [
+ { date: '2026-02-16', action: 'Issue Reported', user: 'Site Supervisor', details: 'Drywall texture inconsistent in main hallway' },
+ { date: '2026-02-05', action: 'Milestone Completed', user: 'Texas Builders LLC', details: 'Drywall and Paint phase finished' },
+ { date: '2026-01-22', action: 'Payment Released', user: 'Marcus Whitfield', details: 'Truss and framing payment - $12,800' },
+ { date: '2026-01-18', action: 'Inspection Passed', user: 'City Inspector', details: 'Plumbing rough-in re-inspection passed' }
+ ],
invoices: [
{ id: 'inv_004_1', amount: 18000, submittedBy: 'con_001', status: 'paid', dueDate: '2025-12-15', datePaid: '2025-12-16' },
{ id: 'inv_004_2', amount: 15500, submittedBy: 'con_001', status: 'paid', dueDate: '2026-01-10', datePaid: '2026-01-12' },
@@ -2230,7 +2287,7 @@ const MOCK_PROJECTS = [
completionPercentage: 38,
healthScore: 42,
openRFIs: 0,
- changeOrderCount: 0,
+ changeOrderCount: 1,
pendingInvoiceCount: 1,
// TODO: cross-reference — Contractor: Texas Builders LLC, Vendors: ABC Supply Co.
milestones: [
@@ -2240,11 +2297,35 @@ const MOCK_PROJECTS = [
{ id: 'ms_005_4', name: 'New Siding Install', dueDate: '2026-02-05', status: 'pending', assignedTo: 'con_001' },
{ id: 'ms_005_5', name: 'Trim and Caulking', dueDate: '2026-02-15', status: 'pending', assignedTo: 'con_001' }
],
+ budgetBreakdown: [
+ { category: 'Materials - Siding', allocated: 12000, committed: 12000, actual: 8000 },
+ { category: 'Labor - Prep & Install', allocated: 9000, committed: 9000, actual: 3500 },
+ { category: 'Equipment Rental', allocated: 2000, committed: 2000, actual: 1500 },
+ { category: 'Structural Repairs (CO)', allocated: 0, committed: 850, actual: 850 },
+ { category: 'Contingency', allocated: 5000, committed: 5000, actual: 350 }
+ ],
invoices: [
{ id: 'inv_005_1', amount: 8400, submittedBy: 'con_001', status: 'paid', dueDate: '2026-01-12', datePaid: '2026-01-13' },
{ id: 'inv_005_2', amount: 5800, submittedBy: 'v3', status: 'paid', dueDate: '2026-01-20', datePaid: '2026-01-21' },
{ id: 'inv_005_3', amount: 6500, submittedBy: 'con_001', status: 'pending', dueDate: '2026-02-10' }
],
+ changeOrders: [
+ { id: 'CO-005-01', title: 'Rotten Fascia Board Replacement', amount: 850, status: 'approved', dateSubmitted: '2026-01-18', description: 'Replace 40 linear feet of rotted fascia discovered during tear-off.' }
+ ],
+ rfis: [
+ { id: 'RFI-005-01', subject: 'Vapor Barrier Specification', status: 'closed', submittedBy: 'Texas Builders LLC', dateOpened: '2026-01-20', dateClosed: '2026-01-21' }
+ ],
+ riskLog: [
+ { id: 'risk_005_1', description: 'Water damage hidden behind existing siding', severity: 'High', likelihood: 'Certain', mitigation: 'Halt work, assess rot, submit change order for structural repair.', status: 'realized' }
+ ],
+ issueLog: [
+ { id: 'issue_005_1', title: 'Defective siding panels delivered', priority: 'Critical', status: 'open', assignedTo: 'ABC Supply Co.', dateReported: '2026-02-04' }
+ ],
+ activityTimeline: [
+ { date: '2026-02-06', action: 'Project Paused', user: 'System', details: 'Status changed to On Hold due to material dispute' },
+ { date: '2026-02-04', action: 'Issue Reported', user: 'Site Supervisor', details: 'Batch of siding panels showed unacceptable warping' },
+ { date: '2026-01-22', action: 'Milestone Completed', user: 'Texas Builders LLC', details: 'Moisture Barrier Install finished' }
+ ],
documents: [
{ name: 'Siding Replacement Contract', url: '#', type: 'contract' },
{ name: 'Dispute Notice - Material Defect', url: '#', type: 'dispute' },
diff --git a/src/pages/owner/OwnerProjectDetail.jsx b/src/pages/owner/OwnerProjectDetail.jsx
index 52cdd23..1bb289e 100644
--- a/src/pages/owner/OwnerProjectDetail.jsx
+++ b/src/pages/owner/OwnerProjectDetail.jsx
@@ -3,16 +3,53 @@ 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
+ 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 },
@@ -41,10 +78,16 @@ const OwnerProjectDetail = () => {
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]);
+ , [projects, projectId, user]);
if (!project) {
return (
@@ -165,11 +208,10 @@ const OwnerProjectDetail = () => {
@@ -179,66 +221,274 @@ const OwnerProjectDetail = () => {
{/* Tab Content */}
- {/* OVERVIEW TAB */}
+ {/* OVERVIEW TAB - COMMAND CENTER */}
{activeTab === 'overview' && (
-
- {/* Project Info */}
-
- Project Details
-
- {[
- ['Project ID', project.id],
- ['Type', project.projectType],
- ['Start Date', project.startDate],
- ['End Date', project.endDate],
- ['Contractor', contractor?.vendorName || project.contractorId],
- ['Margin', `${((project.margin || 0) * 100).toFixed(0)}%`],
- ['Open RFIs', project.openRFIs ?? '—'],
- ['Change Orders', project.changeOrderCount ?? '—'],
- ].map(([label, value], i) => (
-
- ))}
-
-
+
- {/* Progress */}
-
- Milestone Progress
-
- {(project.milestones || []).map((ms, i) => (
-
-
-
-
{ms.name}
-
{ms.dueDate}
-
-
- {ms.status.replace('_', ' ')}
-
+ {/* 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 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 */}
+
+
+
+ {/* 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)
-
-
+
+
+
+
+
Budget Breakdown
+
+
+
+ Allocated vs Committed vs Actual ($k)
+
+
@@ -249,152 +499,201 @@ const OwnerProjectDetail = () => {
-
-
-
-
-
- {['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-red-500' : 'text-emerald-500'}`}>
- {variance > 0 ? '+' : ''}{formatCurrency(variance)}
- |
-
- );
- })}
-
-
-
+
+
+
+
+
+
+ {['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)}
-
+
+
+ No detailed budget breakdown available.
+ Budget: {formatCurrency(project.approvedBudget || project.budget)} · Spent: {formatCurrency(project.actualCost || project.spent)}
+
)}
)}
{/* CHANGE ORDERS TAB */}
{activeTab === 'changeOrders' && (
-
- {(project.changeOrders || []).length > 0 ? (
-
-
-
- {['ID', 'Title', 'Amount', 'Status', 'Date'].map(h => (
- | {h} |
- ))}
-
-
-
- {project.changeOrders.map((co, i) => (
- setSelectedCO(co)}>
- | {co.id} |
-
- {co.title}
- {co.description && {co.description} }
- |
- {formatCurrency(co.amount)} |
-
-
- {co.status}
-
- |
- {co.dateSubmitted} |
-
- ))}
-
-
- ) : (
-
-
-
No change orders for this project.
+
+
+
+
+
Change Orders
- )}
-
+
+
+
+ {(project.changeOrders || []).length > 0 ? (
+
+
+
+ {['ID', 'Title', 'Amount', 'Status', 'Date'].map(h => (
+ | {h} |
+ ))}
+
+
+
+ {project.changeOrders.map((co, i) => (
+ setSelectedCO(co)}>
+ | {co.id} |
+
+ {co.title}
+ {co.description && {co.description} }
+ |
+ {formatCurrency(co.amount)} |
+
+
+ {co.status}
+
+ |
+ {co.dateSubmitted} |
+
+ ))}
+
+
+ ) : (
+
+
+
No change orders found.
+
+ )}
+
+
)}
{/* RFIs TAB */}
{activeTab === 'rfis' && (
-
- {(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.
+
+
+
+
+
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
+
+
+
+
Milestone Timeline
+
{/* Timeline line */}
-
+
{(project.milestones || []).map((ms, i) => (
-
-
-
+
+
+
-
{ms.name}
-
Due: {ms.dueDate} · Assigned to: {ms.assignedTo}
+
{ms.name}
+
Due: {ms.dueDate} · Assigned to: {ms.assignedTo}
-
+
{ms.status.replace('_', ' ')}
@@ -403,158 +702,247 @@ const OwnerProjectDetail = () => {
))}
-
+
)}
{/* INVOICES TAB */}
{activeTab === '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)}>
- | {inv.id} |
-
- {formatCurrency(inv.amount)}
- |
- {inv.submittedBy} |
-
-
- {inv.status}
-
- |
- {inv.dueDate} |
- {inv.datePaid || '—'} |
-
- ))}
-
-
- ) : (
-
-
-
No invoices for this project.
+
+
+
+
+
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)}>
+ | {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
-
+
+
+
+
+
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} |
+
+ {(project.riskLog || []).length > 0 ? (
+
+
+
+ {['ID', 'Description', 'Severity', 'Likelihood', 'Status', 'Mitigation'].map(h => (
+ | {h} |
+ ))}
- ))}
-
-
- ) : (
-
No risk entries for this project.
- )}
-
+
+
+ {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 */}
-
-
-
- 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} |
+
+ {(project.issueLog || []).length > 0 ? (
+
+
+
+ {['ID', 'Title', 'Priority', 'Status', 'Assigned To', 'Reported'].map(h => (
+ | {h} |
+ ))}
- ))}
-
-
- ) : (
-
No issues logged for this project.
- )}
-
+
+
+ {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
+
+
+
+
Activity Timeline
+
{(project.activityTimeline || []).length > 0 ? (
-
-
+
+
{project.activityTimeline.map((a, i) => (
-
-
-
+
+
+
-
{a.action}
- {a.details &&
{a.details}
}
+
{a.action}
+ {a.details &&
{a.details}
}
-
{a.date}
- {a.user &&
{a.user}
}
+
{a.date}
+ {a.user &&
{a.user}
}
@@ -563,12 +951,12 @@ const OwnerProjectDetail = () => {
) : (
-
-
-
No activity logged yet.
+
+
+
No activity logged yet.
)}
-
+
)}
@@ -584,6 +972,13 @@ const OwnerProjectDetail = () => {
onClose={() => setSelectedInvoice(null)}
invoice={selectedInvoice}
/>
+ {createModalConfig && (
+
setCreateModalConfig(null)}
+ {...createModalConfig}
+ />
+ )}
igotsar.matyas | LynkedUpPro - Turns out roofing CRMs don't build themselves. Who knew?
);