REFACTOR ALL APIs

This commit is contained in:
Urtzi Alfaro
2025-10-06 15:27:01 +02:00
parent dc8221bd2f
commit 38fb98bc27
166 changed files with 18454 additions and 13605 deletions

View File

@@ -3,16 +3,15 @@
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { authService } from '../services/auth';
import {
UserRegistration,
UserLogin,
TokenResponse,
PasswordChange,
PasswordReset,
UserResponse,
UserUpdate,
TokenVerificationResponse,
AuthHealthResponse
import {
UserRegistration,
UserLogin,
TokenResponse,
PasswordChange,
PasswordReset,
UserResponse,
UserUpdate,
TokenVerification
} from '../types/auth';
import { ApiError } from '../client';
import { useAuthStore } from '../../stores/auth.store';
@@ -38,9 +37,9 @@ export const useAuthProfile = (
};
export const useAuthHealth = (
options?: Omit<UseQueryOptions<AuthHealthResponse, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<{ status: string; service: string }, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<AuthHealthResponse, ApiError>({
return useQuery<{ status: string; service: string }, ApiError>({
queryKey: authKeys.health(),
queryFn: () => authService.healthCheck(),
staleTime: 30 * 1000, // 30 seconds
@@ -50,9 +49,9 @@ export const useAuthHealth = (
export const useVerifyToken = (
token?: string,
options?: Omit<UseQueryOptions<TokenVerificationResponse, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<TokenVerification, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TokenVerificationResponse, ApiError>({
return useQuery<TokenVerification, ApiError>({
queryKey: authKeys.verify(token),
queryFn: () => authService.verifyToken(token),
enabled: !!token,
@@ -153,7 +152,7 @@ export const useUpdateProfile = (
// Update the auth store user to maintain consistency
const authStore = useAuthStore.getState();
if (authStore.user) {
authStore.updateUser(data);
authStore.updateUser(data as any);
}
},
...options,

View File

@@ -1,75 +0,0 @@
/**
* Classification React Query hooks
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { classificationService } from '../services/classification';
import {
ProductClassificationRequest,
BatchClassificationRequest,
ProductSuggestionResponse
} from '../types/classification';
import { ApiError } from '../client';
// Query Keys
export const classificationKeys = {
all: ['classification'] as const,
suggestions: {
all: () => [...classificationKeys.all, 'suggestions'] as const,
pending: (tenantId: string) => [...classificationKeys.suggestions.all(), 'pending', tenantId] as const,
history: (tenantId: string, limit?: number, offset?: number) =>
[...classificationKeys.suggestions.all(), 'history', tenantId, { limit, offset }] as const,
},
analysis: {
all: () => [...classificationKeys.all, 'analysis'] as const,
businessModel: (tenantId: string) => [...classificationKeys.analysis.all(), 'business-model', tenantId] as const,
},
} as const;
// Mutations
export const useClassifyProduct = (
options?: UseMutationOptions<
ProductSuggestionResponse,
ApiError,
{ tenantId: string; classificationData: ProductClassificationRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
ProductSuggestionResponse,
ApiError,
{ tenantId: string; classificationData: ProductClassificationRequest }
>({
mutationFn: ({ tenantId, classificationData }) =>
classificationService.classifyProduct(tenantId, classificationData),
onSuccess: (data, { tenantId }) => {
// Invalidate pending suggestions to include the new one
queryClient.invalidateQueries({ queryKey: classificationKeys.suggestions.pending(tenantId) });
},
...options,
});
};
export const useClassifyProductsBatch = (
options?: UseMutationOptions<
ProductSuggestionResponse[],
ApiError,
{ tenantId: string; batchData: BatchClassificationRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
ProductSuggestionResponse[],
ApiError,
{ tenantId: string; batchData: BatchClassificationRequest }
>({
mutationFn: ({ tenantId, batchData }) =>
classificationService.classifyProductsBatch(tenantId, batchData),
onSuccess: (data, { tenantId }) => {
// Invalidate pending suggestions to include the new ones
queryClient.invalidateQueries({ queryKey: classificationKeys.suggestions.pending(tenantId) });
},
...options,
});
};

View File

@@ -1,365 +0,0 @@
/**
* 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
// 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();
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.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
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.');
}
// 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: 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';
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,
};
};
// Import-only hook (for when validation has already been done)
export const useImportFileOnly = () => {
const importCsv = useImportCsvFile();
const importJson = useImportJsonData();
const importFile = async (
tenantId: string,
file: File,
options?: {
chunkSize?: number;
onProgress?: (stage: string, progress: number, message: string) => void;
}
): Promise<{
importResult?: ImportProcessResponse;
success: boolean;
error?: string;
}> => {
try {
options?.onProgress?.('importing', 10, 'Iniciando importación de datos...');
const fileExtension = file.name.split('.').pop()?.toLowerCase();
let importResult: ImportProcessResponse;
if (fileExtension === 'csv') {
importResult = await importCsv.mutateAsync({
tenantId,
file,
options: {
skip_validation: true, // Skip validation since already done
chunk_size: options?.chunkSize
}
});
} else if (fileExtension === 'json') {
const jsonData = await file.text().then(text => JSON.parse(text));
importResult = await importJson.mutateAsync({
tenantId,
data: jsonData,
options: {
skip_validation: true, // Skip validation since already done
chunk_size: options?.chunkSize
}
});
} else {
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
}
options?.onProgress?.('completed', 100,
`Importación completada: ${importResult.records_processed} registros procesados`
);
return {
importResult,
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 importando archivo';
options?.onProgress?.('error', 0, errorMessage);
return {
success: false,
error: errorMessage,
};
}
};
return {
importFile,
isImporting: importCsv.isPending || importJson.isPending,
error: importCsv.error || importJson.error,
};
};

View File

@@ -1,384 +0,0 @@
/**
* Food Safety React Query hooks
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { foodSafetyService } from '../services/foodSafety';
import {
FoodSafetyComplianceCreate,
FoodSafetyComplianceUpdate,
FoodSafetyComplianceResponse,
TemperatureLogCreate,
BulkTemperatureLogCreate,
TemperatureLogResponse,
FoodSafetyAlertCreate,
FoodSafetyAlertUpdate,
FoodSafetyAlertResponse,
FoodSafetyFilter,
TemperatureMonitoringFilter,
FoodSafetyMetrics,
TemperatureAnalytics,
FoodSafetyDashboard,
} from '../types/foodSafety';
import { PaginatedResponse } from '../types/inventory';
import { ApiError } from '../client';
// Query Keys
export const foodSafetyKeys = {
all: ['food-safety'] as const,
compliance: {
all: () => [...foodSafetyKeys.all, 'compliance'] as const,
lists: () => [...foodSafetyKeys.compliance.all(), 'list'] as const,
list: (tenantId: string, filter?: FoodSafetyFilter) =>
[...foodSafetyKeys.compliance.lists(), tenantId, filter] as const,
details: () => [...foodSafetyKeys.compliance.all(), 'detail'] as const,
detail: (tenantId: string, recordId: string) =>
[...foodSafetyKeys.compliance.details(), tenantId, recordId] as const,
},
temperature: {
all: () => [...foodSafetyKeys.all, 'temperature'] as const,
lists: () => [...foodSafetyKeys.temperature.all(), 'list'] as const,
list: (tenantId: string, filter?: TemperatureMonitoringFilter) =>
[...foodSafetyKeys.temperature.lists(), tenantId, filter] as const,
analytics: (tenantId: string, location: string, startDate?: string, endDate?: string) =>
[...foodSafetyKeys.temperature.all(), 'analytics', tenantId, location, { startDate, endDate }] as const,
violations: (tenantId: string, limit?: number) =>
[...foodSafetyKeys.temperature.all(), 'violations', tenantId, limit] as const,
},
alerts: {
all: () => [...foodSafetyKeys.all, 'alerts'] as const,
lists: () => [...foodSafetyKeys.alerts.all(), 'list'] as const,
list: (tenantId: string, status?: string, severity?: string, limit?: number, offset?: number) =>
[...foodSafetyKeys.alerts.lists(), tenantId, { status, severity, limit, offset }] as const,
details: () => [...foodSafetyKeys.alerts.all(), 'detail'] as const,
detail: (tenantId: string, alertId: string) =>
[...foodSafetyKeys.alerts.details(), tenantId, alertId] as const,
},
dashboard: (tenantId: string) =>
[...foodSafetyKeys.all, 'dashboard', tenantId] as const,
metrics: (tenantId: string, startDate?: string, endDate?: string) =>
[...foodSafetyKeys.all, 'metrics', tenantId, { startDate, endDate }] as const,
complianceRate: (tenantId: string, startDate?: string, endDate?: string) =>
[...foodSafetyKeys.all, 'compliance-rate', tenantId, { startDate, endDate }] as const,
} as const;
// Compliance Queries
export const useComplianceRecords = (
tenantId: string,
filter?: FoodSafetyFilter,
options?: Omit<UseQueryOptions<PaginatedResponse<FoodSafetyComplianceResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<FoodSafetyComplianceResponse>, ApiError>({
queryKey: foodSafetyKeys.compliance.list(tenantId, filter),
queryFn: () => foodSafetyService.getComplianceRecords(tenantId, filter),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const useComplianceRecord = (
tenantId: string,
recordId: string,
options?: Omit<UseQueryOptions<FoodSafetyComplianceResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<FoodSafetyComplianceResponse, ApiError>({
queryKey: foodSafetyKeys.compliance.detail(tenantId, recordId),
queryFn: () => foodSafetyService.getComplianceRecord(tenantId, recordId),
enabled: !!tenantId && !!recordId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
// Temperature Monitoring Queries
export const useTemperatureLogs = (
tenantId: string,
filter?: TemperatureMonitoringFilter,
options?: Omit<UseQueryOptions<PaginatedResponse<TemperatureLogResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<TemperatureLogResponse>, ApiError>({
queryKey: foodSafetyKeys.temperature.list(tenantId, filter),
queryFn: () => foodSafetyService.getTemperatureLogs(tenantId, filter),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useTemperatureAnalytics = (
tenantId: string,
location: string,
startDate?: string,
endDate?: string,
options?: Omit<UseQueryOptions<TemperatureAnalytics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TemperatureAnalytics, ApiError>({
queryKey: foodSafetyKeys.temperature.analytics(tenantId, location, startDate, endDate),
queryFn: () => foodSafetyService.getTemperatureAnalytics(tenantId, location, startDate, endDate),
enabled: !!tenantId && !!location,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
export const useTemperatureViolations = (
tenantId: string,
limit: number = 20,
options?: Omit<UseQueryOptions<TemperatureLogResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TemperatureLogResponse[], ApiError>({
queryKey: foodSafetyKeys.temperature.violations(tenantId, limit),
queryFn: () => foodSafetyService.getTemperatureViolations(tenantId, limit),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
// Alert Queries
export const useFoodSafetyAlerts = (
tenantId: string,
status?: 'open' | 'in_progress' | 'resolved' | 'dismissed',
severity?: 'critical' | 'warning' | 'info',
limit: number = 50,
offset: number = 0,
options?: Omit<UseQueryOptions<PaginatedResponse<FoodSafetyAlertResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<FoodSafetyAlertResponse>, ApiError>({
queryKey: foodSafetyKeys.alerts.list(tenantId, status, severity, limit, offset),
queryFn: () => foodSafetyService.getFoodSafetyAlerts(tenantId, status, severity, limit, offset),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useFoodSafetyAlert = (
tenantId: string,
alertId: string,
options?: Omit<UseQueryOptions<FoodSafetyAlertResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<FoodSafetyAlertResponse, ApiError>({
queryKey: foodSafetyKeys.alerts.detail(tenantId, alertId),
queryFn: () => foodSafetyService.getFoodSafetyAlert(tenantId, alertId),
enabled: !!tenantId && !!alertId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
// Dashboard and Metrics Queries
export const useFoodSafetyDashboard = (
tenantId: string,
options?: Omit<UseQueryOptions<FoodSafetyDashboard, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<FoodSafetyDashboard, ApiError>({
queryKey: foodSafetyKeys.dashboard(tenantId),
queryFn: () => foodSafetyService.getFoodSafetyDashboard(tenantId),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useFoodSafetyMetrics = (
tenantId: string,
startDate?: string,
endDate?: string,
options?: Omit<UseQueryOptions<FoodSafetyMetrics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<FoodSafetyMetrics, ApiError>({
queryKey: foodSafetyKeys.metrics(tenantId, startDate, endDate),
queryFn: () => foodSafetyService.getFoodSafetyMetrics(tenantId, startDate, endDate),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
export const useComplianceRate = (
tenantId: string,
startDate?: string,
endDate?: string,
options?: Omit<UseQueryOptions<{
overall_rate: number;
by_type: Record<string, number>;
trend: Array<{ date: string; rate: number }>;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<{
overall_rate: number;
by_type: Record<string, number>;
trend: Array<{ date: string; rate: number }>;
}, ApiError>({
queryKey: foodSafetyKeys.complianceRate(tenantId, startDate, endDate),
queryFn: () => foodSafetyService.getComplianceRate(tenantId, startDate, endDate),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
// Compliance Mutations
export const useCreateComplianceRecord = (
options?: UseMutationOptions<
FoodSafetyComplianceResponse,
ApiError,
{ tenantId: string; complianceData: FoodSafetyComplianceCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
FoodSafetyComplianceResponse,
ApiError,
{ tenantId: string; complianceData: FoodSafetyComplianceCreate }
>({
mutationFn: ({ tenantId, complianceData }) =>
foodSafetyService.createComplianceRecord(tenantId, complianceData),
onSuccess: (data, { tenantId }) => {
// Add to cache
queryClient.setQueryData(foodSafetyKeys.compliance.detail(tenantId, data.id), data);
// Invalidate lists
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.compliance.lists() });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.dashboard(tenantId) });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.metrics(tenantId) });
},
...options,
});
};
export const useUpdateComplianceRecord = (
options?: UseMutationOptions<
FoodSafetyComplianceResponse,
ApiError,
{ tenantId: string; recordId: string; updateData: FoodSafetyComplianceUpdate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
FoodSafetyComplianceResponse,
ApiError,
{ tenantId: string; recordId: string; updateData: FoodSafetyComplianceUpdate }
>({
mutationFn: ({ tenantId, recordId, updateData }) =>
foodSafetyService.updateComplianceRecord(tenantId, recordId, updateData),
onSuccess: (data, { tenantId, recordId }) => {
// Update cache
queryClient.setQueryData(foodSafetyKeys.compliance.detail(tenantId, recordId), data);
// Invalidate lists
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.compliance.lists() });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.dashboard(tenantId) });
},
...options,
});
};
// Temperature Mutations
export const useCreateTemperatureLog = (
options?: UseMutationOptions<
TemperatureLogResponse,
ApiError,
{ tenantId: string; logData: TemperatureLogCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
TemperatureLogResponse,
ApiError,
{ tenantId: string; logData: TemperatureLogCreate }
>({
mutationFn: ({ tenantId, logData }) => foodSafetyService.createTemperatureLog(tenantId, logData),
onSuccess: (data, { tenantId }) => {
// Invalidate temperature queries
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.temperature.lists() });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.dashboard(tenantId) });
// If alert was triggered, invalidate alerts
if (data.alert_triggered) {
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.alerts.lists() });
}
},
...options,
});
};
export const useCreateBulkTemperatureLogs = (
options?: UseMutationOptions<
{ created_count: number; failed_count: number; errors?: string[] },
ApiError,
{ tenantId: string; bulkData: BulkTemperatureLogCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ created_count: number; failed_count: number; errors?: string[] },
ApiError,
{ tenantId: string; bulkData: BulkTemperatureLogCreate }
>({
mutationFn: ({ tenantId, bulkData }) => foodSafetyService.createBulkTemperatureLogs(tenantId, bulkData),
onSuccess: (data, { tenantId }) => {
// Invalidate temperature queries
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.temperature.lists() });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.dashboard(tenantId) });
},
...options,
});
};
// Alert Mutations
export const useCreateFoodSafetyAlert = (
options?: UseMutationOptions<
FoodSafetyAlertResponse,
ApiError,
{ tenantId: string; alertData: FoodSafetyAlertCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
FoodSafetyAlertResponse,
ApiError,
{ tenantId: string; alertData: FoodSafetyAlertCreate }
>({
mutationFn: ({ tenantId, alertData }) => foodSafetyService.createFoodSafetyAlert(tenantId, alertData),
onSuccess: (data, { tenantId }) => {
// Add to cache
queryClient.setQueryData(foodSafetyKeys.alerts.detail(tenantId, data.id), data);
// Invalidate lists
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.alerts.lists() });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.dashboard(tenantId) });
},
...options,
});
};
export const useUpdateFoodSafetyAlert = (
options?: UseMutationOptions<
FoodSafetyAlertResponse,
ApiError,
{ tenantId: string; alertId: string; updateData: FoodSafetyAlertUpdate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
FoodSafetyAlertResponse,
ApiError,
{ tenantId: string; alertId: string; updateData: FoodSafetyAlertUpdate }
>({
mutationFn: ({ tenantId, alertId, updateData }) =>
foodSafetyService.updateFoodSafetyAlert(tenantId, alertId, updateData),
onSuccess: (data, { tenantId, alertId }) => {
// Update cache
queryClient.setQueryData(foodSafetyKeys.alerts.detail(tenantId, alertId), data);
// Invalidate lists
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.alerts.lists() });
queryClient.invalidateQueries({ queryKey: foodSafetyKeys.dashboard(tenantId) });
},
...options,
});
};

View File

@@ -16,12 +16,8 @@ import {
ForecastResponse,
BatchForecastRequest,
BatchForecastResponse,
ForecastListResponse,
ForecastByIdResponse,
ForecastStatistics,
DeleteForecastResponse,
GetForecastsParams,
ForecastingHealthResponse,
ListForecastsParams,
ForecastStatisticsParams,
} from '../types/forecasting';
import { ApiError } from '../client/apiClient';
@@ -32,7 +28,7 @@ import { ApiError } from '../client/apiClient';
export const forecastingKeys = {
all: ['forecasting'] as const,
lists: () => [...forecastingKeys.all, 'list'] as const,
list: (tenantId: string, filters?: GetForecastsParams) =>
list: (tenantId: string, filters?: ListForecastsParams) =>
[...forecastingKeys.lists(), tenantId, filters] as const,
details: () => [...forecastingKeys.all, 'detail'] as const,
detail: (tenantId: string, forecastId: string) =>
@@ -51,10 +47,10 @@ export const forecastingKeys = {
*/
export const useTenantForecasts = (
tenantId: string,
params?: GetForecastsParams,
options?: Omit<UseQueryOptions<ForecastListResponse, ApiError>, 'queryKey' | 'queryFn'>
params?: ListForecastsParams,
options?: Omit<UseQueryOptions<{ forecasts: ForecastResponse[]; total: number }, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ForecastListResponse, ApiError>({
return useQuery<{ forecasts: ForecastResponse[]; total: number }, ApiError>({
queryKey: forecastingKeys.list(tenantId, params),
queryFn: () => forecastingService.getTenantForecasts(tenantId, params),
staleTime: 2 * 60 * 1000, // 2 minutes
@@ -69,9 +65,9 @@ export const useTenantForecasts = (
export const useForecastById = (
tenantId: string,
forecastId: string,
options?: Omit<UseQueryOptions<ForecastByIdResponse, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<ForecastResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ForecastByIdResponse, ApiError>({
return useQuery<ForecastResponse, ApiError>({
queryKey: forecastingKeys.detail(tenantId, forecastId),
queryFn: () => forecastingService.getForecastById(tenantId, forecastId),
staleTime: 5 * 60 * 1000, // 5 minutes
@@ -85,9 +81,9 @@ export const useForecastById = (
*/
export const useForecastStatistics = (
tenantId: string,
options?: Omit<UseQueryOptions<ForecastStatistics, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<any, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ForecastStatistics, ApiError>({
return useQuery<any, ApiError>({
queryKey: forecastingKeys.statistics(tenantId),
queryFn: () => forecastingService.getForecastStatistics(tenantId),
staleTime: 5 * 60 * 1000, // 5 minutes
@@ -100,9 +96,9 @@ export const useForecastStatistics = (
* Health check for forecasting service
*/
export const useForecastingHealth = (
options?: Omit<UseQueryOptions<ForecastingHealthResponse, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<{ status: string; service: string }, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ForecastingHealthResponse, ApiError>({
return useQuery<{ status: string; service: string }, ApiError>({
queryKey: forecastingKeys.health(),
queryFn: () => forecastingService.getHealthCheck(),
staleTime: 30 * 1000, // 30 seconds
@@ -119,24 +115,25 @@ export const useForecastingHealth = (
*/
export const useInfiniteTenantForecasts = (
tenantId: string,
baseParams?: Omit<GetForecastsParams, 'skip' | 'limit'>,
options?: Omit<UseInfiniteQueryOptions<ForecastListResponse, ApiError>, 'queryKey' | 'queryFn' | 'getNextPageParam'>
baseParams?: Omit<ListForecastsParams, 'skip' | 'limit'>,
options?: Omit<UseInfiniteQueryOptions<{ forecasts: ForecastResponse[]; total: number }, ApiError>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>
) => {
const limit = baseParams?.limit || 20;
const limit = 20;
return useInfiniteQuery<ForecastListResponse, ApiError>({
return useInfiniteQuery<{ forecasts: ForecastResponse[]; total: number }, ApiError>({
queryKey: [...forecastingKeys.list(tenantId, baseParams), 'infinite'],
queryFn: ({ pageParam = 0 }) => {
const params: GetForecastsParams = {
const params: ListForecastsParams = {
...baseParams,
skip: pageParam as number,
limit,
};
return forecastingService.getTenantForecasts(tenantId, params);
},
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
const totalFetched = allPages.reduce((sum, page) => sum + page.total_returned, 0);
return lastPage.total_returned === limit ? totalFetched : undefined;
const totalFetched = allPages.reduce((sum, page) => sum + page.forecasts.length, 0);
return lastPage.forecasts.length === limit ? totalFetched : undefined;
},
staleTime: 2 * 60 * 1000, // 2 minutes
enabled: !!tenantId,
@@ -222,11 +219,7 @@ export const useCreateBatchForecast = (
data.forecasts.forEach((forecast) => {
queryClient.setQueryData(
forecastingKeys.detail(variables.tenantId, forecast.id),
{
...forecast,
enhanced_features: true,
repository_integration: true,
} as ForecastByIdResponse
forecast
);
});
}
@@ -245,7 +238,7 @@ export const useCreateBatchForecast = (
*/
export const useDeleteForecast = (
options?: UseMutationOptions<
DeleteForecastResponse,
{ message: string },
ApiError,
{ tenantId: string; forecastId: string }
>
@@ -253,7 +246,7 @@ export const useDeleteForecast = (
const queryClient = useQueryClient();
return useMutation<
DeleteForecastResponse,
{ message: string },
ApiError,
{ tenantId: string; forecastId: string }
>({

View File

@@ -3,7 +3,7 @@
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { inventoryService } from '../services/inventory';
import { transformationService } from '../services/transformations';
// inventoryService merged into inventoryService
import {
IngredientCreate,
IngredientUpdate,
@@ -300,7 +300,7 @@ export const useHardDeleteIngredient = (
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.byCategory(tenantId) });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
queryClient.invalidateQueries({ queryKey: inventoryKeys.analytics.all() });
queryClient.invalidateQueries({ queryKey: inventoryKeys.all });
},
...options,
});
@@ -427,7 +427,6 @@ export const useStockOperations = (tenantId: string) => {
// Create stock entry via backend API
const stockData: StockCreate = {
ingredient_id: ingredientId,
quantity,
unit_price: unit_cost || 0,
notes
};
@@ -475,7 +474,7 @@ export const useStockOperations = (tenantId: string) => {
// Create adjustment movement via backend API
const movementData: StockMovementCreate = {
ingredient_id: ingredientId,
movement_type: 'adjustment',
movement_type: 'ADJUSTMENT' as any,
quantity,
notes
};
@@ -512,7 +511,7 @@ export const useTransformations = (
) => {
return useQuery<ProductTransformationResponse[], ApiError>({
queryKey: inventoryKeys.transformations.list(tenantId, options),
queryFn: () => transformationService.getTransformations(tenantId, options),
queryFn: () => inventoryService.getTransformations(tenantId, options),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...queryOptions,
@@ -526,7 +525,7 @@ export const useTransformation = (
) => {
return useQuery<ProductTransformationResponse, ApiError>({
queryKey: inventoryKeys.transformations.detail(tenantId, transformationId),
queryFn: () => transformationService.getTransformation(tenantId, transformationId),
queryFn: () => inventoryService.getTransformation(tenantId, transformationId),
enabled: !!tenantId && !!transformationId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
@@ -540,7 +539,7 @@ export const useTransformationSummary = (
) => {
return useQuery<any, ApiError>({
queryKey: inventoryKeys.transformations.summary(tenantId, daysBack),
queryFn: () => transformationService.getTransformationSummary(tenantId, daysBack),
queryFn: () => inventoryService.getTransformationSummary(tenantId, daysBack),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
@@ -555,7 +554,7 @@ export const useTransformationsByIngredient = (
) => {
return useQuery<ProductTransformationResponse[], ApiError>({
queryKey: inventoryKeys.transformations.byIngredient(tenantId, ingredientId),
queryFn: () => transformationService.getTransformationsForIngredient(tenantId, ingredientId, limit),
queryFn: () => inventoryService.getTransformationsForIngredient(tenantId, ingredientId, limit),
enabled: !!tenantId && !!ingredientId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
@@ -571,7 +570,7 @@ export const useTransformationsByStage = (
) => {
return useQuery<ProductTransformationResponse[], ApiError>({
queryKey: inventoryKeys.transformations.byStage(tenantId, sourceStage, targetStage),
queryFn: () => transformationService.getTransformationsByStage(tenantId, sourceStage, targetStage, limit),
queryFn: () => inventoryService.getTransformationsByStage(tenantId, sourceStage, targetStage, limit),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
@@ -595,7 +594,7 @@ export const useCreateTransformation = (
{ tenantId: string; transformationData: ProductTransformationCreate }
>({
mutationFn: ({ tenantId, transformationData }) =>
transformationService.createTransformation(tenantId, transformationData),
inventoryService.createTransformation(tenantId, transformationData),
onSuccess: (data, { tenantId, transformationData }) => {
// Add to cache
queryClient.setQueryData(
@@ -650,7 +649,7 @@ export const useParBakeTransformation = (
}
>({
mutationFn: ({ tenantId, ...transformationOptions }) =>
transformationService.createParBakeToFreshTransformation(tenantId, transformationOptions),
inventoryService.createParBakeToFreshTransformation(tenantId, transformationOptions),
onSuccess: (data, { tenantId, source_ingredient_id, target_ingredient_id }) => {
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: inventoryKeys.transformations.lists() });
@@ -688,7 +687,7 @@ export const useTransformationOperations = (tenantId: string) => {
expirationHours?: number;
notes?: string;
}) => {
return transformationService.bakeParBakedCroissants(
return inventoryService.bakeParBakedCroissants(
tenantId,
parBakedIngredientId,
freshBakedIngredientId,
@@ -715,7 +714,7 @@ export const useTransformationOperations = (tenantId: string) => {
quantity: number;
notes?: string;
}) => {
return transformationService.transformFrozenToPrepared(
return inventoryService.transformFrozenToPrepared(
tenantId,
frozenIngredientId,
preparedIngredientId,
@@ -735,4 +734,13 @@ export const useTransformationOperations = (tenantId: string) => {
bakeParBakedCroissants,
transformFrozenToPrepared,
};
};
};
// Classification operations
export const useClassifyBatch = (
options?: UseMutationOptions<any, ApiError, { tenantId: string; products: { product_name: string }[] }>
) => {
return useMutation<any, ApiError, { tenantId: string; products: { product_name: string }[] }>({
mutationFn: ({ tenantId, products }) => inventoryService.classifyBatch(tenantId, { products }),
...options,
});
};

View File

@@ -1,183 +0,0 @@
/**
* Inventory Dashboard React Query hooks
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { inventoryDashboardService } from '../services/inventoryDashboard';
import {
InventoryDashboardSummary,
InventoryAnalytics,
BusinessModelInsights,
DashboardFilter,
AlertsFilter,
RecentActivity,
} from '../types/dashboard';
import { ApiError } from '../client';
// Query Keys
export const inventoryDashboardKeys = {
all: ['inventory-dashboard'] as const,
summary: (tenantId: string, filter?: DashboardFilter) =>
[...inventoryDashboardKeys.all, 'summary', tenantId, filter] as const,
analytics: (tenantId: string, startDate?: string, endDate?: string) =>
[...inventoryDashboardKeys.all, 'analytics', tenantId, { startDate, endDate }] as const,
insights: (tenantId: string) =>
[...inventoryDashboardKeys.all, 'business-insights', tenantId] as const,
activity: (tenantId: string, limit?: number) =>
[...inventoryDashboardKeys.all, 'recent-activity', tenantId, limit] as const,
alerts: (tenantId: string, filter?: AlertsFilter) =>
[...inventoryDashboardKeys.all, 'alerts', tenantId, filter] as const,
stockSummary: (tenantId: string) =>
[...inventoryDashboardKeys.all, 'stock-summary', tenantId] as const,
topCategories: (tenantId: string, limit?: number) =>
[...inventoryDashboardKeys.all, 'top-categories', tenantId, limit] as const,
expiryCalendar: (tenantId: string, daysAhead?: number) =>
[...inventoryDashboardKeys.all, 'expiry-calendar', tenantId, daysAhead] as const,
} as const;
// Queries
export const useInventoryDashboardSummary = (
tenantId: string,
filter?: DashboardFilter,
options?: Omit<UseQueryOptions<InventoryDashboardSummary, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<InventoryDashboardSummary, ApiError>({
queryKey: inventoryDashboardKeys.summary(tenantId, filter),
queryFn: () => inventoryDashboardService.getDashboardSummary(tenantId, filter),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useInventoryAnalytics = (
tenantId: string,
startDate?: string,
endDate?: string,
options?: Omit<UseQueryOptions<InventoryAnalytics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<InventoryAnalytics, ApiError>({
queryKey: inventoryDashboardKeys.analytics(tenantId, startDate, endDate),
queryFn: () => inventoryDashboardService.getInventoryAnalytics(tenantId, startDate, endDate),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
export const useBusinessModelInsights = (
tenantId: string,
options?: Omit<UseQueryOptions<BusinessModelInsights, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<BusinessModelInsights, ApiError>({
queryKey: inventoryDashboardKeys.insights(tenantId),
queryFn: () => inventoryDashboardService.getBusinessModelInsights(tenantId),
enabled: !!tenantId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
});
};
export const useRecentActivity = (
tenantId: string,
limit: number = 20,
options?: Omit<UseQueryOptions<RecentActivity[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<RecentActivity[], ApiError>({
queryKey: inventoryDashboardKeys.activity(tenantId, limit),
queryFn: () => inventoryDashboardService.getRecentActivity(tenantId, limit),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useInventoryAlerts = (
tenantId: string,
filter?: AlertsFilter,
options?: Omit<UseQueryOptions<{ items: any[]; total: number }, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<{ items: any[]; total: number }, ApiError>({
queryKey: inventoryDashboardKeys.alerts(tenantId, filter),
queryFn: () => inventoryDashboardService.getAlerts(tenantId, filter),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useStockSummary = (
tenantId: string,
options?: Omit<UseQueryOptions<{
in_stock: number;
low_stock: number;
out_of_stock: number;
overstock: number;
total_value: number;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<{
in_stock: number;
low_stock: number;
out_of_stock: number;
overstock: number;
total_value: number;
}, ApiError>({
queryKey: inventoryDashboardKeys.stockSummary(tenantId),
queryFn: () => inventoryDashboardService.getStockSummary(tenantId),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const useTopCategories = (
tenantId: string,
limit: number = 10,
options?: Omit<UseQueryOptions<Array<{
category: string;
ingredient_count: number;
total_value: number;
low_stock_count: number;
}>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<Array<{
category: string;
ingredient_count: number;
total_value: number;
low_stock_count: number;
}>, ApiError>({
queryKey: inventoryDashboardKeys.topCategories(tenantId, limit),
queryFn: () => inventoryDashboardService.getTopCategories(tenantId, limit),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
export const useExpiryCalendar = (
tenantId: string,
daysAhead: number = 30,
options?: Omit<UseQueryOptions<Array<{
date: string;
items: Array<{
ingredient_name: string;
quantity: number;
batch_number?: string;
}>;
}>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<Array<{
date: string;
items: Array<{
ingredient_name: string;
quantity: number;
batch_number?: string;
}>;
}>, ApiError>({
queryKey: inventoryDashboardKeys.expiryCalendar(tenantId, daysAhead),
queryFn: () => inventoryDashboardService.getExpiryCalendar(tenantId, daysAhead),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};

View File

@@ -10,9 +10,8 @@ import type {
ProductionBatchListResponse,
ProductionDashboardSummary,
DailyProductionRequirements,
ProductionScheduleData,
ProductionScheduleUpdate,
ProductionCapacityStatus,
ProductionRequirements,
ProductionYieldMetrics,
} from '../types/production';
import { ApiError } from '../client';
@@ -152,8 +151,8 @@ export const useYieldMetrics = (
) => {
return useQuery<any, ApiError>({
queryKey: productionKeys.yieldMetrics(tenantId, startDate, endDate),
queryFn: () => productionService.getYieldTrends(tenantId, startDate, endDate),
enabled: !!tenantId && !!startDate && !!endDate,
queryFn: () => productionService.getYieldTrends(tenantId),
enabled: !!tenantId,
staleTime: 15 * 60 * 1000, // 15 minutes (metrics are less frequently changing)
...options,
});

View File

@@ -19,7 +19,6 @@ import type {
RecipeResponse,
RecipeCreate,
RecipeUpdate,
RecipeSearchParams,
RecipeDuplicateRequest,
RecipeFeasibilityResponse,
RecipeStatisticsResponse,
@@ -31,7 +30,7 @@ export const recipesKeys = {
all: ['recipes'] as const,
tenant: (tenantId: string) => [...recipesKeys.all, 'tenant', tenantId] as const,
lists: (tenantId: string) => [...recipesKeys.tenant(tenantId), 'list'] as const,
list: (tenantId: string, filters: RecipeSearchParams) => [...recipesKeys.lists(tenantId), { filters }] as const,
list: (tenantId: string, filters: any) => [...recipesKeys.lists(tenantId), { filters }] as const,
details: (tenantId: string) => [...recipesKeys.tenant(tenantId), 'detail'] as const,
detail: (tenantId: string, id: string) => [...recipesKeys.details(tenantId), id] as const,
statistics: (tenantId: string) => [...recipesKeys.tenant(tenantId), 'statistics'] as const,
@@ -63,7 +62,7 @@ export const useRecipe = (
*/
export const useRecipes = (
tenantId: string,
filters: RecipeSearchParams = {},
filters: any = {},
options?: Omit<UseQueryOptions<RecipeResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<RecipeResponse[], ApiError>({
@@ -80,13 +79,14 @@ export const useRecipes = (
*/
export const useInfiniteRecipes = (
tenantId: string,
filters: Omit<RecipeSearchParams, 'offset'> = {},
options?: Omit<UseInfiniteQueryOptions<RecipeResponse[], ApiError>, 'queryKey' | 'queryFn' | 'getNextPageParam'>
filters: Omit<any, 'offset'> = {},
options?: Omit<UseInfiniteQueryOptions<RecipeResponse[], ApiError>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>
) => {
return useInfiniteQuery<RecipeResponse[], ApiError>({
queryKey: recipesKeys.list(tenantId, filters),
queryFn: ({ pageParam = 0 }) =>
recipesService.searchRecipes(tenantId, { ...filters, offset: pageParam }),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
const limit = filters.limit || 100;
if (lastPage.length < limit) return undefined;

View File

@@ -187,4 +187,29 @@ export const useValidateSalesRecord = (
},
...options,
});
};
};
// Import/Export operations
export const useValidateImportFile = (
options?: UseMutationOptions<any, ApiError, { tenantId: string; file: File }>
) => {
return useMutation<any, ApiError, { tenantId: string; file: File }>({
mutationFn: ({ tenantId, file }) => salesService.validateImportFile(tenantId, file),
...options,
});
};
export const useImportSalesData = (
options?: UseMutationOptions<any, ApiError, { tenantId: string; file: File }>
) => {
const queryClient = useQueryClient();
return useMutation<any, ApiError, { tenantId: string; file: File }>({
mutationFn: ({ tenantId, file }) => salesService.importSalesData(tenantId, file),
onSuccess: (data, { tenantId }) => {
// Invalidate sales lists to include imported data
queryClient.invalidateQueries({ queryKey: salesKeys.lists() });
queryClient.invalidateQueries({ queryKey: salesKeys.analytics(tenantId) });
},
...options,
});
};

View File

@@ -12,23 +12,19 @@ import type {
SupplierResponse,
SupplierSummary,
SupplierApproval,
SupplierQueryParams,
SupplierSearchParams,
SupplierStatistics,
TopSuppliersResponse,
PurchaseOrderCreate,
PurchaseOrderUpdate,
PurchaseOrderResponse,
PurchaseOrderApproval,
PurchaseOrderQueryParams,
PurchaseOrderSearchParams,
DeliveryCreate,
DeliveryUpdate,
DeliveryResponse,
DeliveryReceiptConfirmation,
DeliveryQueryParams,
PerformanceCalculationRequest,
PerformanceMetrics,
PerformanceAlert,
PaginatedResponse,
DeliverySearchParams,
PerformanceMetric,
} from '../types/suppliers';
// Query Keys Factory
@@ -37,7 +33,7 @@ export const suppliersKeys = {
suppliers: {
all: () => [...suppliersKeys.all, 'suppliers'] as const,
lists: () => [...suppliersKeys.suppliers.all(), 'list'] as const,
list: (tenantId: string, params?: SupplierQueryParams) =>
list: (tenantId: string, params?: SupplierSearchParams) =>
[...suppliersKeys.suppliers.lists(), tenantId, params] as const,
details: () => [...suppliersKeys.suppliers.all(), 'detail'] as const,
detail: (tenantId: string, supplierId: string) =>
@@ -52,7 +48,7 @@ export const suppliersKeys = {
purchaseOrders: {
all: () => [...suppliersKeys.all, 'purchase-orders'] as const,
lists: () => [...suppliersKeys.purchaseOrders.all(), 'list'] as const,
list: (params?: PurchaseOrderQueryParams) =>
list: (params?: PurchaseOrderSearchParams) =>
[...suppliersKeys.purchaseOrders.lists(), params] as const,
details: () => [...suppliersKeys.purchaseOrders.all(), 'detail'] as const,
detail: (orderId: string) =>
@@ -61,7 +57,7 @@ export const suppliersKeys = {
deliveries: {
all: () => [...suppliersKeys.all, 'deliveries'] as const,
lists: () => [...suppliersKeys.deliveries.all(), 'list'] as const,
list: (params?: DeliveryQueryParams) =>
list: (params?: DeliverySearchParams) =>
[...suppliersKeys.deliveries.lists(), params] as const,
details: () => [...suppliersKeys.deliveries.all(), 'detail'] as const,
detail: (deliveryId: string) =>
@@ -79,10 +75,10 @@ export const suppliersKeys = {
// Supplier Queries
export const useSuppliers = (
tenantId: string,
queryParams?: SupplierQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
queryParams?: SupplierSearchParams,
options?: Omit<UseQueryOptions<SupplierSummary[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
return useQuery<SupplierSummary[], ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, queryParams),
queryFn: () => suppliersService.getSuppliers(tenantId, queryParams),
enabled: !!tenantId,
@@ -120,11 +116,11 @@ export const useSupplierStatistics = (
export const useActiveSuppliers = (
tenantId: string,
queryParams?: Omit<SupplierQueryParams, 'status'>,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
queryParams?: Omit<SupplierSearchParams, 'status'>,
options?: Omit<UseQueryOptions<SupplierSummary[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, { ...queryParams, status: 'active' }),
return useQuery<SupplierSummary[], ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, { ...queryParams }),
queryFn: () => suppliersService.getActiveSuppliers(tenantId, queryParams),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
@@ -134,9 +130,9 @@ export const useActiveSuppliers = (
export const useTopSuppliers = (
tenantId: string,
options?: Omit<UseQueryOptions<TopSuppliersResponse, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<SupplierResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TopSuppliersResponse, ApiError>({
return useQuery<SupplierResponse[], ApiError>({
queryKey: suppliersKeys.suppliers.top(tenantId),
queryFn: () => suppliersService.getTopSuppliers(tenantId),
enabled: !!tenantId,
@@ -147,10 +143,10 @@ export const useTopSuppliers = (
export const usePendingApprovalSuppliers = (
tenantId: string,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<SupplierSummary[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, { status: 'pending_approval' }),
return useQuery<SupplierSummary[], ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, {}),
queryFn: () => suppliersService.getPendingApprovalSuppliers(tenantId),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
@@ -161,10 +157,10 @@ export const usePendingApprovalSuppliers = (
export const useSuppliersByType = (
tenantId: string,
supplierType: string,
queryParams?: Omit<SupplierQueryParams, 'supplier_type'>,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
queryParams?: Omit<SupplierSearchParams, 'supplier_type'>,
options?: Omit<UseQueryOptions<SupplierSummary[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
return useQuery<SupplierSummary[], ApiError>({
queryKey: suppliersKeys.suppliers.byType(tenantId, supplierType),
queryFn: () => suppliersService.getSuppliersByType(tenantId, supplierType, queryParams),
enabled: !!tenantId && !!supplierType,
@@ -175,25 +171,28 @@ export const useSuppliersByType = (
// Purchase Order Queries
export const usePurchaseOrders = (
queryParams?: PurchaseOrderQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<PurchaseOrderResponse>, ApiError>, 'queryKey' | 'queryFn'>
tenantId: string,
queryParams?: PurchaseOrderSearchParams,
options?: Omit<UseQueryOptions<PurchaseOrderResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<PurchaseOrderResponse>, ApiError>({
return useQuery<PurchaseOrderResponse[], ApiError>({
queryKey: suppliersKeys.purchaseOrders.list(queryParams),
queryFn: () => suppliersService.getPurchaseOrders(queryParams),
queryFn: () => suppliersService.getPurchaseOrders(tenantId, queryParams as any),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const usePurchaseOrder = (
tenantId: string,
orderId: string,
options?: Omit<UseQueryOptions<PurchaseOrderResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PurchaseOrderResponse, ApiError>({
queryKey: suppliersKeys.purchaseOrders.detail(orderId),
queryFn: () => suppliersService.getPurchaseOrder(orderId),
enabled: !!orderId,
queryFn: () => suppliersService.getPurchaseOrder(tenantId, orderId),
enabled: !!tenantId && !!orderId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
@@ -201,25 +200,28 @@ export const usePurchaseOrder = (
// Delivery Queries
export const useDeliveries = (
queryParams?: DeliveryQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<DeliveryResponse>, ApiError>, 'queryKey' | 'queryFn'>
tenantId: string,
queryParams?: DeliverySearchParams,
options?: Omit<UseQueryOptions<DeliveryResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<DeliveryResponse>, ApiError>({
return useQuery<DeliveryResponse[], ApiError>({
queryKey: suppliersKeys.deliveries.list(queryParams),
queryFn: () => suppliersService.getDeliveries(queryParams),
queryFn: () => suppliersService.getDeliveries(tenantId, queryParams as any),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const useDelivery = (
tenantId: string,
deliveryId: string,
options?: Omit<UseQueryOptions<DeliveryResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<DeliveryResponse, ApiError>({
queryKey: suppliersKeys.deliveries.detail(deliveryId),
queryFn: () => suppliersService.getDelivery(deliveryId),
enabled: !!deliveryId,
queryFn: () => suppliersService.getDelivery(tenantId, deliveryId),
enabled: !!tenantId && !!deliveryId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
@@ -229,11 +231,11 @@ export const useDelivery = (
export const useSupplierPerformanceMetrics = (
tenantId: string,
supplierId: string,
options?: Omit<UseQueryOptions<PerformanceMetrics, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<PerformanceMetric[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PerformanceMetrics, ApiError>({
return useQuery<PerformanceMetric[], ApiError>({
queryKey: suppliersKeys.performance.metrics(tenantId, supplierId),
queryFn: () => suppliersService.getSupplierPerformanceMetrics(tenantId, supplierId),
queryFn: () => suppliersService.getPerformanceMetrics(tenantId, supplierId),
enabled: !!tenantId && !!supplierId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
@@ -242,13 +244,13 @@ export const useSupplierPerformanceMetrics = (
export const usePerformanceAlerts = (
tenantId: string,
supplierId?: string,
options?: Omit<UseQueryOptions<PerformanceAlert[], ApiError>, 'queryKey' | 'queryFn'>
supplierId: string,
options?: Omit<UseQueryOptions<any[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PerformanceAlert[], ApiError>({
return useQuery<any[], ApiError>({
queryKey: suppliersKeys.performance.alerts(tenantId, supplierId),
queryFn: () => suppliersService.getPerformanceAlerts(tenantId, supplierId),
enabled: !!tenantId,
queryFn: () => suppliersService.evaluatePerformanceAlerts(tenantId, supplierId),
enabled: !!tenantId && !!supplierId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
@@ -614,7 +616,7 @@ export const useEvaluatePerformanceAlerts = (
ApiError,
{ tenantId: string }
>({
mutationFn: ({ tenantId }) => suppliersService.evaluatePerformanceAlerts(tenantId),
mutationFn: ({ tenantId, supplierId }) => suppliersService.evaluatePerformanceAlerts(tenantId, supplierId),
onSuccess: (_, { tenantId }) => {
// Invalidate performance alerts
queryClient.invalidateQueries({