Fixed mobile form

This commit is contained in:
Eric Gullickson
2025-09-25 14:21:23 -05:00
parent d4ca0ba8ae
commit 82c66dafed
5 changed files with 267 additions and 379 deletions

View File

@@ -44,6 +44,189 @@ import { useDataSync } from './core/hooks/useDataSync';
import { MobileDebugPanel } from './core/debug/MobileDebugPanel';
import { MobileErrorBoundary } from './core/error-boundaries/MobileErrorBoundary';
// Hoisted mobile screen components to stabilize identity and prevent remounts
const DashboardScreen: React.FC = () => (
<div className="space-y-4">
<GlassCard>
<div className="text-center py-12">
<h2 className="text-lg font-semibold text-slate-800 mb-2">Dashboard</h2>
<p className="text-slate-500">Coming soon - Vehicle insights and analytics</p>
</div>
</GlassCard>
</div>
);
const LogFuelScreen: React.FC = () => {
const queryClient = useQueryClient();
const [editingLog, setEditingLog] = useState<FuelLogResponse | null>(null);
const { goBack, canGoBack, navigateToScreen } = useNavigationStore();
useEffect(() => {
console.log('[LogFuelScreen] Mounted');
return () => console.log('[LogFuelScreen] Unmounted');
}, []);
let fuelLogs: FuelLogResponse[] | undefined, isLoading: boolean | undefined, error: any;
try {
const hookResult = useFuelLogs();
fuelLogs = hookResult.fuelLogs;
isLoading = hookResult.isLoading;
error = hookResult.error;
} catch (hookError) {
console.error('[LogFuelScreen] Hook error:', hookError);
error = hookError;
}
const handleEdit = (log: FuelLogResponse) => {
if (!log || !log.id) {
console.error('[LogFuelScreen] Invalid log data for edit:', log);
return;
}
try {
setEditingLog(log);
} catch (error) {
console.error('[LogFuelScreen] Error setting editing log:', error);
}
};
const handleDelete = async (_logId: string) => {
try {
queryClient.invalidateQueries({ queryKey: ['fuelLogs'] });
queryClient.invalidateQueries({ queryKey: ['fuelLogsStats'] });
} catch (error) {
console.error('Failed to refresh fuel logs after delete:', error);
}
};
const handleSaveEdit = async (id: string, data: UpdateFuelLogRequest) => {
try {
await fuelLogsApi.update(id, data);
queryClient.invalidateQueries({ queryKey: ['fuelLogs'] });
queryClient.invalidateQueries({ queryKey: ['fuelLogsStats'] });
setEditingLog(null);
} catch (error) {
console.error('Failed to update fuel log:', error);
throw error;
}
};
const handleCloseEdit = () => setEditingLog(null);
if (error) {
return (
<div className="space-y-4">
<GlassCard>
<div className="text-center py-8">
<p className="text-red-600 mb-4">Failed to load fuel logs</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
>
Retry
</button>
</div>
</GlassCard>
</div>
);
}
if (isLoading === undefined) {
return (
<div className="space-y-4">
<GlassCard>
<div className="text-center py-8 text-slate-500">
Initializing fuel logs...
</div>
</GlassCard>
</div>
);
}
return (
<div className="space-y-4">
<MobileErrorBoundary screenName="FuelLogForm" key="fuel-form">
<FuelLogForm onSuccess={() => {
// Refresh dependent data
try {
queryClient.invalidateQueries({ queryKey: ['fuelLogs'] });
queryClient.invalidateQueries({ queryKey: ['fuelLogsStats'] });
} catch {}
// Navigate back if we have history; otherwise go to Vehicles
if (canGoBack()) {
goBack();
} else {
navigateToScreen('Vehicles', { source: 'fuel-log-added' });
}
}} />
</MobileErrorBoundary>
<MobileErrorBoundary screenName="FuelLogsSection" key="fuel-section">
<GlassCard>
<div className="py-2">
{isLoading ? (
<div className="text-center py-8 text-slate-500">
Loading fuel logs...
</div>
) : (
<FuelLogsList
logs={fuelLogs || []}
onEdit={handleEdit}
onDelete={handleDelete}
/>
)}
</div>
</GlassCard>
</MobileErrorBoundary>
<MobileErrorBoundary screenName="FuelLogEditDialog" key="fuel-edit-dialog">
<FuelLogEditDialog
open={!!editingLog}
log={editingLog}
onClose={handleCloseEdit}
onSave={handleSaveEdit}
/>
</MobileErrorBoundary>
</div>
);
};
interface AddVehicleScreenProps {
onBack: () => void;
onAdded: () => void;
}
const AddVehicleScreen: React.FC<AddVehicleScreenProps> = ({ onBack, onAdded }) => {
const { optimisticCreateVehicle } = useOptimisticVehicles([]);
const handleCreateVehicle = async (data: CreateVehicleRequest) => {
try {
await optimisticCreateVehicle(data);
onAdded();
} catch (error) {
console.error('Failed to create vehicle:', error);
}
};
return (
<div className="space-y-4">
<GlassCard>
<div className="p-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-slate-800">Add Vehicle</h2>
<button
onClick={onBack}
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
>
</button>
</div>
<VehicleForm
onSubmit={handleCreateVehicle}
onCancel={onBack}
/>
</div>
</GlassCard>
</div>
);
};
function App() {
const { isLoading, isAuthenticated, loginWithRedirect, user } = useAuth0();
@@ -152,184 +335,9 @@ function App() {
<MobileDebugPanel visible={import.meta.env.MODE === 'development'} />
);
// Placeholder screens for mobile
const DashboardScreen = () => (
<div className="space-y-4">
<GlassCard>
<div className="text-center py-12">
<h2 className="text-lg font-semibold text-slate-800 mb-2">Dashboard</h2>
<p className="text-slate-500">Coming soon - Vehicle insights and analytics</p>
</div>
</GlassCard>
</div>
);
const LogFuelScreen = () => {
const queryClient = useQueryClient();
const [editingLog, setEditingLog] = useState<FuelLogResponse | null>(null);
// Safe hook usage with error boundary protection
let fuelLogs, isLoading, error;
try {
const hookResult = useFuelLogs();
fuelLogs = hookResult.fuelLogs;
isLoading = hookResult.isLoading;
error = hookResult.error;
} catch (hookError) {
console.error('[LogFuelScreen] Hook error:', hookError);
error = hookError;
}
const handleEdit = (log: FuelLogResponse) => {
// Defensive validation before setting editing log
if (!log || !log.id) {
console.error('[LogFuelScreen] Invalid log data for edit:', log);
return;
}
try {
setEditingLog(log);
} catch (error) {
console.error('[LogFuelScreen] Error setting editing log:', error);
}
};
const handleDelete = async (_logId: string) => {
try {
// Invalidate queries to refresh the data
queryClient.invalidateQueries({ queryKey: ['fuelLogs'] });
queryClient.invalidateQueries({ queryKey: ['fuelLogsStats'] });
} catch (error) {
console.error('Failed to refresh fuel logs after delete:', error);
}
};
const handleSaveEdit = async (id: string, data: UpdateFuelLogRequest) => {
try {
await fuelLogsApi.update(id, data);
// Invalidate queries to refresh the data
queryClient.invalidateQueries({ queryKey: ['fuelLogs'] });
queryClient.invalidateQueries({ queryKey: ['fuelLogsStats'] });
setEditingLog(null);
} catch (error) {
console.error('Failed to update fuel log:', error);
throw error; // Re-throw to let the dialog handle the error
}
};
const handleCloseEdit = () => {
setEditingLog(null);
};
if (error) {
return (
<div className="space-y-4">
<GlassCard>
<div className="text-center py-8">
<p className="text-red-600 mb-4">Failed to load fuel logs</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
>
Retry
</button>
</div>
</GlassCard>
</div>
);
}
// Add loading state for component initialization
if (isLoading === undefined) {
return (
<div className="space-y-4">
<GlassCard>
<div className="text-center py-8 text-slate-500">
Initializing fuel logs...
</div>
</GlassCard>
</div>
);
}
return (
<div className="space-y-4">
<MobileErrorBoundary screenName="FuelLogForm" key="fuel-form">
<FuelLogForm />
</MobileErrorBoundary>
<MobileErrorBoundary screenName="FuelLogsSection" key="fuel-section">
<GlassCard>
<div className="py-2">
{isLoading ? (
<div className="text-center py-8 text-slate-500">
Loading fuel logs...
</div>
) : (
<FuelLogsList
logs={fuelLogs || []}
onEdit={handleEdit}
onDelete={handleDelete}
/>
)}
</div>
</GlassCard>
</MobileErrorBoundary>
{/* Edit Dialog */}
<MobileErrorBoundary screenName="FuelLogEditDialog" key="fuel-edit-dialog">
<FuelLogEditDialog
open={!!editingLog}
log={editingLog}
onClose={handleCloseEdit}
onSave={handleSaveEdit}
/>
</MobileErrorBoundary>
</div>
);
};
// Mobile settings now uses the dedicated MobileSettingsScreen component
const SettingsScreen = MobileSettingsScreen;
const AddVehicleScreen = () => {
// Vehicle creation logic
const { optimisticCreateVehicle } = useOptimisticVehicles([]);
const handleCreateVehicle = async (data: CreateVehicleRequest) => {
try {
await optimisticCreateVehicle(data);
// Success - navigate back to list
handleVehicleAdded();
} catch (error) {
console.error('Failed to create vehicle:', error);
// Error handling is done by the useOptimisticVehicles hook via toast
}
};
return (
<div className="space-y-4">
<GlassCard>
<div className="p-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-slate-800">Add Vehicle</h2>
<button
onClick={handleBackToList}
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
>
</button>
</div>
<VehicleForm
onSubmit={handleCreateVehicle}
onCancel={handleBackToList}
/>
</div>
</GlassCard>
</div>
);
};
if (isLoading) {
if (mobileMode) {
return (
@@ -425,7 +433,7 @@ function App() {
>
<MobileErrorBoundary screenName="Vehicles">
{vehicleSubScreen === 'add' || showAddVehicle ? (
<AddVehicleScreen />
<AddVehicleScreen onBack={handleBackToList} onAdded={handleVehicleAdded} />
) : selectedVehicle && (vehicleSubScreen === 'detail') ? (
<VehicleDetailMobile
vehicle={selectedVehicle}