feat: consolidate Drivers and Vehicles into tabbed Fleet page

Replaces separate /drivers and /vehicles routes with a single /fleet
page using tabs. Old routes redirect for backward compatibility.
Navigation sidebar now shows one "Fleet" item instead of two.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 21:42:32 +01:00
parent 16c0fb65a6
commit 858793d698
6 changed files with 123 additions and 44 deletions

View File

@@ -15,8 +15,7 @@ import { Dashboard } from '@/pages/Dashboard';
import { CommandCenter } from '@/pages/CommandCenter'; import { CommandCenter } from '@/pages/CommandCenter';
import { VIPList } from '@/pages/VipList'; import { VIPList } from '@/pages/VipList';
import { VIPSchedule } from '@/pages/VIPSchedule'; import { VIPSchedule } from '@/pages/VIPSchedule';
import { DriverList } from '@/pages/DriverList'; import { FleetPage } from '@/pages/FleetPage';
import { VehicleList } from '@/pages/VehicleList';
import { EventList } from '@/pages/EventList'; import { EventList } from '@/pages/EventList';
import { FlightList } from '@/pages/FlightList'; import { FlightList } from '@/pages/FlightList';
import { UserList } from '@/pages/UserList'; import { UserList } from '@/pages/UserList';
@@ -115,8 +114,9 @@ function App() {
<Route path="/command-center" element={<CommandCenter />} /> <Route path="/command-center" element={<CommandCenter />} />
<Route path="/vips" element={<VIPList />} /> <Route path="/vips" element={<VIPList />} />
<Route path="/vips/:id/schedule" element={<VIPSchedule />} /> <Route path="/vips/:id/schedule" element={<VIPSchedule />} />
<Route path="/drivers" element={<DriverList />} /> <Route path="/fleet" element={<FleetPage />} />
<Route path="/vehicles" element={<VehicleList />} /> <Route path="/drivers" element={<Navigate to="/fleet?tab=drivers" replace />} />
<Route path="/vehicles" element={<Navigate to="/fleet?tab=vehicles" replace />} />
<Route path="/events" element={<EventList />} /> <Route path="/events" element={<EventList />} />
<Route path="/flights" element={<FlightList />} /> <Route path="/flights" element={<FlightList />} />
<Route path="/users" element={<UserList />} /> <Route path="/users" element={<UserList />} />

View File

@@ -9,7 +9,6 @@ import {
Plane, Plane,
Users, Users,
Car, Car,
Truck,
Calendar, Calendar,
UserCog, UserCog,
LayoutDashboard, LayoutDashboard,
@@ -71,8 +70,7 @@ export function Layout({ children }: LayoutProps) {
{ name: 'My Profile', href: '/profile', icon: UserCog, driverOnly: true }, { name: 'My Profile', href: '/profile', icon: UserCog, driverOnly: true },
{ name: 'War Room', href: '/command-center', icon: Radio, requireRead: 'ScheduleEvent' as const, coordinatorOnly: true }, { name: 'War Room', href: '/command-center', icon: Radio, requireRead: 'ScheduleEvent' as const, coordinatorOnly: true },
{ name: 'VIPs', href: '/vips', icon: Users, requireRead: 'VIP' as const, coordinatorOnly: true }, { name: 'VIPs', href: '/vips', icon: Users, requireRead: 'VIP' as const, coordinatorOnly: true },
{ name: 'Drivers', href: '/drivers', icon: Car, requireRead: 'Driver' as const, coordinatorOnly: true }, { name: 'Fleet', href: '/fleet', icon: Car, requireRead: 'Driver' as const, coordinatorOnly: true },
{ name: 'Vehicles', href: '/vehicles', icon: Truck, requireRead: 'Vehicle' as const, coordinatorOnly: true },
{ name: 'Activities', href: '/events', icon: Calendar, requireRead: 'ScheduleEvent' as const, coordinatorOnly: true }, { name: 'Activities', href: '/events', icon: Calendar, requireRead: 'ScheduleEvent' as const, coordinatorOnly: true },
{ name: 'Flights', href: '/flights', icon: Plane, requireRead: 'Flight' as const, coordinatorOnly: true }, { name: 'Flights', href: '/flights', icon: Plane, requireRead: 'Flight' as const, coordinatorOnly: true },
]; ];

View File

@@ -378,7 +378,7 @@ export function CommandCenter() {
alerts.push({ alerts.push({
type: 'warning', type: 'warning',
message: 'No vehicles available - all vehicles in use', message: 'No vehicles available - all vehicles in use',
link: `/vehicles`, link: `/fleet?tab=vehicles`,
}); });
} }
@@ -902,7 +902,7 @@ export function CommandCenter() {
Events Events
</Link> </Link>
<Link <Link
to="/drivers" to="/fleet?tab=drivers"
className="flex items-center justify-center gap-1 px-2 py-2 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded text-xs font-medium hover:bg-purple-200 dark:hover:bg-purple-900/50" className="flex items-center justify-center gap-1 px-2 py-2 bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-300 rounded text-xs font-medium hover:bg-purple-200 dark:hover:bg-purple-900/50"
> >
<Users className="h-3 w-3" /> <Users className="h-3 w-3" />
@@ -916,7 +916,7 @@ export function CommandCenter() {
VIPs VIPs
</Link> </Link>
<Link <Link
to="/vehicles" to="/fleet?tab=vehicles"
className="flex items-center justify-center gap-1 px-2 py-2 bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 rounded text-xs font-medium hover:bg-orange-200 dark:hover:bg-orange-900/50" className="flex items-center justify-center gap-1 px-2 py-2 bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 rounded text-xs font-medium hover:bg-orange-200 dark:hover:bg-orange-900/50"
> >
<Car className="h-3 w-3" /> <Car className="h-3 w-3" />

View File

@@ -14,7 +14,7 @@ import { DriverChatModal } from '@/components/DriverChatModal';
import { DriverScheduleModal } from '@/components/DriverScheduleModal'; import { DriverScheduleModal } from '@/components/DriverScheduleModal';
import { useUnreadCounts } from '@/hooks/useSignalMessages'; import { useUnreadCounts } from '@/hooks/useSignalMessages';
export function DriverList() { export function DriverList({ embedded = false }: { embedded?: boolean }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingDriver, setEditingDriver] = useState<Driver | null>(null); const [editingDriver, setEditingDriver] = useState<Driver | null>(null);
@@ -234,6 +234,7 @@ export function DriverList() {
if (isLoading) { if (isLoading) {
return ( return (
<div> <div>
{!embedded && (
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6"> <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">Drivers</h1> <h1 className="text-2xl sm:text-3xl font-bold text-foreground">Drivers</h1>
<button <button
@@ -245,6 +246,7 @@ export function DriverList() {
Add Driver Add Driver
</button> </button>
</div> </div>
)}
<div className="hidden lg:block"> <div className="hidden lg:block">
<TableSkeleton rows={8} /> <TableSkeleton rows={8} />
</div> </div>
@@ -257,6 +259,7 @@ export function DriverList() {
return ( return (
<div> <div>
{!embedded ? (
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6"> <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">Drivers</h1> <h1 className="text-2xl sm:text-3xl font-bold text-foreground">Drivers</h1>
<button <button
@@ -268,6 +271,18 @@ export function DriverList() {
Add Driver Add Driver
</button> </button>
</div> </div>
) : (
<div className="flex justify-end mb-4">
<button
onClick={handleAdd}
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-primary hover:bg-primary/90 transition-colors"
style={{ minHeight: '44px' }}
>
<Plus className="h-5 w-5 mr-2" />
Add Driver
</button>
</div>
)}
{/* Search and Filter Section */} {/* Search and Filter Section */}
<div className="bg-card border border-border shadow-soft rounded-lg p-4 mb-6"> <div className="bg-card border border-border shadow-soft rounded-lg p-4 mb-6">

View File

@@ -0,0 +1,54 @@
import { useSearchParams } from 'react-router-dom';
import { Car, Truck } from 'lucide-react';
import { DriverList } from '@/pages/DriverList';
import { VehicleList } from '@/pages/VehicleList';
type FleetTab = 'drivers' | 'vehicles';
const TABS: { id: FleetTab; label: string; icon: typeof Car }[] = [
{ id: 'drivers', label: 'Drivers', icon: Car },
{ id: 'vehicles', label: 'Vehicles', icon: Truck },
];
export function FleetPage() {
const [searchParams, setSearchParams] = useSearchParams();
const rawTab = searchParams.get('tab');
const activeTab: FleetTab = rawTab === 'vehicles' ? 'vehicles' : 'drivers';
const handleTabChange = (tab: FleetTab) => {
setSearchParams({ tab }, { replace: true });
};
return (
<div>
<div className="mb-6">
<h1 className="text-2xl sm:text-3xl font-bold text-foreground">Fleet Management</h1>
</div>
{/* Tab Bar */}
<div className="border-b border-border mb-6">
<div className="flex gap-1 -mb-px">
{TABS.map((tab) => (
<button
key={tab.id}
onClick={() => handleTabChange(tab.id)}
className={`px-4 py-3 text-sm font-medium transition-colors flex items-center gap-2 border-b-2 ${
activeTab === tab.id
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
<tab.icon className="h-4 w-4" />
{tab.label}
</button>
))}
</div>
</div>
{/* Tab Content */}
{activeTab === 'drivers' && <DriverList embedded />}
{activeTab === 'vehicles' && <VehicleList embedded />}
</div>
);
}

View File

@@ -37,7 +37,7 @@ const VEHICLE_STATUS = [
{ value: 'RESERVED', label: 'Reserved', color: 'yellow' }, { value: 'RESERVED', label: 'Reserved', color: 'yellow' },
]; ];
export function VehicleList() { export function VehicleList({ embedded = false }: { embedded?: boolean }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingVehicle, setEditingVehicle] = useState<Vehicle | null>(null); const [editingVehicle, setEditingVehicle] = useState<Vehicle | null>(null);
@@ -189,6 +189,7 @@ export function VehicleList() {
return ( return (
<div> <div>
{!embedded ? (
<div className="flex justify-between items-center mb-6"> <div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-foreground">Vehicle Management</h1> <h1 className="text-3xl font-bold text-foreground">Vehicle Management</h1>
<button <button
@@ -199,6 +200,17 @@ export function VehicleList() {
{showForm ? 'Cancel' : 'Add Vehicle'} {showForm ? 'Cancel' : 'Add Vehicle'}
</button> </button>
</div> </div>
) : (
<div className="flex justify-end mb-4">
<button
onClick={() => setShowForm(!showForm)}
className="inline-flex items-center px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors"
>
<Plus className="h-5 w-5 mr-2" />
{showForm ? 'Cancel' : 'Add Vehicle'}
</button>
</div>
)}
{/* Stats Summary */} {/* Stats Summary */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6"> <div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">