Update to real api
All checks were successful
Deploy Apartment Dashboard / deploy (push) Successful in 9m59s

This commit is contained in:
2025-07-15 22:28:03 -06:00
parent c1a7857787
commit 763e5a0785

View File

@ -1,133 +1,102 @@
import React, { useState, useEffect } from 'react'; 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 { 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 ApartmentDashboard = () => {
const [selectedUnit, setSelectedUnit] = useState(null); const [selectedUnit, setSelectedUnit] = useState(null);
const [timeRange, setTimeRange] = useState('30d');
const [viewMode, setViewMode] = useState('overview'); 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 // Real data state
const [mockData] = useState({ const [dailySummary, setDailySummary] = useState(null);
dailySummary: { const [priceHistory, setPriceHistory] = useState([]);
date: '2025-01-15', const [availableUnits, setAvailableUnits] = useState([]);
newUnits: 3, const [recentActivity, setRecentActivity] = useState([]);
rentedUnits: 5, const [planStats, setPlanStats] = useState([]);
staleUnits: 1,
netChange: -3, // API base URL
turnoverRate: 8.2, const API_BASE = '/api';
totalAvailable: 47
}, // Fetch data from API
priceHistory: [ const fetchData = async () => {
{ date: '2025-01-01', avgPrice: 3850, unitCount: 52 }, setLoading(true);
{ date: '2025-01-02', avgPrice: 3845, unitCount: 48 }, setError(null);
{ date: '2025-01-03', avgPrice: 3860, unitCount: 51 },
{ date: '2025-01-04', avgPrice: 3875, unitCount: 49 }, try {
{ date: '2025-01-05', avgPrice: 3820, unitCount: 53 }, console.log('🔄 Fetching apartment data...');
{ date: '2025-01-06', avgPrice: 3835, unitCount: 50 },
{ date: '2025-01-07', avgPrice: 3890, unitCount: 47 }, // Fetch all endpoints in parallel
{ date: '2025-01-08', avgPrice: 3865, unitCount: 52 }, const [
{ date: '2025-01-09', avgPrice: 3880, unitCount: 48 }, dailySummaryRes,
{ date: '2025-01-10', avgPrice: 3855, unitCount: 51 }, priceHistoryRes,
{ date: '2025-01-11', avgPrice: 3870, unitCount: 49 }, availableUnitsRes,
{ date: '2025-01-12', avgPrice: 3825, unitCount: 54 }, recentActivityRes,
{ date: '2025-01-13', avgPrice: 3845, unitCount: 50 }, planStatsRes
{ date: '2025-01-14', avgPrice: 3885, unitCount: 46 }, ] = await Promise.all([
{ date: '2025-01-15', avgPrice: 3875, unitCount: 47 } fetch(`${API_BASE}/daily-summary`),
], fetch(`${API_BASE}/price-history`),
availableUnits: [ fetch(`${API_BASE}/available-units`),
{ fetch(`${API_BASE}/recent-activity`),
unitCode: 'W2506', fetch(`${API_BASE}/plan-stats`)
planName: 'Maroon Peak', ]);
bedCount: 2,
bathCount: 2, // Check if all requests were successful
area: 1089, if (!dailySummaryRes.ok || !priceHistoryRes.ok || !availableUnitsRes.ok ||
currentPrice: 3750, !recentActivityRes.ok || !planStatsRes.ok) {
minPrice: 3650, throw new Error('One or more API requests failed');
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 }
]
} }
],
recentActivity: [ // Parse JSON responses
{ const [
type: 'new', dailySummaryData,
unitCode: 'W1408', priceHistoryData,
planName: 'Maroon Peak', availableUnitsData,
price: 3825, recentActivityData,
date: '2025-01-15', planStatsData
bedCount: 2, ] = await Promise.all([
bathCount: 2 dailySummaryRes.json(),
}, priceHistoryRes.json(),
{ availableUnitsRes.json(),
type: 'rented', recentActivityRes.json(),
unitCode: 'T0912', planStatsRes.json()
planName: 'North Maroon Peak', ]);
price: 3950,
date: '2025-01-15', console.log('✅ Data fetched successfully:', {
bedCount: 2, dailySummary: dailySummaryData,
bathCount: 2 priceHistory: priceHistoryData.length,
}, availableUnits: availableUnitsData.length,
{ recentActivity: recentActivityData.length,
type: 'price_drop', planStats: planStatsData.length
unitCode: 'W2506', });
oldPrice: 3775,
newPrice: 3750, // Update state with real data
date: '2025-01-15', setDailySummary(dailySummaryData);
planName: 'Maroon Peak' setPriceHistory(priceHistoryData);
} setAvailableUnits(availableUnitsData);
], setRecentActivity(recentActivityData);
planStats: [ setPlanStats(planStatsData);
{ name: 'Maroon Peak', count: 15, avgPrice: 3780, avgArea: 1089 }, setLastUpdated(new Date());
{ name: 'North Maroon Peak', count: 8, avgPrice: 3920, avgArea: 1150 }, setLoading(false);
{ name: 'Snowmass', count: 12, avgPrice: 2850, avgArea: 750 },
{ name: 'Aspen', count: 7, avgPrice: 3200, avgArea: 950 }, } catch (err) {
{ name: 'Capitol Peak', count: 5, avgPrice: 4200, avgArea: 1350 } 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" }) => ( const MetricCard = ({ title, value, subtitle, icon: Icon, trend, color = "blue" }) => (
<div className="bg-white rounded-lg shadow-md p-6 border-l-4" style={{borderLeftColor: color === 'blue' ? '#3B82F6' : color === 'green' ? '#10B981' : color === 'red' ? '#EF4444' : '#F59E0B'}}> <div className="bg-white rounded-lg shadow-md p-6 border-l-4" style={{borderLeftColor: color === 'blue' ? '#3B82F6' : color === 'green' ? '#10B981' : color === 'red' ? '#EF4444' : '#F59E0B'}}>
@ -155,13 +124,64 @@ const ApartmentDashboard = () => {
const COLORS = ['#8B0000', '#DC143C', '#B22222', '#CD5C5C', '#F08080']; const COLORS = ['#8B0000', '#DC143C', '#B22222', '#CD5C5C', '#F08080'];
// Loading state
if (loading && !dailySummary) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<RefreshCw className="w-12 h-12 animate-spin text-blue-600 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Loading Apartment Data</h2>
<p className="text-gray-600">Fetching real-time market information...</p>
</div>
</div>
);
}
// Error state
if (error && !dailySummary) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<WifiOff className="w-12 h-12 text-red-600 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Connection Error</h2>
<p className="text-gray-600 mb-4">Unable to fetch apartment data: {error}</p>
<button
onClick={fetchData}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors"
>
Try Again
</button>
</div>
</div>
);
}
return ( return (
<div className="min-h-screen bg-gray-50 p-6"> <div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
{/* Header */} {/* Header */}
<div className="mb-8"> <div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">Country Club Towers & Gardens</h1> <div className="flex items-center justify-between">
<p className="text-gray-600">Real-time apartment market dashboard</p> <div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Country Club Towers & Gardens</h1>
<p className="text-gray-600">Real-time apartment market dashboard</p>
{lastUpdated && (
<div className="flex items-center mt-2 text-sm text-gray-500">
<Wifi className="w-4 h-4 mr-1" />
Last updated: {lastUpdated.toLocaleTimeString()}
</div>
)}
</div>
<button
onClick={fetchData}
disabled={loading}
className="flex items-center space-x-2 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
<span>Refresh</span>
</button>
</div>
<div className="flex items-center space-x-4 mt-4"> <div className="flex items-center space-x-4 mt-4">
<button <button
onClick={() => setViewMode('overview')} onClick={() => setViewMode('overview')}
@ -173,7 +193,7 @@ const ApartmentDashboard = () => {
onClick={() => setViewMode('units')} onClick={() => setViewMode('units')}
className={`px-4 py-2 rounded-md ${viewMode === 'units' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`} className={`px-4 py-2 rounded-md ${viewMode === 'units' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
> >
Available Units Available Units ({availableUnits.length})
</button> </button>
<button <button
onClick={() => setViewMode('analytics')} onClick={() => setViewMode('analytics')}
@ -184,33 +204,34 @@ const ApartmentDashboard = () => {
</div> </div>
</div> </div>
{viewMode === 'overview' && ( {/* Overview Tab */}
{viewMode === 'overview' && dailySummary && (
<> <>
{/* Daily Metrics */} {/* Daily Metrics */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<MetricCard <MetricCard
title="Available Units" title="Available Units"
value={mockData.dailySummary.totalAvailable} value={dailySummary.totalAvailable || availableUnits.length}
icon={Home} icon={Home}
color="blue" color="blue"
/> />
<MetricCard <MetricCard
title="New Today" title="New Today"
value={mockData.dailySummary.newUnits} value={dailySummary.newUnits || dailySummary.new_units_count || 0}
subtitle="Units available" subtitle="Units available"
icon={TrendingUp} icon={TrendingUp}
color="green" color="green"
/> />
<MetricCard <MetricCard
title="Rented Today" title="Rented Today"
value={mockData.dailySummary.rentedUnits} value={dailySummary.rentedUnits || dailySummary.rented_units_count || 0}
subtitle="Units leased" subtitle="Units leased"
icon={TrendingDown} icon={TrendingDown}
color="red" color="red"
/> />
<MetricCard <MetricCard
title="Turnover Rate" title="Turnover Rate"
value={`${mockData.dailySummary.turnoverRate}%`} value={`${(dailySummary.turnoverRate || dailySummary.turnover_rate || 0).toFixed(1)}%`}
subtitle="Daily activity" subtitle="Daily activity"
icon={Activity} icon={Activity}
color="orange" color="orange"
@ -220,9 +241,9 @@ const ApartmentDashboard = () => {
{/* Price Trends */} {/* Price Trends */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-md p-6"> <div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Average Price Trend (15 days)</h3> <h3 className="text-lg font-semibold mb-4">Average Price Trend ({priceHistory.length} days)</h3>
<ResponsiveContainer width="100%" height={300}> <ResponsiveContainer width="100%" height={300}>
<AreaChart data={mockData.priceHistory}> <AreaChart data={priceHistory.filter(d => d.avgPrice !== null)}>
<CartesianGrid strokeDasharray="3 3" /> <CartesianGrid strokeDasharray="3 3" />
<XAxis <XAxis
dataKey="date" dataKey="date"
@ -241,7 +262,7 @@ const ApartmentDashboard = () => {
<div className="bg-white rounded-lg shadow-md p-6"> <div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Unit Availability</h3> <h3 className="text-lg font-semibold mb-4">Unit Availability</h3>
<ResponsiveContainer width="100%" height={300}> <ResponsiveContainer width="100%" height={300}>
<AreaChart data={mockData.priceHistory}> <AreaChart data={priceHistory}>
<CartesianGrid strokeDasharray="3 3" /> <CartesianGrid strokeDasharray="3 3" />
<XAxis <XAxis
dataKey="date" dataKey="date"
@ -259,106 +280,111 @@ const ApartmentDashboard = () => {
</div> </div>
{/* Plan Distribution */} {/* Plan Distribution */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> {planStats.length > 0 && (
<div className="bg-white rounded-lg shadow-md p-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<h3 className="text-lg font-semibold mb-4">Units by Floor Plan</h3> <div className="bg-white rounded-lg shadow-md p-6">
<ResponsiveContainer width="100%" height={300}> <h3 className="text-lg font-semibold mb-4">Units by Floor Plan</h3>
<PieChart> <ResponsiveContainer width="100%" height={300}>
<Pie <PieChart>
data={mockData.planStats} <Pie
cx="50%" data={planStats}
cy="50%" cx="50%"
labelLine={false} cy="50%"
label={({ name, count }) => `${name}: ${count}`} labelLine={false}
outerRadius={80} label={({ name, count }) => `${name}: ${count}`}
fill="#8884d8" outerRadius={80}
dataKey="count" fill="#8884d8"
> dataKey="count"
{mockData.planStats.map((entry, index) => ( >
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> {planStats.map((entry, index) => (
))} <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
</Pie> ))}
<Tooltip /> </Pie>
</PieChart> <Tooltip />
</ResponsiveContainer> </PieChart>
</div> </ResponsiveContainer>
</div>
<div className="bg-white rounded-lg shadow-md p-6"> <div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Average Price by Plan</h3> <h3 className="text-lg font-semibold mb-4">Average Price by Plan</h3>
<ResponsiveContainer width="100%" height={300}> <ResponsiveContainer width="100%" height={300}>
<BarChart data={mockData.planStats}> <BarChart data={planStats}>
<CartesianGrid strokeDasharray="3 3" /> <CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} /> <XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
<YAxis tickFormatter={(value) => `$${value}`} /> <YAxis tickFormatter={(value) => `$${value}`} />
<Tooltip formatter={(value, name) => [formatPrice(value), 'Avg Price']} /> <Tooltip formatter={(value, name) => [formatPrice(value), 'Avg Price']} />
<Bar dataKey="avgPrice" fill="#8B0000" /> <Bar dataKey="avgPrice" fill="#8B0000" />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div>
</div> </div>
</div> )}
{/* Recent Activity */} {/* Recent Activity */}
<div className="bg-white rounded-lg shadow-md p-6"> {recentActivity.length > 0 && (
<h3 className="text-lg font-semibold mb-4">Recent Activity</h3> <div className="bg-white rounded-lg shadow-md p-6">
<div className="space-y-4"> <h3 className="text-lg font-semibold mb-4">Recent Activity</h3>
{mockData.recentActivity.map((activity, index) => ( <div className="space-y-4">
<div key={index} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg"> {recentActivity.map((activity, index) => (
<div className="flex items-center space-x-3"> <div key={index} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
{activity.type === 'new' && <div className="w-3 h-3 bg-green-500 rounded-full"></div>} <div className="flex items-center space-x-3">
{activity.type === 'rented' && <div className="w-3 h-3 bg-red-500 rounded-full"></div>} {activity.type === 'new' && <div className="w-3 h-3 bg-green-500 rounded-full"></div>}
{activity.type === 'price_drop' && <div className="w-3 h-3 bg-orange-500 rounded-full"></div>} {activity.type === 'rented' && <div className="w-3 h-3 bg-red-500 rounded-full"></div>}
<div> {activity.type === 'price_drop' && <div className="w-3 h-3 bg-orange-500 rounded-full"></div>}
<p className="font-medium"> <div>
{activity.type === 'new' && `New unit available: ${activity.unitCode}`} <p className="font-medium">
{activity.type === 'rented' && `Unit rented: ${activity.unitCode}`} {activity.type === 'new' && `New unit available: ${activity.unitCode}`}
{activity.type === 'price_drop' && `Price drop: ${activity.unitCode}`} {activity.type === 'rented' && `Unit rented: ${activity.unitCode}`}
</p> {activity.type === 'price_drop' && `Price drop: ${activity.unitCode}`}
<p className="text-sm text-gray-500"> </p>
{activity.planName} {activity.bedCount}BR/{activity.bathCount}BA <p className="text-sm text-gray-500">
{activity.type === 'price_drop' {activity.planName} {activity.bedCount}BR/{activity.bathCount}BA
? `${formatPrice(activity.oldPrice)}${formatPrice(activity.newPrice)}` {activity.type === 'price_drop'
: `${formatPrice(activity.price)}` ? `${formatPrice(activity.oldPrice)}${formatPrice(activity.newPrice)}`
} : activity.price ? `${formatPrice(activity.price)}` : ''
</p> }
</p>
</div>
</div> </div>
<span className="text-sm text-gray-500">{formatDate(activity.date)}</span>
</div> </div>
<span className="text-sm text-gray-500">{formatDate(activity.date)}</span> ))}
</div> </div>
))}
</div> </div>
</div> )}
</> </>
)} )}
{/* Units Tab */}
{viewMode === 'units' && ( {viewMode === 'units' && (
<div className="space-y-6"> <div className="space-y-6">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h2 className="text-xl font-semibold">Available Units ({mockData.availableUnits.length})</h2> <h2 className="text-xl font-semibold">Available Units ({availableUnits.length})</h2>
<div className="flex space-x-2"> <div className="flex space-x-2">
<select className="border rounded-md px-3 py-2"> <select className="border rounded-md px-3 py-2">
<option>All Plans</option> <option>All Plans</option>
<option>Maroon Peak</option> {[...new Set(availableUnits.map(u => u.planName))].map(plan => (
<option>North Maroon Peak</option> <option key={plan}>{plan}</option>
<option>Snowmass</option> ))}
</select> </select>
<select className="border rounded-md px-3 py-2"> <select className="border rounded-md px-3 py-2">
<option>All Bedrooms</option> <option>All Bedrooms</option>
<option>1 BR</option> {[...new Set(availableUnits.map(u => u.bedCount))].sort().map(beds => (
<option>2 BR</option> <option key={beds}>{beds} BR</option>
<option>3 BR</option> ))}
</select> </select>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{mockData.availableUnits.map((unit) => ( {availableUnits.map((unit) => (
<div key={unit.unitCode} className="bg-white rounded-lg shadow-md overflow-hidden"> <div key={unit.unitCode} className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="p-6"> <div className="p-6">
<div className="flex justify-between items-start mb-4"> <div className="flex justify-between items-start mb-4">
<div> <div>
<h3 className="text-lg font-bold">{unit.unitCode}</h3> <h3 className="text-lg font-bold">{unit.unitCode}</h3>
<p className="text-gray-600">{unit.planName}</p> <p className="text-gray-600">{unit.planName}</p>
{unit.planName.toLowerCase().includes('maroon peak') && {unit.planName && unit.planName.toLowerCase().includes('maroon peak') && unit.currentPrice < 3800 &&
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 mt-1"> <span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 mt-1">
<Star className="w-3 h-3 mr-1" /> <Star className="w-3 h-3 mr-1" />
Priority Priority
@ -386,7 +412,7 @@ const ApartmentDashboard = () => {
</div> </div>
<div className="text-center p-3 bg-gray-50 rounded"> <div className="text-center p-3 bg-gray-50 rounded">
<p className="text-sm text-gray-600">Available</p> <p className="text-sm text-gray-600">Available</p>
<p className="font-semibold">{unit.daysAvailable} days</p> <p className="font-semibold">{unit.daysAvailable || 0} days</p>
</div> </div>
</div> </div>
@ -399,7 +425,9 @@ const ApartmentDashboard = () => {
<div <div
className="bg-blue-600 h-2 rounded-full" className="bg-blue-600 h-2 rounded-full"
style={{ style={{
width: `${((unit.currentPrice - unit.minPrice) / (unit.maxPrice - unit.minPrice)) * 100}%` width: unit.maxPrice > unit.minPrice
? `${((unit.currentPrice - unit.minPrice) / (unit.maxPrice - unit.minPrice)) * 100}%`
: '50%'
}} }}
></div> ></div>
</div> </div>
@ -415,9 +443,18 @@ const ApartmentDashboard = () => {
</div> </div>
))} ))}
</div> </div>
{availableUnits.length === 0 && !loading && (
<div className="text-center py-12">
<Home className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">No Available Units</h3>
<p className="text-gray-600">No apartment units are currently available.</p>
</div>
)}
</div> </div>
)} )}
{/* Analytics Tab */}
{viewMode === 'analytics' && ( {viewMode === 'analytics' && (
<div className="space-y-6"> <div className="space-y-6">
<h2 className="text-xl font-semibold">Market Analytics</h2> <h2 className="text-xl font-semibold">Market Analytics</h2>
@ -426,7 +463,7 @@ const ApartmentDashboard = () => {
<div className="bg-white rounded-lg shadow-md p-6"> <div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Price Distribution by Plan</h3> <h3 className="text-lg font-semibold mb-4">Price Distribution by Plan</h3>
<div className="space-y-4"> <div className="space-y-4">
{mockData.planStats.map((plan, index) => ( {planStats.map((plan, index) => (
<div key={plan.name} className="flex items-center justify-between"> <div key={plan.name} className="flex items-center justify-between">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<div <div
@ -453,18 +490,29 @@ const ApartmentDashboard = () => {
Maroon Peak units under $3,800 are highlighted as priority opportunities based on your preferences. Maroon Peak units under $3,800 are highlighted as priority opportunities based on your preferences.
</p> </p>
</div> </div>
<div className="p-4 bg-blue-50 rounded-lg border-l-4 border-blue-500"> {dailySummary && (
<h4 className="font-semibold text-blue-800">Market Activity</h4> <div className="p-4 bg-blue-50 rounded-lg border-l-4 border-blue-500">
<p className="text-sm text-blue-700"> <h4 className="font-semibold text-blue-800">Market Activity</h4>
Daily turnover rate of {mockData.dailySummary.turnoverRate}% indicates moderate market activity. <p className="text-sm text-blue-700">
</p> Daily turnover rate of {(dailySummary.turnoverRate || dailySummary.turnover_rate || 0).toFixed(1)}% indicates {
</div> (dailySummary.turnoverRate || dailySummary.turnover_rate || 0) > 10 ? 'high' :
<div className="p-4 bg-green-50 rounded-lg border-l-4 border-green-500"> (dailySummary.turnoverRate || dailySummary.turnover_rate || 0) > 5 ? 'moderate' : 'low'
<h4 className="font-semibold text-green-800">Price Trends</h4> } market activity.
<p className="text-sm text-green-700"> </p>
Average prices have stabilized around $3,850 with minimal daily fluctuation. </div>
</p> )}
</div> {priceHistory.length > 0 && (
<div className="p-4 bg-green-50 rounded-lg border-l-4 border-green-500">
<h4 className="font-semibold text-green-800">Price Trends</h4>
<p className="text-sm text-green-700">
{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.'
)}
</p>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@ -483,7 +531,7 @@ const ApartmentDashboard = () => {
</div> </div>
<button <button
onClick={() => setSelectedUnit(null)} onClick={() => setSelectedUnit(null)}
className="text-gray-500 hover:text-gray-700" className="text-gray-500 hover:text-gray-700 text-2xl"
> >
</button> </button>
@ -515,28 +563,30 @@ const ApartmentDashboard = () => {
</div> </div>
<div className="text-center"> <div className="text-center">
<p className="text-sm text-gray-600">Days Available</p> <p className="text-sm text-gray-600">Days Available</p>
<p className="text-xl font-semibold">{selectedUnit.daysAvailable}</p> <p className="text-xl font-semibold">{selectedUnit.daysAvailable || 0}</p>
</div> </div>
</div> </div>
<div className="mb-6"> {selectedUnit.priceHistory && selectedUnit.priceHistory.length > 0 && (
<h3 className="font-semibold mb-4">Price History</h3> <div className="mb-6">
<ResponsiveContainer width="100%" height={200}> <h3 className="font-semibold mb-4">Price History</h3>
<LineChart data={selectedUnit.priceHistory}> <ResponsiveContainer width="100%" height={200}>
<CartesianGrid strokeDasharray="3 3" /> <LineChart data={selectedUnit.priceHistory}>
<XAxis <CartesianGrid strokeDasharray="3 3" />
dataKey="date" <XAxis
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} dataKey="date"
/> tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
<YAxis tickFormatter={(value) => `$${value}`} /> />
<Tooltip <YAxis tickFormatter={(value) => `$${value}`} />
formatter={(value, name) => [formatPrice(value), 'Price']} <Tooltip
labelFormatter={(date) => formatDate(date)} formatter={(value, name) => [formatPrice(value), 'Price']}
/> labelFormatter={(date) => formatDate(date)}
<Line type="monotone" dataKey="price" stroke="#8B0000" strokeWidth={2} /> />
</LineChart> <Line type="monotone" dataKey="price" stroke="#8B0000" strokeWidth={2} />
</ResponsiveContainer> </LineChart>
</div> </ResponsiveContainer>
</div>
)}
</div> </div>
</div> </div>
</div> </div>