import React, { useEffect, useState, useMemo } from 'react';
import { useMockStore } from '../data/mockStore';
import { useAuth } from '../context/AuthContext';
import { Link } from 'react-router-dom';
import {
DollarSign, Flame, Calendar, CloudRain, Wind, Droplets, Thermometer,
MapPin, Clock, ArrowUpRight, Sun, Cloud, AlertTriangle, Loader2, ShieldCheck
} from 'lucide-react';
import { SpotlightCard } from '../components/SpotlightCard';
import { config } from '../config/env';
import { pipelineValue, pipelineFunnel, winRate, revenueSummary, stormAttribution } from '../data/selectors';
// New Widget Imports
import { RoofConditionChart } from '../components/dashboard/RoofConditionChart';
import { TopSalesLeaderboard } from '../components/dashboard/TopSalesLeaderboard';
import { RevenueByNeighborhood } from '../components/dashboard/RevenueByNeighborhood';
import { GoldenLeadsScatter } from '../components/dashboard/GoldenLeadsScatter';
import { ActionCenterWidget } from '../components/dashboard/ActionCenterWidget';
import { WeatherRiskGauge } from '../components/dashboard/WeatherRiskGauge';
import AnimatedNumber from '../components/AnimatedNumber';
import Loader from '../components/Loader';
const Dashboard = () => {
const { properties, meetings, projects, kanbanLeads, resolvePerson } = useMockStore();
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(true);
// Company-wide analytics (admin sees ALL projects + ALL kanbanLeads — no owner scoping)
const analytics = useMemo(() => ({
pipeline: pipelineValue(kanbanLeads),
funnel: pipelineFunnel(kanbanLeads),
win: winRate(kanbanLeads),
rev: revenueSummary(projects),
storm: stormAttribution(kanbanLeads, projects),
}), [kanbanLeads, projects]);
useEffect(() => {
// Simulate initial data fetch
const timer = setTimeout(() => setIsLoading(false), 800);
return () => clearTimeout(timer);
}, []);
// --- Weather State (Lifted) ---
const [weather, setWeather] = useState(null);
const [forecast, setForecast] = useState([]);
const [weatherLoading, setWeatherLoading] = useState(true);
const [weatherError, setWeatherError] = useState(null);
const [lastUpdated, setLastUpdated] = useState(null);
const fetchWeather = async () => {
const API_KEY = config.weatherApiKey;
const CITY = "Plano,US";
try {
setWeatherLoading(true);
if (config.isDemoMode(API_KEY)) {
await new Promise(resolve => setTimeout(resolve, 800));
const mockCurrent = {
name: "Plano",
sys: { country: "US" },
main: { temp: 75, temp_max: 82, temp_min: 68, feels_like: 77, humidity: 45 },
weather: [{ id: 800, description: "Clear Sky (Demo)" }],
wind: { speed: 8 }
};
const mockForecast = Array.from({ length: 5 }).map((_, i) => ({
dt: Math.floor(Date.now() / 1000) + (i * 10800),
main: { temp: 75 + (i % 2 === 0 ? 2 : -2) },
weather: [{ id: 800 + (i * 10) }]
}));
setWeather(mockCurrent);
setForecast(mockForecast);
setLastUpdated(new Date());
setWeatherLoading(false);
setWeatherError(null);
return;
}
const currentRes = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${CITY}&units=imperial&appid=${API_KEY}`);
if (!currentRes.ok) throw new Error(currentRes.statusText);
const currentData = await currentRes.json();
const forecastRes = await fetch(`https://api.openweathermap.org/data/2.5/forecast?q=${CITY}&units=imperial&appid=${API_KEY}`);
if (!forecastRes.ok) throw new Error(forecastRes.statusText);
const forecastData = await forecastRes.json();
setWeather(currentData);
setForecast(forecastData.list?.slice(0, 5) || []);
setLastUpdated(new Date());
setWeatherLoading(false);
setWeatherError(null);
} catch (err) {
console.error("Weather fetch failed", err);
setWeatherError(err.message);
setWeatherLoading(false);
}
};
useEffect(() => {
fetchWeather();
const interval = setInterval(fetchWeather, 600000);
return () => clearInterval(interval);
}, []);
// Clock for Plano, TX
const [currentTime, setCurrentTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => setCurrentTime(new Date()), 60000); // Every minute
return () => clearInterval(timer);
}, []);
// Derived Metrics & Split Schedule
const hotLeads = properties.filter(p => p.canvassingStatus === 'Hot Lead');
const allMyMeetings = meetings.filter(m => m.agentId === user?.id || m.agentId === 'e1');
const today = new Date();
today.setHours(0, 0, 0, 0);
const upcomingMeetings = allMyMeetings
.filter(m => new Date(m.date) >= today)
.sort((a, b) => new Date(a.date) - new Date(b.date));
const pastMeetings = allMyMeetings
.filter(m => new Date(m.date) < today)
.sort((a, b) => new Date(b.date) - new Date(a.date)); // Most recent first
if (isLoading) return
Welcome back, here's what's happening with your territory. {'Ѕ'}{'а'}{'t'}{'у'}{'а'}{'m'} {'R'}{'а'}{'ѕ'}{'t'}{'о'}{'g'}{'і'}
Open Pipeline
{analytics.funnel.reduce((s, f) => s + f.count, 0)} leadsWin Rate
Storm-Attributed Revenue
P&L Summary
Revenue
${(analytics.rev.revenue / 1000).toFixed(0)}K
Gross Profit
= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-500 dark:text-red-400'}`}> ${(analytics.rev.grossProfit / 1000).toFixed(0)}K
Commissions
${(analytics.rev.commissions / 1000).toFixed(0)}K
Net Profit
= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-500 dark:text-red-400'}`}> ${(analytics.rev.netProfit / 1000).toFixed(0)}K
{lead.propertyData.propertyType} • ${lead.propertyData.currentEstimatedMarketValue.toLocaleString()}
No upcoming meetings.
) : ( upcomingMeetings.map(meeting => (No past meetings.
) : ( pastMeetings.slice(0, 5).map(meeting => ({meeting.customerName}
{title}
{error}
{(error.includes('401') || error.includes('Invalid API key')) && (
Please configure your VITE_OPENWEATHER_API_KEY in the .env file.
Updated: {lastUpdated?.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}