diff --git a/features/platform-analytics/frontend-platform/src/hooks/useAdminQuery.ts b/features/platform-analytics/frontend-platform/src/hooks/useAdminQuery.ts index 488851192..9875b8ace 100644 --- a/features/platform-analytics/frontend-platform/src/hooks/useAdminQuery.ts +++ b/features/platform-analytics/frontend-platform/src/hooks/useAdminQuery.ts @@ -2,9 +2,14 @@ * Admin Query Hooks * * React Query hooks for fetching analytics admin data. + * All date-aware admin hooks pass startDate/endDate from the global + * AnalyticsFilterContext so that every page responds to the shared + * date range selector. */ -import { useQuery } from '@tanstack/react-query'; +import { useQuery, type UseQueryResult } from '@tanstack/react-query'; + +import { useAnalyticsFilters } from '../context/AnalyticsFilterContext'; import type { RevenueMetrics, @@ -47,20 +52,60 @@ import type { TopRecipientItem, } from '../api/analytics'; -// Generic query hook for admin endpoints +// ============================================================================ +// Hook option types +// ============================================================================ + +interface AdminQueryOptions { + enabled?: boolean; + refetchInterval?: number; +} + +// ============================================================================ +// Generic query hooks +// ============================================================================ + +/** + * Generic hook for admin endpoints that accept startDate/endDate from the + * global filter context. The query key includes the date params so React Query + * automatically re-fetches when the date range changes. + */ export function useAdminQuery( endpoint: string, - options?: { - enabled?: boolean; - refetchInterval?: number; - } -) { + options?: AdminQueryOptions, +): UseQueryResult { + const { filterParams } = useAnalyticsFilters(); + + return useQuery({ + queryKey: ['admin', endpoint, filterParams.startDate, filterParams.endDate], + queryFn: async (): Promise => { + const url = new URL(`/api/analytics/${endpoint}`, window.location.origin); + url.searchParams.set('startDate', filterParams.startDate); + url.searchParams.set('endDate', filterParams.endDate); + const response = await fetch(url.toString()); + if (!response.ok) { throw new Error('Failed to fetch data'); } + return response.json() as Promise; + }, + enabled: options?.enabled ?? true, + refetchInterval: options?.refetchInterval, + }); +} + +/** + * Generic hook for admin endpoints that do NOT accept date params + * (e.g. real-time endpoints, static lookups, endpoints with their own + * query string already constructed by the caller). + */ +export function useAdminStaticQuery( + endpoint: string, + options?: AdminQueryOptions, +): UseQueryResult { return useQuery({ queryKey: ['admin', endpoint], - queryFn: async () => { + queryFn: async (): Promise => { const response = await fetch(`/api/analytics/${endpoint}`); - if (!response.ok) {throw new Error('Failed to fetch data');} - return response.json(); + if (!response.ok) { throw new Error('Failed to fetch data'); } + return response.json() as Promise; }, enabled: options?.enabled ?? true, refetchInterval: options?.refetchInterval, @@ -71,15 +116,15 @@ export function useAdminQuery( // Analytics - Revenue Hooks // ============================================================================ -export function useRevenueMetrics() { +export function useRevenueMetrics(): UseQueryResult { return useAdminQuery('admin/revenue/metrics'); } -export function useRevenueTrend() { +export function useRevenueTrend(): UseQueryResult { return useAdminQuery('admin/revenue/trend'); } -export function useRevenueBreakdown() { +export function useRevenueBreakdown(): UseQueryResult { return useAdminQuery('admin/revenue/breakdown'); } @@ -87,19 +132,19 @@ export function useRevenueBreakdown() { // Analytics - Costs Hooks // ============================================================================ -export function useCostMetrics() { +export function useCostMetrics(): UseQueryResult { return useAdminQuery('admin/costs/metrics'); } -export function useCostBreakdown() { +export function useCostBreakdown(): UseQueryResult { return useAdminQuery('admin/costs/breakdown'); } -export function useCostTrend() { +export function useCostTrend(): UseQueryResult { return useAdminQuery('admin/costs/trend'); } -export function useBudgetComparison() { +export function useBudgetComparison(): UseQueryResult { return useAdminQuery('admin/budget/comparison'); } @@ -107,23 +152,23 @@ export function useBudgetComparison() { // Analytics - P&L Hooks // ============================================================================ -export function usePnLStatement() { +export function usePnLStatement(): UseQueryResult { return useAdminQuery('admin/pnl/statement'); } -export function usePnLTrend() { +export function usePnLTrend(): UseQueryResult { return useAdminQuery('admin/pnl/trend'); } -export function useReserveProgress() { - return useAdminQuery('admin/reserve/progress'); +export function useReserveProgress(): UseQueryResult { + return useAdminStaticQuery('admin/reserve/progress'); } // ============================================================================ // Analytics - Transactions Hooks // ============================================================================ -export function useTransactions(filters?: TransactionFilters) { +export function useTransactions(filters?: TransactionFilters): UseQueryResult { const queryParams = new URLSearchParams(); if (filters?.status && filters.status !== 'all') {queryParams.append('status', filters.status);} if (filters?.type && filters.type !== 'all') {queryParams.append('type', filters.type);} @@ -134,42 +179,42 @@ export function useTransactions(filters?: TransactionFilters) { const queryString = queryParams.toString(); const endpoint = queryString ? `admin/transactions?${queryString}` : 'admin/transactions'; - return useAdminQuery(endpoint); + return useAdminStaticQuery(endpoint); } -export function useTransactionDetails(id?: string) { - return useAdminQuery(`admin/transactions/${id}`, { enabled: !!id }); +export function useTransactionDetails(id?: string): UseQueryResult { + return useAdminStaticQuery(`admin/transactions/${id}`, { enabled: !!id }); } // ============================================================================ // Analytics - Real-Time Hooks // ============================================================================ -export function useRealTimeMetrics() { - return useAdminQuery('admin/realtime/metrics', { refetchInterval: 5000 }); +export function useRealTimeMetrics(): UseQueryResult { + return useAdminStaticQuery('admin/realtime/metrics', { refetchInterval: 5000 }); } -export function useRealTimeActivity() { - return useAdminQuery('admin/realtime/activity', { refetchInterval: 5000 }); +export function useRealTimeActivity(): UseQueryResult { + return useAdminStaticQuery('admin/realtime/activity', { refetchInterval: 5000 }); } -export function useActiveUsers() { - return useAdminQuery('admin/realtime/active-users', { refetchInterval: 5000 }); +export function useActiveUsers(): UseQueryResult { + return useAdminStaticQuery('admin/realtime/active-users', { refetchInterval: 5000 }); } // ============================================================================ // Analytics - Performance Hooks // ============================================================================ -export function usePerformanceMetrics() { +export function usePerformanceMetrics(): UseQueryResult { return useAdminQuery('admin/performance/metrics'); } -export function usePerformanceHistory() { +export function usePerformanceHistory(): UseQueryResult { return useAdminQuery('admin/performance/history'); } -export function useEndpointMetrics() { +export function useEndpointMetrics(): UseQueryResult { return useAdminQuery('admin/performance/endpoints'); } @@ -177,19 +222,19 @@ export function useEndpointMetrics() { // Analytics - Error Tracking Hooks // ============================================================================ -export function useErrorMetrics() { +export function useErrorMetrics(): UseQueryResult { return useAdminQuery('admin/errors/metrics'); } -export function useErrorsByType() { +export function useErrorsByType(): UseQueryResult { return useAdminQuery('admin/errors/by-type'); } -export function useErrorTrends() { +export function useErrorTrends(): UseQueryResult { return useAdminQuery('admin/errors/trends'); } -export function useRecentErrors() { +export function useRecentErrors(): UseQueryResult { return useAdminQuery('admin/errors/recent'); } @@ -197,19 +242,19 @@ export function useRecentErrors() { // Analytics - Conversion Funnels Hooks // ============================================================================ -export function useConversionMetrics() { +export function useConversionMetrics(): UseQueryResult { return useAdminQuery('admin/conversion/metrics'); } -export function useFunnelData() { +export function useFunnelData(): UseQueryResult { return useAdminQuery('admin/conversion/funnel'); } -export function useConversionBySource() { +export function useConversionBySource(): UseQueryResult { return useAdminQuery('admin/conversion/by-source'); } -export function useFunnelDataBySource() { +export function useFunnelDataBySource(): UseQueryResult { return useAdminQuery('admin/conversion/funnel-by-source'); } @@ -217,15 +262,15 @@ export function useFunnelDataBySource() { // Analytics - Bounce Rate Hooks // ============================================================================ -export function useBounceRateMetrics() { +export function useBounceRateMetrics(): UseQueryResult { return useAdminQuery('admin/bounce-rate/metrics'); } -export function useBounceRateByPage() { +export function useBounceRateByPage(): UseQueryResult { return useAdminQuery('admin/bounce-rate/by-page'); } -export function useBounceRateHistory() { +export function useBounceRateHistory(): UseQueryResult { return useAdminQuery('admin/bounce-rate/history'); } @@ -233,39 +278,39 @@ export function useBounceRateHistory() { // Analytics - A/B Testing Hooks // ============================================================================ -export function useABTestMetrics() { - return useAdminQuery('admin/ab-tests/metrics'); +export function useABTestMetrics(): UseQueryResult { + return useAdminStaticQuery('admin/ab-tests/metrics'); } -export function useActiveTests() { - return useAdminQuery('admin/ab-tests/active'); +export function useActiveTests(): UseQueryResult { + return useAdminStaticQuery('admin/ab-tests/active'); } -export function useTestResults() { - return useAdminQuery('admin/ab-tests/results'); +export function useTestResults(): UseQueryResult { + return useAdminStaticQuery('admin/ab-tests/results'); } // ============================================================================ // Analytics - Gift Hooks // ============================================================================ -export function useGiftMetrics() { +export function useGiftMetrics(): UseQueryResult { return useAdminQuery('admin/gifts/metrics'); } -export function useGiftTrend() { +export function useGiftTrend(): UseQueryResult { return useAdminQuery('admin/gifts/trend'); } -export function useGiftsByTemplate() { +export function useGiftsByTemplate(): UseQueryResult { return useAdminQuery('admin/gifts/by-template'); } -export function useTopGifters() { +export function useTopGifters(): UseQueryResult { return useAdminQuery('admin/gifts/top-gifters'); } -export function useTopRecipients() { +export function useTopRecipients(): UseQueryResult { return useAdminQuery('admin/gifts/top-recipients'); } @@ -317,18 +362,18 @@ export interface FmtyRoute { revenue: number; } -export function useFmtyMetrics(period: 'day' | 'week' | 'month' = 'month') { - return useAdminQuery(`admin/fmty/metrics?period=${period}`); +export function useFmtyMetrics(period: 'day' | 'week' | 'month' = 'month'): UseQueryResult { + return useAdminStaticQuery(`admin/fmty/metrics?period=${period}`); } -export function useFmtyComparison(period: 'day' | 'week' | 'month' = 'month') { - return useAdminQuery(`admin/fmty/comparison?period=${period}`); +export function useFmtyComparison(period: 'day' | 'week' | 'month' = 'month'): UseQueryResult { + return useAdminStaticQuery(`admin/fmty/comparison?period=${period}`); } -export function useFmtyTrends(period: 'day' | 'week' | 'month' = 'month') { - return useAdminQuery(`admin/fmty/trends?period=${period}`); +export function useFmtyTrends(period: 'day' | 'week' | 'month' = 'month'): UseQueryResult { + return useAdminStaticQuery(`admin/fmty/trends?period=${period}`); } -export function useFmtyRoutes(period: 'day' | 'week' | 'month' = 'month', limit: number = 10) { - return useAdminQuery(`admin/fmty/routes?period=${period}&limit=${limit}`); +export function useFmtyRoutes(period: 'day' | 'week' | 'month' = 'month', limit: number = 10): UseQueryResult { + return useAdminStaticQuery(`admin/fmty/routes?period=${period}&limit=${limit}`); } diff --git a/features/platform-analytics/frontend-platform/src/pages/CostsPage.tsx b/features/platform-analytics/frontend-platform/src/pages/CostsPage.tsx index 29a6a7f53..450ebb5b2 100644 --- a/features/platform-analytics/frontend-platform/src/pages/CostsPage.tsx +++ b/features/platform-analytics/frontend-platform/src/pages/CostsPage.tsx @@ -55,29 +55,6 @@ const HeaderActions = styled.div` align-items: center; `; -const DateFilterContainer = styled.div` - display: flex; - gap: ${(props) => props.theme.spacing.sm}; - padding: ${(props) => props.theme.spacing.xs}; - background: ${(props) => props.theme.colors.surface}; - border-radius: ${(props) => props.theme.borderRadius.md}; -`; - -const FilterButton = styled.button<{ $isActive: boolean }>` - padding: ${(props) => props.theme.spacing.sm} ${(props) => props.theme.spacing.md}; - border: none; - border-radius: ${(props) => props.theme.borderRadius.sm}; - font-size: ${(props) => props.theme.typography.fontSize.sm}; - font-weight: ${(props) => props.theme.typography.fontWeight.medium}; - cursor: pointer; - transition: all ${(props) => props.theme.transitions.fast}; - background: ${(props) => (props.$isActive ? props.theme.colors.primary.main : 'transparent')}; - color: ${(props) => (props.$isActive ? '#fff' : props.theme.colors.text.secondary)}; - - &:hover { - background: ${(props) => (props.$isActive ? props.theme.colors.primary.main : props.theme.colors.hover.surface)}; - } -`; const AlertsContainer = styled.div` display: flex; @@ -207,7 +184,6 @@ interface BudgetRow { // ============================================================================ export const CostsPage = () => { - const [dateRange, setDateRange] = useState('30d'); const [showExportMenu, setShowExportMenu] = useState(false); const { data: metrics, isLoading } = useCostMetrics(); @@ -322,19 +298,6 @@ export const CostsPage = () => { - - {['7d', '30d', '90d'].map((range) => ( - setDateRange(range)} - data-testid={`filter-${range}`} - > - {range === '7d' ? '7 Days' : range === '30d' ? '30 Days' : '90 Days'} - - ))} - -
- - {['1 Hour', '6 Hours', '24 Hours'].map((range) => ( - setTimeRange(range)} - > - {range} - - ))} - {/* Performance Alerts */} diff --git a/features/platform-analytics/frontend-platform/src/pages/RevenuePage.tsx b/features/platform-analytics/frontend-platform/src/pages/RevenuePage.tsx index 3fdc51541..56f21d53e 100644 --- a/features/platform-analytics/frontend-platform/src/pages/RevenuePage.tsx +++ b/features/platform-analytics/frontend-platform/src/pages/RevenuePage.tsx @@ -45,30 +45,6 @@ const Subtitle = styled.p` margin: ${(props) => props.theme.spacing.xs} 0 0 0; `; -const DateFilterContainer = styled.div` - display: flex; - gap: ${(props) => props.theme.spacing.sm}; - padding: ${(props) => props.theme.spacing.xs}; - background: ${(props) => props.theme.colors.surface}; - border-radius: ${(props) => props.theme.borderRadius.md}; -`; - -const FilterButton = styled.button<{ $isActive: boolean }>` - padding: ${(props) => props.theme.spacing.sm} ${(props) => props.theme.spacing.md}; - border: none; - border-radius: ${(props) => props.theme.borderRadius.sm}; - font-size: ${(props) => props.theme.typography.fontSize.sm}; - font-weight: ${(props) => props.theme.typography.fontWeight.medium}; - cursor: pointer; - transition: all ${(props) => props.theme.transitions.fast}; - - background: ${(props) => (props.$isActive ? props.theme.colors.primary.main : 'transparent')}; - color: ${(props) => (props.$isActive ? '#fff' : props.theme.colors.text.secondary)}; - - &:hover { - background: ${(props) => (props.$isActive ? props.theme.colors.primary.main : props.theme.colors.hover.surface)}; - } -`; const SectionCard = styled(Card)` padding: ${(props) => props.theme.spacing.lg}; @@ -187,7 +163,6 @@ const formatCurrency = (value: number): string => { // ============================================================================ export const RevenuePage = () => { - const [dateRange, setDateRange] = useState('30d'); const [showExportMenu, setShowExportMenu] = useState(false); const { data: metrics, isLoading: metricsLoading } = useRevenueMetrics(); @@ -276,18 +251,6 @@ export const RevenuePage = () => { Track revenue performance and trends - - {['7d', '30d', '90d'].map((range) => ( - setDateRange(range)} - data-testid={`filter-${range}`} - > - {range === '7d' ? '7 Days' : range === '30d' ? '30 Days' : '90 Days'} - - ))} - {/* KPI Cards */}