import React, { useState, useMemo } from 'react'; import { ProjectState, Priority, AiSuggestion, BOQItem, DPR, RiskAssessment, WeatherForecast } from '../types'; import { TrendingUp, Activity, AlertCircle, Wallet, Sparkles, Flag, Zap, Check, Trash2, Clock, ArrowRight, AlertTriangle, Calendar, ChevronRight, BarChart3, Sun, ShieldAlert, Leaf } from 'lucide-react'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Cell } from 'recharts'; import { generateProjectInsights, generatePredictiveRiskAssessment } from '../services/localAnalysisService'; import ReactMarkdown from 'react-markdown'; import RiskAssessmentComponent from './RiskAssessment'; import WeatherWidget from './WeatherWidget'; interface DashboardProps { data: ProjectState; onApplySuggestion: (suggestionId: string) => void; onDismissSuggestion: (suggestionId: string) => void; onUpdateProject?: (updater: (proj: ProjectState) => ProjectState) => void; } // Helper for Gantt Chart const getDaysDiff = (d1: string, d2: string) => { const date1 = new Date(d1); const date2 = new Date(d2); return Math.max(1, (date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)); }; const ProjectGantt: React.FC<{ data: ProjectState }> = ({ data }) => { const projectStart = new Date(data.startDate); const projectEnd = new Date(data.endDate); const totalDuration = getDaysDiff(data.startDate, data.endDate); // Calculate Today's position relative to project duration const today = new Date(); const todayPercent = Math.max(0, Math.min(100, (today.getTime() - projectStart.getTime()) / (projectEnd.getTime() - projectStart.getTime()) * 100)); const getPositionPercent = (dateStr: string) => { const date = new Date(dateStr); const diff = (date.getTime() - projectStart.getTime()) / (1000 * 3600 * 24); return Math.max(0, Math.min(100, (diff / totalDuration) * 100)); }; // Derive Item Execution Windows from DPRs const itemTimelines = useMemo(() => { const timelines: { itemId: string; name: string; start: string; end: string; progress: number; priority: Priority; status: string }[] = []; // Only look at items that have started or are high priority const activeItems = data.boq.filter(b => b.executedQty > 0 || b.priority === 'HIGH'); activeItems.forEach(item => { const itemDprs = data.dprs.filter(d => d.linkedBoqId === item.id).sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); let startDate = itemDprs.length > 0 ? itemDprs[0].date : new Date().toISOString().split('T')[0]; let endDate = itemDprs.length > 0 ? itemDprs[itemDprs.length - 1].date : startDate; // If only one DPR or duration is 0, give it a visual width (e.g. 15 days) if (startDate === endDate) { const end = new Date(startDate); end.setDate(end.getDate() + 15); endDate = end.toISOString().split('T')[0]; } // If item hasn't started (no DPRs) but is High Priority, visualize it as "Planned" starting now if (itemDprs.length === 0) { startDate = new Date().toISOString().split('T')[0]; const end = new Date(startDate); end.setDate(end.getDate() + 30); endDate = end.toISOString().split('T')[0]; } const progress = Math.min(100, (item.executedQty / item.plannedQty) * 100); let status = 'On Track'; if (progress < 100 && new Date(endDate) < today) status = 'Delayed'; if (progress >= 100) status = 'Completed'; timelines.push({ itemId: item.id, name: item.description, start: startDate, end: endDate, progress, priority: item.priority || 'LOW', status }); }); return timelines.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()).slice(0, 10); }, [data.boq, data.dprs]); return (

Timeline & Progress

