import React, { useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/Components/ui/card'; import { Badge } from '@/Components/ui/badge'; import { TrendingUp, Calendar, CheckCircle2, Clock, Activity, Flag, Layers, CheckCircle } from 'lucide-react'; interface EvmPoint { date: string; full_date: string; is_today?: boolean; planned_pv: number; actual_ev: number | null; schedule_variance: number | null; spi: number | null; } interface MilestoneTurnoverItem { id: number; name: string; description?: string; weight_percentage: number; planned_date: string | null; actual_date: string | null; tasks_count: number; completed_tasks_count: number; progress_percentage: number; is_turnovered: boolean; turnover_status: string; turnover_status_color: 'emerald' | 'amber' | 'blue' | 'rose' | 'indigo' | 'slate'; days_variance: number; } interface EvmSummary { planned_pv: number; actual_ev: number; schedule_variance: number; spi: number; status: string; target_end_date: string; projected_end_date: string; days_variance: number; is_completed?: boolean; } interface Props { evmData: { timeSeries: EvmPoint[]; summary: EvmSummary; milestones?: MilestoneTurnoverItem[]; }; } export default function EvmSCurveChart({ evmData }: Props) { const [hoveredIdx, setHoveredIdx] = useState(null); if (!evmData || !evmData.timeSeries || evmData.timeSeries.length === 0) { return null; } const { timeSeries, summary, milestones } = evmData; const width = 800; const height = 320; const padding = 45; const maxVal = 100; const pointsCount = timeSeries.length; const getX = (index: number) => padding + (index / Math.max(1, pointsCount - 1)) * (width - 2 * padding); const getY = (val: number) => height - padding - (Math.max(0, Math.min(100, val)) / maxVal) * (height - 2 * padding); // 1. Build SVG path string for Planned PV const pvCoords = timeSeries.map((pt, i) => ({ x: getX(i), y: getY(pt.planned_pv), pt, idx: i })); const pvPath = pvCoords.reduce((acc, coord, i) => (i === 0 ? `M ${coord.x} ${coord.y}` : `${acc} L ${coord.x} ${coord.y}`), ''); const pvAreaPath = `${pvPath} L ${getX(pointsCount - 1)} ${getY(0)} L ${getX(0)} ${getY(0)} Z`; // 2. Build SVG path string for Actual EV const actualPoints = timeSeries .map((pt, idx) => ({ pt, idx, x: getX(idx), y: getY(pt.actual_ev ?? 0) })) .filter(item => item.pt.actual_ev !== null && item.pt.actual_ev !== undefined); // If actualPoints does not start at index 0, anchor it to (0, 0%) const evDrawCoords: { x: number; y: number; pt: EvmPoint; idx: number }[] = []; if (actualPoints.length > 0) { if (actualPoints[0].idx > 0) { evDrawCoords.push({ x: getX(0), y: getY(0), pt: { ...timeSeries[0], actual_ev: 0 }, idx: 0 }); } evDrawCoords.push(...actualPoints); } const evPath = evDrawCoords.reduce((acc, coord, i) => (i === 0 ? `M ${coord.x} ${coord.y}` : `${acc} L ${coord.x} ${coord.y}`), ''); const lastEvCoord = evDrawCoords.length > 0 ? evDrawCoords[evDrawCoords.length - 1] : null; const evAreaPath = evDrawCoords.length > 0 ? `${evPath} L ${lastEvCoord?.x} ${getY(0)} L ${evDrawCoords[0].x} ${getY(0)} Z` : ''; const isAhead = summary.schedule_variance >= 0; const statusColor = isAhead ? 'bg-emerald-100 text-emerald-800 border-emerald-300' : summary.schedule_variance > -5 ? 'bg-amber-100 text-amber-800 border-amber-300' : 'bg-rose-100 text-rose-800 border-rose-300'; const activePoint = hoveredIdx !== null ? timeSeries[hoveredIdx] : null; const getStatusBadgeClass = (color: string) => { switch (color) { case 'emerald': return 'bg-emerald-50 text-emerald-700 border-emerald-200'; case 'blue': return 'bg-blue-50 text-blue-700 border-blue-200'; case 'amber': return 'bg-amber-50 text-amber-700 border-amber-200'; case 'rose': return 'bg-rose-50 text-rose-700 border-rose-200'; case 'indigo': return 'bg-indigo-50 text-indigo-700 border-indigo-200'; default: return 'bg-slate-50 text-slate-700 border-slate-200'; } }; return (
{/* Executive KPI Cards */}

Scheduled Baseline (PV)

{summary.planned_pv}%

Estimated progress as of today

Real-Time Progress (EV)

{summary.actual_ev}%

Verified actual field progress

Schedule Performance (SPI)

{summary.spi}

{summary.status}

Schedule Variance: = 0 ? "text-emerald-600" : "text-rose-600"}> {summary.schedule_variance > 0 ? `+${summary.schedule_variance}%` : `${summary.schedule_variance}%`}

