Files
motovaultpro/frontend/src/features/maintenance/mobile/MaintenanceMobileScreen.tsx
Eric Gullickson 2059afaaef
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
fix: mobile dashboard navigation for Add Vehicle and Maintenance
- 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>
2026-01-02 22:40:42 -06:00

241 lines
8.5 KiB
TypeScript

/**
* @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;