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:
@@ -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 />} />
|
||||||
|
|||||||
@@ -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 },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
54
frontend/src/pages/FleetPage.tsx
Normal file
54
frontend/src/pages/FleetPage.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user