feat: implement community Q&A, scraper, and dynamic bus routes display without secrets
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Search, ChevronRight, MapPin } from 'lucide-react';
|
||||
import { Search, ChevronRight, MapPin, Loader2 } from 'lucide-react';
|
||||
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||
import BusCard from '@/components/BusCard/BusCard';
|
||||
import FacultyCard from '@/components/FacultyCard/FacultyCard';
|
||||
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||
import { BUS_ROUTES, FACULTY_DATA, CAMPUS_LOCATIONS, DEPARTMENTS } from '@/constants';
|
||||
import { FACULTY_DATA, CAMPUS_LOCATIONS, DEPARTMENTS } from '@/constants';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
import type { BusRoute } from '@/types';
|
||||
|
||||
type Tab = 'map' | 'bus' | 'faculty';
|
||||
|
||||
@@ -15,6 +16,27 @@ export default function Campus() {
|
||||
const [searchFaculty, setSearchFaculty] = useState('');
|
||||
const [selectedDept, setSelectedDept] = useState('All Departments');
|
||||
const [busSearch, setBusSearch] = useState('');
|
||||
const [busRoutes, setBusRoutes] = useState<BusRoute[]>([]);
|
||||
const [loadingBus, setLoadingBus] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'bus' && busRoutes.length === 0) {
|
||||
setLoadingBus(true);
|
||||
fetch('http://localhost:8080/api/bus-routes')
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error('Failed to fetch');
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setBusRoutes(data);
|
||||
setLoadingBus(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Error fetching bus routes:', err);
|
||||
setLoadingBus(false);
|
||||
});
|
||||
}
|
||||
}, [activeTab, busRoutes.length]);
|
||||
|
||||
const filteredFaculty = FACULTY_DATA.filter((f) => {
|
||||
const matchSearch = f.name.toLowerCase().includes(searchFaculty.toLowerCase()) ||
|
||||
@@ -23,7 +45,7 @@ export default function Campus() {
|
||||
return matchSearch && matchDept;
|
||||
});
|
||||
|
||||
const filteredRoutes = BUS_ROUTES.filter((r) =>
|
||||
const filteredRoutes = busRoutes.filter((r) =>
|
||||
r.name.toLowerCase().includes(busSearch.toLowerCase()) ||
|
||||
r.from.toLowerCase().includes(busSearch.toLowerCase()) ||
|
||||
r.number.toLowerCase().includes(busSearch.toLowerCase())
|
||||
@@ -179,13 +201,29 @@ export default function Campus() {
|
||||
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||
/>
|
||||
</div>
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{filteredRoutes.map((route) => (
|
||||
<StaggerItem key={route.id}>
|
||||
<BusCard route={route} />
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
|
||||
{loadingBus ? (
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
||||
<Loader2 className="w-8 h-8 text-[#F97316] animate-spin" />
|
||||
<p className="text-sm text-[#94A3B8]" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
Loading live bus routes from RIT Transport...
|
||||
</p>
|
||||
</div>
|
||||
) : filteredRoutes.length === 0 ? (
|
||||
<div className="text-center py-16 text-[#94A3B8]">
|
||||
<p className="text-sm font-medium" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||
No bus routes found matching "{busSearch}"
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{filteredRoutes.map((route) => (
|
||||
<StaggerItem key={route.id}>
|
||||
<BusCard route={route} />
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user