From 5dff6cfaeafa966233b5150588c59b4ae50935cd Mon Sep 17 00:00:00 2001 From: Satyam Rastogi Date: Fri, 29 May 2026 17:30:16 +0530 Subject: [PATCH] feat(projects): interactive lifecycle progression control with stage history --- src/pages/owner/OwnerProjectDetail.jsx | 185 +++++++++++++++++++++---- 1 file changed, 155 insertions(+), 30 deletions(-) diff --git a/src/pages/owner/OwnerProjectDetail.jsx b/src/pages/owner/OwnerProjectDetail.jsx index d406792..e50a2e5 100644 --- a/src/pages/owner/OwnerProjectDetail.jsx +++ b/src/pages/owner/OwnerProjectDetail.jsx @@ -2,6 +2,7 @@ import React, { useState, useMemo, useRef, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; import { useMockStore } from '../../data/mockStore'; +import { LIFECYCLE_STAGES, REWORK_STAGE, nextStage, prevStage, stageToPct } from '../../data/lifecycle'; import { SpotlightCard } from '../../components/SpotlightCard'; import { AnimatedCounter } from '../../components/AnimatedCounter'; import { motion } from 'framer-motion'; @@ -190,6 +191,7 @@ const OwnerProjectDetail = () => { updateOrgCommissionDefaults, setUserCommissionOverride, removeUserCommissionOverride, updateProject, addJobTeamMember, removeJobTeamMember, updateJobTeamMember, + advanceLifecycleStage, } = useMockStore(); const navigate = useNavigate(); const { can } = usePermissions(); @@ -554,41 +556,164 @@ const OwnerProjectDetail = () => {
{/* 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} - + {/* Current stage label */} + {(() => { + const currentStage = project.lifecycleStage || LIFECYCLE_STAGES[0]; + const isRework = currentStage === REWORK_STAGE; + const currentIdx = LIFECYCLE_STAGES.indexOf(currentStage); + // Condensed milestone labels for stepper display + const STEPPER_STAGES = ['Lead', 'Damage Verified', 'Contract Signed', 'Work In Progress', 'Project Completed', 'Final Payment Received — Closed']; + const STEPPER_LABELS = ['Lead', 'Damage Verified', 'Contract Signed', 'Work In Progress', 'Completed', 'Closed']; + // Consistency rule: WIP uses stored %, all others use stageToPct + const displayPct = currentStage === 'Work In Progress' + ? (project.completionPercentage || 0) + : stageToPct(currentStage); + const isOwnerAdmin = ['OWNER', 'ADMIN'].includes(user?.role); + const nextStageName = nextStage(currentStage); + const isAtFinalStage = currentStage === 'Final Payment Received — Closed'; + const isFinalInspectionStage = currentStage === 'Final Inspection Scheduled' || currentStage === 'Final Inspection In Progress'; + + return ( + <> +
+ + + {currentStage} + + {displayPct}% complete +
+ + {/* Stepper */} +
+
+ +
+ {STEPPER_STAGES.map((step, i) => { + const stepIdx = LIFECYCLE_STAGES.indexOf(step); + const isActive = isRework + ? stepIdx <= LIFECYCLE_STAGES.indexOf('Work In Progress') + : stepIdx <= currentIdx; + const isCurrent = step === currentStage; + return ( +
+
+ {isActive ? : } +
+ + {STEPPER_LABELS[i]} + +
+ ); + })}
- ); - })} -
-
-
- - {project.completionPercentage || 0}% CMP -
+
+ + {/* Rework indicator */} + {isRework && ( +
+ + Rework in progress — project re-enters Final Inspection after rework is resolved +
+ )} + + {/* Progress bar */} +
+ + {displayPct}% CMP +
+ + {/* Owner/Admin Advance controls */} + {isOwnerAdmin && ( +
+ {!isAtFinalStage && ( + + )} + {isFinalInspectionStage && ( + + )} + {isAtFinalStage && ( + + Project fully closed + + )} +
+ )} + + {/* Stage History Timeline */} + {(project.stageHistory || []).length > 0 && ( +
+

Stage History

+
+
+
+ {[...(project.stageHistory)].reverse().map((entry, i) => ( +
+
+
+ {entry.stage} +
+ {entry.date} + {entry.by && {entry.by}} +
+ {entry.note && ( + {entry.note} + )} +
+
+ ))} +
+
+
+ )} + + ); + })()} {/* Relationship Intelligence */}