Add new API in the frontend

This commit is contained in:
Urtzi Alfaro
2025-09-11 18:21:32 +02:00
parent 523b926854
commit 55f31a3630
16 changed files with 2719 additions and 1806 deletions

View File

@@ -0,0 +1,655 @@
/**
* POS React Query hooks
* Provides data fetching and mutation hooks for POS operations
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { posService } from '../services/pos';
import type {
POSConfiguration,
POSTransaction,
POSSyncLog,
POSWebhookLog,
GetPOSConfigurationsRequest,
GetPOSConfigurationsResponse,
CreatePOSConfigurationRequest,
CreatePOSConfigurationResponse,
GetPOSConfigurationRequest,
GetPOSConfigurationResponse,
UpdatePOSConfigurationRequest,
UpdatePOSConfigurationResponse,
DeletePOSConfigurationRequest,
DeletePOSConfigurationResponse,
TestPOSConnectionRequest,
TestPOSConnectionResponse,
GetSupportedPOSSystemsResponse,
POSSystem,
} from '../types/pos';
import { ApiError } from '../client';
// ============================================================================
// QUERY KEYS
// ============================================================================
export const posKeys = {
all: ['pos'] as const,
// Configurations
configurations: () => [...posKeys.all, 'configurations'] as const,
configurationsList: (tenantId: string, filters?: { pos_system?: POSSystem; is_active?: boolean }) =>
[...posKeys.configurations(), 'list', tenantId, filters] as const,
configuration: (tenantId: string, configId: string) =>
[...posKeys.configurations(), 'detail', tenantId, configId] as const,
// Supported Systems
supportedSystems: () => [...posKeys.all, 'supported-systems'] as const,
// Transactions
transactions: () => [...posKeys.all, 'transactions'] as const,
transactionsList: (tenantId: string, filters?: any) =>
[...posKeys.transactions(), 'list', tenantId, filters] as const,
transaction: (tenantId: string, transactionId: string) =>
[...posKeys.transactions(), 'detail', tenantId, transactionId] as const,
// Sync Logs
syncLogs: () => [...posKeys.all, 'sync-logs'] as const,
syncLogsList: (tenantId: string, filters?: any) =>
[...posKeys.syncLogs(), 'list', tenantId, filters] as const,
// Webhook Logs
webhookLogs: () => [...posKeys.all, 'webhook-logs'] as const,
webhookLogsList: (tenantId: string, filters?: any) =>
[...posKeys.webhookLogs(), 'list', tenantId, filters] as const,
} as const;
// ============================================================================
// CONFIGURATION QUERIES
// ============================================================================
/**
* Get POS configurations for a tenant
*/
export const usePOSConfigurations = (
params: GetPOSConfigurationsRequest,
options?: Omit<UseQueryOptions<GetPOSConfigurationsResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<GetPOSConfigurationsResponse, ApiError>({
queryKey: posKeys.configurationsList(params.tenant_id, {
pos_system: params.pos_system,
is_active: params.is_active
}),
queryFn: () => posService.getPOSConfigurations(params),
enabled: !!params.tenant_id,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
/**
* Get a specific POS configuration
*/
export const usePOSConfiguration = (
params: GetPOSConfigurationRequest,
options?: Omit<UseQueryOptions<GetPOSConfigurationResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<GetPOSConfigurationResponse, ApiError>({
queryKey: posKeys.configuration(params.tenant_id, params.config_id),
queryFn: () => posService.getPOSConfiguration(params),
enabled: !!params.tenant_id && !!params.config_id,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
/**
* Get supported POS systems
*/
export const useSupportedPOSSystems = (
options?: Omit<UseQueryOptions<GetSupportedPOSSystemsResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<GetSupportedPOSSystemsResponse, ApiError>({
queryKey: posKeys.supportedSystems(),
queryFn: () => posService.getSupportedPOSSystems(),
staleTime: 30 * 60 * 1000, // 30 minutes - this data rarely changes
...options,
});
};
// ============================================================================
// CONFIGURATION MUTATIONS
// ============================================================================
/**
* Create a new POS configuration
*/
export const useCreatePOSConfiguration = (
options?: UseMutationOptions<CreatePOSConfigurationResponse, ApiError, CreatePOSConfigurationRequest>
) => {
const queryClient = useQueryClient();
return useMutation<CreatePOSConfigurationResponse, ApiError, CreatePOSConfigurationRequest>({
mutationFn: (params) => posService.createPOSConfiguration(params),
onSuccess: (data, variables) => {
// Invalidate and refetch configurations list
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// If we have the created configuration, add it to the cache
if (data.configuration) {
queryClient.setQueryData(
posKeys.configuration(variables.tenant_id, data.id),
{ configuration: data.configuration }
);
}
},
...options,
});
};
/**
* Update a POS configuration
*/
export const useUpdatePOSConfiguration = (
options?: UseMutationOptions<UpdatePOSConfigurationResponse, ApiError, UpdatePOSConfigurationRequest>
) => {
const queryClient = useQueryClient();
return useMutation<UpdatePOSConfigurationResponse, ApiError, UpdatePOSConfigurationRequest>({
mutationFn: (params) => posService.updatePOSConfiguration(params),
onSuccess: (data, variables) => {
// Invalidate and refetch configurations list
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// Update the specific configuration cache if we have the updated data
if (data.configuration) {
queryClient.setQueryData(
posKeys.configuration(variables.tenant_id, variables.config_id),
{ configuration: data.configuration }
);
} else {
// Invalidate the specific configuration to refetch
queryClient.invalidateQueries({
queryKey: posKeys.configuration(variables.tenant_id, variables.config_id)
});
}
},
...options,
});
};
/**
* Delete a POS configuration
*/
export const useDeletePOSConfiguration = (
options?: UseMutationOptions<DeletePOSConfigurationResponse, ApiError, DeletePOSConfigurationRequest>
) => {
const queryClient = useQueryClient();
return useMutation<DeletePOSConfigurationResponse, ApiError, DeletePOSConfigurationRequest>({
mutationFn: (params) => posService.deletePOSConfiguration(params),
onSuccess: (data, variables) => {
// Remove from configurations list cache
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// Remove the specific configuration from cache
queryClient.removeQueries({
queryKey: posKeys.configuration(variables.tenant_id, variables.config_id)
});
},
...options,
});
};
/**
* Test POS connection
*/
export const useTestPOSConnection = (
options?: UseMutationOptions<TestPOSConnectionResponse, ApiError, TestPOSConnectionRequest>
) => {
const queryClient = useQueryClient();
return useMutation<TestPOSConnectionResponse, ApiError, TestPOSConnectionRequest>({
mutationFn: (params) => posService.testPOSConnection(params),
onSuccess: (data, variables) => {
// Invalidate the configurations list to refresh connection status
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
// Invalidate the specific configuration to refresh connection status
queryClient.invalidateQueries({
queryKey: posKeys.configuration(variables.tenant_id, variables.config_id)
});
},
...options,
});
};
// ============================================================================
// TRANSACTION QUERIES
// ============================================================================
/**
* Get POS transactions for a tenant (Updated to match backend)
*/
export const usePOSTransactions = (
params: {
tenant_id: string;
pos_system?: string;
start_date?: string;
end_date?: string;
status?: string;
is_synced?: boolean;
limit?: number;
offset?: number;
},
options?: Omit<UseQueryOptions<{
transactions: POSTransaction[];
total: number;
has_more: boolean;
summary: {
total_amount: number;
transaction_count: number;
sync_status: {
synced: number;
pending: number;
failed: number;
};
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.transactionsList(params.tenant_id, params),
queryFn: () => posService.getPOSTransactions(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds - transaction data should be fresh
...options,
});
};
/**
* Get a specific POS transaction
*/
export const usePOSTransaction = (
params: {
tenant_id: string;
transaction_id: string;
},
options?: Omit<UseQueryOptions<{
transaction: POSTransaction;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.transaction(params.tenant_id, params.transaction_id),
queryFn: () => posService.getPOSTransaction(params),
enabled: !!params.tenant_id && !!params.transaction_id,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
// ============================================================================
// SYNC OPERATIONS
// ============================================================================
/**
* Trigger manual sync
*/
export const useTriggerManualSync = (
options?: UseMutationOptions<
{
sync_id: string;
message: string;
status: string;
sync_type: string;
data_types: string[];
estimated_duration: string;
},
ApiError,
{
tenant_id: string;
config_id: string;
sync_type?: 'full' | 'incremental';
data_types?: string[];
from_date?: string;
to_date?: string;
}
>
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params) => posService.triggerManualSync(params),
onSuccess: (data, variables) => {
// Invalidate sync logs to show the new sync
queryClient.invalidateQueries({
queryKey: posKeys.syncLogsList(variables.tenant_id)
});
// Invalidate configurations to update last sync info
queryClient.invalidateQueries({
queryKey: posKeys.configurationsList(variables.tenant_id)
});
},
...options,
});
};
/**
* Get sync status for a configuration
*/
export const usePOSSyncStatus = (
params: {
tenant_id: string;
config_id: string;
limit?: number;
},
options?: Omit<UseQueryOptions<{
current_sync: any;
last_successful_sync: any;
recent_syncs: any[];
sync_health: {
status: string;
success_rate: number;
average_duration_minutes: number;
last_error?: string;
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.configurations(), 'sync-status', params.tenant_id, params.config_id],
queryFn: () => posService.getSyncStatus(params),
enabled: !!params.tenant_id && !!params.config_id,
staleTime: 30 * 1000, // 30 seconds - sync status should be fresh
...options,
});
};
/**
* Get detailed sync logs for a configuration
*/
export const useDetailedSyncLogs = (
params: {
tenant_id: string;
config_id: string;
limit?: number;
offset?: number;
status?: string;
sync_type?: string;
data_type?: string;
},
options?: Omit<UseQueryOptions<{
logs: any[];
total: number;
has_more: boolean;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.syncLogs(), 'detailed', params.tenant_id, params.config_id, params],
queryFn: () => posService.getDetailedSyncLogs(params),
enabled: !!params.tenant_id && !!params.config_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
/**
* Sync single transaction
*/
export const useSyncSingleTransaction = (
options?: UseMutationOptions<
{ message: string; transaction_id: string; sync_status: string; sales_record_id: string },
ApiError,
{ tenant_id: string; transaction_id: string; force?: boolean }
>
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params) => posService.syncSingleTransaction(params),
onSuccess: (data, variables) => {
// Invalidate transactions list to update sync status
queryClient.invalidateQueries({
queryKey: posKeys.transactionsList(variables.tenant_id)
});
// Invalidate specific transaction
queryClient.invalidateQueries({
queryKey: posKeys.transaction(variables.tenant_id, variables.transaction_id)
});
},
...options,
});
};
/**
* Get sync performance analytics
*/
export const usePOSSyncAnalytics = (
params: {
tenant_id: string;
days?: number;
},
options?: Omit<UseQueryOptions<{
period_days: number;
total_syncs: number;
successful_syncs: number;
failed_syncs: number;
success_rate: number;
average_duration_minutes: number;
total_transactions_synced: number;
total_revenue_synced: number;
sync_frequency: {
daily_average: number;
peak_day?: string;
peak_count: number;
};
error_analysis: {
common_errors: any[];
error_trends: any[];
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.all, 'analytics', params.tenant_id, params.days],
queryFn: () => posService.getSyncAnalytics(params),
enabled: !!params.tenant_id,
staleTime: 5 * 60 * 1000, // 5 minutes - analytics don't change frequently
...options,
});
};
/**
* Resync failed transactions
*/
export const useResyncFailedTransactions = (
options?: UseMutationOptions<
{ message: string; job_id: string; scope: string; estimated_transactions: number },
ApiError,
{ tenant_id: string; days_back?: number }
>
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params) => posService.resyncFailedTransactions(params),
onSuccess: (data, variables) => {
// Invalidate sync logs and analytics
queryClient.invalidateQueries({
queryKey: posKeys.syncLogsList(variables.tenant_id)
});
queryClient.invalidateQueries({
queryKey: [...posKeys.all, 'analytics', variables.tenant_id]
});
},
...options,
});
};
/**
* Get sync logs
*/
export const usePOSSyncLogs = (
params: {
tenant_id: string;
config_id?: string;
status?: string;
limit?: number;
offset?: number;
},
options?: Omit<UseQueryOptions<{
sync_logs: POSSyncLog[];
total: number;
has_more: boolean;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.syncLogsList(params.tenant_id, params),
queryFn: () => posService.getSyncLogs(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
// ============================================================================
// WEBHOOK LOGS
// ============================================================================
/**
* Get webhook logs
*/
export const usePOSWebhookLogs = (
params: {
tenant_id: string;
pos_system?: POSSystem;
status?: string;
limit?: number;
offset?: number;
},
options?: Omit<UseQueryOptions<{
webhook_logs: POSWebhookLog[];
total: number;
has_more: boolean;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.webhookLogsList(params.tenant_id, params),
queryFn: () => posService.getWebhookLogs(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
/**
* Get webhook status for a POS system
*/
export const useWebhookStatus = (
pos_system: POSSystem,
options?: Omit<UseQueryOptions<{
pos_system: string;
status: string;
endpoint: string;
supported_events: {
events: string[];
format: string;
authentication: string;
};
last_received?: string;
total_received: number;
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.webhookLogs(), 'status', pos_system],
queryFn: () => posService.getWebhookStatus(pos_system),
enabled: !!pos_system,
staleTime: 5 * 60 * 1000, // 5 minutes - webhook status doesn't change often
...options,
});
};
// ============================================================================
// UTILITY HOOKS
// ============================================================================
/**
* Hook to get POS service utility functions
*/
export const usePOSUtils = () => {
return {
formatPrice: posService.formatPrice,
getPOSSystemDisplayName: posService.getPOSSystemDisplayName,
getConnectionStatusColor: posService.getConnectionStatusColor,
getSyncStatusColor: posService.getSyncStatusColor,
formatSyncInterval: posService.formatSyncInterval,
validateCredentials: posService.validateCredentials,
};
};
// ============================================================================
// COMPOSITE HOOKS (Convenience)
// ============================================================================
/**
* Hook that combines configurations and supported systems for the configuration UI
*/
export const usePOSConfigurationData = (tenantId: string) => {
const configurationsQuery = usePOSConfigurations(
{ tenant_id: tenantId },
{ enabled: !!tenantId }
);
const supportedSystemsQuery = useSupportedPOSSystems();
return {
configurations: configurationsQuery.data?.configurations || [],
supportedSystems: supportedSystemsQuery.data?.systems || [],
isLoading: configurationsQuery.isLoading || supportedSystemsQuery.isLoading,
error: configurationsQuery.error || supportedSystemsQuery.error,
refetch: () => {
configurationsQuery.refetch();
supportedSystemsQuery.refetch();
},
};
};
/**
* Hook for POS configuration management with all CRUD operations
*/
export const usePOSConfigurationManager = (tenantId: string) => {
const utils = usePOSUtils();
const createMutation = useCreatePOSConfiguration();
const updateMutation = useUpdatePOSConfiguration();
const deleteMutation = useDeletePOSConfiguration();
const testConnectionMutation = useTestPOSConnection();
return {
// Utility functions
...utils,
// Mutations
createConfiguration: createMutation.mutateAsync,
updateConfiguration: updateMutation.mutateAsync,
deleteConfiguration: deleteMutation.mutateAsync,
testConnection: testConnectionMutation.mutateAsync,
// Mutation states
isCreating: createMutation.isPending,
isUpdating: updateMutation.isPending,
isDeleting: deleteMutation.isPending,
isTesting: testConnectionMutation.isPending,
// Errors
createError: createMutation.error,
updateError: updateMutation.error,
deleteError: deleteMutation.error,
testError: testConnectionMutation.error,
};
};