[TASK-2026-02-03] fix: Convert remaining hooks from @tanstack/react-query to useState/useEffect

Converts 5 modules to use the project's standard hook pattern:
- dashboard: useDashboard hooks
- geolocation: useGeolocation, useCountries, useRegions, useCities, etc.
- mcp: useMcpServers, useMcpTools, useMcpResources, etc.
- payment-terminals: usePaymentTerminals, usePaymentTerminalTransactions, etc.
- scanning: useScanningSessions, useScanningDevices, etc.

All hooks now use useState/useEffect instead of @tanstack/react-query.
Build passes with 0 TypeScript errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-04 01:09:04 -06:00
parent b9068be3d9
commit da2e391b7c
5 changed files with 1948 additions and 1245 deletions

View File

@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
import { useState, useEffect, useCallback } from 'react';
import { dashboardApi } from '../api/dashboard.api';
import type {
Dashboard,
@ -11,224 +11,382 @@ import type {
DashboardMetrics,
} from '../../shared/types/api.types';
// Query Keys
export const dashboardKeys = {
all: ['dashboard'] as const,
lists: () => [...dashboardKeys.all, 'list'] as const,
list: (filters: DashboardFilters) => [...dashboardKeys.lists(), filters] as const,
details: () => [...dashboardKeys.all, 'detail'] as const,
detail: (id: string) => [...dashboardKeys.details(), id] as const,
widgets: (id: string) => [...dashboardKeys.detail(id), 'widgets'] as const,
metrics: (id: string) => [...dashboardKeys.detail(id), 'metrics'] as const,
};
// ============================================================================
// Query Hooks (useState/useEffect pattern)
// ============================================================================
// Queries
export const useDashboards = (
filters?: DashboardFilters,
options?: Omit<UseQueryOptions<DashboardsResponse>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: dashboardKeys.list(filters || {}),
queryFn: () => dashboardApi.getAll(filters),
...options,
});
};
/**
* Hook para obtener lista de dashboards con filtros
*/
export function useDashboards(filters?: DashboardFilters) {
const [data, setData] = useState<DashboardsResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useDashboard = (
id: string,
options?: Omit<UseQueryOptions<Dashboard>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: dashboardKeys.detail(id),
queryFn: () => dashboardApi.getById(id),
enabled: !!id,
...options,
});
};
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await dashboardApi.getAll(filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener dashboards');
} finally {
setIsLoading(false);
}
}, [filters?.search, filters?.page, filters?.limit, filters?.sortBy, filters?.sortOrder]);
export const useDefaultDashboard = (
options?: Omit<UseQueryOptions<Dashboard>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...dashboardKeys.all, 'default'],
queryFn: () => dashboardApi.getDefault(),
...options,
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useDashboardWidgets = (
dashboardId: string,
options?: Omit<UseQueryOptions<DashboardWidget[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: dashboardKeys.widgets(dashboardId),
queryFn: () => dashboardApi.getWidgets(dashboardId),
enabled: !!dashboardId,
...options,
});
};
const create = useCallback(async (createData: CreateDashboardDto): Promise<Dashboard | null> => {
setIsLoading(true);
setError(null);
try {
const newDashboard = await dashboardApi.create(createData);
await fetch();
return newDashboard;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al crear dashboard');
return null;
} finally {
setIsLoading(false);
}
}, [fetch]);
export const useDashboardMetrics = (
dashboardId: string,
filters?: any,
options?: Omit<UseQueryOptions<DashboardMetrics>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...dashboardKeys.metrics(dashboardId), filters],
queryFn: () => dashboardApi.getMetrics(dashboardId, filters),
enabled: !!dashboardId,
...options,
});
};
const deleteDashboard = useCallback(async (id: string): Promise<boolean> => {
setIsLoading(true);
setError(null);
try {
await dashboardApi.delete(id);
await fetch();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al eliminar dashboard');
return false;
} finally {
setIsLoading(false);
}
}, [fetch]);
// Mutations
export const useCreateDashboard = () => {
const queryClient = useQueryClient();
const duplicate = useCallback(async (id: string, name: string): Promise<Dashboard | null> => {
setIsLoading(true);
setError(null);
try {
const duplicated = await dashboardApi.duplicate(id, name);
await fetch();
return duplicated;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al duplicar dashboard');
return null;
} finally {
setIsLoading(false);
}
}, [fetch]);
return useMutation({
mutationFn: (data: CreateDashboardDto) => dashboardApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.lists() });
},
});
};
const importDashboard = useCallback(async (file: File): Promise<Dashboard | null> => {
setIsLoading(true);
setError(null);
try {
const imported = await dashboardApi.import(file);
await fetch();
return imported;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al importar dashboard');
return null;
} finally {
setIsLoading(false);
}
}, [fetch]);
export const useUpdateDashboard = () => {
const queryClient = useQueryClient();
return {
data,
isLoading,
error,
refresh: fetch,
create,
delete: deleteDashboard,
duplicate,
import: importDashboard,
};
}
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateDashboardDto }) =>
dashboardApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.lists() });
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(id) });
},
});
};
/**
* Hook para obtener un dashboard por ID
*/
export function useDashboard(id: string) {
const [data, setData] = useState<Dashboard | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useDeleteDashboard = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await dashboardApi.getById(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener dashboard');
} finally {
setIsLoading(false);
}
}, [id]);
return useMutation({
mutationFn: (id: string) => dashboardApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.lists() });
},
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useDuplicateDashboard = () => {
const queryClient = useQueryClient();
const update = useCallback(async (updateData: UpdateDashboardDto): Promise<Dashboard | null> => {
if (!id) return null;
setIsLoading(true);
setError(null);
try {
const updated = await dashboardApi.update(id, updateData);
setData(updated);
return updated;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al actualizar dashboard');
return null;
} finally {
setIsLoading(false);
}
}, [id]);
return useMutation({
mutationFn: ({ id, name }: { id: string; name: string }) => dashboardApi.duplicate(id, name),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.lists() });
},
});
};
const setAsDefault = useCallback(async (): Promise<boolean> => {
if (!id) return false;
setIsLoading(true);
setError(null);
try {
await dashboardApi.setAsDefault(id);
await fetch();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al establecer como predeterminado');
return false;
} finally {
setIsLoading(false);
}
}, [id, fetch]);
export const useSetAsDefaultDashboard = () => {
const queryClient = useQueryClient();
const share = useCallback(async (userIds: string[]): Promise<boolean> => {
if (!id) return false;
setIsLoading(true);
setError(null);
try {
await dashboardApi.share(id, userIds);
await fetch();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al compartir dashboard');
return false;
} finally {
setIsLoading(false);
}
}, [id, fetch]);
return useMutation({
mutationFn: (id: string) => dashboardApi.setAsDefault(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.lists() });
queryClient.invalidateQueries({ queryKey: [...dashboardKeys.all, 'default'] });
},
});
};
const unshare = useCallback(async (userIds: string[]): Promise<boolean> => {
if (!id) return false;
setIsLoading(true);
setError(null);
try {
await dashboardApi.unshare(id, userIds);
await fetch();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al dejar de compartir dashboard');
return false;
} finally {
setIsLoading(false);
}
}, [id, fetch]);
export const useShareDashboard = () => {
const queryClient = useQueryClient();
const refreshDashboard = useCallback(async (): Promise<Dashboard | null> => {
if (!id) return null;
setIsLoading(true);
setError(null);
try {
const refreshed = await dashboardApi.refresh(id);
setData(refreshed);
return refreshed;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al refrescar dashboard');
return null;
} finally {
setIsLoading(false);
}
}, [id]);
return useMutation({
mutationFn: ({ id, userIds }: { id: string; userIds: string[] }) =>
dashboardApi.share(id, userIds),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(id) });
},
});
};
const exportDashboard = useCallback(async (format: 'json' | 'pdf' | 'png'): Promise<Blob | null> => {
if (!id) return null;
setIsLoading(true);
setError(null);
try {
const blob = await dashboardApi.export(id, format);
return blob;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al exportar dashboard');
return null;
} finally {
setIsLoading(false);
}
}, [id]);
export const useUnshareDashboard = () => {
const queryClient = useQueryClient();
return {
data,
isLoading,
error,
refresh: fetch,
update,
setAsDefault,
share,
unshare,
refreshDashboard,
export: exportDashboard,
};
}
return useMutation({
mutationFn: ({ id, userIds }: { id: string; userIds: string[] }) =>
dashboardApi.unshare(id, userIds),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(id) });
},
});
};
/**
* Hook para obtener el dashboard predeterminado
*/
export function useDefaultDashboard() {
const [data, setData] = useState<Dashboard | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useAddWidget = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await dashboardApi.getDefault();
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener dashboard predeterminado');
} finally {
setIsLoading(false);
}
}, []);
return useMutation({
mutationFn: ({ dashboardId, widget }: { dashboardId: string; widget: CreateDashboardDto }) =>
dashboardApi.addWidget(dashboardId, widget),
onSuccess: (_, { dashboardId }) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.widgets(dashboardId) });
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(dashboardId) });
},
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useUpdateWidget = () => {
const queryClient = useQueryClient();
return { data, isLoading, error, refresh: fetch };
}
return useMutation({
mutationFn: ({ dashboardId, widgetId, config }: { dashboardId: string; widgetId: string; config: WidgetConfig }) =>
dashboardApi.updateWidget(dashboardId, widgetId, config),
onSuccess: (_, { dashboardId }) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.widgets(dashboardId) });
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(dashboardId) });
},
});
};
/**
* Hook para obtener widgets de un dashboard
*/
export function useDashboardWidgets(dashboardId: string) {
const [data, setData] = useState<DashboardWidget[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useRemoveWidget = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
if (!dashboardId) return;
setIsLoading(true);
setError(null);
try {
const response = await dashboardApi.getWidgets(dashboardId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener widgets');
} finally {
setIsLoading(false);
}
}, [dashboardId]);
return useMutation({
mutationFn: ({ dashboardId, widgetId }: { dashboardId: string; widgetId: string }) =>
dashboardApi.removeWidget(dashboardId, widgetId),
onSuccess: (_, { dashboardId }) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.widgets(dashboardId) });
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(dashboardId) });
},
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useRefreshDashboard = () => {
const queryClient = useQueryClient();
const addWidget = useCallback(async (widget: CreateDashboardDto): Promise<DashboardWidget | null> => {
if (!dashboardId) return null;
setIsLoading(true);
setError(null);
try {
const newWidget = await dashboardApi.addWidget(dashboardId, widget);
await fetch();
return newWidget;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al agregar widget');
return null;
} finally {
setIsLoading(false);
}
}, [dashboardId, fetch]);
return useMutation({
mutationFn: (id: string) => dashboardApi.refresh(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: dashboardKeys.metrics(id) });
},
});
};
const updateWidget = useCallback(async (widgetId: string, config: WidgetConfig): Promise<DashboardWidget | null> => {
if (!dashboardId) return null;
setIsLoading(true);
setError(null);
try {
const updated = await dashboardApi.updateWidget(dashboardId, widgetId, config);
await fetch();
return updated;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al actualizar widget');
return null;
} finally {
setIsLoading(false);
}
}, [dashboardId, fetch]);
export const useExportDashboard = () => {
return useMutation({
mutationFn: ({ id, format }: { id: string; format: 'json' | 'pdf' | 'png' }) =>
dashboardApi.export(id, format),
});
};
const removeWidget = useCallback(async (widgetId: string): Promise<boolean> => {
if (!dashboardId) return false;
setIsLoading(true);
setError(null);
try {
await dashboardApi.removeWidget(dashboardId, widgetId);
await fetch();
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al eliminar widget');
return false;
} finally {
setIsLoading(false);
}
}, [dashboardId, fetch]);
export const useImportDashboard = () => {
const queryClient = useQueryClient();
return {
data,
isLoading,
error,
refresh: fetch,
addWidget,
updateWidget,
removeWidget,
};
}
return useMutation({
mutationFn: (file: File) => dashboardApi.import(file),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: dashboardKeys.lists() });
},
});
};
/**
* Hook para obtener metricas de un dashboard
*/
export function useDashboardMetrics(dashboardId: string, filters?: Record<string, unknown>) {
const [data, setData] = useState<DashboardMetrics | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const filtersKey = filters ? JSON.stringify(filters) : '';
const fetch = useCallback(async () => {
if (!dashboardId) return;
setIsLoading(true);
setError(null);
try {
const response = await dashboardApi.getMetrics(dashboardId, filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener metricas');
} finally {
setIsLoading(false);
}
}, [dashboardId, filtersKey]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}

View File

@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
import { useState, useEffect, useCallback } from 'react';
import { geolocationApi } from '../api/geolocation.api';
import type {
Geolocation,
@ -7,171 +7,317 @@ import type {
CreateGeolocationDto,
UpdateGeolocationDto,
GeolocationQuery,
} from '../types/api.types';
} from '../../shared/types/api.types';
// Query Keys
export const geolocationKeys = {
all: ['geolocation'] as const,
lists: () => [...geolocationKeys.all, 'list'] as const,
list: (filters: GeolocationFilters) => [...geolocationKeys.lists(), filters] as const,
details: () => [...geolocationKeys.all, 'detail'] as const,
detail: (id: string) => [...geolocationKeys.details(), id] as const,
countries: () => [...geolocationKeys.all, 'countries'] as const,
regions: (countryId: string) => [...geolocationKeys.all, 'regions', countryId] as const,
cities: (regionId: string) => [...geolocationKeys.all, 'cities', regionId] as const,
};
// Hook para obtener lista de geolocalizaciones con filtros
export function useGeolocations(filters?: GeolocationFilters) {
const [data, setData] = useState<GeolocationsResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Queries
export const useGeolocations = (
filters?: GeolocationFilters,
options?: Omit<UseQueryOptions<GeolocationsResponse>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: geolocationKeys.list(filters || {}),
queryFn: () => geolocationApi.getAll(filters),
...options,
});
};
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.getAll(filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener geolocalizaciones');
} finally {
setIsLoading(false);
}
}, [
filters?.search,
filters?.page,
filters?.limit,
filters?.sortBy,
filters?.sortOrder,
filters?.countryId,
filters?.regionId,
filters?.cityId,
filters?.postalCode,
]);
export const useGeolocation = (
id: string,
options?: Omit<UseQueryOptions<Geolocation>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: geolocationKeys.detail(id),
queryFn: () => geolocationApi.getById(id),
enabled: !!id,
...options,
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useCountries = (
options?: Omit<UseQueryOptions<Geolocation[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: geolocationKeys.countries(),
queryFn: () => geolocationApi.getCountries(),
...options,
});
};
const create = useCallback(async (createData: CreateGeolocationDto): Promise<Geolocation> => {
const created = await geolocationApi.create(createData);
await fetch();
return created;
}, [fetch]);
export const useRegions = (
countryId: string,
options?: Omit<UseQueryOptions<Geolocation[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: geolocationKeys.regions(countryId),
queryFn: () => geolocationApi.getRegions(countryId),
enabled: !!countryId,
...options,
});
};
const update = useCallback(async (id: string, updateData: UpdateGeolocationDto): Promise<Geolocation> => {
const updated = await geolocationApi.update(id, updateData);
await fetch();
return updated;
}, [fetch]);
export const useCities = (
regionId: string,
options?: Omit<UseQueryOptions<Geolocation[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: geolocationKeys.cities(regionId),
queryFn: () => geolocationApi.getCities(regionId),
enabled: !!regionId,
...options,
});
};
const remove = useCallback(async (id: string): Promise<void> => {
await geolocationApi.delete(id);
await fetch();
}, [fetch]);
export const useGeocode = (
query: GeolocationQuery,
options?: Omit<UseQueryOptions<Geolocation[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...geolocationKeys.all, 'geocode', query],
queryFn: () => geolocationApi.geocode(query),
enabled: !!(query.address || query.postalCode || query.city || query.country),
...options,
});
};
return { data, isLoading, error, refresh: fetch, create, update, remove };
}
export const useReverseGeocode = (
latitude: number,
longitude: number,
options?: Omit<UseQueryOptions<Geolocation>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...geolocationKeys.all, 'reverse', latitude, longitude],
queryFn: () => geolocationApi.reverseGeocode(latitude, longitude),
enabled: !!(latitude && longitude),
...options,
});
};
// Hook para obtener una geolocalización por ID
export function useGeolocation(id: string) {
const [data, setData] = useState<Geolocation | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useTimezone = (
latitude: number,
longitude: number,
options?: Omit<UseQueryOptions<string>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...geolocationKeys.all, 'timezone', latitude, longitude],
queryFn: () => geolocationApi.getTimezone(latitude, longitude),
enabled: !!(latitude && longitude),
...options,
});
};
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.getById(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener geolocalización');
} finally {
setIsLoading(false);
}
}, [id]);
export const useDistance = (
useEffect(() => {
fetch();
}, [fetch]);
const update = useCallback(async (updateData: UpdateGeolocationDto): Promise<Geolocation> => {
const updated = await geolocationApi.update(id, updateData);
setData(updated);
return updated;
}, [id]);
const remove = useCallback(async (): Promise<void> => {
await geolocationApi.delete(id);
setData(null);
}, [id]);
return { data, isLoading, error, refresh: fetch, update, remove };
}
// Hook para obtener países
export function useCountries() {
const [data, setData] = useState<Geolocation[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.getCountries();
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener países');
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook para obtener regiones de un país
export function useRegions(countryId: string) {
const [data, setData] = useState<Geolocation[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!countryId) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.getRegions(countryId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener regiones');
} finally {
setIsLoading(false);
}
}, [countryId]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook para obtener ciudades de una región
export function useCities(regionId: string) {
const [data, setData] = useState<Geolocation[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!regionId) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.getCities(regionId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener ciudades');
} finally {
setIsLoading(false);
}
}, [regionId]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook para geocodificación (dirección a coordenadas)
export function useGeocode(query: GeolocationQuery) {
const [data, setData] = useState<Geolocation[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const isEnabled = !!(query.address || query.postalCode || query.city || query.country);
const fetch = useCallback(async () => {
if (!isEnabled) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.geocode(query);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error en geocodificación');
} finally {
setIsLoading(false);
}
}, [query.address, query.postalCode, query.city, query.country, query.limit, isEnabled]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook para geocodificación inversa (coordenadas a dirección)
export function useReverseGeocode(latitude: number, longitude: number) {
const [data, setData] = useState<Geolocation | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const isEnabled = !!(latitude && longitude);
const fetch = useCallback(async () => {
if (!isEnabled) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.reverseGeocode(latitude, longitude);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error en geocodificación inversa');
} finally {
setIsLoading(false);
}
}, [latitude, longitude, isEnabled]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook para obtener zona horaria
export function useTimezone(latitude: number, longitude: number) {
const [data, setData] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const isEnabled = !!(latitude && longitude);
const fetch = useCallback(async () => {
if (!isEnabled) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.getTimezone(latitude, longitude);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al obtener zona horaria');
} finally {
setIsLoading(false);
}
}, [latitude, longitude, isEnabled]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook para calcular distancia entre dos puntos
export function useDistance(
fromLat: number,
fromLng: number,
toLat: number,
toLng: number,
unit: 'km' | 'miles' = 'km',
options?: Omit<UseQueryOptions<number>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...geolocationKeys.all, 'distance', fromLat, fromLng, toLat, toLng, unit],
queryFn: () => geolocationApi.calculateDistance(fromLat, fromLng, toLat, toLng, unit),
enabled: !!(fromLat && fromLng && toLat && toLng),
...options,
});
};
unit: 'km' | 'miles' = 'km'
) {
const [data, setData] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Mutations
export const useCreateGeolocation = () => {
const queryClient = useQueryClient();
const isEnabled = !!(fromLat && fromLng && toLat && toLng);
return useMutation({
mutationFn: (data: CreateGeolocationDto) => geolocationApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: geolocationKeys.lists() });
},
});
};
const fetch = useCallback(async () => {
if (!isEnabled) return;
setIsLoading(true);
setError(null);
try {
const response = await geolocationApi.calculateDistance(fromLat, fromLng, toLat, toLng, unit);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error al calcular distancia');
} finally {
setIsLoading(false);
}
}, [fromLat, fromLng, toLat, toLng, unit, isEnabled]);
export const useUpdateGeolocation = () => {
const queryClient = useQueryClient();
useEffect(() => {
fetch();
}, [fetch]);
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateGeolocationDto }) =>
geolocationApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: geolocationKeys.lists() });
queryClient.invalidateQueries({ queryKey: geolocationKeys.detail(id) });
},
});
};
return { data, isLoading, error, refresh: fetch };
}
export const useDeleteGeolocation = () => {
const queryClient = useQueryClient();
// Función para crear geolocalización (sin hook)
export async function createGeolocation(data: CreateGeolocationDto): Promise<Geolocation> {
return geolocationApi.create(data);
}
return useMutation({
mutationFn: (id: string) => geolocationApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: geolocationKeys.lists() });
},
});
};
// Función para actualizar geolocalización (sin hook)
export async function updateGeolocation(id: string, data: UpdateGeolocationDto): Promise<Geolocation> {
return geolocationApi.update(id, data);
}
export const useValidatePostalCode = () => {
return useMutation({
mutationFn: ({ postalCode, countryId }: { postalCode: string; countryId?: string }) =>
geolocationApi.validatePostalCode(postalCode, countryId),
});
};
// Función para eliminar geolocalización (sin hook)
export async function deleteGeolocation(id: string): Promise<void> {
return geolocationApi.delete(id);
}
// Función para validar código postal (sin hook)
export async function validatePostalCode(postalCode: string, countryId?: string): Promise<boolean> {
return geolocationApi.validatePostalCode(postalCode, countryId);
}

