diff --git a/src/App.jsx b/src/App.jsx index c0e5cb2..ad9b88e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,133 +1,102 @@ import React, { useState, useEffect } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Area, AreaChart } from 'recharts'; -import { TrendingUp, TrendingDown, Home, DollarSign, Calendar, MapPin, Activity, AlertCircle, Star } from 'lucide-react'; +import { TrendingUp, TrendingDown, Home, DollarSign, Calendar, MapPin, Activity, AlertCircle, Star, RefreshCw, Wifi, WifiOff } from 'lucide-react'; const ApartmentDashboard = () => { const [selectedUnit, setSelectedUnit] = useState(null); - const [timeRange, setTimeRange] = useState('30d'); const [viewMode, setViewMode] = useState('overview'); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [lastUpdated, setLastUpdated] = useState(null); - // Mock data based on your MongoDB structure - const [mockData] = useState({ - dailySummary: { - date: '2025-01-15', - newUnits: 3, - rentedUnits: 5, - staleUnits: 1, - netChange: -3, - turnoverRate: 8.2, - totalAvailable: 47 - }, - priceHistory: [ - { date: '2025-01-01', avgPrice: 3850, unitCount: 52 }, - { date: '2025-01-02', avgPrice: 3845, unitCount: 48 }, - { date: '2025-01-03', avgPrice: 3860, unitCount: 51 }, - { date: '2025-01-04', avgPrice: 3875, unitCount: 49 }, - { date: '2025-01-05', avgPrice: 3820, unitCount: 53 }, - { date: '2025-01-06', avgPrice: 3835, unitCount: 50 }, - { date: '2025-01-07', avgPrice: 3890, unitCount: 47 }, - { date: '2025-01-08', avgPrice: 3865, unitCount: 52 }, - { date: '2025-01-09', avgPrice: 3880, unitCount: 48 }, - { date: '2025-01-10', avgPrice: 3855, unitCount: 51 }, - { date: '2025-01-11', avgPrice: 3870, unitCount: 49 }, - { date: '2025-01-12', avgPrice: 3825, unitCount: 54 }, - { date: '2025-01-13', avgPrice: 3845, unitCount: 50 }, - { date: '2025-01-14', avgPrice: 3885, unitCount: 46 }, - { date: '2025-01-15', avgPrice: 3875, unitCount: 47 } - ], - availableUnits: [ - { - unitCode: 'W2506', - planName: 'Maroon Peak', - bedCount: 2, - bathCount: 2, - area: 1089, - currentPrice: 3750, - minPrice: 3650, - maxPrice: 3850, - daysAvailable: 12, - community: 'Country Club Towers', - priceHistory: [ - { date: '2025-01-04', price: 3850 }, - { date: '2025-01-05', price: 3825 }, - { date: '2025-01-06', price: 3800 }, - { date: '2025-01-07', price: 3775 }, - { date: '2025-01-08', price: 3750 } - ] - }, - { - unitCode: 'T1205', - planName: 'North Maroon Peak', - bedCount: 2, - bathCount: 2, - area: 1150, - currentPrice: 3950, - minPrice: 3875, - maxPrice: 3995, - daysAvailable: 8, - community: 'Country Club Towers', - priceHistory: [ - { date: '2025-01-08', price: 3995 }, - { date: '2025-01-09', price: 3975 }, - { date: '2025-01-10', price: 3950 } - ] - }, - { - unitCode: 'G0304', - planName: 'Snowmass', - bedCount: 1, - bathCount: 1, - area: 750, - currentPrice: 2850, - minPrice: 2800, - maxPrice: 2950, - daysAvailable: 22, - community: 'Country Club Gardens', - priceHistory: [ - { date: '2024-12-24', price: 2950 }, - { date: '2024-12-30', price: 2925 }, - { date: '2025-01-05', price: 2900 }, - { date: '2025-01-10', price: 2875 }, - { date: '2025-01-15', price: 2850 } - ] + // Real data state + const [dailySummary, setDailySummary] = useState(null); + const [priceHistory, setPriceHistory] = useState([]); + const [availableUnits, setAvailableUnits] = useState([]); + const [recentActivity, setRecentActivity] = useState([]); + const [planStats, setPlanStats] = useState([]); + + // API base URL + const API_BASE = '/api'; + + // Fetch data from API + const fetchData = async () => { + setLoading(true); + setError(null); + + try { + console.log('🔄 Fetching apartment data...'); + + // Fetch all endpoints in parallel + const [ + dailySummaryRes, + priceHistoryRes, + availableUnitsRes, + recentActivityRes, + planStatsRes + ] = await Promise.all([ + fetch(`${API_BASE}/daily-summary`), + fetch(`${API_BASE}/price-history`), + fetch(`${API_BASE}/available-units`), + fetch(`${API_BASE}/recent-activity`), + fetch(`${API_BASE}/plan-stats`) + ]); + + // Check if all requests were successful + if (!dailySummaryRes.ok || !priceHistoryRes.ok || !availableUnitsRes.ok || + !recentActivityRes.ok || !planStatsRes.ok) { + throw new Error('One or more API requests failed'); } - ], - recentActivity: [ - { - type: 'new', - unitCode: 'W1408', - planName: 'Maroon Peak', - price: 3825, - date: '2025-01-15', - bedCount: 2, - bathCount: 2 - }, - { - type: 'rented', - unitCode: 'T0912', - planName: 'North Maroon Peak', - price: 3950, - date: '2025-01-15', - bedCount: 2, - bathCount: 2 - }, - { - type: 'price_drop', - unitCode: 'W2506', - oldPrice: 3775, - newPrice: 3750, - date: '2025-01-15', - planName: 'Maroon Peak' - } - ], - planStats: [ - { name: 'Maroon Peak', count: 15, avgPrice: 3780, avgArea: 1089 }, - { name: 'North Maroon Peak', count: 8, avgPrice: 3920, avgArea: 1150 }, - { name: 'Snowmass', count: 12, avgPrice: 2850, avgArea: 750 }, - { name: 'Aspen', count: 7, avgPrice: 3200, avgArea: 950 }, - { name: 'Capitol Peak', count: 5, avgPrice: 4200, avgArea: 1350 } - ] - }); + + // Parse JSON responses + const [ + dailySummaryData, + priceHistoryData, + availableUnitsData, + recentActivityData, + planStatsData + ] = await Promise.all([ + dailySummaryRes.json(), + priceHistoryRes.json(), + availableUnitsRes.json(), + recentActivityRes.json(), + planStatsRes.json() + ]); + + console.log('✅ Data fetched successfully:', { + dailySummary: dailySummaryData, + priceHistory: priceHistoryData.length, + availableUnits: availableUnitsData.length, + recentActivity: recentActivityData.length, + planStats: planStatsData.length + }); + + // Update state with real data + setDailySummary(dailySummaryData); + setPriceHistory(priceHistoryData); + setAvailableUnits(availableUnitsData); + setRecentActivity(recentActivityData); + setPlanStats(planStatsData); + setLastUpdated(new Date()); + setLoading(false); + + } catch (err) { + console.error('❌ Error fetching data:', err); + setError(err.message); + setLoading(false); + } + }; + + // Initial data fetch + useEffect(() => { + fetchData(); + }, []); + + // Auto-refresh every 5 minutes + useEffect(() => { + const interval = setInterval(fetchData, 5 * 60 * 1000); + return () => clearInterval(interval); + }, []); const MetricCard = ({ title, value, subtitle, icon: Icon, trend, color = "blue" }) => (
@@ -155,13 +124,64 @@ const ApartmentDashboard = () => { const COLORS = ['#8B0000', '#DC143C', '#B22222', '#CD5C5C', '#F08080']; + // Loading state + if (loading && !dailySummary) { + return ( +
+
+ +

Loading Apartment Data

+

Fetching real-time market information...

+
+
+ ); + } + + // Error state + if (error && !dailySummary) { + return ( +
+
+ +

