Start integrating the onboarding flow with backend 7
This commit is contained in:
212
frontend/src/api/hooks/dataImport.ts
Normal file
212
frontend/src/api/hooks/dataImport.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Data Import React Query hooks
|
||||
* Provides data fetching, caching, and state management for data import operations
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { dataImportService } from '../services/dataImport';
|
||||
import { ApiError } from '../client/apiClient';
|
||||
import type {
|
||||
ImportValidationResponse,
|
||||
ImportProcessResponse,
|
||||
ImportStatusResponse,
|
||||
} from '../types/dataImport';
|
||||
|
||||
// Query Keys Factory
|
||||
export const dataImportKeys = {
|
||||
all: ['data-import'] as const,
|
||||
status: (tenantId: string, importId: string) =>
|
||||
[...dataImportKeys.all, 'status', tenantId, importId] as const,
|
||||
} as const;
|
||||
|
||||
// Status Query
|
||||
export const useImportStatus = (
|
||||
tenantId: string,
|
||||
importId: string,
|
||||
options?: Omit<UseQueryOptions<ImportStatusResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ImportStatusResponse, ApiError>({
|
||||
queryKey: dataImportKeys.status(tenantId, importId),
|
||||
queryFn: () => dataImportService.getImportStatus(tenantId, importId),
|
||||
enabled: !!tenantId && !!importId,
|
||||
refetchInterval: 5000, // Poll every 5 seconds for active imports
|
||||
staleTime: 1000, // Consider data stale after 1 second
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Validation Mutations
|
||||
export const useValidateJsonData = (
|
||||
options?: UseMutationOptions<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any }
|
||||
>({
|
||||
mutationFn: ({ tenantId, data }) => dataImportService.validateJsonData(tenantId, data),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useValidateCsvFile = (
|
||||
options?: UseMutationOptions<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportValidationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File }
|
||||
>({
|
||||
mutationFn: ({ tenantId, file }) => dataImportService.validateCsvFile(tenantId, file),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Import Mutations
|
||||
export const useImportJsonData = (
|
||||
options?: UseMutationOptions<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; data: any; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>({
|
||||
mutationFn: ({ tenantId, data, options: importOptions }) =>
|
||||
dataImportService.importJsonData(tenantId, data, importOptions),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useImportCsvFile = (
|
||||
options?: UseMutationOptions<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>
|
||||
) => {
|
||||
return useMutation<
|
||||
ImportProcessResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; file: File; options?: { skip_validation?: boolean; chunk_size?: number } }
|
||||
>({
|
||||
mutationFn: ({ tenantId, file, options: importOptions }) =>
|
||||
dataImportService.importCsvFile(tenantId, file, importOptions),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Combined validation and import hook for easier use
|
||||
export const useValidateAndImportFile = () => {
|
||||
const validateCsv = useValidateCsvFile();
|
||||
const validateJson = useValidateJsonData();
|
||||
const importCsv = useImportCsvFile();
|
||||
const importJson = useImportJsonData();
|
||||
|
||||
const processFile = async (
|
||||
tenantId: string,
|
||||
file: File,
|
||||
options?: {
|
||||
skipValidation?: boolean;
|
||||
chunkSize?: number;
|
||||
onProgress?: (stage: string, progress: number, message: string) => void;
|
||||
}
|
||||
): Promise<{
|
||||
validationResult?: ImportValidationResponse;
|
||||
importResult?: ImportProcessResponse;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> => {
|
||||
try {
|
||||
let validationResult: ImportValidationResponse | undefined;
|
||||
|
||||
// Step 1: Validation (unless skipped)
|
||||
if (!options?.skipValidation) {
|
||||
options?.onProgress?.('validating', 20, 'Validando estructura del archivo...');
|
||||
|
||||
const fileExtension = file.name.split('.').pop()?.toLowerCase();
|
||||
if (fileExtension === 'csv') {
|
||||
validationResult = await validateCsv.mutateAsync({ tenantId, file });
|
||||
} else if (fileExtension === 'json') {
|
||||
const jsonData = await file.text().then(text => JSON.parse(text));
|
||||
validationResult = await validateJson.mutateAsync({ tenantId, data: jsonData });
|
||||
} else {
|
||||
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
|
||||
}
|
||||
|
||||
options?.onProgress?.('validating', 50, 'Verificando integridad de datos...');
|
||||
|
||||
if (!validationResult.valid) {
|
||||
throw new Error(`Archivo inválido: ${validationResult.errors?.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Import
|
||||
options?.onProgress?.('importing', 70, 'Importando datos...');
|
||||
|
||||
const importOptions = {
|
||||
skip_validation: options?.skipValidation || false,
|
||||
chunk_size: options?.chunkSize,
|
||||
};
|
||||
|
||||
let importResult: ImportProcessResponse;
|
||||
const fileExtension = file.name.split('.').pop()?.toLowerCase();
|
||||
|
||||
if (fileExtension === 'csv') {
|
||||
importResult = await importCsv.mutateAsync({
|
||||
tenantId,
|
||||
file,
|
||||
options: importOptions
|
||||
});
|
||||
} else if (fileExtension === 'json') {
|
||||
const jsonData = await file.text().then(text => JSON.parse(text));
|
||||
importResult = await importJson.mutateAsync({
|
||||
tenantId,
|
||||
data: jsonData,
|
||||
options: importOptions
|
||||
});
|
||||
} else {
|
||||
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
|
||||
}
|
||||
|
||||
options?.onProgress?.('completed', 100, 'Importación completada');
|
||||
|
||||
return {
|
||||
validationResult,
|
||||
importResult,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error procesando archivo';
|
||||
options?.onProgress?.('error', 0, errorMessage);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
processFile,
|
||||
validateCsv,
|
||||
validateJson,
|
||||
importCsv,
|
||||
importJson,
|
||||
isValidating: validateCsv.isPending || validateJson.isPending,
|
||||
isImporting: importCsv.isPending || importJson.isPending,
|
||||
isLoading: validateCsv.isPending || validateJson.isPending || importCsv.isPending || importJson.isPending,
|
||||
error: validateCsv.error || validateJson.error || importCsv.error || importJson.error,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user