View File

@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
import { useState, useEffect, useCallback } from 'react';
import { mcpApi } from '../api/mcp.api';
import type {
McpServer,
@ -10,354 +10,574 @@ import type {
CreateMcpServerDto,
UpdateMcpServerDto,
McpServerStatus,
McpExecutionResult,
McpToolCall,
McpResourceAccess,
} from '../../shared/types/api.types';
// Query Keys
export const mcpKeys = {
all: ['mcp'] as const,
lists: () => [...mcpKeys.all, 'list'] as const,
list: (filters: McpFilters) => [...mcpKeys.lists(), filters] as const,
details: () => [...mcpKeys.all, 'detail'] as const,
detail: (id: string) => [...mcpKeys.details(), id] as const,
status: (id: string) => [...mcpKeys.detail(id), 'status'] as const,
tools: (serverId: string) => [...mcpKeys.detail(serverId), 'tools'] as const,
tool: (serverId: string, toolName: string) =>
[...mcpKeys.tools(serverId), toolName] as const,
resources: (serverId: string) => [...mcpKeys.detail(serverId), 'resources'] as const,
resource: (serverId: string, resourceName: string) =>
[...mcpKeys.resources(serverId), resourceName] as const,
prompts: (serverId: string) => [...mcpKeys.detail(serverId), 'prompts'] as const,
prompt: (serverId: string, promptName: string) =>
[...mcpKeys.prompts(serverId), promptName] as const,
toolHistory: (serverId: string) => [...mcpKeys.detail(serverId), 'toolHistory'] as const,
resourceHistory: (serverId: string) => [...mcpKeys.detail(serverId), 'resourceHistory'] as const,
logs: (id: string) => [...mcpKeys.detail(id), 'logs'] as const,
metrics: (id: string) => [...mcpKeys.detail(id), 'metrics'] as const,
};
// ============================================================================
// Query Hooks - Using useState/useEffect pattern
// ============================================================================
// Queries
export const useMcpServers = (
filters?: McpFilters,
options?: Omit<UseQueryOptions<McpServersResponse>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.list(filters || {}),
queryFn: () => mcpApi.getAll(filters),
...options,
});
};
export function useMcpServers(filters?: McpFilters) {
const [data, setData] = useState<McpServersResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useMcpServer = (
id: string,
options?: Omit<UseQueryOptions<McpServer>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.detail(id),
queryFn: () => mcpApi.getById(id),
enabled: !!id,
...options,
});
};
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getAll(filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching MCP servers');
} finally {
setIsLoading(false);
}
}, [
filters?.search,
filters?.status,
filters?.protocol,
filters?.isConnected,
filters?.page,
filters?.limit,
filters?.sortBy,
filters?.sortOrder,
]);
export const useMcpServerStatus = (
id: string,
options?: Omit<UseQueryOptions<McpServerStatus>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.status(id),
queryFn: () => mcpApi.getStatus(id),
enabled: !!id,
...options,
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useMcpServerTools = (
serverId: string,
options?: Omit<UseQueryOptions<McpTool[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.tools(serverId),
queryFn: () => mcpApi.getTools(serverId),
enabled: !!serverId,
...options,
});
};
const createServer = useCallback(async (serverData: CreateMcpServerDto): Promise<McpServer> => {
const newServer = await mcpApi.create(serverData);
await fetch();
return newServer;
}, [fetch]);
export const useMcpServerTool = (
serverId: string,
toolName: string,
options?: Omit<UseQueryOptions<McpTool>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.tool(serverId, toolName),
queryFn: () => mcpApi.getTool(serverId, toolName),
enabled: !!(serverId && toolName),
...options,
});
};
const updateServer = useCallback(async (id: string, serverData: UpdateMcpServerDto): Promise<McpServer> => {
const updatedServer = await mcpApi.update(id, serverData);
await fetch();
return updatedServer;
}, [fetch]);
export const useMcpServerResources = (
serverId: string,
options?: Omit<UseQueryOptions<McpResource[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.resources(serverId),
queryFn: () => mcpApi.getResources(serverId),
enabled: !!serverId,
...options,
});
};
const deleteServer = useCallback(async (id: string): Promise<void> => {
await mcpApi.delete(id);
await fetch();
}, [fetch]);
export const useMcpServerResource = (
serverId: string,
resourceName: string,
options?: Omit<UseQueryOptions<McpResource>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.resource(serverId, resourceName),
queryFn: () => mcpApi.getResource(serverId, resourceName),
enabled: !!(serverId && resourceName),
...options,
});
};
const importConfig = useCallback(async (file: File): Promise<McpServer> => {
const importedServer = await mcpApi.importConfig(file);
await fetch();
return importedServer;
}, [fetch]);
export const useMcpServerPrompts = (
serverId: string,
options?: Omit<UseQueryOptions<McpPrompt[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.prompts(serverId),
queryFn: () => mcpApi.getPrompts(serverId),
enabled: !!serverId,
...options,
});
};
return {
data,
isLoading,
error,
refresh: fetch,
createServer,
updateServer,
deleteServer,
importConfig,
};
}
export const useMcpServerPrompt = (
serverId: string,
promptName: string,
options?: Omit<UseQueryOptions<McpPrompt>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: mcpKeys.prompt(serverId, promptName),
queryFn: () => mcpApi.getPrompt(serverId, promptName),
enabled: !!(serverId && promptName),
...options,
});
};
export function useMcpServer(id: string) {
const [data, setData] = useState<McpServer | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useMcpToolHistory = (
serverId: string,
filters?: {
toolName?: string;
startDate?: string;
endDate?: string;
status?: 'success' | 'error';
page?: number;
limit?: number;
},
options?: Omit<UseQueryOptions<{ executions: McpToolCall[]; total: number }>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...mcpKeys.toolHistory(serverId), filters],
queryFn: () => mcpApi.getToolHistory(serverId, filters),
enabled: !!serverId,
...options,
});
};
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getById(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching MCP server');
} finally {
setIsLoading(false);
}
}, [id]);
export const useMcpResourceHistory = (
serverId: string,
filters?: {
resourceName?: string;
startDate?: string;
endDate?: string;
page?: number;
limit?: number;
},
options?: Omit<UseQueryOptions<{ accesses: McpResourceAccess[]; total: number }>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...mcpKeys.resourceHistory(serverId), filters],
queryFn: () => mcpApi.getResourceHistory(serverId, filters),
enabled: !!serverId,
...options,
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useMcpServerLogs = (
id: string,
filters?: {
startDate?: string;
endDate?: string;
level?: 'error' | 'warn' | 'info' | 'debug';
page?: number;
limit?: number;
},
options?: Omit<UseQueryOptions<{ logs: any[]; total: number }>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...mcpKeys.logs(id), filters],
queryFn: () => mcpApi.getLogs(id, filters),
enabled: !!id,
...options,
});
};
const update = useCallback(async (serverData: UpdateMcpServerDto): Promise<McpServer> => {
const updatedServer = await mcpApi.update(id, serverData);
setData(updatedServer);
return updatedServer;
}, [id]);
// Mutations
export const useCreateMcpServer = () => {
const queryClient = useQueryClient();
const connect = useCallback(async (): Promise<McpServer> => {
const connectedServer = await mcpApi.connect(id);
setData(connectedServer);
return connectedServer;
}, [id]);
return useMutation({
mutationFn: (data: CreateMcpServerDto) => mcpApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: mcpKeys.lists() });
},
});
};
const disconnect = useCallback(async (): Promise<McpServer> => {
const disconnectedServer = await mcpApi.disconnect(id);
setData(disconnectedServer);
return disconnectedServer;
}, [id]);
export const useUpdateMcpServer = () => {
const queryClient = useQueryClient();
const restart = useCallback(async (): Promise<McpServer> => {
const restartedServer = await mcpApi.restart(id);
setData(restartedServer);
return restartedServer;
}, [id]);
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateMcpServerDto }) =>
mcpApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: mcpKeys.lists() });
queryClient.invalidateQueries({ queryKey: mcpKeys.detail(id) });
},
});
};
const exportConfig = useCallback(async (): Promise<Blob> => {
return await mcpApi.exportConfig(id);
}, [id]);
export const useDeleteMcpServer = () => {
const queryClient = useQueryClient();
return {
data,
isLoading,
error,
refresh: fetch,
update,
connect,
disconnect,
restart,
exportConfig,
};
}
return useMutation({
mutationFn: (id: string) => mcpApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: mcpKeys.lists() });
},
});
};
export function useMcpServerStatus(id: string) {
const [data, setData] = useState<McpServerStatus | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useConnectMcpServer = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getStatus(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching server status');
} finally {
setIsLoading(false);
}
}, [id]);
return useMutation({
mutationFn: (id: string) => mcpApi.connect(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: mcpKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.status(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.tools(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.resources(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.prompts(id) });
},
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useDisconnectMcpServer = () => {
const queryClient = useQueryClient();
return { data, isLoading, error, refresh: fetch };
}
return useMutation({
mutationFn: (id: string) => mcpApi.disconnect(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: mcpKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.status(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.tools(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.resources(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.prompts(id) });
},
});
};
export function useMcpServerTools(serverId: string) {
const [data, setData] = useState<McpTool[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useExecuteMcpTool = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
if (!serverId) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getTools(serverId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching server tools');
} finally {
setIsLoading(false);
}
}, [serverId]);
return useMutation({
mutationFn: ({
serverId,
toolName,
args
}: {
serverId: string;
toolName: string;
args: Record<string, any>;
}) => mcpApi.executeTool(serverId, toolName, args),
onSuccess: (_, { serverId }) => {
queryClient.invalidateQueries({ queryKey: mcpKeys.toolHistory(serverId) });
},
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useReadMcpResource = () => {
const queryClient = useQueryClient();
return { data, isLoading, error, refresh: fetch };
}
return useMutation({
mutationFn: ({
serverId,
resourceName,
uri
}: {
serverId: string;
resourceName: string;
uri?: string;
}) => mcpApi.readResource(serverId, resourceName, uri),
onSuccess: (_, { serverId }) => {
queryClient.invalidateQueries({ queryKey: mcpKeys.resourceHistory(serverId) });
},
});
};
export function useMcpServerTool(serverId: string, toolName: string) {
const [data, setData] = useState<McpTool | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useExecuteMcpPrompt = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
if (!serverId || !toolName) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getTool(serverId, toolName);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching tool details');
} finally {
setIsLoading(false);
}
}, [serverId, toolName]);
return useMutation({
mutationFn: ({
serverId,
promptName,
args
}: {
serverId: string;
promptName: string;
args?: Record<string, any>;
}) => mcpApi.executePrompt(serverId, promptName, args),
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useTestMcpConnection = () => {
return useMutation({
mutationFn: (serverConfig: CreateMcpServerDto) => mcpApi.testConnection(serverConfig),
});
};
const execute = useCallback(async (args: Record<string, any>) => {
return await mcpApi.executeTool(serverId, toolName, args);
}, [serverId, toolName]);
export const useRestartMcpServer = () => {
const queryClient = useQueryClient();
return { data, isLoading, error, refresh: fetch, execute };
}
return useMutation({
mutationFn: (id: string) => mcpApi.restart(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: mcpKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: mcpKeys.status(id) });
},
});
};
export function useMcpServerResources(serverId: string) {
const [data, setData] = useState<McpResource[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useExportMcpConfig = () => {
return useMutation({
mutationFn: (id: string) => mcpApi.exportConfig(id),
});
};
const fetch = useCallback(async () => {
if (!serverId) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getResources(serverId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching server resources');
} finally {
setIsLoading(false);
}
}, [serverId]);
export const useImportMcpConfig = () => {
const queryClient = useQueryClient();
useEffect(() => {
fetch();
}, [fetch]);
return useMutation({
mutationFn: (file: File) => mcpApi.importConfig(file),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: mcpKeys.lists() });
},
});
};
return { data, isLoading, error, refresh: fetch };
}
export function useMcpServerResource(serverId: string, resourceName: string) {
const [data, setData] = useState<McpResource | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!serverId || !resourceName) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getResource(serverId, resourceName);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching resource details');
} finally {
setIsLoading(false);
}
}, [serverId, resourceName]);
useEffect(() => {
fetch();
}, [fetch]);
const read = useCallback(async (uri?: string) => {
return await mcpApi.readResource(serverId, resourceName, uri);
}, [serverId, resourceName]);
return { data, isLoading, error, refresh: fetch, read };
}
export function useMcpServerPrompts(serverId: string) {
const [data, setData] = useState<McpPrompt[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!serverId) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getPrompts(serverId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching server prompts');
} finally {
setIsLoading(false);
}
}, [serverId]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
export function useMcpServerPrompt(serverId: string, promptName: string) {
const [data, setData] = useState<McpPrompt | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!serverId || !promptName) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getPrompt(serverId, promptName);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching prompt details');
} finally {
setIsLoading(false);
}
}, [serverId, promptName]);
useEffect(() => {
fetch();
}, [fetch]);
const execute = useCallback(async (args?: Record<string, any>) => {
return await mcpApi.executePrompt(serverId, promptName, args);
}, [serverId, promptName]);
return { data, isLoading, error, refresh: fetch, execute };
}
interface ToolHistoryFilters {
toolName?: string;
startDate?: string;
endDate?: string;
status?: 'success' | 'error';
page?: number;
limit?: number;
}
export function useMcpToolHistory(serverId: string, filters?: ToolHistoryFilters) {
const [data, setData] = useState<{ executions: McpToolCall[]; total: number } | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!serverId) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getToolHistory(serverId, filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching tool history');
} finally {
setIsLoading(false);
}
}, [
serverId,
filters?.toolName,
filters?.startDate,
filters?.endDate,
filters?.status,
filters?.page,
filters?.limit,
]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
interface ResourceHistoryFilters {
resourceName?: string;
startDate?: string;
endDate?: string;
page?: number;
limit?: number;
}
export function useMcpResourceHistory(serverId: string, filters?: ResourceHistoryFilters) {
const [data, setData] = useState<{ accesses: McpResourceAccess[]; total: number } | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!serverId) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getResourceHistory(serverId, filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching resource history');
} finally {
setIsLoading(false);
}
}, [
serverId,
filters?.resourceName,
filters?.startDate,
filters?.endDate,
filters?.page,
filters?.limit,
]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
interface LogFilters {
startDate?: string;
endDate?: string;
level?: 'error' | 'warn' | 'info' | 'debug';
page?: number;
limit?: number;
}
export function useMcpServerLogs(id: string, filters?: LogFilters) {
const [data, setData] = useState<{ logs: any[]; total: number } | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getLogs(id, filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching server logs');
} finally {
setIsLoading(false);
}
}, [
id,
filters?.startDate,
filters?.endDate,
filters?.level,
filters?.page,
filters?.limit,
]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
export function useMcpServerMetrics(id: string) {
const [data, setData] = useState<{
uptime: number;
totalRequests: number;
successfulRequests: number;
failedRequests: number;
averageResponseTime: number;
memoryUsage: number;
cpuUsage: number;
} | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await mcpApi.getMetrics(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching server metrics');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// ============================================================================
// Action Hooks - Simple async functions for mutations
// ============================================================================
export function useMcpActions() {
const testConnection = useCallback(async (serverConfig: CreateMcpServerDto) => {
return await mcpApi.testConnection(serverConfig);
}, []);
const createServer = useCallback(async (data: CreateMcpServerDto) => {
return await mcpApi.create(data);
}, []);
const updateServer = useCallback(async (id: string, data: UpdateMcpServerDto) => {
return await mcpApi.update(id, data);
}, []);
const deleteServer = useCallback(async (id: string) => {
return await mcpApi.delete(id);
}, []);
const connectServer = useCallback(async (id: string) => {
return await mcpApi.connect(id);
}, []);
const disconnectServer = useCallback(async (id: string) => {
return await mcpApi.disconnect(id);
}, []);
const restartServer = useCallback(async (id: string) => {
return await mcpApi.restart(id);
}, []);
const executeTool = useCallback(async (
serverId: string,
toolName: string,
args: Record<string, any>
) => {
return await mcpApi.executeTool(serverId, toolName, args);
}, []);
const readResource = useCallback(async (
serverId: string,
resourceName: string,
uri?: string
) => {
return await mcpApi.readResource(serverId, resourceName, uri);
}, []);
const executePrompt = useCallback(async (
serverId: string,
promptName: string,
args?: Record<string, any>
) => {
return await mcpApi.executePrompt(serverId, promptName, args);
}, []);
const exportConfig = useCallback(async (id: string) => {
return await mcpApi.exportConfig(id);
}, []);
const importConfig = useCallback(async (file: File) => {
return await mcpApi.importConfig(file);
}, []);
return {
testConnection,
createServer,
updateServer,
deleteServer,
connectServer,
disconnectServer,
restartServer,
executeTool,
readResource,
executePrompt,
exportConfig,
importConfig,
};
}

View File

@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
import { useState, useEffect, useCallback } from 'react';
import { paymentTerminalsApi } from '../api/payment-terminals.api';
import type {
PaymentTerminal,
@ -12,320 +12,420 @@ import type {
PaymentMethod,
} from '../../shared/types/api.types';
// Query Keys
export const paymentTerminalsKeys = {
all: ['payment-terminals'] as const,
lists: () => [...paymentTerminalsKeys.all, 'list'] as const,
list: (filters: PaymentTerminalFilters) => [...paymentTerminalsKeys.lists(), filters] as const,
details: () => [...paymentTerminalsKeys.all, 'detail'] as const,
detail: (id: string) => [...paymentTerminalsKeys.details(), id] as const,
transactions: (terminalId: string) => [...paymentTerminalsKeys.detail(terminalId), 'transactions'] as const,
transaction: (terminalId: string, transactionId: string) =>
[...paymentTerminalsKeys.transactions(terminalId), transactionId] as const,
status: (id: string) => [...paymentTerminalsKeys.detail(id), 'status'] as const,
config: (id: string) => [...paymentTerminalsKeys.detail(id), 'config'] as const,
logs: (id: string) => [...paymentTerminalsKeys.detail(id), 'logs'] as const,
metrics: (id: string) => [...paymentTerminalsKeys.detail(id), 'metrics'] as const,
};
// Types for hook returns
interface UseQueryResult<T> {
data: T | null;
isLoading: boolean;
error: string | null;
refresh: () => Promise<void>;
}
// Queries
export const usePaymentTerminals = (
filters?: PaymentTerminalFilters,
options?: Omit<UseQueryOptions<PaymentTerminalsResponse>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: paymentTerminalsKeys.list(filters || {}),
queryFn: () => paymentTerminalsApi.getAll(filters),
...options,
});
};
interface TransactionsResponse {
transactions: PaymentTerminalTransaction[];
total: number;
}
export const usePaymentTerminal = (
id: string,
options?: Omit<UseQueryOptions<PaymentTerminal>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: paymentTerminalsKeys.detail(id),
queryFn: () => paymentTerminalsApi.getById(id),
enabled: !!id,
...options,
});
};
interface LogsResponse {
logs: any[];
total: number;
}
export const usePaymentTerminalTransactions = (
interface HealthCheckResponse {
healthy: boolean;
issues: string[];
}
interface TransactionFilters {
startDate?: string;
endDate?: string;
status?: TransactionStatus;
paymentMethod?: PaymentMethod;
page?: number;
limit?: number;
}
interface LogsFilters {
startDate?: string;
endDate?: string;
level?: 'error' | 'warn' | 'info' | 'debug';
page?: number;
limit?: number;
}
interface PaymentData {
amount: number;
currency: string;
paymentMethod: PaymentMethod;
reference?: string;
metadata?: Record<string, any>;
}
// Hook: usePaymentTerminals
export function usePaymentTerminals(
filters?: PaymentTerminalFilters
): UseQueryResult<PaymentTerminalsResponse> & {
create: (data: CreatePaymentTerminalDto) => Promise<PaymentTerminal>;
remove: (id: string) => Promise<void>;
} {
const [data, setData] = useState<PaymentTerminalsResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getAll(filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching payment terminals');
} finally {
setIsLoading(false);
}
}, [
filters?.search,
filters?.status,
filters?.storeId,
filters?.page,
filters?.limit,
filters?.sortBy,
filters?.sortOrder,
]);
useEffect(() => {
fetch();
}, [fetch]);
const create = useCallback(async (createData: CreatePaymentTerminalDto): Promise<PaymentTerminal> => {
const newTerminal = await paymentTerminalsApi.create(createData);
await fetch();
return newTerminal;
}, [fetch]);
const remove = useCallback(async (id: string): Promise<void> => {
await paymentTerminalsApi.delete(id);
await fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch, create, remove };
}
// Hook: usePaymentTerminal
export function usePaymentTerminal(
id: string
): UseQueryResult<PaymentTerminal> & {
update: (data: UpdatePaymentTerminalDto) => Promise<PaymentTerminal>;
activate: () => Promise<void>;
deactivate: () => Promise<void>;
sync: () => Promise<void>;
reboot: () => Promise<void>;
} {
const [data, setData] = useState<PaymentTerminal | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getById(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching payment terminal');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
const update = useCallback(async (updateData: UpdatePaymentTerminalDto): Promise<PaymentTerminal> => {
const updated = await paymentTerminalsApi.update(id, updateData);
setData(updated);
return updated;
}, [id]);
const activate = useCallback(async (): Promise<void> => {
await paymentTerminalsApi.activate(id);
await fetch();
}, [id, fetch]);
const deactivate = useCallback(async (): Promise<void> => {
await paymentTerminalsApi.deactivate(id);
await fetch();
}, [id, fetch]);
const sync = useCallback(async (): Promise<void> => {
await paymentTerminalsApi.sync(id);
await fetch();
}, [id, fetch]);
const reboot = useCallback(async (): Promise<void> => {
await paymentTerminalsApi.reboot(id);
await fetch();
}, [id, fetch]);
return { data, isLoading, error, refresh: fetch, update, activate, deactivate, sync, reboot };
}
// Hook: usePaymentTerminalTransactions
export function usePaymentTerminalTransactions(
terminalId: string,
filters?: {
startDate?: string;
endDate?: string;
status?: TransactionStatus;
paymentMethod?: PaymentMethod;
page?: number;
limit?: number;
},
options?: Omit<UseQueryOptions<{ transactions: PaymentTerminalTransaction[]; total: number }>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...paymentTerminalsKeys.transactions(terminalId), filters],
queryFn: () => paymentTerminalsApi.getTransactions(terminalId, filters),
enabled: !!terminalId,
...options,
});
};
filters?: TransactionFilters
): UseQueryResult<TransactionsResponse> & {
processPayment: (paymentData: PaymentData) => Promise<PaymentTerminalTransaction>;
refund: (transactionId: string, amount?: number, reason?: string) => Promise<void>;
voidTransaction: (transactionId: string, reason?: string) => Promise<void>;
exportTransactions: (format: 'json' | 'csv' | 'pdf', exportFilters?: { startDate?: string; endDate?: string; status?: TransactionStatus }) => Promise<Blob>;
} {
const [data, setData] = useState<TransactionsResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const usePaymentTerminalTransaction = (
const fetch = useCallback(async () => {
if (!terminalId) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getTransactions(terminalId, filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching transactions');
} finally {
setIsLoading(false);
}
}, [
terminalId,
filters?.startDate,
filters?.endDate,
filters?.status,
filters?.paymentMethod,
filters?.page,
filters?.limit,
]);
useEffect(() => {
fetch();
}, [fetch]);
const processPayment = useCallback(async (paymentData: PaymentData): Promise<PaymentTerminalTransaction> => {
const transaction = await paymentTerminalsApi.processPayment(terminalId, paymentData);
await fetch();
return transaction;
}, [terminalId, fetch]);
const refund = useCallback(async (transactionId: string, amount?: number, reason?: string): Promise<void> => {
await paymentTerminalsApi.refundTransaction(terminalId, transactionId, amount, reason);
await fetch();
}, [terminalId, fetch]);
const voidTransaction = useCallback(async (transactionId: string, reason?: string): Promise<void> => {
await paymentTerminalsApi.voidTransaction(terminalId, transactionId, reason);
await fetch();
}, [terminalId, fetch]);
const exportTransactions = useCallback(async (
format: 'json' | 'csv' | 'pdf',
exportFilters?: { startDate?: string; endDate?: string; status?: TransactionStatus }
): Promise<Blob> => {
return paymentTerminalsApi.exportTransactions(terminalId, format, exportFilters);
}, [terminalId]);
return { data, isLoading, error, refresh: fetch, processPayment, refund, voidTransaction, exportTransactions };
}
// Hook: usePaymentTerminalTransaction
export function usePaymentTerminalTransaction(
terminalId: string,
transactionId: string,
options?: Omit<UseQueryOptions<PaymentTerminalTransaction>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: paymentTerminalsKeys.transaction(terminalId, transactionId),
queryFn: () => paymentTerminalsApi.getTransaction(terminalId, transactionId),
enabled: !!(terminalId && transactionId),
...options,
});
};
transactionId: string
): UseQueryResult<PaymentTerminalTransaction> {
const [data, setData] = useState<PaymentTerminalTransaction | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const usePaymentTerminalStatus = (
const fetch = useCallback(async () => {
if (!terminalId || !transactionId) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getTransaction(terminalId, transactionId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching transaction');
} finally {
setIsLoading(false);
}
}, [terminalId, transactionId]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook: usePaymentTerminalStatus
export function usePaymentTerminalStatus(id: string): UseQueryResult<TerminalStatus> {
const [data, setData] = useState<TerminalStatus | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getStatus(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching terminal status');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook: usePaymentTerminalConfig
export function usePaymentTerminalConfig(
id: string
): UseQueryResult<any> & {
updateConfig: (config: any) => Promise<void>;
} {
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getConfig(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching terminal config');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
const updateConfig = useCallback(async (config: any): Promise<void> => {
await paymentTerminalsApi.updateConfig(id, config);
await fetch();
}, [id, fetch]);
return { data, isLoading, error, refresh: fetch, updateConfig };
}
// Hook: usePaymentTerminalHealthCheck
export function usePaymentTerminalHealthCheck(id: string): UseQueryResult<HealthCheckResponse> {
const [data, setData] = useState<HealthCheckResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.healthCheck(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error checking terminal health');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch };
}
// Hook: usePaymentTerminalLogs
export function usePaymentTerminalLogs(
id: string,
options?: Omit<UseQueryOptions<TerminalStatus>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: paymentTerminalsKeys.status(id),
queryFn: () => paymentTerminalsApi.getStatus(id),
enabled: !!id,
...options,
});
};
filters?: LogsFilters
): UseQueryResult<LogsResponse> {
const [data, setData] = useState<LogsResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const usePaymentTerminalConfig = (
id: string,
options?: Omit<UseQueryOptions<any>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: paymentTerminalsKeys.config(id),
queryFn: () => paymentTerminalsApi.getConfig(id),
enabled: !!id,
...options,
});
};
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await paymentTerminalsApi.getLogs(id, filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching terminal logs');
} finally {
setIsLoading(false);
}
}, [
id,
filters?.startDate,
filters?.endDate,
filters?.level,
filters?.page,
filters?.limit,
]);
export const usePaymentTerminalHealthCheck = (
id: string,
options?: Omit<UseQueryOptions<{ healthy: boolean; issues: string[] }>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...paymentTerminalsKeys.detail(id), 'health'],
queryFn: () => paymentTerminalsApi.healthCheck(id),
enabled: !!id,
...options,
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const usePaymentTerminalLogs = (
id: string,
filters?: {
startDate?: string;
endDate?: string;
level?: 'error' | 'warn' | 'info' | 'debug';
page?: number;
limit?: number;
},
options?: Omit<UseQueryOptions<{ logs: any[]; total: number }>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...paymentTerminalsKeys.logs(id), filters],
queryFn: () => paymentTerminalsApi.getLogs(id, filters),
enabled: !!id,
...options,
});
};
return { data, isLoading, error, refresh: fetch };
}
export const usePaymentTerminalMetrics = (
id: string,
options?: Omit<UseQueryOptions<any>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: paymentTerminalsKeys.metrics(id),
queryFn: () => paymentTerminalsApi.getMetrics(id),
enabled: !!id,
...options,
});
};
// Hook: usePaymentTerminalMetrics
// Uses status and health check to build metrics data
export function usePaymentTerminalMetrics(id: string): UseQueryResult<any> {
const [data, setData] = useState<any | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Mutations
export const useCreatePaymentTerminal = () => {
const queryClient = useQueryClient();
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
// Fetch status and health check to build metrics
const [status, health] = await Promise.all([
paymentTerminalsApi.getStatus(id),
paymentTerminalsApi.healthCheck(id),
]);
setData({
status,
health,
lastUpdated: new Date().toISOString(),
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching terminal metrics');
} finally {
setIsLoading(false);
}
}, [id]);
return useMutation({
mutationFn: (data: CreatePaymentTerminalDto) => paymentTerminalsApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.lists() });
},
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useUpdatePaymentTerminal = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdatePaymentTerminalDto }) =>
paymentTerminalsApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.lists() });
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.detail(id) });
},
});
};
export const useDeletePaymentTerminal = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => paymentTerminalsApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.lists() });
},
});
};
export const useActivatePaymentTerminal = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => paymentTerminalsApi.activate(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.lists() });
},
});
};
export const useDeactivatePaymentTerminal = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => paymentTerminalsApi.deactivate(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.lists() });
},
});
};
export const useProcessPayment = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
terminalId,
paymentData
}: {
terminalId: string;
paymentData: {
amount: number;
currency: string;
paymentMethod: PaymentMethod;
reference?: string;
metadata?: Record<string, any>;
};
}) => paymentTerminalsApi.processPayment(terminalId, paymentData),
onSuccess: (_, { terminalId }) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.transactions(terminalId) });
},
});
};
export const useRefundTransaction = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
terminalId,
transactionId,
amount,
reason
}: {
terminalId: string;
transactionId: string;
amount?: number;
reason?: string;
}) => paymentTerminalsApi.refundTransaction(terminalId, transactionId, amount, reason),
onSuccess: (_, { terminalId }) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.transactions(terminalId) });
},
});
};
export const useVoidTransaction = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
terminalId,
transactionId,
reason
}: {
terminalId: string;
transactionId: string;
reason?: string;
}) => paymentTerminalsApi.voidTransaction(terminalId, transactionId, reason),
onSuccess: (_, { terminalId }) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.transactions(terminalId) });
},
});
};
export const useSyncPaymentTerminal = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => paymentTerminalsApi.sync(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.status(id) });
},
});
};
export const useUpdatePaymentTerminalConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, config }: { id: string; config: any }) =>
paymentTerminalsApi.updateConfig(id, config),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.config(id) });
},
});
};
export const useRebootPaymentTerminal = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => paymentTerminalsApi.reboot(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: paymentTerminalsKeys.status(id) });
},
});
};
export const useExportTransactions = () => {
return useMutation({
mutationFn: ({
terminalId,
format,
filters
}: {
terminalId: string;
format: 'json' | 'csv' | 'pdf';
filters?: {
startDate?: string;
endDate?: string;
status?: TransactionStatus;
};
}) => paymentTerminalsApi.exportTransactions(terminalId, format, filters),
});
};
return { data, isLoading, error, refresh: fetch };
}