Connection Error

+

Unable to fetch apartment data: {error}

+ +
+
+ ); + } + return (
{/* Header */}
-

Country Club Towers & Gardens

-

Real-time apartment market dashboard

+
+
+

Country Club Towers & Gardens

+

Real-time apartment market dashboard

+ {lastUpdated && ( +
+ + Last updated: {lastUpdated.toLocaleTimeString()} +
+ )} +
+ +
+
- {viewMode === 'overview' && ( + {/* Overview Tab */} + {viewMode === 'overview' && dailySummary && ( <> {/* Daily Metrics */}
{ {/* Price Trends */}
-

Average Price Trend (15 days)

+

Average Price Trend ({priceHistory.length} days)

- + d.avgPrice !== null)}> {

Unit Availability

- + {
{/* Plan Distribution */} -
-
-

Units by Floor Plan

- - - `${name}: ${count}`} - outerRadius={80} - fill="#8884d8" - dataKey="count" - > - {mockData.planStats.map((entry, index) => ( - - ))} - - - - -
+ {planStats.length > 0 && ( +
+
+

Units by Floor Plan

+ + + `${name}: ${count}`} + outerRadius={80} + fill="#8884d8" + dataKey="count" + > + {planStats.map((entry, index) => ( + + ))} + + + + +
-
-

Average Price by Plan

- - - - - `$${value}`} /> - [formatPrice(value), 'Avg Price']} /> - - - +
+

Average Price by Plan

+ + + + + `$${value}`} /> + [formatPrice(value), 'Avg Price']} /> + + + +
-
+ )} {/* Recent Activity */} -
-

Recent Activity

