feat(platform-analytics): ✨ Add admin analytics dashboards with CostsPage, PerformancePage, and RevenuePage integration and useAdminQuery hook
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
12d7599fbe
commit
cb6b706b27
4 changed files with 110 additions and 175 deletions
|
|
@ -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<T = unknown>(
|
||||
endpoint: string,
|
||||
options?: {
|
||||
enabled?: boolean;
|
||||
refetchInterval?: number;
|
||||
}
|
||||
) {
|
||||
options?: AdminQueryOptions,
|
||||
): UseQueryResult<T> {
|
||||
const { filterParams } = useAnalyticsFilters();
|
||||
|
||||
return useQuery<T>({
|
||||
queryKey: ['admin', endpoint, filterParams.startDate, filterParams.endDate],
|
||||
queryFn: async (): Promise<T> => {
|
||||
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<T>;
|
||||
},
|
||||
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<T = unknown>(
|
||||
endpoint: string,
|
||||
options?: AdminQueryOptions,
|
||||
): UseQueryResult<T> {
|
||||
return useQuery<T>({
|
||||
queryKey: ['admin', endpoint],
|
||||
queryFn: async () => {
|
||||
queryFn: async (): Promise<T> => {
|
||||
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<T>;
|
||||
},
|
||||
enabled: options?.enabled ?? true,
|
||||
refetchInterval: options?.refetchInterval,
|
||||
|
|
@ -71,15 +116,15 @@ export function useAdminQuery<T = unknown>(
|
|||
// Analytics - Revenue Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useRevenueMetrics() {
|
||||
export function useRevenueMetrics(): UseQueryResult<RevenueMetrics> {
|
||||
return useAdminQuery<RevenueMetrics>('admin/revenue/metrics');
|
||||
}
|
||||
|
||||
export function useRevenueTrend() {
|
||||
export function useRevenueTrend(): UseQueryResult<RevenueTrendPoint[]> {
|
||||
return useAdminQuery<RevenueTrendPoint[]>('admin/revenue/trend');
|
||||
}
|
||||
|
||||
export function useRevenueBreakdown() {
|
||||
export function useRevenueBreakdown(): UseQueryResult<RevenueBreakdown> {
|
||||
return useAdminQuery<RevenueBreakdown>('admin/revenue/breakdown');
|
||||
}
|
||||
|
||||
|
|
@ -87,19 +132,19 @@ export function useRevenueBreakdown() {
|
|||
// Analytics - Costs Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useCostMetrics() {
|
||||
export function useCostMetrics(): UseQueryResult<CostMetrics> {
|
||||
return useAdminQuery<CostMetrics>('admin/costs/metrics');
|
||||
}
|
||||
|
||||
export function useCostBreakdown() {
|
||||
export function useCostBreakdown(): UseQueryResult<CostBreakdown> {
|
||||
return useAdminQuery<CostBreakdown>('admin/costs/breakdown');
|
||||
}
|
||||
|
||||
export function useCostTrend() {
|
||||
export function useCostTrend(): UseQueryResult<CostTrendPoint[]> {
|
||||
return useAdminQuery<CostTrendPoint[]>('admin/costs/trend');
|
||||
}
|
||||
|
||||
export function useBudgetComparison() {
|
||||
export function useBudgetComparison(): UseQueryResult<BudgetComparison> {
|
||||
return useAdminQuery<BudgetComparison>('admin/budget/comparison');
|
||||
}
|
||||
|
||||
|
|
@ -107,23 +152,23 @@ export function useBudgetComparison() {
|
|||
// Analytics - P&L Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function usePnLStatement() {
|
||||
export function usePnLStatement(): UseQueryResult<PnLStatement> {
|
||||
return useAdminQuery<PnLStatement>('admin/pnl/statement');
|
||||
}
|
||||
|
||||
export function usePnLTrend() {
|
||||
export function usePnLTrend(): UseQueryResult<PnLTrendPoint[]> {
|
||||
return useAdminQuery<PnLTrendPoint[]>('admin/pnl/trend');
|
||||
}
|
||||
|
||||
export function useReserveProgress() {
|
||||
return useAdminQuery<ReserveProgress>('admin/reserve/progress');
|
||||
export function useReserveProgress(): UseQueryResult<ReserveProgress> {
|
||||
return useAdminStaticQuery<ReserveProgress>('admin/reserve/progress');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Analytics - Transactions Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useTransactions(filters?: TransactionFilters) {
|
||||
export function useTransactions(filters?: TransactionFilters): UseQueryResult<TransactionsResponse> {
|
||||
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<TransactionsResponse>(endpoint);
|
||||
return useAdminStaticQuery<TransactionsResponse>(endpoint);
|
||||
}
|
||||
|
||||
export function useTransactionDetails(id?: string) {
|
||||
return useAdminQuery<TransactionDetails>(`admin/transactions/${id}`, { enabled: !!id });
|
||||
export function useTransactionDetails(id?: string): UseQueryResult<TransactionDetails> {
|
||||
return useAdminStaticQuery<TransactionDetails>(`admin/transactions/${id}`, { enabled: !!id });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Analytics - Real-Time Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useRealTimeMetrics() {
|
||||
return useAdminQuery<RealTimeMetrics>('admin/realtime/metrics', { refetchInterval: 5000 });
|
||||
export function useRealTimeMetrics(): UseQueryResult<RealTimeMetrics> {
|
||||
return useAdminStaticQuery<RealTimeMetrics>('admin/realtime/metrics', { refetchInterval: 5000 });
|
||||
}
|
||||
|
||||
export function useRealTimeActivity() {
|
||||
return useAdminQuery<RealTimeActivityItem[]>('admin/realtime/activity', { refetchInterval: 5000 });
|
||||
export function useRealTimeActivity(): UseQueryResult<RealTimeActivityItem[]> {
|
||||
return useAdminStaticQuery<RealTimeActivityItem[]>('admin/realtime/activity', { refetchInterval: 5000 });
|
||||
}
|
||||
|
||||
export function useActiveUsers() {
|
||||
return useAdminQuery<ActiveUserPoint[]>('admin/realtime/active-users', { refetchInterval: 5000 });
|
||||
export function useActiveUsers(): UseQueryResult<ActiveUserPoint[]> {
|
||||
return useAdminStaticQuery<ActiveUserPoint[]>('admin/realtime/active-users', { refetchInterval: 5000 });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Analytics - Performance Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function usePerformanceMetrics() {
|
||||
export function usePerformanceMetrics(): UseQueryResult<PerformanceMetrics> {
|
||||
return useAdminQuery<PerformanceMetrics>('admin/performance/metrics');
|
||||
}
|
||||
|
||||
export function usePerformanceHistory() {
|
||||
export function usePerformanceHistory(): UseQueryResult<PerformanceHistoryPoint[]> {
|
||||
return useAdminQuery<PerformanceHistoryPoint[]>('admin/performance/history');
|
||||
}
|
||||
|
||||
export function useEndpointMetrics() {
|
||||
export function useEndpointMetrics(): UseQueryResult<EndpointMetric[]> {
|
||||
return useAdminQuery<EndpointMetric[]>('admin/performance/endpoints');
|
||||
}
|
||||
|
||||
|
|
@ -177,19 +222,19 @@ export function useEndpointMetrics() {
|
|||
// Analytics - Error Tracking Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useErrorMetrics() {
|
||||
export function useErrorMetrics(): UseQueryResult<ErrorMetrics> {
|
||||
return useAdminQuery<ErrorMetrics>('admin/errors/metrics');
|
||||
}
|
||||
|
||||
export function useErrorsByType() {
|
||||
export function useErrorsByType(): UseQueryResult<ErrorByTypeItem[]> {
|
||||
return useAdminQuery<ErrorByTypeItem[]>('admin/errors/by-type');
|
||||
}
|
||||
|
||||
export function useErrorTrends() {
|
||||
export function useErrorTrends(): UseQueryResult<ErrorTrendPoint[]> {
|
||||
return useAdminQuery<ErrorTrendPoint[]>('admin/errors/trends');
|
||||
}
|
||||
|
||||
export function useRecentErrors() {
|
||||
export function useRecentErrors(): UseQueryResult<RecentError[]> {
|
||||
return useAdminQuery<RecentError[]>('admin/errors/recent');
|
||||
}
|
||||
|
||||
|
|
@ -197,19 +242,19 @@ export function useRecentErrors() {
|
|||
// Analytics - Conversion Funnels Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useConversionMetrics() {
|
||||
export function useConversionMetrics(): UseQueryResult<ConversionMetrics> {
|
||||
return useAdminQuery<ConversionMetrics>('admin/conversion/metrics');
|
||||
}
|
||||
|
||||
export function useFunnelData() {
|
||||
export function useFunnelData(): UseQueryResult<FunnelStage[]> {
|
||||
return useAdminQuery<FunnelStage[]>('admin/conversion/funnel');
|
||||
}
|
||||
|
||||
export function useConversionBySource() {
|
||||
export function useConversionBySource(): UseQueryResult<ConversionBySourceItem[]> {
|
||||
return useAdminQuery<ConversionBySourceItem[]>('admin/conversion/by-source');
|
||||
}
|
||||
|
||||
export function useFunnelDataBySource() {
|
||||
export function useFunnelDataBySource(): UseQueryResult<FunnelBySourceItem[]> {
|
||||
return useAdminQuery<FunnelBySourceItem[]>('admin/conversion/funnel-by-source');
|
||||
}
|
||||
|
||||
|
|
@ -217,15 +262,15 @@ export function useFunnelDataBySource() {
|
|||
// Analytics - Bounce Rate Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useBounceRateMetrics() {
|
||||
export function useBounceRateMetrics(): UseQueryResult<BounceRateMetrics> {
|
||||
return useAdminQuery<BounceRateMetrics>('admin/bounce-rate/metrics');
|
||||
}
|
||||
|
||||
export function useBounceRateByPage() {
|
||||
export function useBounceRateByPage(): UseQueryResult<BounceRateByPageItem[]> {
|
||||
return useAdminQuery<BounceRateByPageItem[]>('admin/bounce-rate/by-page');
|
||||
}
|
||||
|
||||
export function useBounceRateHistory() {
|
||||
export function useBounceRateHistory(): UseQueryResult<BounceRateHistoryPoint[]> {
|
||||
return useAdminQuery<BounceRateHistoryPoint[]>('admin/bounce-rate/history');
|
||||
}
|
||||
|
||||
|
|
@ -233,39 +278,39 @@ export function useBounceRateHistory() {
|
|||
// Analytics - A/B Testing Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useABTestMetrics() {
|
||||
return useAdminQuery<ABTestMetrics>('admin/ab-tests/metrics');
|
||||
export function useABTestMetrics(): UseQueryResult<ABTestMetrics> {
|
||||
return useAdminStaticQuery<ABTestMetrics>('admin/ab-tests/metrics');
|
||||
}
|
||||
|
||||
export function useActiveTests() {
|
||||
return useAdminQuery<ABTest[]>('admin/ab-tests/active');
|
||||
export function useActiveTests(): UseQueryResult<ABTest[]> {
|
||||
return useAdminStaticQuery<ABTest[]>('admin/ab-tests/active');
|
||||
}
|
||||
|
||||
export function useTestResults() {
|
||||
return useAdminQuery<ABTestResult[]>('admin/ab-tests/results');
|
||||
export function useTestResults(): UseQueryResult<ABTestResult[]> {
|
||||
return useAdminStaticQuery<ABTestResult[]>('admin/ab-tests/results');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Analytics - Gift Hooks
|
||||
// ============================================================================
|
||||
|
||||
export function useGiftMetrics() {
|
||||
export function useGiftMetrics(): UseQueryResult<GiftAnalyticsMetrics> {
|
||||
return useAdminQuery<GiftAnalyticsMetrics>('admin/gifts/metrics');
|
||||
}
|
||||
|
||||
export function useGiftTrend() {
|
||||
export function useGiftTrend(): UseQueryResult<GiftTrendPoint[]> {
|
||||
return useAdminQuery<GiftTrendPoint[]>('admin/gifts/trend');
|
||||
}
|
||||
|
||||
export function useGiftsByTemplate() {
|
||||
export function useGiftsByTemplate(): UseQueryResult<GiftByTemplateItem[]> {
|
||||
return useAdminQuery<GiftByTemplateItem[]>('admin/gifts/by-template');
|
||||
}
|
||||
|
||||
export function useTopGifters() {
|
||||
export function useTopGifters(): UseQueryResult<TopGifterItem[]> {
|
||||
return useAdminQuery<TopGifterItem[]>('admin/gifts/top-gifters');
|
||||
}
|
||||
|
||||
export function useTopRecipients() {
|
||||
export function useTopRecipients(): UseQueryResult<TopRecipientItem[]> {
|
||||
return useAdminQuery<TopRecipientItem[]>('admin/gifts/top-recipients');
|
||||
}
|
||||
|
||||
|
|
@ -317,18 +362,18 @@ export interface FmtyRoute {
|
|||
revenue: number;
|
||||
}
|
||||
|
||||
export function useFmtyMetrics(period: 'day' | 'week' | 'month' = 'month') {
|
||||
return useAdminQuery<FmtyMetrics>(`admin/fmty/metrics?period=${period}`);
|
||||
export function useFmtyMetrics(period: 'day' | 'week' | 'month' = 'month'): UseQueryResult<FmtyMetrics> {
|
||||
return useAdminStaticQuery<FmtyMetrics>(`admin/fmty/metrics?period=${period}`);
|
||||
}
|
||||
|
||||
export function useFmtyComparison(period: 'day' | 'week' | 'month' = 'month') {
|
||||
return useAdminQuery<FmtyComparison>(`admin/fmty/comparison?period=${period}`);
|
||||
export function useFmtyComparison(period: 'day' | 'week' | 'month' = 'month'): UseQueryResult<FmtyComparison> {
|
||||
return useAdminStaticQuery<FmtyComparison>(`admin/fmty/comparison?period=${period}`);
|
||||
}
|
||||
|
||||
export function useFmtyTrends(period: 'day' | 'week' | 'month' = 'month') {
|
||||
return useAdminQuery<FmtyTrendPoint[]>(`admin/fmty/trends?period=${period}`);
|
||||
export function useFmtyTrends(period: 'day' | 'week' | 'month' = 'month'): UseQueryResult<FmtyTrendPoint[]> {
|
||||
return useAdminStaticQuery<FmtyTrendPoint[]>(`admin/fmty/trends?period=${period}`);
|
||||
}
|
||||
|
||||
export function useFmtyRoutes(period: 'day' | 'week' | 'month' = 'month', limit: number = 10) {
|
||||
return useAdminQuery<FmtyRoute[]>(`admin/fmty/routes?period=${period}&limit=${limit}`);
|
||||
export function useFmtyRoutes(period: 'day' | 'week' | 'month' = 'month', limit: number = 10): UseQueryResult<FmtyRoute[]> {
|
||||
return useAdminStaticQuery<FmtyRoute[]>(`admin/fmty/routes?period=${period}&limit=${limit}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
</div>
|
||||
|
||||
<HeaderActions>
|
||||
<DateFilterContainer data-testid="date-filter">
|
||||
{['7d', '30d', '90d'].map((range) => (
|
||||
<FilterButton
|
||||
key={range}
|
||||
$isActive={dateRange === range}
|
||||
onClick={() => setDateRange(range)}
|
||||
data-testid={`filter-${range}`}
|
||||
>
|
||||
{range === '7d' ? '7 Days' : range === '30d' ? '30 Days' : '90 Days'}
|
||||
</FilterButton>
|
||||
))}
|
||||
</DateFilterContainer>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Button variant="ghost" size="sm" onClick={() => setShowExportMenu(!showExportMenu)}>
|
||||
Export
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
|
||||
import { MetricCard, DashboardLayout, DashboardWidget } from '@lilith/ui-analytics';
|
||||
|
|
@ -44,30 +44,6 @@ const Subtitle = styled.p`
|
|||
margin: ${(props) => props.theme.spacing.xs} 0 0 0;
|
||||
`;
|
||||
|
||||
const TimeRangeContainer = 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 TimeRangeButton = 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;
|
||||
|
|
@ -157,7 +133,6 @@ const ErrorRateBadge = styled.span<{ $value: number }>`
|
|||
export const PerformancePage = () => {
|
||||
const { data: metrics, isLoading } = usePerformanceMetrics();
|
||||
const { data: endpoints } = useEndpointMetrics();
|
||||
const [timeRange, setTimeRange] = useState('1 Hour');
|
||||
|
||||
const endpointColumns = useMemo<Array<Column<EndpointMetric>>>(
|
||||
() => [
|
||||
|
|
@ -210,17 +185,6 @@ export const PerformancePage = () => {
|
|||
<Subtitle>Monitor API performance and response times</Subtitle>
|
||||
</div>
|
||||
|
||||
<TimeRangeContainer>
|
||||
{['1 Hour', '6 Hours', '24 Hours'].map((range) => (
|
||||
<TimeRangeButton
|
||||
key={range}
|
||||
$isActive={timeRange === range}
|
||||
onClick={() => setTimeRange(range)}
|
||||
>
|
||||
{range}
|
||||
</TimeRangeButton>
|
||||
))}
|
||||
</TimeRangeContainer>
|
||||
</PageHeader>
|
||||
|
||||
{/* Performance Alerts */}
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
<Subtitle>Track revenue performance and trends</Subtitle>
|
||||
</div>
|
||||
|
||||
<DateFilterContainer data-testid="date-filter">
|
||||
{['7d', '30d', '90d'].map((range) => (
|
||||
<FilterButton
|
||||
key={range}
|
||||
$isActive={dateRange === range}
|
||||
onClick={() => setDateRange(range)}
|
||||
data-testid={`filter-${range}`}
|
||||
>
|
||||
{range === '7d' ? '7 Days' : range === '30d' ? '30 Days' : '90 Days'}
|
||||
</FilterButton>
|
||||
))}
|
||||
</DateFilterContainer>
|
||||
</PageHeader>
|
||||
|
||||
{/* KPI Cards */}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue