Initial Commit
This commit is contained in:
44
frontend/src/core/hooks/useDataSync.ts
Normal file
44
frontend/src/core/hooks/useDataSync.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
};
|
||||
175
frontend/src/core/hooks/useFormState.ts
Normal file
175
frontend/src/core/hooks/useFormState.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user