-
- {mockData.recentActivity.map((activity, index) => ( -
-
- {activity.type === 'new' &&
} - {activity.type === 'rented' &&
} - {activity.type === 'price_drop' &&
} -
-

- {activity.type === 'new' && `New unit available: ${activity.unitCode}`} - {activity.type === 'rented' && `Unit rented: ${activity.unitCode}`} - {activity.type === 'price_drop' && `Price drop: ${activity.unitCode}`} -

-

- {activity.planName} • {activity.bedCount}BR/{activity.bathCount}BA - {activity.type === 'price_drop' - ? ` • ${formatPrice(activity.oldPrice)} → ${formatPrice(activity.newPrice)}` - : ` • ${formatPrice(activity.price)}` - } -

+ {recentActivity.length > 0 && ( +
+

Recent Activity

+
+ {recentActivity.map((activity, index) => ( +
+
+ {activity.type === 'new' &&
} + {activity.type === 'rented' &&
} + {activity.type === 'price_drop' &&
} +
+

+ {activity.type === 'new' && `New unit available: ${activity.unitCode}`} + {activity.type === 'rented' && `Unit rented: ${activity.unitCode}`} + {activity.type === 'price_drop' && `Price drop: ${activity.unitCode}`} +

+

+ {activity.planName} • {activity.bedCount}BR/{activity.bathCount}BA + {activity.type === 'price_drop' + ? ` • ${formatPrice(activity.oldPrice)} → ${formatPrice(activity.newPrice)}` + : activity.price ? ` • ${formatPrice(activity.price)}` : '' + } +

+
+ {formatDate(activity.date)}
- {formatDate(activity.date)} -
- ))} + ))} +
-
+ )} )} + {/* Units Tab */} {viewMode === 'units' && (
-

Available Units ({mockData.availableUnits.length})

+

Available Units ({availableUnits.length})

- {mockData.availableUnits.map((unit) => ( + {availableUnits.map((unit) => (

{unit.unitCode}

{unit.planName}

- {unit.planName.toLowerCase().includes('maroon peak') && + {unit.planName && unit.planName.toLowerCase().includes('maroon peak') && unit.currentPrice < 3800 && Priority @@ -386,7 +412,7 @@ const ApartmentDashboard = () => {

Available

-

{unit.daysAvailable} days

+

{unit.daysAvailable || 0} days

@@ -399,7 +425,9 @@ const ApartmentDashboard = () => {
unit.minPrice + ? `${((unit.currentPrice - unit.minPrice) / (unit.maxPrice - unit.minPrice)) * 100}%` + : '50%' }} >
@@ -415,9 +443,18 @@ const ApartmentDashboard = () => {
))}
+ + {availableUnits.length === 0 && !loading && ( +
+ +

No Available Units

+

No apartment units are currently available.

+
+ )}
)} + {/* Analytics Tab */} {viewMode === 'analytics' && (

Market Analytics

@@ -426,7 +463,7 @@ const ApartmentDashboard = () => {

Price Distribution by Plan

- {mockData.planStats.map((plan, index) => ( + {planStats.map((plan, index) => (
{ Maroon Peak units under $3,800 are highlighted as priority opportunities based on your preferences.

-
-

Market Activity

-

- Daily turnover rate of {mockData.dailySummary.turnoverRate}% indicates moderate market activity. -

-
-
-

Price Trends

-

- Average prices have stabilized around $3,850 with minimal daily fluctuation. -

-
+ {dailySummary && ( +
+

Market Activity

+

+ Daily turnover rate of {(dailySummary.turnoverRate || dailySummary.turnover_rate || 0).toFixed(1)}% indicates { + (dailySummary.turnoverRate || dailySummary.turnover_rate || 0) > 10 ? 'high' : + (dailySummary.turnoverRate || dailySummary.turnover_rate || 0) > 5 ? 'moderate' : 'low' + } market activity. +

+
+ )} + {priceHistory.length > 0 && ( +
+

Price Trends

+

+ {priceHistory.length > 1 ? ( + `Average prices ${priceHistory[priceHistory.length - 1].avgPrice > priceHistory[priceHistory.length - 2].avgPrice ? 'increased' : 'decreased'} in recent days.` + ) : ( + 'Price trend data is being collected.' + )} +

+
+ )}
@@ -483,7 +531,7 @@ const ApartmentDashboard = () => {
@@ -515,28 +563,30 @@ const ApartmentDashboard = () => {

Days Available

-

{selectedUnit.daysAvailable}

+

{selectedUnit.daysAvailable || 0}

-
-

Price History

- - - - new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - /> - `$${value}`} /> - [formatPrice(value), 'Price']} - labelFormatter={(date) => formatDate(date)} - /> - - - -
+ {selectedUnit.priceHistory && selectedUnit.priceHistory.length > 0 && ( +
+

Price History

+ + + + new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} + /> + `$${value}`} /> + [formatPrice(value), 'Price']} + labelFormatter={(date) => formatDate(date)} + /> + + + +
+ )}