{summary.is_completed ? 'Actual vs Target End' : 'Target vs Forecasted End'}

{summary.projected_end_date}

Target: {summary.target_end_date}{' '} {summary.days_variance > 0 ? ( (+{summary.days_variance}d delay) ) : summary.days_variance < 0 ? ( ({Math.abs(summary.days_variance)}d ahead) ) : ( (On schedule) )}

{/* Interactive S-Curve Visual Card */}
Earned Value S-Curve Baseline Comparison Contrasting Planned Schedule Curve (PV) against Verified Real-Time Field Accomplishments (EV)
Baseline Target (PV)
Real-Time Actuals (EV)
{/* Hover Tooltip Overlay */} {activePoint && (
{activePoint.full_date} {activePoint.is_today && [Today]}
Planned (PV): {activePoint.planned_pv}%
{activePoint.actual_ev !== null && (
Verified (EV): {activePoint.actual_ev}%
)} {activePoint.schedule_variance !== null && (
Variance (SV): = 0 ? "text-emerald-400" : "text-rose-400"}> {activePoint.schedule_variance > 0 ? `+${activePoint.schedule_variance}%` : `${activePoint.schedule_variance}%`}
)}
)}
{/* Horizontal Gridlines */} {[0, 25, 50, 75, 100].map(val => ( {val}% ))} {/* Today Vertical Line Indicator */} {timeSeries.map((pt, idx) => { if (!pt.is_today) return null; const todayX = getX(idx); return ( Today ); })} {/* Area Fills */} {pvAreaPath && } {evAreaPath && } {/* Planned Baseline PV Path */} {/* Real-Time Actuals EV Path */} {evPath && ( )} {/* Planned PV Data Points */} {pvCoords.map((coord) => ( ))} {/* Real-Time Actuals EV Data Points */} {evDrawCoords.map((coord) => ( {coord.pt.is_today && ( )} ))} {/* Interactive Hover Columns */} {timeSeries.map((pt, idx) => ( setHoveredIdx(idx)} onMouseLeave={() => setHoveredIdx(null)} /> ))} {/* X-Axis Dates */} {timeSeries.map((pt, idx) => ( {pt.date} ))}
{/* Milestone Turnover & Stage Gate Delivery Coordination Card */} {milestones && milestones.length > 0 && (
Milestone Turnover & Stage Gate Coordination Tasks drive granular field progress, while milestones govern stage-gate turnover and handover deadlines.
{milestones.filter(m => m.is_turnovered).length} of {milestones.length} Turnovered
{milestones.map((m) => ( ))}
Milestone Stage Weight Task Completion Scheduled Turnover Actual Turnover Gate Status
{m.name}
{m.description && (

{m.description}

)}
{m.weight_percentage}%
{m.completed_tasks_count}/{m.tasks_count} tasks {m.progress_percentage}%
= 100 ? 'bg-emerald-500' : m.progress_percentage > 0 ? 'bg-blue-500' : 'bg-slate-300' }`} style={{ width: `${Math.min(100, Math.max(0, m.progress_percentage))}%` }} />
{m.planned_date || Not set} {m.actual_date ? ( {m.actual_date} ) : ( Pending Handover )} {m.turnover_status}
)}
); }