diff --git a/src/features/dashboard/hooks/useDashboard.ts b/src/features/dashboard/hooks/useDashboard.ts index 5183e6a..dbc249b 100644 --- a/src/features/dashboard/hooks/useDashboard.ts +++ b/src/features/dashboard/hooks/useDashboard.ts @@ -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, '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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -export const useDashboard = ( - id: string, - options?: Omit, '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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: [...dashboardKeys.all, 'default'], - queryFn: () => dashboardApi.getDefault(), - ...options, - }); -}; + useEffect(() => { + fetch(); + }, [fetch]); -export const useDashboardWidgets = ( - dashboardId: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: dashboardKeys.widgets(dashboardId), - queryFn: () => dashboardApi.getWidgets(dashboardId), - enabled: !!dashboardId, - ...options, - }); -}; + const create = useCallback(async (createData: CreateDashboardDto): Promise => { + 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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: [...dashboardKeys.metrics(dashboardId), filters], - queryFn: () => dashboardApi.getMetrics(dashboardId, filters), - enabled: !!dashboardId, - ...options, - }); -}; + const deleteDashboard = useCallback(async (id: string): Promise => { + 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 => { + 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 => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + 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 => { + 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 => { + 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) { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 }; +} diff --git a/src/features/geolocation/hooks/useGeolocation.ts b/src/features/geolocation/hooks/useGeolocation.ts index bb12345..930da99 100644 --- a/src/features/geolocation/hooks/useGeolocation.ts +++ b/src/features/geolocation/hooks/useGeolocation.ts @@ -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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -// Queries -export const useGeolocations = ( - filters?: GeolocationFilters, - options?: Omit, '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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: geolocationKeys.detail(id), - queryFn: () => geolocationApi.getById(id), - enabled: !!id, - ...options, - }); -}; + useEffect(() => { + fetch(); + }, [fetch]); -export const useCountries = ( - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: geolocationKeys.countries(), - queryFn: () => geolocationApi.getCountries(), - ...options, - }); -}; + const create = useCallback(async (createData: CreateGeolocationDto): Promise => { + const created = await geolocationApi.create(createData); + await fetch(); + return created; + }, [fetch]); -export const useRegions = ( - countryId: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: geolocationKeys.regions(countryId), - queryFn: () => geolocationApi.getRegions(countryId), - enabled: !!countryId, - ...options, - }); -}; + const update = useCallback(async (id: string, updateData: UpdateGeolocationDto): Promise => { + const updated = await geolocationApi.update(id, updateData); + await fetch(); + return updated; + }, [fetch]); -export const useCities = ( - regionId: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: geolocationKeys.cities(regionId), - queryFn: () => geolocationApi.getCities(regionId), - enabled: !!regionId, - ...options, - }); -}; + const remove = useCallback(async (id: string): Promise => { + await geolocationApi.delete(id); + await fetch(); + }, [fetch]); -export const useGeocode = ( - query: GeolocationQuery, - options?: Omit, '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, '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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -export const useTimezone = ( - latitude: number, - longitude: number, - options?: Omit, '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 => { + const updated = await geolocationApi.update(id, updateData); + setData(updated); + return updated; + }, [id]); + + const remove = useCallback(async (): Promise => { + 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([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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, '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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 { + 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 { + 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 { + return geolocationApi.delete(id); +} + +// Función para validar código postal (sin hook) +export async function validatePostalCode(postalCode: string, countryId?: string): Promise { + return geolocationApi.validatePostalCode(postalCode, countryId); +} diff --git a/src/features/mcp/hooks/useMcp.ts b/src/features/mcp/hooks/useMcp.ts index da28650..f975c97 100644 --- a/src/features/mcp/hooks/useMcp.ts +++ b/src/features/mcp/hooks/useMcp.ts @@ -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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: mcpKeys.list(filters || {}), - queryFn: () => mcpApi.getAll(filters), - ...options, - }); -}; +export function useMcpServers(filters?: McpFilters) { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -export const useMcpServer = ( - id: string, - options?: Omit, '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, '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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: mcpKeys.tools(serverId), - queryFn: () => mcpApi.getTools(serverId), - enabled: !!serverId, - ...options, - }); -}; + const createServer = useCallback(async (serverData: CreateMcpServerDto): Promise => { + const newServer = await mcpApi.create(serverData); + await fetch(); + return newServer; + }, [fetch]); -export const useMcpServerTool = ( - serverId: string, - toolName: string, - options?: Omit, '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 => { + const updatedServer = await mcpApi.update(id, serverData); + await fetch(); + return updatedServer; + }, [fetch]); -export const useMcpServerResources = ( - serverId: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: mcpKeys.resources(serverId), - queryFn: () => mcpApi.getResources(serverId), - enabled: !!serverId, - ...options, - }); -}; + const deleteServer = useCallback(async (id: string): Promise => { + await mcpApi.delete(id); + await fetch(); + }, [fetch]); -export const useMcpServerResource = ( - serverId: string, - resourceName: string, - options?: Omit, '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 => { + const importedServer = await mcpApi.importConfig(file); + await fetch(); + return importedServer; + }, [fetch]); -export const useMcpServerPrompts = ( - serverId: string, - options?: Omit, '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, '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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -export const useMcpToolHistory = ( - serverId: string, - filters?: { - toolName?: string; - startDate?: string; - endDate?: string; - status?: 'success' | 'error'; - page?: number; - limit?: number; - }, - options?: Omit, '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, '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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: [...mcpKeys.logs(id), filters], - queryFn: () => mcpApi.getLogs(id, filters), - enabled: !!id, - ...options, - }); -}; + const update = useCallback(async (serverData: UpdateMcpServerDto): Promise => { + const updatedServer = await mcpApi.update(id, serverData); + setData(updatedServer); + return updatedServer; + }, [id]); -// Mutations -export const useCreateMcpServer = () => { - const queryClient = useQueryClient(); + const connect = useCallback(async (): Promise => { + 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 => { + const disconnectedServer = await mcpApi.disconnect(id); + setData(disconnectedServer); + return disconnectedServer; + }, [id]); -export const useUpdateMcpServer = () => { - const queryClient = useQueryClient(); + const restart = useCallback(async (): Promise => { + 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 => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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; - }) => 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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; - }) => 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) => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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) => { + 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(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(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(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(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 + ) => { + 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 + ) => { + 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, + }; +} diff --git a/src/features/payment-terminals/hooks/usePaymentTerminals.ts b/src/features/payment-terminals/hooks/usePaymentTerminals.ts index a851165..293aad7 100644 --- a/src/features/payment-terminals/hooks/usePaymentTerminals.ts +++ b/src/features/payment-terminals/hooks/usePaymentTerminals.ts @@ -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 { + data: T | null; + isLoading: boolean; + error: string | null; + refresh: () => Promise; +} -// Queries -export const usePaymentTerminals = ( - filters?: PaymentTerminalFilters, - options?: Omit, '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, '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; +} + +// Hook: usePaymentTerminals +export function usePaymentTerminals( + filters?: PaymentTerminalFilters +): UseQueryResult & { + create: (data: CreatePaymentTerminalDto) => Promise; + remove: (id: string) => Promise; +} { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + const newTerminal = await paymentTerminalsApi.create(createData); + await fetch(); + return newTerminal; + }, [fetch]); + + const remove = useCallback(async (id: string): Promise => { + await paymentTerminalsApi.delete(id); + await fetch(); + }, [fetch]); + + return { data, isLoading, error, refresh: fetch, create, remove }; +} + +// Hook: usePaymentTerminal +export function usePaymentTerminal( + id: string +): UseQueryResult & { + update: (data: UpdatePaymentTerminalDto) => Promise; + activate: () => Promise; + deactivate: () => Promise; + sync: () => Promise; + reboot: () => Promise; +} { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + const updated = await paymentTerminalsApi.update(id, updateData); + setData(updated); + return updated; + }, [id]); + + const activate = useCallback(async (): Promise => { + await paymentTerminalsApi.activate(id); + await fetch(); + }, [id, fetch]); + + const deactivate = useCallback(async (): Promise => { + await paymentTerminalsApi.deactivate(id); + await fetch(); + }, [id, fetch]); + + const sync = useCallback(async (): Promise => { + await paymentTerminalsApi.sync(id); + await fetch(); + }, [id, fetch]); + + const reboot = useCallback(async (): Promise => { + 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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: [...paymentTerminalsKeys.transactions(terminalId), filters], - queryFn: () => paymentTerminalsApi.getTransactions(terminalId, filters), - enabled: !!terminalId, - ...options, - }); -}; + filters?: TransactionFilters +): UseQueryResult & { + processPayment: (paymentData: PaymentData) => Promise; + refund: (transactionId: string, amount?: number, reason?: string) => Promise; + voidTransaction: (transactionId: string, reason?: string) => Promise; + exportTransactions: (format: 'json' | 'csv' | 'pdf', exportFilters?: { startDate?: string; endDate?: string; status?: TransactionStatus }) => Promise; +} { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + const transaction = await paymentTerminalsApi.processPayment(terminalId, paymentData); + await fetch(); + return transaction; + }, [terminalId, fetch]); + + const refund = useCallback(async (transactionId: string, amount?: number, reason?: string): Promise => { + await paymentTerminalsApi.refundTransaction(terminalId, transactionId, amount, reason); + await fetch(); + }, [terminalId, fetch]); + + const voidTransaction = useCallback(async (transactionId: string, reason?: string): Promise => { + 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 => { + 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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: paymentTerminalsKeys.transaction(terminalId, transactionId), - queryFn: () => paymentTerminalsApi.getTransaction(terminalId, transactionId), - enabled: !!(terminalId && transactionId), - ...options, - }); -}; + transactionId: string +): UseQueryResult { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 & { + updateConfig: (config: any) => Promise; +} { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + await paymentTerminalsApi.updateConfig(id, config); + await fetch(); + }, [id, fetch]); + + return { data, isLoading, error, refresh: fetch, updateConfig }; +} + +// Hook: usePaymentTerminalHealthCheck +export function usePaymentTerminalHealthCheck(id: string): UseQueryResult { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: paymentTerminalsKeys.status(id), - queryFn: () => paymentTerminalsApi.getStatus(id), - enabled: !!id, - ...options, - }); -}; + filters?: LogsFilters +): UseQueryResult { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -export const usePaymentTerminalConfig = ( - id: string, - options?: Omit, '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, '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, '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, '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 { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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; - }; - }) => 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 }; +} diff --git a/src/features/scanning/hooks/useScanning.ts b/src/features/scanning/hooks/useScanning.ts index d563386..a6ec20e 100644 --- a/src/features/scanning/hooks/useScanning.ts +++ b/src/features/scanning/hooks/useScanning.ts @@ -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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.list(filters || {}), - queryFn: () => scanningApi.getAll(filters), - ...options, - }); -}; +export function useScanningSessions(filters?: ScanningFilters) { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); -export const useScanningSession = ( - id: string, - options?: Omit, '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, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.devices(), - queryFn: () => scanningApi.getDevices(), - ...options, - }); -}; + useEffect(() => { + fetch(); + }, [fetch]); -export const useScanningDevice = ( - id: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.device(id), - queryFn: () => scanningApi.getDevice(id), - enabled: !!id, - ...options, - }); -}; + const create = useCallback(async (dto: CreateScanningSessionDto): Promise => { + const session = await scanningApi.create(dto); + await fetch(); + return session; + }, [fetch]); -export const useScanningTemplates = ( - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.templates(), - queryFn: () => scanningApi.getTemplates(), - ...options, - }); -}; + const update = useCallback(async (id: string, dto: UpdateScanningSessionDto): Promise => { + const session = await scanningApi.update(id, dto); + await fetch(); + return session; + }, [fetch]); -export const useScanningTemplate = ( - id: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.template(id), - queryFn: () => scanningApi.getTemplate(id), - enabled: !!id, - ...options, - }); -}; + const remove = useCallback(async (id: string): Promise => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + const session = await scanningApi.update(id, dto); + setData(session); + return session; + }, [id]); + + const start = useCallback(async (): Promise => { + const session = await scanningApi.start(id); + setData(session); + return session; + }, [id]); + + const pause = useCallback(async (): Promise => { + const session = await scanningApi.pause(id); + setData(session); + return session; + }, [id]); + + const resume = useCallback(async (): Promise => { + const session = await scanningApi.resume(id); + setData(session); + return session; + }, [id]); + + const stop = useCallback(async (): Promise => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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): Promise => { + const registered = await scanningApi.registerDevice(device); + await fetch(); + return registered; + }, [fetch]); + + const update = useCallback(async (id: string, device: Partial): Promise => { + const updated = await scanningApi.updateDevice(id, device); + await fetch(); + return updated; + }, [fetch]); + + const remove = useCallback(async (id: string): Promise => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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): Promise => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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): Promise => { + const created = await scanningApi.createTemplate(template); + await fetch(); + return created; + }, [fetch]); + + const update = useCallback(async (id: string, template: Partial): Promise => { + const updated = await scanningApi.updateTemplate(id, template); + await fetch(); + return updated; + }, [fetch]); + + const remove = useCallback(async (id: string): Promise => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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): Promise => { + 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(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 => { + return scanningApi.exportResults(sessionId, format); + }, [sessionId]); + + const importData = useCallback(async (file: File): Promise => { + await scanningApi.importData(file, sessionId); + await fetch(); + }, [sessionId, fetch]); + + const validateData = useCallback(async (data: unknown, templateId: string): Promise => { + return scanningApi.validateData(data, templateId); + }, []); + + return { data, isLoading, error, refresh: fetch, exportResults, importData, validateData }; +} + +// ============================================================================ +// Config Hook +// ============================================================================ + +export function useScanningConfig() { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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): Promise => { + 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 { + return scanningApi.create(dto); +} + +export async function exportScanningResults( sessionId: string, - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.results(sessionId), - queryFn: () => scanningApi.getResults(sessionId), - enabled: !!sessionId, - ...options, - }); -}; + format: 'json' | 'csv' | 'pdf' +): Promise { + return scanningApi.exportResults(sessionId, format); +} -export const useScanningConfig = ( - options?: Omit, 'queryKey' | 'queryFn'> -) => { - return useQuery({ - queryKey: scanningKeys.config(), - queryFn: () => scanningApi.getConfig(), - ...options, - }); -}; +export async function importScanningData(file: File, sessionId?: string): Promise { + 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) => scanningApi.registerDevice(device), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: scanningKeys.devices() }); - }, - }); -}; - -export const useUpdateScanningDevice = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ id, device }: { id: string; device: Partial }) => - 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) => scanningApi.createTemplate(template), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: scanningKeys.templates() }); - }, - }); -}; - -export const useUpdateScanningTemplate = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: ({ id, template }: { id: string; template: Partial }) => - 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) => 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 { + return scanningApi.validateData(data, templateId); +}