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,44 @@
/**
* @ai-summary React hook for data synchronization management
*/
import { useEffect, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { DataSyncManager } from '../sync/data-sync';
import { useNavigationStore } from '../store/navigation';
export const useDataSync = () => {
const queryClient = useQueryClient();
const syncManagerRef = useRef<DataSyncManager | null>(null);
const navigationStore = useNavigationStore();
useEffect(() => {
// Initialize data sync manager
syncManagerRef.current = new DataSyncManager(queryClient, {
enableCrossTabs: true,
enableOptimisticUpdates: true,
enableBackgroundSync: true,
syncInterval: 30000,
});
return () => {
syncManagerRef.current?.cleanup();
};
}, [queryClient]);
// Listen for navigation changes and trigger prefetching
useEffect(() => {
if (syncManagerRef.current) {
syncManagerRef.current.prefetchForNavigation(navigationStore.activeScreen);
}
}, [navigationStore.activeScreen]);
return {
optimisticVehicleUpdate: (vehicleId: string, updates: any) => {
syncManagerRef.current?.optimisticVehicleUpdate(vehicleId, updates);
},
prefetchForNavigation: (screen: string) => {
syncManagerRef.current?.prefetchForNavigation(screen);
},
};
};

View File

@@ -0,0 +1,175 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { useNavigationStore } from '../store/navigation';
export interface UseFormStateOptions<T> {
formId: string;
defaultValues: T;
autoSave?: boolean;
saveDelay?: number;
onRestore?: (data: T) => void;
onSave?: (data: T) => void;
validate?: (data: T) => Record<string, string> | null;
}
export interface FormStateReturn<T> {
formData: T;
updateFormData: (updates: Partial<T>) => void;
setFormData: (data: T) => void;
resetForm: () => void;
submitForm: () => Promise<void>;
hasChanges: boolean;
isRestored: boolean;
isSaving: boolean;
errors: Record<string, string>;
isValid: boolean;
}
export const useFormState = <T extends Record<string, any>>({
formId,
defaultValues,
autoSave = true,
saveDelay = 1000,
onRestore,
onSave,
validate,
}: UseFormStateOptions<T>): FormStateReturn<T> => {
const { saveFormState, restoreFormState, clearFormState } = useNavigationStore();
const [formData, setFormDataState] = useState<T>(defaultValues);
const [hasChanges, setHasChanges] = useState(false);
const [isRestored, setIsRestored] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const initialDataRef = useRef<T>(defaultValues);
const formDataRef = useRef<T>(formData);
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Update ref when formData changes
useEffect(() => {
formDataRef.current = formData;
}, [formData]);
// Validation
const validateForm = useCallback((data: T) => {
if (!validate) return {};
const validationErrors = validate(data);
return validationErrors || {};
}, [validate]);
// Restore form state on mount
useEffect(() => {
const restoredState = restoreFormState(formId);
if (restoredState && !isRestored) {
const restoredData = { ...defaultValues, ...restoredState.data };
setFormDataState(restoredData);
setHasChanges(restoredState.isDirty);
setIsRestored(true);
if (onRestore) {
onRestore(restoredData);
}
}
}, [formId, restoreFormState, defaultValues, isRestored, onRestore]);
// Auto-save with debounce
useEffect(() => {
if (!autoSave || !hasChanges) return;
// Clear existing timeout
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current);
}
// Set new timeout
saveTimeoutRef.current = setTimeout(async () => {
try {
setIsSaving(true);
saveFormState(formId, formDataRef.current, hasChanges);
if (onSave) {
await onSave(formDataRef.current);
}
} catch (error) {
console.warn('Form auto-save failed:', error);
} finally {
setIsSaving(false);
}
}, saveDelay);
// Cleanup timeout
return () => {
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current);
}
};
}, [formData, hasChanges, autoSave, saveDelay, formId, saveFormState, onSave]);
// Validate when form data changes
useEffect(() => {
if (hasChanges) {
const validationErrors = validateForm(formData);
setErrors(validationErrors);
}
}, [formData, hasChanges, validateForm]);
const updateFormData = useCallback((updates: Partial<T>) => {
setFormDataState((current) => {
const updated = { ...current, ...updates };
const hasActualChanges = JSON.stringify(updated) !== JSON.stringify(initialDataRef.current);
setHasChanges(hasActualChanges);
return updated;
});
}, []);
const setFormData = useCallback((data: T) => {
setFormDataState(data);
const hasActualChanges = JSON.stringify(data) !== JSON.stringify(initialDataRef.current);
setHasChanges(hasActualChanges);
}, []);
const resetForm = useCallback(() => {
setFormDataState(defaultValues);
setHasChanges(false);
setErrors({});
clearFormState(formId);
initialDataRef.current = { ...defaultValues };
}, [defaultValues, formId, clearFormState]);
const submitForm = useCallback(async () => {
const validationErrors = validateForm(formDataRef.current);
setErrors(validationErrors);
if (Object.keys(validationErrors).length > 0) {
throw new Error('Form validation failed');
}
try {
setHasChanges(false);
clearFormState(formId);
initialDataRef.current = { ...formDataRef.current };
if (onSave) {
await onSave(formDataRef.current);
}
} catch (error) {
setHasChanges(true); // Restore changes state on error
throw error;
}
}, [validateForm, formId, clearFormState, onSave]);
const isValid = Object.keys(errors).length === 0;
return {
formData,
updateFormData,
setFormData,
resetForm,
submitForm,
hasChanges,
isRestored,
isSaving,
errors,
isValid,
};
};