Initial commit: Apartment dashboard React app
This commit is contained in:
549
src/App.jsx
Normal file
549
src/App.jsx
Normal file
@ -0,0 +1,549 @@
|
||||
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';
|
||||
|
||||
const ApartmentDashboard = () => {
|
||||
const [selectedUnit, setSelectedUnit] = useState(null);
|
||||
const [timeRange, setTimeRange] = useState('30d');
|
||||
const [viewMode, setViewMode] = useState('overview');
|
||||
|
||||
// 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 }
|
||||
]
|
||||
}
|
||||
],
|
||||
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 }
|
||||
]
|
||||
});
|
||||
|
||||
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="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-600">{title}</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{value}</p>
|
||||
{subtitle && <p className="text-sm text-gray-500">{subtitle}</p>}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
{trend && (
|
||||
<span className={`text-sm ${trend > 0 ? 'text-green-600' : 'text-red-600'} flex items-center`}>
|
||||
{trend > 0 ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
|
||||
{Math.abs(trend)}%
|
||||
</span>
|
||||
)}
|
||||
<Icon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const formatPrice = (price) => `$${price?.toLocaleString()}`;
|
||||
const formatDate = (dateStr) => new Date(dateStr).toLocaleDateString();
|
||||
|
||||
const COLORS = ['#8B0000', '#DC143C', '#B22222', '#CD5C5C', '#F08080'];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<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>
|
||||
<div className="flex items-center space-x-4 mt-4">
|
||||
<button
|
||||
onClick={() => setViewMode('overview')}
|
||||
className={`px-4 py-2 rounded-md ${viewMode === 'overview' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
||||
>
|
||||
Overview
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('units')}
|
||||
className={`px-4 py-2 rounded-md ${viewMode === 'units' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
||||
>
|
||||
Available Units
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode('analytics')}
|
||||
className={`px-4 py-2 rounded-md ${viewMode === 'analytics' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
||||
>
|
||||
Market Analytics
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{viewMode === 'overview' && (
|
||||
<>
|
||||
{/* Daily Metrics */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<MetricCard
|
||||
title="Available Units"
|
||||
value={mockData.dailySummary.totalAvailable}
|
||||
icon={Home}
|
||||
color="blue"
|
||||
/>
|
||||
<MetricCard
|
||||
title="New Today"
|
||||
value={mockData.dailySummary.newUnits}
|
||||
subtitle="Units available"
|
||||
icon={TrendingUp}
|
||||
color="green"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Rented Today"
|
||||
value={mockData.dailySummary.rentedUnits}
|
||||
subtitle="Units leased"
|
||||
icon={TrendingDown}
|
||||
color="red"
|
||||
/>
|
||||
<MetricCard
|
||||
title="Turnover Rate"
|
||||
value={`${mockData.dailySummary.turnoverRate}%`}
|
||||
subtitle="Daily activity"
|
||||
icon={Activity}
|
||||
color="orange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Price Trends */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Average Price Trend (15 days)</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<AreaChart data={mockData.priceHistory}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
/>
|
||||
<YAxis tickFormatter={(value) => `$${value}`} />
|
||||
<Tooltip
|
||||
formatter={(value, name) => [formatPrice(value), 'Avg Price']}
|
||||
labelFormatter={(date) => formatDate(date)}
|
||||
/>
|
||||
<Area type="monotone" dataKey="avgPrice" stroke="#8B0000" fill="#8B0000" fillOpacity={0.1} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Unit Availability</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<AreaChart data={mockData.priceHistory}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
/>
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
formatter={(value, name) => [value, 'Available Units']}
|
||||
labelFormatter={(date) => formatDate(date)}
|
||||
/>
|
||||
<Area type="monotone" dataKey="unitCount" stroke="#3B82F6" fill="#3B82F6" fillOpacity={0.2} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plan Distribution */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Units by Floor Plan</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={mockData.planStats}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
label={({ name, count }) => `${name}: ${count}`}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
dataKey="count"
|
||||
>
|
||||
{mockData.planStats.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Average Price by Plan</h3>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={mockData.planStats}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
|
||||
<YAxis tickFormatter={(value) => `$${value}`} />
|
||||
<Tooltip formatter={(value, name) => [formatPrice(value), 'Avg Price']} />
|
||||
<Bar dataKey="avgPrice" fill="#8B0000" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Recent Activity</h3>
|
||||
<div className="space-y-4">
|
||||
{mockData.recentActivity.map((activity, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
{activity.type === 'new' && <div className="w-3 h-3 bg-green-500 rounded-full"></div>}
|
||||
{activity.type === 'rented' && <div className="w-3 h-3 bg-red-500 rounded-full"></div>}
|
||||
{activity.type === 'price_drop' && <div className="w-3 h-3 bg-orange-500 rounded-full"></div>}
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{activity.type === 'new' && `New unit available: ${activity.unitCode}`}
|
||||
{activity.type === 'rented' && `Unit rented: ${activity.unitCode}`}
|
||||
{activity.type === 'price_drop' && `Price drop: ${activity.unitCode}`}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{activity.planName} • {activity.bedCount}BR/{activity.bathCount}BA
|
||||
{activity.type === 'price_drop'
|
||||
? ` • ${formatPrice(activity.oldPrice)} → ${formatPrice(activity.newPrice)}`
|
||||
: ` • ${formatPrice(activity.price)}`
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-500">{formatDate(activity.date)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{viewMode === 'units' && (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-semibold">Available Units ({mockData.availableUnits.length})</h2>
|
||||
<div className="flex space-x-2">
|
||||
<select className="border rounded-md px-3 py-2">
|
||||
<option>All Plans</option>
|
||||
<option>Maroon Peak</option>
|
||||
<option>North Maroon Peak</option>
|
||||
<option>Snowmass</option>
|
||||
</select>
|
||||
<select className="border rounded-md px-3 py-2">
|
||||
<option>All Bedrooms</option>
|
||||
<option>1 BR</option>
|
||||
<option>2 BR</option>
|
||||
<option>3 BR</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{mockData.availableUnits.map((unit) => (
|
||||
<div key={unit.unitCode} className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold">{unit.unitCode}</h3>
|
||||
<p className="text-gray-600">{unit.planName}</p>
|
||||
{unit.planName.toLowerCase().includes('maroon peak') &&
|
||||
<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" />
|
||||
Priority
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-2xl font-bold text-green-600">{formatPrice(unit.currentPrice)}</p>
|
||||
<p className="text-sm text-gray-500">Current Price</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<div className="text-center p-3 bg-gray-50 rounded">
|
||||
<p className="text-sm text-gray-600">Bedrooms</p>
|
||||
<p className="font-semibold">{unit.bedCount}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 rounded">
|
||||
<p className="text-sm text-gray-600">Bathrooms</p>
|
||||
<p className="font-semibold">{unit.bathCount}</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 rounded">
|
||||
<p className="text-sm text-gray-600">Area</p>
|
||||
<p className="font-semibold">{unit.area} sqft</p>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 rounded">
|
||||
<p className="text-sm text-gray-600">Available</p>
|
||||
<p className="font-semibold">{unit.daysAvailable} days</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4">
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span>Price Range:</span>
|
||||
<span>{formatPrice(unit.minPrice)} - {formatPrice(unit.maxPrice)}</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-600 h-2 rounded-full"
|
||||
style={{
|
||||
width: `${((unit.currentPrice - unit.minPrice) / (unit.maxPrice - unit.minPrice)) * 100}%`
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setSelectedUnit(unit)}
|
||||
className="w-full mt-4 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{viewMode === 'analytics' && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-semibold">Market Analytics</h2>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Price Distribution by Plan</h3>
|
||||
<div className="space-y-4">
|
||||
{mockData.planStats.map((plan, index) => (
|
||||
<div key={plan.name} className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div
|
||||
className="w-4 h-4 rounded"
|
||||
style={{ backgroundColor: COLORS[index % COLORS.length] }}
|
||||
></div>
|
||||
<span className="font-medium">{plan.name}</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-semibold">{formatPrice(plan.avgPrice)}</p>
|
||||
<p className="text-sm text-gray-500">{plan.count} units</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Market Insights</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-red-50 rounded-lg border-l-4 border-red-500">
|
||||
<h4 className="font-semibold text-red-800">Maroon Peak Priority</h4>
|
||||
<p className="text-sm text-red-700">
|
||||
Maroon Peak units under $3,800 are highlighted as priority opportunities based on your preferences.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-blue-50 rounded-lg border-l-4 border-blue-500">
|
||||
<h4 className="font-semibold text-blue-800">Market Activity</h4>
|
||||
<p className="text-sm text-blue-700">
|
||||
Daily turnover rate of {mockData.dailySummary.turnoverRate}% indicates moderate market activity.
|
||||
</p>
|
||||
</div>
|
||||
<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">
|
||||
Average prices have stabilized around $3,850 with minimal daily fluctuation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Unit Detail Modal */}
|
||||
{selectedUnit && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold">{selectedUnit.unitCode}</h2>
|
||||
<p className="text-gray-600">{selectedUnit.planName} • {selectedUnit.community}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedUnit(null)}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2">Current Price</h3>
|
||||
<p className="text-3xl font-bold text-green-600">{formatPrice(selectedUnit.currentPrice)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2">Price Range</h3>
|
||||
<p className="text-lg">{formatPrice(selectedUnit.minPrice)} - {formatPrice(selectedUnit.maxPrice)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4 mb-6">
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600">Bedrooms</p>
|
||||
<p className="text-xl font-semibold">{selectedUnit.bedCount}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600">Bathrooms</p>
|
||||
<p className="text-xl font-semibold">{selectedUnit.bathCount}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600">Area</p>
|
||||
<p className="text-xl font-semibold">{selectedUnit.area}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-600">Days Available</p>
|
||||
<p className="text-xl font-semibold">{selectedUnit.daysAvailable}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="font-semibold mb-4">Price History</h3>
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<LineChart data={selectedUnit.priceHistory}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(date) => new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
||||
/>
|
||||
<YAxis tickFormatter={(value) => `$${value}`} />
|
||||
<Tooltip
|
||||
formatter={(value, name) => [formatPrice(value), 'Price']}
|
||||
labelFormatter={(date) => formatDate(date)}
|
||||
/>
|
||||
<Line type="monotone" dataKey="price" stroke="#8B0000" strokeWidth={2} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApartmentDashboard;
|
||||
Reference in New Issue
Block a user