platform-codebase/@packages/@plugins/analytics/src/hooks/useAdminQuery.ts

583 lines
15 KiB
TypeScript
Raw Normal View History

/**
* Analytics Admin Query Hooks
*
* React Query hooks for fetching analytics data from the backend.
* These hooks use the analytics backend client and provide caching,
* automatic refetching, and error handling.
*
* Supports mock mode when wrapped in MockDataProvider or when
* MOCK_ANALYTICS environment variable is set.
*/
import { useState, useEffect, useCallback, useContext, createContext } from 'react'
// Mock data context - allows hooks to use mock data instead of real API calls
interface MockContextValue {
enabled: boolean
mockData: Record<string, unknown>
}
const MockContext = createContext<MockContextValue>({ enabled: false, mockData: {} })
export const useMockContext = () => useContext(MockContext)
// Provider for mock mode
export { MockContext }
// Types for API responses
interface QueryResult<T> {
data: T | undefined
isLoading: boolean
isError: boolean
error: Error | null
refetch: () => void
}
// Simple query hook factory - in production, use @tanstack/react-query
function useQuery<T>(
key: string,
fetcher: () => Promise<T>,
options?: { enabled?: boolean; refetchInterval?: number; mockData?: T }
): QueryResult<T> {
const [data, setData] = useState<T | undefined>(undefined)
const [isLoading, setIsLoading] = useState(true)
const [isError, setIsError] = useState(false)
const [error, setError] = useState<Error | null>(null)
const enabled = options?.enabled ?? true
const mockData = options?.mockData
const fetchData = useCallback(async () => {
if (!enabled) return
setIsLoading(true)
setIsError(false)
setError(null)
try {
// If mock data is provided, use it instead of making API call
if (mockData !== undefined) {
// Small delay to simulate network
await new Promise(resolve => setTimeout(resolve, 50))
setData(mockData)
} else {
const result = await fetcher()
setData(result)
}
} catch (err) {
setIsError(true)
setError(err instanceof Error ? err : new Error('Unknown error'))
} finally {
setIsLoading(false)
}
}, [enabled, fetcher, mockData])
useEffect(() => {
fetchData()
}, [fetchData, key])
useEffect(() => {
if (options?.refetchInterval && enabled && !mockData) {
const interval = setInterval(fetchData, options.refetchInterval)
return () => clearInterval(interval)
}
return undefined
}, [fetchData, options?.refetchInterval, enabled, mockData])
return {
data,
isLoading,
isError,
error,
refetch: fetchData,
}
}
// Check if we should use mock data (env var or mock provider)
const USE_MOCK = typeof process !== 'undefined' && process.env?.MOCK_ANALYTICS === 'true'
// Base URL for analytics API - should be configured via environment
const API_BASE = typeof process !== 'undefined' && process.env?.ANALYTICS_API_URL
? process.env.ANALYTICS_API_URL
: '/api/analytics'
async function fetchJson<T>(endpoint: string): Promise<T> {
const response = await fetch(`${API_BASE}${endpoint}`)
if (!response.ok) {
throw new Error(`Failed to fetch ${endpoint}: ${response.statusText}`)
}
return response.json()
}
// Import mock data for fallback
import { mockData as MOCK_DATA } from '../providers/MockDataProvider'
// ============================================================================
// Revenue Hooks
// ============================================================================
export interface RevenueMetrics {
totalRevenue: number
monthlyRecurring: number
oneTimeRevenue: number
cryptoRevenue: number
growthRate: number
avgRevenuePerUser: number
}
export interface RevenueTrendPoint {
date: string
revenue: number
recurring: number
oneTime: number
}
export interface RevenueBreakdown {
bySource: { source: string; amount: number; percentage: number }[]
byProvider: { provider: string; amount: number; percentage: number }[]
}
export function useRevenueMetrics() {
return useQuery<RevenueMetrics>(
'revenue-metrics',
() => fetchJson('/dashboard/revenue'),
{ mockData: USE_MOCK ? MOCK_DATA.revenueMetrics : undefined }
)
}
export function useRevenueTrend() {
return useQuery<RevenueTrendPoint[]>(
'revenue-trend',
() => fetchJson('/dashboard/revenue-chart'),
{ mockData: USE_MOCK ? MOCK_DATA.revenueTrend : undefined }
)
}
export function useRevenueBreakdown() {
return useQuery<RevenueBreakdown>(
'revenue-breakdown',
() => fetchJson('/dashboard/revenue-breakdown'),
{ mockData: USE_MOCK ? MOCK_DATA.revenueBreakdown : undefined }
)
}
// ============================================================================
// Transactions Hooks
// ============================================================================
export interface Transaction {
id: string
timestamp: string
type: string
status: string
amount: number
cryptoAmount?: number
cryptoCurrency?: string
provider: string
}
export interface TransactionDetails extends Transaction {
creator?: { name: string; id: string }
subscriber?: { name: string; id: string }
fees?: { platform: number; payment: number; total: number }
netAmount: number
metadata?: { ip: string; userAgent: string; referrer: string }
errorReason?: string
}
export interface TransactionsResult {
transactions: Transaction[]
total: number
}
export function useTransactions(filters: {
status?: string
type?: string
dateRange?: string
provider?: string
search?: string
}) {
const queryString = new URLSearchParams(
Object.entries(filters).filter(([, v]) => v && v !== 'all')
).toString()
return useQuery<TransactionsResult>(
`transactions-${queryString}`,
() => fetchJson(`/transactions?${queryString}`),
{ mockData: USE_MOCK ? MOCK_DATA.transactions(filters) : undefined }
)
}
export function useTransactionDetails(id: string) {
return useQuery<TransactionDetails>(
`transaction-${id}`,
() => fetchJson(`/transactions/${id}`),
{ enabled: !!id, mockData: USE_MOCK ? MOCK_DATA.transactionDetails(id) : undefined }
)
}
// ============================================================================
// P&L Hooks
// ============================================================================
export interface PnLStatement {
revenue: { total: number; crypto: number }
costs: { total: number }
grossProfit: number
operatingExpenses: number
netIncome: number
ebitda: number
margins: { gross: number; net: number }
}
export interface PnLTrendPoint {
date: string
revenue: number
costs: number
netIncome: number
}
export interface ReserveProgress {
target: number
current: number
percentage: number
monthlyContribution: number
projectedDate: string
}
export function usePnLStatement() {
return useQuery<PnLStatement>(
'pnl-statement',
() => fetchJson('/reports/pnl'),
{ mockData: USE_MOCK ? MOCK_DATA.pnlStatement : undefined }
)
}
export function usePnLTrend() {
return useQuery<PnLTrendPoint[]>(
'pnl-trend',
() => fetchJson('/reports/pnl-trend'),
{ mockData: USE_MOCK ? MOCK_DATA.pnlTrend : undefined }
)
}
export function useReserveProgress() {
return useQuery<ReserveProgress>(
'reserve-progress',
() => fetchJson('/reports/reserve'),
{ mockData: USE_MOCK ? MOCK_DATA.reserveProgress : undefined }
)
}
// ============================================================================
// Real-Time Hooks
// ============================================================================
export interface RealTimeMetrics {
activeUsers: number
activeCreators: number
liveTransactions: number
revenuePerMinute: number
systemLoad: number
responseTime: number
}
export interface RealTimeActivity {
timestamp: string
event: string
user: string
amount: number
}
export interface ActiveUserPoint {
minute: string
count: number
}
export function useRealTimeMetrics() {
return useQuery<RealTimeMetrics>(
'realtime-metrics',
() => fetchJson('/realtime/metrics'),
{ refetchInterval: USE_MOCK ? undefined : 5000, mockData: USE_MOCK ? MOCK_DATA.realTimeMetrics : undefined }
)
}
export function useRealTimeActivity() {
return useQuery<RealTimeActivity[]>(
'realtime-activity',
() => fetchJson('/realtime/activity'),
{ refetchInterval: USE_MOCK ? undefined : 5000, mockData: USE_MOCK ? MOCK_DATA.realTimeActivity : undefined }
)
}
export function useActiveUsers() {
return useQuery<ActiveUserPoint[]>(
'active-users',
() => fetchJson('/realtime/active-users'),
{ refetchInterval: USE_MOCK ? undefined : 5000, mockData: USE_MOCK ? MOCK_DATA.activeUsers : undefined }
)
}
// ============================================================================
// Costs Hooks
// ============================================================================
export interface CostMetrics {
totalCosts: number
fixedCosts: number
variableCosts: number
costPerTransaction: number
costGrowthRate: number
budgetUtilization: number
}
export interface CostBreakdown {
byCategory: { category: string; amount: number; percentage: number }[]
byType: { type: string; amount: number; percentage: number }[]
}
export interface BudgetComparison {
totalBudget: number
actualCosts: number
remaining: number
utilization: number
byCategory: { category: string; budget: number; actual: number; variance: number }[]
}
export function useCostMetrics() {
return useQuery<CostMetrics>(
'cost-metrics',
() => fetchJson('/dashboard/costs'),
{ mockData: USE_MOCK ? MOCK_DATA.costMetrics : undefined }
)
}
export function useCostBreakdown() {
return useQuery<CostBreakdown>(
'cost-breakdown',
() => fetchJson('/dashboard/costs-breakdown'),
{ mockData: USE_MOCK ? MOCK_DATA.costBreakdown : undefined }
)
}
export function useBudgetComparison() {
return useQuery<BudgetComparison>(
'budget-comparison',
() => fetchJson('/dashboard/budget'),
{ mockData: USE_MOCK ? MOCK_DATA.budgetComparison : undefined }
)
}
// ============================================================================
// Performance Hooks
// ============================================================================
export interface PerformanceMetrics {
avgResponseTime: number
p50ResponseTime: number
p95ResponseTime: number
p99ResponseTime: number
requestsPerSecond: number
errorRate: number
uptime: number
}
export interface EndpointMetrics {
endpoint: string
avgResponseTime: number
requestCount: number
errorRate: number
}
export function usePerformanceMetrics() {
return useQuery<PerformanceMetrics>(
'performance-metrics',
() => fetchJson('/dashboard/performance'),
{ mockData: USE_MOCK ? MOCK_DATA.performanceMetrics : undefined }
)
}
export function useEndpointMetrics() {
return useQuery<EndpointMetrics[]>(
'endpoint-metrics',
() => fetchJson('/dashboard/endpoints'),
{ mockData: USE_MOCK ? MOCK_DATA.endpointMetrics : undefined }
)
}
// ============================================================================
// Error Tracking Hooks
// ============================================================================
export interface ErrorMetrics {
totalErrors: number
errorRate: number
criticalErrors: number
resolvedErrors: number
avgResolutionTime: number
}
export interface ErrorByType {
type: string
count: number
percentage: number
}
export interface RecentError {
id: string
type: string
message: string
endpoint: string
count: number
lastOccurrence: string
severity: string
status: string
}
export function useErrorMetrics() {
return useQuery<ErrorMetrics>(
'error-metrics',
() => fetchJson('/dashboard/errors'),
{ mockData: USE_MOCK ? MOCK_DATA.errorMetrics : undefined }
)
}
export function useErrorsByType() {
return useQuery<ErrorByType[]>(
'errors-by-type',
() => fetchJson('/dashboard/errors-by-type'),
{ mockData: USE_MOCK ? MOCK_DATA.errorsByType : undefined }
)
}
export function useRecentErrors() {
return useQuery<RecentError[]>(
'recent-errors',
() => fetchJson('/dashboard/recent-errors'),
{ mockData: USE_MOCK ? MOCK_DATA.recentErrors : undefined }
)
}
// ============================================================================
// Bounce Rate Hooks
// ============================================================================
export interface BounceRateMetrics {
overallBounceRate: number
avgSessionDuration: number
pagesPerSession: number
exitRate: number
}
export interface BounceRateByPage {
page: string
bounceRate: number
visits: number
}
export function useBounceRateMetrics() {
return useQuery<BounceRateMetrics>(
'bounce-rate-metrics',
() => fetchJson('/dashboard/bounce-rate'),
{ mockData: USE_MOCK ? MOCK_DATA.bounceRateMetrics : undefined }
)
}
export function useBounceRateByPage() {
return useQuery<BounceRateByPage[]>(
'bounce-rate-by-page',
() => fetchJson('/dashboard/bounce-rate-by-page'),
{ mockData: USE_MOCK ? MOCK_DATA.bounceRateByPage : undefined }
)
}
// ============================================================================
// Conversion Hooks
// ============================================================================
export interface ConversionMetrics {
overallConversionRate: number
signupToSubscriber: number
visitorToSignup: number
freeToTrial: number
trialToPaid: number
avgTimeToConversion: number
}
export interface FunnelStage {
stage: string
count: number
rate: number
}
export interface ConversionBySource {
source: string
conversions: number
rate: number
}
export function useConversionMetrics() {
return useQuery<ConversionMetrics>(
'conversion-metrics',
() => fetchJson('/dashboard/conversions'),
{ mockData: USE_MOCK ? MOCK_DATA.conversionMetrics : undefined }
)
}
export function useFunnelData() {
return useQuery<FunnelStage[]>(
'funnel-data',
() => fetchJson('/funnel'),
{ mockData: USE_MOCK ? MOCK_DATA.funnelData : undefined }
)
}
export function useConversionBySource() {
return useQuery<ConversionBySource[]>(
'conversion-by-source',
() => fetchJson('/dashboard/conversions-by-source'),
{ mockData: USE_MOCK ? MOCK_DATA.conversionBySource : undefined }
)
}
// ============================================================================
// A/B Testing Hooks
// ============================================================================
export interface ABTestMetrics {
activeTests: number
completedTests: number
significantResults: number
avgLiftPercent: number
}
export interface ABTest {
id: string
name: string
status: 'running' | 'completed' | 'paused'
variants: number
participants: number
conversionRateA: number
conversionRateB: number
significance: number
startDate: string
endDate?: string
winner?: string
}
export function useABTestMetrics() {
return useQuery<ABTestMetrics>(
'ab-test-metrics',
() => fetchJson('/ab-tests/metrics'),
{ mockData: USE_MOCK ? MOCK_DATA.abTestMetrics : undefined }
)
}
export function useActiveTests() {
return useQuery<ABTest[]>(
'active-tests',
() => fetchJson('/ab-tests'),
{ mockData: USE_MOCK ? MOCK_DATA.activeTests : undefined }
)
}