High Priority
Scheduled
Today
{/* Timeline Header (Months) */}
{data.startDate} Project Duration ({Math.ceil(totalDuration)} Days) {data.endDate}
{/* Grid Lines */}
{[0, 25, 50, 75, 100].map(p => (
))}
{/* Today Marker */}
Today
{/* Milestones Track */}
{data.milestones.map(m => { const pos = getPositionPercent(m.date); return (
{/* Tooltip for Milestone */}
{m.title}
{m.date} • {m.status}
{/* Date label always visible if needed, but keeping it clean */}
{m.date.split('-').slice(1).join('/')}
); })}
{/* Items Tracks */}
{itemTimelines.map(item => { const left = getPositionPercent(item.start); const width = Math.max(2, getPositionPercent(item.end) - left); // Min width 2% return (
{item.name} {item.start} — {item.end}
{item.status} {item.progress.toFixed(0)}%
{/* The Background Bar (Planned/Total Window) */}
{/* The Actual Progress Bar */}
{/* Glossy overlay */}
{/* Animated stripes if active */} {item.status !== 'Completed' && (
)}
{/* Hover Details */}
{item.name}
Duration: {getDaysDiff(item.start, item.end).toFixed(0)}d
Progress: {item.progress.toFixed(1)}%
Start: {item.start}
End: {item.end}
); })} {itemTimelines.length === 0 && (
No active work timelines generated yet. Add DPRs to visualize progress.
)}
); }; const Dashboard: React.FC = ({ data, onApplySuggestion, onDismissSuggestion, onUpdateProject }) => { const [insight, setInsight] = useState(null); const [loadingInsight, setLoadingInsight] = useState(false); const [loadingRisk, setLoadingRisk] = useState(false); // Calculate high-level metrics const totalPlannedValue = data.boq.reduce((sum, item) => sum + (item.plannedQty * item.rate), 0); const totalExecutedValue = data.boq.reduce((sum, item) => sum + (item.executedQty * item.rate), 0); const progressPercentage = Math.round((totalExecutedValue / totalPlannedValue) * 100) || 0; const totalLiabilities = data.liabilities.reduce((sum, item) => sum + item.amount, 0); const totalBilled = data.bills.filter(b => b.type === 'CLIENT_RA').reduce((sum, b) => sum + b.amount, 0); // Calculate Pending Work for Key Points const pendingHighPriorityItems = data.boq .filter(item => item.priority === 'HIGH' && item.executedQty < item.plannedQty) .map(item => ({ ...item, pendingQty: item.plannedQty - item.executedQty, pendingValue: (item.plannedQty - item.executedQty) * item.rate, progress: (item.executedQty / item.plannedQty) * 100 })) .sort((a, b) => b.pendingValue - a.pendingValue) .slice(0, 5); const chartData = [ { name: 'Planned', amount: totalPlannedValue }, { name: 'Executed', amount: totalExecutedValue }, { name: 'Billed', amount: totalBilled }, { name: 'Liabilities', amount: totalLiabilities }, ]; const handleGenerateInsights = async () => { setLoadingInsight(true); const result = await generateProjectInsights(data); setInsight(result); setLoadingInsight(false); }; const handleGenerateRisks = async () => { if (!onUpdateProject) return; setLoadingRisk(true); const result = await generatePredictiveRiskAssessment(data); if (result) { onUpdateProject(proj => ({ ...proj, riskAssessment: result })); } setLoadingRisk(false); }; const getPriorityColor = (p: Priority) => { switch(p) { case 'HIGH': return 'bg-red-50 text-red-700 border-red-100'; case 'MEDIUM': return 'bg-amber-50 text-amber-700 border-amber-100'; case 'LOW': return 'bg-blue-50 text-blue-700 border-blue-100'; default: return 'bg-slate-50 text-slate-700 border-slate-100'; } }; const pendingSuggestions = data.aiSuggestions.filter(s => s.status === 'PENDING'); return (

Project Overview

{data.priority} Priority

Monitoring and insights for {data.name}

{onUpdateProject && ( )}
{/* Mini Health Dashboard */}
70 ? 'bg-red-50 text-red-600' : 'bg-emerald-50 text-emerald-600'}`}>

Risk Score

{data.riskAssessment?.overallRiskScore || 0}%

Weather Impact

{data.weatherForecast?.find(f => f.impactOnSite !== 'NONE') ? 'Active Advisory' : 'Operational'}

Execution Index

{progressPercentage > 0 ? (progressPercentage / getDaysDiff(data.startDate, new Date().toISOString()) * 100).toFixed(1) : '0.0'}% avg.

{/* KPI Cards */}

Completed

{progressPercentage}%

Contract

৳{(data.contractValue / 1000000).toFixed(1)}M

Total Budget

Billed

৳{totalBilled > 1000 ? (totalBilled / 1000).toFixed(0) + 'k' : totalBilled}

Certified Work

Liabilities

৳{totalLiabilities > 1000 ? (totalLiabilities / 1000).toFixed(0) + 'k' : totalLiabilities}

Unpaid Sum

Emissions

{data.sustainabilityMetrics?.carbonFootprint || 0}kg

Carbon Output

{/* Gantt Chart Section */}
{data.riskAssessment && } {data.weatherForecast && }
{/* KEY POINTS: Pending Progress Table */}

Critical Pending Work

Action Needed
{pendingHighPriorityItems.length > 0 ? pendingHighPriorityItems.map(item => ( )) : ( )}
Item Description Balance Value Progress
{item.description}
{item.id}
{item.pendingQty.toLocaleString()} {item.unit} ৳{item.pendingValue.toLocaleString()}
{item.progress.toFixed(0)}%

No high-priority pending items detected.

{/* Financial Chart */}

Financial Position

Budget
Done
`৳${value >= 1000000 ? (value/1000000).toFixed(1) + 'M' : (value/1000).toFixed(0) + 'k'}`} /> [`৳${value.toLocaleString()}`, 'Amount']} /> {chartData.map((entry, index) => ( ))}
{insight && (

AI Strategy & Insights

Generative Analysis

{insight}
)}
{/* Sidebar */}
{/* NEW KEY POINTS: Milestone Tracker */}

Key Project Points

{data.milestones && data.milestones.length > 0 ? data.milestones.map((milestone) => (

{milestone.title}

{milestone.status.replace('_', ' ')}
{milestone.date}
{milestone.description &&

{milestone.description}

}
)) : (
No milestones tracked.
)}
{/* AI Action Sidebar */}

AI Action Feed

Actionable data extracted from your recent document scans.

{pendingSuggestions.length > 0 ? pendingSuggestions.map((s) => (
{s.type.replace('_', ' ')}

{s.title}

{s.description}

)) : (

No pending actions. Try an AI Deep Scan on your documents.

)}
{pendingSuggestions.length > 0 && ( )}
); }; export default Dashboard;