Add URL routing for pages
Some checks failed
Deploy Apartment Dashboard / deploy (push) Failing after 1s
Some checks failed
Deploy Apartment Dashboard / deploy (push) Failing after 1s
- Install react-router-dom for client-side routing - Overview accessible at / - Available Units accessible at /units - Analytics accessible at /analytics - Unknown routes redirect to overview - Update page title to Towers Tracking
This commit is contained in:
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>Towers Tracking</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
58
package-lock.json
generated
58
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router-dom": "^7.12.0",
|
||||||
"recharts": "^3.1.0"
|
"recharts": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1980,6 +1981,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -3642,6 +3656,44 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
|
||||||
|
"integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
|
||||||
|
"integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.12.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
@ -3848,6 +3900,12 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router-dom": "^7.12.0",
|
||||||
"recharts": "^3.1.0"
|
"recharts": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
58
src/App.jsx
58
src/App.jsx
@ -1,11 +1,14 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Routes, Route, Link, useLocation, Navigate } from 'react-router-dom';
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Area, AreaChart, ScatterChart, Scatter, ComposedChart, ReferenceLine } from 'recharts';
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Area, AreaChart, ScatterChart, Scatter, ComposedChart, ReferenceLine } from 'recharts';
|
||||||
import { TrendingUp, TrendingDown, Home, DollarSign, Calendar, MapPin, Activity, AlertCircle, Star, RefreshCw, Wifi, WifiOff, Bell, Target, Filter, Search, ArrowUp, ArrowDown, Zap, Clock, Eye } from 'lucide-react';
|
import { TrendingUp, TrendingDown, Home, DollarSign, Calendar, MapPin, Activity, AlertCircle, Star, RefreshCw, Wifi, WifiOff, Bell, Target, Filter, Search, ArrowUp, ArrowDown, Zap, Clock, Eye } from 'lucide-react';
|
||||||
|
|
||||||
const ApartmentDashboard = () => {
|
const ApartmentDashboard = () => {
|
||||||
|
// Router
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
// Basic state
|
// Basic state
|
||||||
const [selectedUnit, setSelectedUnit] = useState(null);
|
const [selectedUnit, setSelectedUnit] = useState(null);
|
||||||
const [viewMode, setViewMode] = useState('overview');
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [lastUpdated, setLastUpdated] = useState(new Date());
|
const [lastUpdated, setLastUpdated] = useState(new Date());
|
||||||
@ -297,10 +300,10 @@ const ApartmentDashboard = () => {
|
|||||||
|
|
||||||
// Fetch analytics when switching to analytics tab
|
// Fetch analytics when switching to analytics tab
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (viewMode === 'analytics' && !analyticsData) {
|
if (location.pathname === '/analytics' && !analyticsData) {
|
||||||
fetchAnalytics();
|
fetchAnalytics();
|
||||||
}
|
}
|
||||||
}, [viewMode]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
// Calculate insights from available data
|
// Calculate insights from available data
|
||||||
const calculateInsights = () => {
|
const calculateInsights = () => {
|
||||||
@ -442,30 +445,31 @@ const ApartmentDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4 mt-4">
|
<div className="flex items-center space-x-4 mt-4">
|
||||||
<button
|
<Link
|
||||||
onClick={() => setViewMode('overview')}
|
to="/"
|
||||||
className={`px-4 py-2 rounded-md ${viewMode === 'overview' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
className={`px-4 py-2 rounded-md ${location.pathname === '/' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
||||||
>
|
>
|
||||||
Overview
|
Overview
|
||||||
</button>
|
</Link>
|
||||||
<button
|
<Link
|
||||||
onClick={() => setViewMode('units')}
|
to="/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 ${location.pathname === '/units' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
||||||
>
|
>
|
||||||
Available Units ({availableUnits.length})
|
Available Units ({availableUnits.length})
|
||||||
</button>
|
</Link>
|
||||||
<button
|
<Link
|
||||||
onClick={() => setViewMode('analytics')}
|
to="/analytics"
|
||||||
className={`px-4 py-2 rounded-md ${viewMode === 'analytics' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
className={`px-4 py-2 rounded-md ${location.pathname === '/analytics' ? 'bg-blue-600 text-white' : 'bg-white text-gray-700 border'}`}
|
||||||
>
|
>
|
||||||
Analytics
|
Analytics
|
||||||
</button>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Overview Tab */}
|
<Routes>
|
||||||
{viewMode === 'overview' && (
|
{/* Overview Page */}
|
||||||
<>
|
<Route path="/" element={
|
||||||
|
<>
|
||||||
{/* Key Metrics */}
|
{/* Key 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
|
||||||
@ -616,10 +620,10 @@ const ApartmentDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
} />
|
||||||
|
|
||||||
{/* Units Tab */}
|
{/* Units Page */}
|
||||||
{viewMode === 'units' && (
|
<Route path="/units" element={
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-6">
|
<div className="bg-white rounded-lg shadow-md p-6">
|
||||||
@ -730,10 +734,10 @@ const ApartmentDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
} />
|
||||||
|
|
||||||
{/* Analytics Tab */}
|
{/* Analytics Page */}
|
||||||
{viewMode === 'analytics' && (
|
<Route path="/analytics" element={
|
||||||
<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">Market Analytics</h2>
|
<h2 className="text-xl font-semibold">Market Analytics</h2>
|
||||||
@ -937,7 +941,11 @@ const ApartmentDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
} />
|
||||||
|
|
||||||
|
{/* Catch-all redirect to overview */}
|
||||||
|
<Route path="*" element={<Navigate to="/" replace />} />
|
||||||
|
</Routes>
|
||||||
|
|
||||||
{/* Unit Detail Modal */}
|
{/* Unit Detail Modal */}
|
||||||
{selectedUnit && (
|
{selectedUnit && (
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user