import React, { useEffect, useState } 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
} from 'lucide-react';
import { SpotlightCard } from '../components/SpotlightCard';
import { config } from '../config/env';
const Dashboard = () => {
const { properties, meetings } = useMockStore();
const { user } = useAuth();
// 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');
// Filter meetings for current user (or fallback to e1 for dev)
const allMyMeetings = meetings.filter(m => m.agentId === user?.id || m.agentId === 'e1');
// Split into Upcoming vs Past
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
return (
{/* Ambient Background Glows */}
Dashboard Overview
Welcome back, here's what's happening with your territory.
{/* Real-time Clock Widget */}
{currentTime.toLocaleTimeString('en-US', { timeZone: 'America/Chicago', hour: 'numeric', minute: '2-digit', hour12: false })}
{currentTime.toLocaleDateString('en-US', { timeZone: 'America/Chicago', weekday: 'long', month: 'long', day: 'numeric' })} • Plano, TX
{/* Top Metrics Grid */}
{/* Main Content Grid */}
{/* Left Col: Hot Leads and Schedule */}
{/* Hot Leads Panel */}
{hotLeads.length === 0 ? (
No hot leads active. Time to canvas!
) : (
hotLeads.slice(0, 5).map(lead => (
{lead.id}
{lead.propertyData.propertyAddress.split(',')[0]}
{lead.propertyData.propertyType} • ${lead.propertyData.currentEstimatedMarketValue.toLocaleString()}
))
)}
{/* Split Schedule System */}
{/* Upcoming Meetings */}
{upcomingMeetings.length === 0 ? (
No upcoming meetings.
) : (
upcomingMeetings.map(meeting => (
))
)}
{/* Past / Completed Meetings */}
{pastMeetings.length === 0 ? (
No past meetings.
) : (
pastMeetings.slice(0, 5).map(meeting => (
))
)}
{/* Right Col: Weather Widget */}
);
};
// --- Sub Components ---
const MeetingRow = ({ meeting, type }) => (
{new Date(meeting.date).toLocaleDateString('en-US', { month: 'short' })}
{new Date(meeting.date).getDate()}
{meeting.customerName}
{meeting.time}
{meeting.status}
);
const MetricCard = ({ title, value, trend, icon: Icon, trendColor, bgGlow }) => (
{/* Decorative Neomorphic Inner Shadow */}
);
const RealWeatherWidget = () => {
const [weather, setWeather] = useState(null);
const [forecast, setForecast] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [lastUpdated, setLastUpdated] = useState(null);
const API_KEY = config.weatherApiKey;
const CITY = "Plano,US";
const fetchWeather = async () => {
try {
setLoading(true);
// --- DEMO MODE CHECK ---
if (config.isDemoMode(API_KEY)) {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 800));
// MOCK DATA
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 }
};
// Mock Forecast (Next 15 hours)
const mockForecast = Array.from({ length: 5 }).map((_, i) => ({
dt: Math.floor(Date.now() / 1000) + (i * 10800), // +3 hours each
main: { temp: 75 + (i % 2 === 0 ? 2 : -2) },
weather: [{ id: 800 + (i * 10) }] // Varying sunny/cloudy codes
}));
setWeather(mockCurrent);
setForecast(mockForecast);
setLastUpdated(new Date());
setLoading(false);
setError(null);
return; // Exit early
}
// --- REAL API FETCH ---
// Fetch Current Weather
const currentRes = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${CITY}&units=imperial&appid=${API_KEY}`
);
if (!currentRes.ok) {
const errData = await currentRes.json().catch(() => ({ message: currentRes.statusText }));
throw new Error(errData.message || `API Error: ${currentRes.status}`);
}
const currentData = await currentRes.json();
// Fetch 5 Day / 3 Hour Forecast
const forecastRes = await fetch(
`https://api.openweathermap.org/data/2.5/forecast?q=${CITY}&units=imperial&appid=${API_KEY}`
);
if (!forecastRes.ok) {
const errData = await forecastRes.json().catch(() => ({ message: forecastRes.statusText }));
throw new Error(errData.message || `Forecast Error: ${forecastRes.status}`);
}
const forecastData = await forecastRes.json();
setWeather(currentData);
setForecast(forecastData.list?.slice(0, 5) || []);
setLastUpdated(new Date());
setLoading(false);
setError(null);
} catch (err) {
console.error("Weather fetch failed", err);
setError(err.message);
setLoading(false);
}
};
useEffect(() => {
fetchWeather();
// Refresh every 10 minutes
const interval = setInterval(fetchWeather, 600000);
return () => clearInterval(interval);
}, []);
const getWeatherIcon = (code) => {
// OWM codes: https://openweathermap.org/weather-conditions
if (code === 800) return ;
if (code > 800) return ;
if (code >= 500 && code < 600) return ;
if (code >= 200 && code < 300) return ;
return ;
};
const getMiniIcon = (code) => {
if (code === 800) return ;
if (code > 800) return ;
if (code >= 500) return ;
return ;
};
if (loading && !weather) return Syncing OWM...
;
if (error) return (
Weather Unavailable
{error}
{(error.includes('401') || error.includes('Invalid API key')) && (
Please configure your VITE_OPENWEATHER_API_KEY in the .env file.
)}
);
if (!weather || !weather.main) return Weather Unavailable
;
return (
// Added text-zinc-900 to ensure text is visible in light mode
{/* Dynamic Weather Background Gradient - subtle */}
{weather.name || "Plano"}, {weather.sys?.country}
{Math.round(weather.main.temp)}
°F
Updated: {lastUpdated?.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{getWeatherIcon(weather.weather[0].id)}
{weather.weather[0].description}
H: {Math.round(weather.main.temp_max)}°F L: {Math.round(weather.main.temp_min)}°F
Feels: {Math.round(weather.main.feels_like)}°F
Wind
{Math.round(weather.wind.speed)} mph
Humidity
{weather.main.humidity}%
Forecast (3-Hour Steps)
{forecast.map((item, index) => {
const date = new Date(item.dt * 1000);
const displayTime = date.toLocaleTimeString('en-US', { hour: 'numeric', hour12: true });
return (
{displayTime}
{getMiniIcon(item.weather[0].id)}
{Math.round(item.main.temp)}°
)
})}
);
};
export default Dashboard;