View File

@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
import { useState, useEffect, useCallback } from 'react';
import { scanningApi } from '../api/scanning.api';
import type {
ScanningSession,
@ -10,302 +10,381 @@ import type {
UpdateScanningSessionDto,
ScanningConfig,
ScanningTemplate,
DeviceStatus,
ScanningStatus,
} from '../../shared/types/api.types';
// Query Keys
export const scanningKeys = {
all: ['scanning'] as const,
lists: () => [...scanningKeys.all, 'list'] as const,
list: (filters: ScanningFilters) => [...scanningKeys.lists(), filters] as const,
details: () => [...scanningKeys.all, 'detail'] as const,
detail: (id: string) => [...scanningKeys.details(), id] as const,
devices: () => [...scanningKeys.all, 'devices'] as const,
device: (id: string) => [...scanningKeys.devices(), id] as const,
templates: () => [...scanningKeys.all, 'templates'] as const,
template: (id: string) => [...scanningKeys.templates(), id] as const,
results: (sessionId: string) => [...scanningKeys.detail(sessionId), 'results'] as const,
config: () => [...scanningKeys.all, 'config'] as const,
};
// ============================================================================
// Sessions Hooks
// ============================================================================
// Queries
export const useScanningSessions = (
filters?: ScanningFilters,
options?: Omit<UseQueryOptions<ScanningSessionsResponse>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.list(filters || {}),
queryFn: () => scanningApi.getAll(filters),
...options,
});
};
export function useScanningSessions(filters?: ScanningFilters) {
const [data, setData] = useState<ScanningSessionsResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
export const useScanningSession = (
id: string,
options?: Omit<UseQueryOptions<ScanningSession>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.detail(id),
queryFn: () => scanningApi.getById(id),
enabled: !!id,
...options,
});
};
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getAll(filters);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching sessions');
} finally {
setIsLoading(false);
}
}, [
filters?.search,
filters?.page,
filters?.limit,
filters?.status,
filters?.deviceId,
filters?.startDate,
filters?.endDate,
]);
export const useScanningDevices = (
options?: Omit<UseQueryOptions<ScanningDevice[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.devices(),
queryFn: () => scanningApi.getDevices(),
...options,
});
};
useEffect(() => {
fetch();
}, [fetch]);
export const useScanningDevice = (
id: string,
options?: Omit<UseQueryOptions<ScanningDevice>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.device(id),
queryFn: () => scanningApi.getDevice(id),
enabled: !!id,
...options,
});
};
const create = useCallback(async (dto: CreateScanningSessionDto): Promise<ScanningSession> => {
const session = await scanningApi.create(dto);
await fetch();
return session;
}, [fetch]);
export const useScanningTemplates = (
options?: Omit<UseQueryOptions<ScanningTemplate[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.templates(),
queryFn: () => scanningApi.getTemplates(),
...options,
});
};
const update = useCallback(async (id: string, dto: UpdateScanningSessionDto): Promise<ScanningSession> => {
const session = await scanningApi.update(id, dto);
await fetch();
return session;
}, [fetch]);
export const useScanningTemplate = (
id: string,
options?: Omit<UseQueryOptions<ScanningTemplate>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.template(id),
queryFn: () => scanningApi.getTemplate(id),
enabled: !!id,
...options,
});
};
const remove = useCallback(async (id: string): Promise<void> => {
await scanningApi.delete(id);
await fetch();
}, [fetch]);
export const useScanningResults = (
return { data, isLoading, error, refresh: fetch, create, update, remove };
}
export function useScanningSession(id: string) {
const [data, setData] = useState<ScanningSession | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getById(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching session');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
const update = useCallback(async (dto: UpdateScanningSessionDto): Promise<ScanningSession> => {
const session = await scanningApi.update(id, dto);
setData(session);
return session;
}, [id]);
const start = useCallback(async (): Promise<ScanningSession> => {
const session = await scanningApi.start(id);
setData(session);
return session;
}, [id]);
const pause = useCallback(async (): Promise<ScanningSession> => {
const session = await scanningApi.pause(id);
setData(session);
return session;
}, [id]);
const resume = useCallback(async (): Promise<ScanningSession> => {
const session = await scanningApi.resume(id);
setData(session);
return session;
}, [id]);
const stop = useCallback(async (): Promise<ScanningSession> => {
const session = await scanningApi.stop(id);
setData(session);
return session;
}, [id]);
return {
data,
isLoading,
error,
refresh: fetch,
update,
start,
pause,
resume,
stop,
};
}
// ============================================================================
// Devices Hooks
// ============================================================================
export function useScanningDevices() {
const [data, setData] = useState<ScanningDevice[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getDevices();
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching devices');
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetch();
}, [fetch]);
const register = useCallback(async (device: Partial<ScanningDevice>): Promise<ScanningDevice> => {
const registered = await scanningApi.registerDevice(device);
await fetch();
return registered;
}, [fetch]);
const update = useCallback(async (id: string, device: Partial<ScanningDevice>): Promise<ScanningDevice> => {
const updated = await scanningApi.updateDevice(id, device);
await fetch();
return updated;
}, [fetch]);
const remove = useCallback(async (id: string): Promise<void> => {
await scanningApi.removeDevice(id);
await fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch, register, update, remove };
}
export function useScanningDevice(id: string) {
const [data, setData] = useState<ScanningDevice | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getDevice(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching device');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
const update = useCallback(async (device: Partial<ScanningDevice>): Promise<ScanningDevice> => {
const updated = await scanningApi.updateDevice(id, device);
setData(updated);
return updated;
}, [id]);
return { data, isLoading, error, refresh: fetch, update };
}
// ============================================================================
// Templates Hooks
// ============================================================================
export function useScanningTemplates() {
const [data, setData] = useState<ScanningTemplate[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getTemplates();
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching templates');
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetch();
}, [fetch]);
const create = useCallback(async (template: Partial<ScanningTemplate>): Promise<ScanningTemplate> => {
const created = await scanningApi.createTemplate(template);
await fetch();
return created;
}, [fetch]);
const update = useCallback(async (id: string, template: Partial<ScanningTemplate>): Promise<ScanningTemplate> => {
const updated = await scanningApi.updateTemplate(id, template);
await fetch();
return updated;
}, [fetch]);
const remove = useCallback(async (id: string): Promise<void> => {
await scanningApi.deleteTemplate(id);
await fetch();
}, [fetch]);
return { data, isLoading, error, refresh: fetch, create, update, remove };
}
export function useScanningTemplate(id: string) {
const [data, setData] = useState<ScanningTemplate | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!id) return;
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getTemplate(id);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching template');
} finally {
setIsLoading(false);
}
}, [id]);
useEffect(() => {
fetch();
}, [fetch]);
const update = useCallback(async (template: Partial<ScanningTemplate>): Promise<ScanningTemplate> => {
const updated = await scanningApi.updateTemplate(id, template);
setData(updated);
return updated;
}, [id]);
return { data, isLoading, error, refresh: fetch, update };
}
// ============================================================================
// Results Hooks
// ============================================================================
export function useScanningResults(sessionId: string) {
const [data, setData] = useState<ScanningResult[] | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
if (!sessionId) return;
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getResults(sessionId);
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching results');
} finally {
setIsLoading(false);
}
}, [sessionId]);
useEffect(() => {
fetch();
}, [fetch]);
const exportResults = useCallback(async (format: 'json' | 'csv' | 'pdf'): Promise<Blob> => {
return scanningApi.exportResults(sessionId, format);
}, [sessionId]);
const importData = useCallback(async (file: File): Promise<void> => {
await scanningApi.importData(file, sessionId);
await fetch();
}, [sessionId, fetch]);
const validateData = useCallback(async (data: unknown, templateId: string): Promise<unknown> => {
return scanningApi.validateData(data, templateId);
}, []);
return { data, isLoading, error, refresh: fetch, exportResults, importData, validateData };
}
// ============================================================================
// Config Hook
// ============================================================================
export function useScanningConfig() {
const [data, setData] = useState<ScanningConfig | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetch = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await scanningApi.getConfig();
setData(response);
} catch (err) {
setError(err instanceof Error ? err.message : 'Error fetching config');
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetch();
}, [fetch]);
const update = useCallback(async (config: Partial<ScanningConfig>): Promise<ScanningConfig> => {
const updated = await scanningApi.updateConfig(config);
setData(updated);
return updated;
}, []);
return { data, isLoading, error, refresh: fetch, update };
}
// ============================================================================
// Utility Functions (Non-hook async functions)
// ============================================================================
export async function createScanningSession(dto: CreateScanningSessionDto): Promise<ScanningSession> {
return scanningApi.create(dto);
}
export async function exportScanningResults(
sessionId: string,
options?: Omit<UseQueryOptions<ScanningResult[]>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.results(sessionId),
queryFn: () => scanningApi.getResults(sessionId),
enabled: !!sessionId,
...options,
});
};
format: 'json' | 'csv' | 'pdf'
): Promise<Blob> {
return scanningApi.exportResults(sessionId, format);
}
export const useScanningConfig = (
options?: Omit<UseQueryOptions<ScanningConfig>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: scanningKeys.config(),
queryFn: () => scanningApi.getConfig(),
...options,
});
};
export async function importScanningData(file: File, sessionId?: string): Promise<void> {
await scanningApi.importData(file, sessionId);
}
// Mutations
export const useCreateScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateScanningSessionDto) => scanningApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.lists() });
},
});
};
export const useUpdateScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateScanningSessionDto }) =>
scanningApi.update(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.lists() });
queryClient.invalidateQueries({ queryKey: scanningKeys.detail(id) });
},
});
};
export const useDeleteScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.lists() });
},
});
};
export const useStartScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.start(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: scanningKeys.lists() });
},
});
};
export const usePauseScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.pause(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.detail(id) });
},
});
};
export const useResumeScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.resume(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.detail(id) });
},
});
};
export const useStopScanningSession = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.stop(id),
onSuccess: (_, id) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: scanningKeys.results(id) });
},
});
};
export const useRegisterScanningDevice = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (device: Partial<ScanningDevice>) => scanningApi.registerDevice(device),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.devices() });
},
});
};
export const useUpdateScanningDevice = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, device }: { id: string; device: Partial<ScanningDevice> }) =>
scanningApi.updateDevice(id, device),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.devices() });
queryClient.invalidateQueries({ queryKey: scanningKeys.device(id) });
},
});
};
export const useRemoveScanningDevice = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.removeDevice(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.devices() });
},
});
};
export const useCreateScanningTemplate = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (template: Partial<ScanningTemplate>) => scanningApi.createTemplate(template),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.templates() });
},
});
};
export const useUpdateScanningTemplate = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, template }: { id: string; template: Partial<ScanningTemplate> }) =>
scanningApi.updateTemplate(id, template),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: scanningKeys.templates() });
queryClient.invalidateQueries({ queryKey: scanningKeys.template(id) });
},
});
};
export const useDeleteScanningTemplate = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => scanningApi.deleteTemplate(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.templates() });
},
});
};
export const useUpdateScanningConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (config: Partial<ScanningConfig>) => scanningApi.updateConfig(config),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: scanningKeys.config() });
},
});
};
export const useExportScanningResults = () => {
return useMutation({
mutationFn: ({ sessionId, format }: { sessionId: string; format: 'json' | 'csv' | 'pdf' }) =>
scanningApi.exportResults(sessionId, format),
});
};
export const useImportScanningData = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ file, sessionId }: { file: File; sessionId?: string }) =>
scanningApi.importData(file, sessionId),
onSuccess: (_, { sessionId }) => {
if (sessionId) {
queryClient.invalidateQueries({ queryKey: scanningKeys.results(sessionId) });
}
},
});
};
export const useValidateScanningData = () => {
return useMutation({
mutationFn: ({ data, templateId }: { data: any; templateId: string }) =>
scanningApi.validateData(data, templateId),
});
};
export async function validateScanningData(data: unknown, templateId: string): Promise<unknown> {
return scanningApi.validateData(data, templateId);
}