Initial Commit

This commit is contained in:
Eric Gullickson
2025-09-17 16:09:15 -05:00
parent 0cdb9803de
commit a052040e3a
373 changed files with 437090 additions and 6773 deletions

View File

@@ -0,0 +1,254 @@
/**
* @ai-summary Data synchronization layer integrating React Query with Zustand stores
*/
import { QueryClient } from '@tanstack/react-query';
import { useNavigationStore } from '../store/navigation';
import { useUserStore } from '../store/user';
import { Vehicle } from '../../features/vehicles/types/vehicles.types';
interface SyncConfig {
enableCrossTabs: boolean;
enableOptimisticUpdates: boolean;
enableBackgroundSync: boolean;
syncInterval: number;
}
export class DataSyncManager {
private queryClient: QueryClient;
private config: SyncConfig;
private syncInterval?: NodeJS.Timeout;
private isOnline: boolean = navigator.onLine;
constructor(queryClient: QueryClient, config: Partial<SyncConfig> = {}) {
this.queryClient = queryClient;
this.config = {
enableCrossTabs: true,
enableOptimisticUpdates: true,
enableBackgroundSync: true,
syncInterval: 30000, // 30 seconds
...config,
};
this.initializeSync();
}
private initializeSync() {
// Listen to online/offline events
window.addEventListener('online', this.handleOnline.bind(this));
window.addEventListener('offline', this.handleOffline.bind(this));
// Cross-tab synchronization
if (this.config.enableCrossTabs) {
this.initializeCrossTabSync();
}
// Background sync
if (this.config.enableBackgroundSync) {
this.startBackgroundSync();
}
}
private handleOnline() {
this.isOnline = true;
useUserStore.getState().setOnlineStatus(true);
// Trigger cache revalidation when coming back online
this.queryClient.invalidateQueries();
console.log('DataSync: Back online, revalidating cache');
}
private handleOffline() {
this.isOnline = false;
useUserStore.getState().setOnlineStatus(false);
console.log('DataSync: Offline mode enabled');
}
private initializeCrossTabSync() {
// Listen for storage changes from other tabs
window.addEventListener('storage', (event) => {
if (event.key?.startsWith('motovaultpro-')) {
// Another tab updated store data
if (event.key.includes('user-context')) {
// User data changed in another tab - sync React Query cache
this.syncUserDataFromStorage();
} else if (event.key.includes('mobile-navigation')) {
// Navigation state changed - could affect cache keys
this.syncNavigationFromStorage();
}
}
});
}
private async syncUserDataFromStorage() {
try {
const userData = useUserStore.getState().userProfile;
if (userData) {
// Update query cache with latest user data
this.queryClient.setQueryData(['user', userData.id], userData);
console.log('DataSync: User data synchronized from another tab');
}
} catch (error) {
console.warn('DataSync: Failed to sync user data from storage:', error);
}
}
private async syncNavigationFromStorage() {
try {
const navigationState = useNavigationStore.getState();
// If the selected vehicle changed in another tab, preload its data
if (navigationState.selectedVehicleId) {
await this.queryClient.prefetchQuery({
queryKey: ['vehicles', navigationState.selectedVehicleId],
queryFn: () => this.fetchVehicleById(navigationState.selectedVehicleId!),
});
console.log('DataSync: Vehicle data preloaded from navigation sync');
}
} catch (error) {
console.warn('DataSync: Failed to sync navigation from storage:', error);
}
}
private startBackgroundSync() {
this.syncInterval = setInterval(() => {
if (this.isOnline) {
this.performBackgroundSync();
}
}, this.config.syncInterval);
}
private async performBackgroundSync() {
try {
// Update last sync timestamp
useUserStore.getState().updateLastSync();
// Strategically refresh critical data
const navigationState = useNavigationStore.getState();
// If on vehicles screen, refresh vehicles data
if (navigationState.activeScreen === 'Vehicles') {
await this.queryClient.invalidateQueries({ queryKey: ['vehicles'] });
}
// If viewing specific vehicle, refresh its data
if (navigationState.selectedVehicleId) {
await this.queryClient.invalidateQueries({
queryKey: ['vehicles', navigationState.selectedVehicleId]
});
}
console.log('DataSync: Background sync completed');
} catch (error) {
console.warn('DataSync: Background sync failed:', error);
}
}
// Helper method to fetch vehicle by ID (would normally import from vehicles API)
private async fetchVehicleById(id: string): Promise<Vehicle | null> {
try {
const response = await fetch(`/api/vehicles/${id}`, {
headers: {
'Authorization': this.getAuthHeader(),
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.warn(`Failed to fetch vehicle ${id}:`, error);
return null;
}
}
private getAuthHeader(): string {
// This would integrate with Auth0 token from interceptor
// For now, return empty string as token is handled by axios interceptor
return '';
}
// Public methods for optimistic updates
public async optimisticVehicleUpdate(vehicleId: string, updates: Partial<Vehicle>) {
if (!this.config.enableOptimisticUpdates) return;
try {
// Optimistically update query cache
this.queryClient.setQueryData(['vehicles', vehicleId], (old: Vehicle | undefined) => {
if (!old) return old;
return { ...old, ...updates };
});
// Also update the vehicles list cache
this.queryClient.setQueryData(['vehicles'], (old: Vehicle[] | undefined) => {
if (!old) return old;
return old.map(vehicle =>
vehicle.id === vehicleId ? { ...vehicle, ...updates } : vehicle
);
});
console.log('DataSync: Optimistic vehicle update applied');
} catch (error) {
console.warn('DataSync: Optimistic update failed:', error);
}
}
public async prefetchForNavigation(targetScreen: string) {
try {
switch (targetScreen) {
case 'Vehicles':
// Prefetch vehicles list if not already cached
await this.queryClient.prefetchQuery({
queryKey: ['vehicles'],
queryFn: () => this.fetchVehicles(),
staleTime: 1000 * 60 * 5, // 5 minutes
});
break;
case 'Log Fuel':
// Prefetch vehicles for fuel logging dropdown
await this.queryClient.prefetchQuery({
queryKey: ['vehicles'],
queryFn: () => this.fetchVehicles(),
});
break;
default:
// No specific prefetching needed
break;
}
} catch (error) {
console.warn('DataSync: Prefetch failed for', targetScreen, error);
}
}
private async fetchVehicles(): Promise<Vehicle[]> {
try {
const response = await fetch('/api/vehicles', {
headers: {
'Authorization': this.getAuthHeader(),
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.warn('Failed to fetch vehicles:', error);
return [];
}
}
public cleanup() {
if (this.syncInterval) {
clearInterval(this.syncInterval);
}
window.removeEventListener('online', this.handleOnline);
window.removeEventListener('offline', this.handleOffline);
}
}