Start integrating the onboarding flow with backend 10
This commit is contained in:
@@ -108,6 +108,72 @@ export const useImportCsvFile = (
|
||||
};
|
||||
|
||||
// Combined validation and import hook for easier use
|
||||
// Validation-only hook for onboarding
|
||||
export const useValidateFileOnly = () => {
|
||||
const validateCsv = useValidateCsvFile();
|
||||
const validateJson = useValidateJsonData();
|
||||
|
||||
const validateFile = async (
|
||||
tenantId: string,
|
||||
file: File,
|
||||
options?: {
|
||||
onProgress?: (stage: string, progress: number, message: string) => void;
|
||||
}
|
||||
): Promise<{
|
||||
validationResult?: ImportValidationResponse;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> => {
|
||||
try {
|
||||
let validationResult: ImportValidationResponse | undefined;
|
||||
|
||||
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.is_valid) {
|
||||
const errorMessage = validationResult.errors && validationResult.errors.length > 0
|
||||
? validationResult.errors.join(', ')
|
||||
: 'Error de validación desconocido';
|
||||
throw new Error(`Archivo inválido: ${errorMessage}`);
|
||||
}
|
||||
|
||||
// Report validation success with details
|
||||
options?.onProgress?.('completed', 100,
|
||||
`Archivo validado: ${validationResult.valid_records} registros válidos de ${validationResult.total_records} totales`
|
||||
);
|
||||
|
||||
return {
|
||||
validationResult,
|
||||
success: true,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error validando archivo';
|
||||
options?.onProgress?.('error', 0, errorMessage);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
validateFile,
|
||||
};
|
||||
};
|
||||
|
||||
// Full validation + import hook (for later use)
|
||||
export const useValidateAndImportFile = () => {
|
||||
const validateCsv = useValidateCsvFile();
|
||||
const validateJson = useValidateJsonData();
|
||||
@@ -147,9 +213,17 @@ export const useValidateAndImportFile = () => {
|
||||
|
||||
options?.onProgress?.('validating', 50, 'Verificando integridad de datos...');
|
||||
|
||||
if (!validationResult.valid) {
|
||||
throw new Error(`Archivo inválido: ${validationResult.errors?.join(', ')}`);
|
||||
if (!validationResult.is_valid) {
|
||||
const errorMessage = validationResult.errors && validationResult.errors.length > 0
|
||||
? validationResult.errors.join(', ')
|
||||
: 'Error de validación desconocido';
|
||||
throw new Error(`Archivo inválido: ${errorMessage}`);
|
||||
}
|
||||
|
||||
// Report validation success with details
|
||||
options?.onProgress?.('validating', 60,
|
||||
`Archivo validado: ${validationResult.valid_records} registros válidos de ${validationResult.total_records} totales`
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Import
|
||||
@@ -180,12 +254,18 @@ export const useValidateAndImportFile = () => {
|
||||
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
|
||||
}
|
||||
|
||||
options?.onProgress?.('completed', 100, 'Importación completada');
|
||||
// Report completion with details
|
||||
const completionMessage = importResult.success
|
||||
? `Importación completada: ${importResult.records_processed} registros procesados`
|
||||
: `Importación fallida: ${importResult.errors?.join(', ') || 'Error desconocido'}`;
|
||||
|
||||
options?.onProgress?.('completed', 100, completionMessage);
|
||||
|
||||
return {
|
||||
validationResult,
|
||||
importResult,
|
||||
success: true,
|
||||
success: importResult.success,
|
||||
error: importResult.success ? undefined : (importResult.errors?.join(', ') || 'Error en la importación'),
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error procesando archivo';
|
||||
|
||||
@@ -384,6 +384,7 @@ export {
|
||||
useValidateCsvFile,
|
||||
useImportJsonData,
|
||||
useImportCsvFile,
|
||||
useValidateFileOnly,
|
||||
useValidateAndImportFile,
|
||||
dataImportKeys,
|
||||
} from './hooks/dataImport';
|
||||
|
||||
@@ -27,9 +27,12 @@ export class DataImportService {
|
||||
tenantId: string,
|
||||
file: File
|
||||
): Promise<ImportValidationResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return apiClient.uploadFile<ImportValidationResponse>(
|
||||
`${this.baseUrl}/${tenantId}/sales/import/validate-csv`,
|
||||
file
|
||||
formData
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,16 @@ import { apiClient } from '../client';
|
||||
import { UserProgress, UpdateStepRequest } from '../types/onboarding';
|
||||
|
||||
export class OnboardingService {
|
||||
private readonly baseUrl = '/onboarding';
|
||||
private readonly baseUrl = '/users/me/onboarding';
|
||||
|
||||
async getUserProgress(userId: string): Promise<UserProgress> {
|
||||
return apiClient.get<UserProgress>(`${this.baseUrl}/progress/${userId}`);
|
||||
// Backend uses current user from auth token, so userId parameter is ignored
|
||||
return apiClient.get<UserProgress>(`${this.baseUrl}/progress`);
|
||||
}
|
||||
|
||||
async updateStep(userId: string, stepData: UpdateStepRequest): Promise<UserProgress> {
|
||||
return apiClient.put<UserProgress>(`${this.baseUrl}/progress/${userId}/step`, stepData);
|
||||
// Backend uses current user from auth token, so userId parameter is ignored
|
||||
return apiClient.put<UserProgress>(`${this.baseUrl}/step`, stepData);
|
||||
}
|
||||
|
||||
async markStepCompleted(
|
||||
@@ -20,14 +22,19 @@ export class OnboardingService {
|
||||
stepName: string,
|
||||
data?: Record<string, any>
|
||||
): Promise<UserProgress> {
|
||||
return apiClient.post<UserProgress>(`${this.baseUrl}/progress/${userId}/complete`, {
|
||||
// Backend uses current user from auth token, so userId parameter is ignored
|
||||
// Backend expects UpdateStepRequest format for completion
|
||||
return apiClient.put<UserProgress>(`${this.baseUrl}/step`, {
|
||||
step_name: stepName,
|
||||
completed: true,
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
async resetProgress(userId: string): Promise<UserProgress> {
|
||||
return apiClient.post<UserProgress>(`${this.baseUrl}/progress/${userId}/reset`);
|
||||
// Note: Backend doesn't have a reset endpoint, this might need to be implemented
|
||||
// For now, we'll throw an error
|
||||
throw new Error('Reset progress functionality not implemented in backend');
|
||||
}
|
||||
|
||||
async getStepDetails(stepName: string): Promise<{
|
||||
@@ -36,7 +43,8 @@ export class OnboardingService {
|
||||
dependencies: string[];
|
||||
estimated_time_minutes: number;
|
||||
}> {
|
||||
return apiClient.get(`${this.baseUrl}/steps/${stepName}`);
|
||||
// This endpoint doesn't exist in backend, we'll need to implement it or mock it
|
||||
throw new Error('getStepDetails functionality not implemented in backend');
|
||||
}
|
||||
|
||||
async getAllSteps(): Promise<Array<{
|
||||
@@ -45,7 +53,23 @@ export class OnboardingService {
|
||||
dependencies: string[];
|
||||
estimated_time_minutes: number;
|
||||
}>> {
|
||||
return apiClient.get(`${this.baseUrl}/steps`);
|
||||
// This endpoint doesn't exist in backend, we'll need to implement it or mock it
|
||||
throw new Error('getAllSteps functionality not implemented in backend');
|
||||
}
|
||||
|
||||
async getNextStep(): Promise<{ step: string; completed?: boolean }> {
|
||||
// This endpoint exists in backend
|
||||
return apiClient.get(`${this.baseUrl}/next-step`);
|
||||
}
|
||||
|
||||
async canAccessStep(stepName: string): Promise<{ can_access: boolean; reason?: string }> {
|
||||
// This endpoint exists in backend
|
||||
return apiClient.get(`${this.baseUrl}/can-access/${stepName}`);
|
||||
}
|
||||
|
||||
async completeOnboarding(): Promise<{ success: boolean; message: string }> {
|
||||
// This endpoint exists in backend
|
||||
return apiClient.post(`${this.baseUrl}/complete`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,30 @@ export interface ImportValidationRequest {
|
||||
}
|
||||
|
||||
export interface ImportValidationResponse {
|
||||
valid: boolean;
|
||||
is_valid: boolean;
|
||||
total_records: number;
|
||||
valid_records: number;
|
||||
invalid_records: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
record_count?: number;
|
||||
sample_records?: any[];
|
||||
summary: {
|
||||
status: string;
|
||||
file_format: string;
|
||||
file_size_bytes: number;
|
||||
file_size_mb: number;
|
||||
estimated_processing_time_seconds: number;
|
||||
validation_timestamp: string;
|
||||
detected_columns: string[];
|
||||
suggestions: string[];
|
||||
};
|
||||
unique_products: number;
|
||||
product_list: string[];
|
||||
message: string;
|
||||
details: {
|
||||
total_records: number;
|
||||
format: string;
|
||||
};
|
||||
sample_records?: any[]; // Keep for backward compatibility
|
||||
}
|
||||
|
||||
export interface ImportProcessRequest {
|
||||
@@ -33,6 +52,18 @@ export interface ImportProcessResponse {
|
||||
records_failed: number;
|
||||
import_id?: string;
|
||||
errors?: string[];
|
||||
import_summary?: {
|
||||
total_records: number;
|
||||
successful_imports: number;
|
||||
failed_imports: number;
|
||||
processing_time_seconds: number;
|
||||
timestamp: string;
|
||||
};
|
||||
details?: {
|
||||
tenant_id: string;
|
||||
file_name?: string;
|
||||
processing_status: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ImportStatusResponse {
|
||||
|
||||
Reference in New Issue
Block a user