/** * useStatistics Hook * * Computed hook for InfoCenter statistics. * Calculates statistics from pages and analyses data using useMemo. * * @layer Presentation */ import { useMemo } from 'react'; import { ReliabilityStatus } from '@archer/domain'; import { AnalysisStatus } from '@/domain/entities/DiscoveredPage'; import { useCrawledPages } from './useCrawledPages'; import { usePageAnalyses } from './usePageAnalyses'; /** * InfoCenter Statistics * * Calculated statistics for a tenant's InfoCenter data */ export interface InfoCenterStats { totalCrawledPages: number; totalAnalyzedPages: number; totalElements: number; coveragePercentage: number; pendingAnalysis: number; failedAnalysis: number; elementsByType: Record; lastCrawlDate?: string; lastAnalysisDate?: string; /** Reliability status breakdown (count by status) */ analysesByReliability: Record; /** Total analyses with pattern detection data */ withPatterns: number; } /** * useStatistics Hook Options */ export interface UseStatisticsOptions { /** * Tenant ID to calculate statistics for */ tenantId?: string; /** * Maximum number of analyses to load for statistics * @default 100 */ limit?: number; } /** * useStatistics Hook Return Type */ export interface UseStatisticsReturn { /** * Computed statistics */ stats: InfoCenterStats; /** * Loading state (true if either pages or analyses are loading) */ isLoading: boolean; /** * Error state */ error: Error | null; } /** * Calculate InfoCenter statistics from pages and analyses * * Statistics are computed from domain entities using useMemo for performance. * Automatically recalculates when pages or analyses data changes. * * @param options - Query options * @returns Statistics with loading/error states * * @example * ```tsx * function StatisticsCards() { * const { stats, isLoading } = useStatistics({ * tenantId: 'tenant-123', * }); * * if (isLoading) return ; * * return ( *
* Total Pages: {stats.totalCrawledPages} * Analyzed: {stats.totalAnalyzedPages} * Coverage: {stats.coveragePercentage}% *
* ); * } * ``` */ export function useStatistics(options: UseStatisticsOptions = {}): UseStatisticsReturn { const { tenantId, limit = 100 } = options; // Fetch pages and analyses const { pages, isLoading: pagesLoading, error: pagesError } = useCrawledPages({ tenantId }); const { analyses, isLoading: analysesLoading, error: analysesError } = usePageAnalyses({ tenantId, limit, }); // Calculate statistics const stats = useMemo((): InfoCenterStats => { const totalCrawledPages = pages.length; const totalAnalyzedPages = pages.filter((p) => p.isAnalyzed()).length; const totalElements = analyses.reduce((sum, analysis) => sum + analysis.elementCount, 0); const coveragePercentage = totalCrawledPages > 0 ? Math.round((totalAnalyzedPages / totalCrawledPages) * 100) : 0; const pendingAnalysis = pages.filter((p) => p.needsAnalysis()).length; const failedAnalysis = pages.filter((p) => p.analysisStatus === AnalysisStatus.FAILED).length; // Calculate elements by type const elementsByType: Record = {}; analyses.forEach((analysis) => { const typeCounts = analysis.getElementCountByType(); Object.entries(typeCounts).forEach(([type, count]) => { elementsByType[type] = (elementsByType[type] || 0) + count; }); }); // Calculate reliability status breakdown const analysesByReliability: Record = { [ReliabilityStatus.DRAFT]: 0, [ReliabilityStatus.PENDING_CONFIRMATION]: 0, [ReliabilityStatus.CONFIRMED]: 0, [ReliabilityStatus.REJECTED]: 0, unknown: 0, }; analyses.forEach((analysis) => { const status = analysis.reliabilityStatus; if (status) { analysesByReliability[status] = (analysesByReliability[status] || 0) + 1; } else { analysesByReliability.unknown += 1; } }); // Count analyses with pattern detection data const withPatterns = analyses.filter((analysis) => analysis.hasPatterns()).length; // Get last crawl and analysis dates const crawlDates = pages.map(p => p.discoveredAt.getTime()).filter(d => !isNaN(d)); const analysisDates = analyses.map(a => a.analyzedAt.getTime()).filter(d => !isNaN(d)); const lastCrawlDate = crawlDates.length > 0 ? new Date(Math.max(...crawlDates)).toISOString() : undefined; const lastAnalysisDate = analysisDates.length > 0 ? new Date(Math.max(...analysisDates)).toISOString() : undefined; return { totalCrawledPages, totalAnalyzedPages, totalElements, coveragePercentage, pendingAnalysis, failedAnalysis, elementsByType, lastCrawlDate, lastAnalysisDate, analysesByReliability, withPatterns, }; }, [pages, analyses]); return { stats, isLoading: pagesLoading || analysesLoading, error: pagesError || analysesError, }; }