fix: mobile dashboard navigation for Add Vehicle and Maintenance
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m36s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 36s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 5s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m36s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 36s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 5s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- Add Vehicle now navigates to Vehicles screen and opens add form - Add Maintenance mobile screen with records/schedules tabs - Add 'Maintenance' to MobileScreen type - Wire up onViewMaintenance callback to navigate to Maintenance screen refs #2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ const StationsMobileScreen = lazy(() => import('./features/stations/mobile/Stati
|
|||||||
const VehiclesMobileScreen = lazy(() => import('./features/vehicles/mobile/VehiclesMobileScreen').then(m => ({ default: m.VehiclesMobileScreen })));
|
const VehiclesMobileScreen = lazy(() => import('./features/vehicles/mobile/VehiclesMobileScreen').then(m => ({ default: m.VehiclesMobileScreen })));
|
||||||
const VehicleDetailMobile = lazy(() => import('./features/vehicles/mobile/VehicleDetailMobile').then(m => ({ default: m.VehicleDetailMobile })));
|
const VehicleDetailMobile = lazy(() => import('./features/vehicles/mobile/VehicleDetailMobile').then(m => ({ default: m.VehicleDetailMobile })));
|
||||||
const DocumentsMobileScreen = lazy(() => import('./features/documents/mobile/DocumentsMobileScreen'));
|
const DocumentsMobileScreen = lazy(() => import('./features/documents/mobile/DocumentsMobileScreen'));
|
||||||
|
const MaintenanceMobileScreen = lazy(() => import('./features/maintenance/mobile/MaintenanceMobileScreen'));
|
||||||
|
|
||||||
// Admin pages (lazy-loaded)
|
// Admin pages (lazy-loaded)
|
||||||
const AdminUsersPage = lazy(() => import('./pages/admin/AdminUsersPage').then(m => ({ default: m.AdminUsersPage })));
|
const AdminUsersPage = lazy(() => import('./pages/admin/AdminUsersPage').then(m => ({ default: m.AdminUsersPage })));
|
||||||
@@ -633,7 +634,12 @@ function App() {
|
|||||||
<DashboardFeature
|
<DashboardFeature
|
||||||
onNavigate={navigateToScreen}
|
onNavigate={navigateToScreen}
|
||||||
onVehicleClick={handleVehicleSelect}
|
onVehicleClick={handleVehicleSelect}
|
||||||
onAddVehicle={() => setShowAddVehicle(true)}
|
onViewMaintenance={() => navigateToScreen('Maintenance', { source: 'dashboard-maintenance' })}
|
||||||
|
onAddVehicle={() => {
|
||||||
|
setShowAddVehicle(true);
|
||||||
|
navigateToScreen('Vehicles', { source: 'dashboard-add-vehicle' });
|
||||||
|
navigateToVehicleSubScreen('add', undefined, { source: 'dashboard-add-vehicle' });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</MobileErrorBoundary>
|
</MobileErrorBoundary>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -685,6 +691,31 @@ function App() {
|
|||||||
</MobileErrorBoundary>
|
</MobileErrorBoundary>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
{activeScreen === "Maintenance" && (
|
||||||
|
<motion.div
|
||||||
|
key="maintenance"
|
||||||
|
initial={{opacity:0, y:8}}
|
||||||
|
animate={{opacity:1, y:0}}
|
||||||
|
exit={{opacity:0, y:-8}}
|
||||||
|
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
<MobileErrorBoundary screenName="Maintenance">
|
||||||
|
<React.Suspense fallback={
|
||||||
|
<div className="space-y-4">
|
||||||
|
<GlassCard>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="text-slate-500 py-6 text-center">
|
||||||
|
Loading maintenance screen...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GlassCard>
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<MaintenanceMobileScreen />
|
||||||
|
</React.Suspense>
|
||||||
|
</MobileErrorBoundary>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
{activeScreen === "Settings" && (
|
{activeScreen === "Settings" && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="settings"
|
key="settings"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { create } from 'zustand';
|
|||||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||||
import { safeStorage } from '../utils/safe-storage';
|
import { safeStorage } from '../utils/safe-storage';
|
||||||
|
|
||||||
export type MobileScreen = 'Dashboard' | 'Vehicles' | 'Log Fuel' | 'Stations' | 'Documents' | 'Settings' | 'Security' | 'AdminUsers' | 'AdminCatalog' | 'AdminCommunityStations' | 'AdminEmailTemplates' | 'AdminBackup';
|
export type MobileScreen = 'Dashboard' | 'Vehicles' | 'Log Fuel' | 'Maintenance' | 'Stations' | 'Documents' | 'Settings' | 'Security' | 'AdminUsers' | 'AdminCatalog' | 'AdminCommunityStations' | 'AdminEmailTemplates' | 'AdminBackup';
|
||||||
export type VehicleSubScreen = 'list' | 'detail' | 'add' | 'edit';
|
export type VehicleSubScreen = 'list' | 'detail' | 'add' | 'edit';
|
||||||
|
|
||||||
interface NavigationHistory {
|
interface NavigationHistory {
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
/**
|
||||||
|
* @ai-summary Mobile maintenance screen with tabs for records and schedules
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { Box, Tabs, Tab } from '@mui/material';
|
||||||
|
import { GlassCard } from '../../../shared-minimal/components/mobile/GlassCard';
|
||||||
|
import { Button } from '../../../shared-minimal/components/Button';
|
||||||
|
import { useMaintenanceRecords } from '../hooks/useMaintenanceRecords';
|
||||||
|
import { MaintenanceRecordForm } from '../components/MaintenanceRecordForm';
|
||||||
|
import { MaintenanceRecordsList } from '../components/MaintenanceRecordsList';
|
||||||
|
import { MaintenanceRecordEditDialog } from '../components/MaintenanceRecordEditDialog';
|
||||||
|
import { MaintenanceScheduleForm } from '../components/MaintenanceScheduleForm';
|
||||||
|
import { MaintenanceSchedulesList } from '../components/MaintenanceSchedulesList';
|
||||||
|
import { MaintenanceScheduleEditDialog } from '../components/MaintenanceScheduleEditDialog';
|
||||||
|
import type { MaintenanceRecordResponse, UpdateMaintenanceRecordRequest, MaintenanceScheduleResponse, UpdateScheduleRequest } from '../types/maintenance.types';
|
||||||
|
|
||||||
|
export const MaintenanceMobileScreen: React.FC = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { records, schedules, isRecordsLoading, isSchedulesLoading, recordsError, schedulesError, updateRecord, deleteRecord, updateSchedule, deleteSchedule } = useMaintenanceRecords();
|
||||||
|
|
||||||
|
const [activeTab, setActiveTab] = useState<'records' | 'schedules'>('records');
|
||||||
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [editingRecord, setEditingRecord] = useState<MaintenanceRecordResponse | null>(null);
|
||||||
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
|
const [editingSchedule, setEditingSchedule] = useState<MaintenanceScheduleResponse | null>(null);
|
||||||
|
const [scheduleEditDialogOpen, setScheduleEditDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleEdit = (record: MaintenanceRecordResponse) => {
|
||||||
|
setEditingRecord(record);
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSave = async (id: string, data: UpdateMaintenanceRecordRequest) => {
|
||||||
|
try {
|
||||||
|
await updateRecord({ id, data });
|
||||||
|
queryClient.refetchQueries({ queryKey: ['maintenanceRecords'] });
|
||||||
|
setEditDialogOpen(false);
|
||||||
|
setEditingRecord(null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update maintenance record:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditClose = () => {
|
||||||
|
setEditDialogOpen(false);
|
||||||
|
setEditingRecord(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (recordId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteRecord(recordId);
|
||||||
|
queryClient.refetchQueries({ queryKey: ['maintenanceRecords', 'all'] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete maintenance record:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScheduleEdit = (schedule: MaintenanceScheduleResponse) => {
|
||||||
|
setEditingSchedule(schedule);
|
||||||
|
setScheduleEditDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScheduleEditSave = async (id: string, data: UpdateScheduleRequest) => {
|
||||||
|
try {
|
||||||
|
await updateSchedule({ id, data });
|
||||||
|
queryClient.refetchQueries({ queryKey: ['maintenanceSchedules'] });
|
||||||
|
setScheduleEditDialogOpen(false);
|
||||||
|
setEditingSchedule(null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update maintenance schedule:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScheduleEditClose = () => {
|
||||||
|
setScheduleEditDialogOpen(false);
|
||||||
|
setEditingSchedule(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScheduleDelete = async (scheduleId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteSchedule(scheduleId);
|
||||||
|
queryClient.refetchQueries({ queryKey: ['maintenanceSchedules'] });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete maintenance schedule:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoading = activeTab === 'records' ? isRecordsLoading : isSchedulesLoading;
|
||||||
|
const hasError = activeTab === 'records' ? recordsError : schedulesError;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Header */}
|
||||||
|
<GlassCard>
|
||||||
|
<div className="p-4">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-800 dark:text-avus mb-3">Maintenance</h2>
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={(_, v) => {
|
||||||
|
setActiveTab(v as 'records' | 'schedules');
|
||||||
|
setShowForm(false);
|
||||||
|
}}
|
||||||
|
aria-label="Maintenance tabs"
|
||||||
|
sx={{
|
||||||
|
minHeight: 40,
|
||||||
|
'& .MuiTab-root': {
|
||||||
|
minHeight: 40,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
textTransform: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab label="Records" value="records" />
|
||||||
|
<Tab label="Schedules" value="schedules" />
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Add Button */}
|
||||||
|
<div className="flex justify-end mb-3">
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowForm(!showForm)}
|
||||||
|
className="min-h-[44px]"
|
||||||
|
>
|
||||||
|
{showForm ? 'Cancel' : activeTab === 'records' ? 'Add Record' : 'Add Schedule'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Loading State */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="text-slate-500 dark:text-titanio py-6 text-center">
|
||||||
|
Loading maintenance {activeTab}...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Error State */}
|
||||||
|
{hasError && (
|
||||||
|
<div className="py-6 text-center">
|
||||||
|
<p className="text-red-600 text-sm mb-3">Failed to load maintenance {activeTab}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm"
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</GlassCard>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
{showForm && !isLoading && !hasError && (
|
||||||
|
<GlassCard>
|
||||||
|
<div className="p-4">
|
||||||
|
<h3 className="text-base font-semibold text-slate-800 dark:text-avus mb-3">
|
||||||
|
{activeTab === 'records' ? 'New Maintenance Record' : 'New Maintenance Schedule'}
|
||||||
|
</h3>
|
||||||
|
{activeTab === 'records' ? (
|
||||||
|
<MaintenanceRecordForm />
|
||||||
|
) : (
|
||||||
|
<MaintenanceScheduleForm />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</GlassCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Records List */}
|
||||||
|
{activeTab === 'records' && !isLoading && !hasError && (
|
||||||
|
<GlassCard>
|
||||||
|
<div className="p-4">
|
||||||
|
<h3 className="text-base font-semibold text-slate-800 dark:text-avus mb-3">
|
||||||
|
Recent Records
|
||||||
|
</h3>
|
||||||
|
{records && records.length === 0 ? (
|
||||||
|
<div className="py-8 text-center">
|
||||||
|
<p className="text-slate-600 dark:text-titanio text-sm mb-3">No maintenance records yet</p>
|
||||||
|
<p className="text-slate-500 dark:text-titanio text-xs">
|
||||||
|
Add a record to track your vehicle maintenance
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<MaintenanceRecordsList
|
||||||
|
records={records || []}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</GlassCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Schedules List */}
|
||||||
|
{activeTab === 'schedules' && !isLoading && !hasError && (
|
||||||
|
<GlassCard>
|
||||||
|
<div className="p-4">
|
||||||
|
<h3 className="text-base font-semibold text-slate-800 dark:text-avus mb-3">
|
||||||
|
Maintenance Schedules
|
||||||
|
</h3>
|
||||||
|
{schedules && schedules.length === 0 ? (
|
||||||
|
<div className="py-8 text-center">
|
||||||
|
<p className="text-slate-600 dark:text-titanio text-sm mb-3">No schedules yet</p>
|
||||||
|
<p className="text-slate-500 dark:text-titanio text-xs">
|
||||||
|
Create a schedule to get maintenance reminders
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<MaintenanceSchedulesList
|
||||||
|
schedules={schedules || []}
|
||||||
|
onEdit={handleScheduleEdit}
|
||||||
|
onDelete={handleScheduleDelete}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</GlassCard>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Edit Dialogs */}
|
||||||
|
<MaintenanceRecordEditDialog
|
||||||
|
open={editDialogOpen}
|
||||||
|
record={editingRecord}
|
||||||
|
onClose={handleEditClose}
|
||||||
|
onSave={handleEditSave}
|
||||||
|
/>
|
||||||
|
<MaintenanceScheduleEditDialog
|
||||||
|
open={scheduleEditDialogOpen}
|
||||||
|
schedule={editingSchedule}
|
||||||
|
onClose={handleScheduleEditClose}
|
||||||
|
onSave={handleScheduleEditSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MaintenanceMobileScreen;
|
||||||
Reference in New Issue
Block a user