Break down 948-line monolithic component into 11 focused modules following Vercel React best practices: **Module Structure:** - types.ts: Centralized type definitions and constants - hooks/: Custom hooks for data management (useReportData, useChartData, useReportFilters) - components/: Reusable UI components (MetricSelector, ViewModeToggle, UserFilter, ReportChart, Tooltips) - utils/: Parser and aggregator utility functions **Key Improvements:** - Reduced main component from 948 to ~200 lines (79% reduction) - Separated concerns: data fetching, state management, and UI rendering - Enhanced reusability and testability of individual components - Maintained backward compatibility with existing imports **Additional Fixes:** - Fixed tooltip displaying duplicate average time values - Formatted time values to 1 decimal place in both views - Removed redundant time display from tooltip footer **Performance Optimizations Applied:** - Moved tooltip components outside parent component (rerender-no-inline-components) - Hoisted regex pattern creation outside loops (js-hoist-regexp) - Used functional setState updates (rerender-functional-setState) - Memoized expensive computations and callbacks All changes maintain existing functionality while improving code quality and maintainability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
/**
|
|
* Custom hook for fetching and managing report data
|
|
* Handles data loading, parsing, and error states
|
|
*/
|
|
|
|
import { useState, useCallback, useEffect } from 'react'
|
|
import { ReportMetrics } from '../types'
|
|
import { parseReportData } from '../utils/parser'
|
|
|
|
interface UseReportDataResult {
|
|
isLoading: boolean
|
|
error: string | null
|
|
reportData: ReportMetrics[]
|
|
loadAndAnalyzeReports: () => Promise<void>
|
|
clearData: () => void
|
|
}
|
|
|
|
/**
|
|
* Hook for managing report data fetching and parsing
|
|
*
|
|
* @param isAdmin - Whether the current user has admin privileges
|
|
* @param isOpen - Whether the dialog is open
|
|
* @returns Report data state and control functions
|
|
*/
|
|
export const useReportData = (isAdmin: boolean, isOpen: boolean): UseReportDataResult => {
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [reportData, setReportData] = useState<ReportMetrics[]>([])
|
|
|
|
const loadAndAnalyzeReports = useCallback(async () => {
|
|
if (!isAdmin) return
|
|
|
|
setIsLoading(true)
|
|
setError(null)
|
|
|
|
try {
|
|
// 1. Fetch report list
|
|
const listResult = await window.electron.report.listAll()
|
|
if (!listResult.success || !listResult.data) {
|
|
throw new Error(listResult.error || '获取报告列表失败')
|
|
}
|
|
|
|
const reports = listResult.data
|
|
const metricsList: ReportMetrics[] = []
|
|
|
|
// 2. Fetch content for each report (in chunks to avoid memory/network issues)
|
|
// Rule: async-parallel - Using Promise.all for parallel fetching
|
|
const chunkSize = 10
|
|
for (let i = 0; i < reports.length; i += chunkSize) {
|
|
const chunk = reports.slice(i, i + chunkSize)
|
|
const contentPromises = chunk.map(async (report) => {
|
|
try {
|
|
const contentResult = await window.electron.report.download(report.key)
|
|
if (contentResult.success && contentResult.data) {
|
|
return { report, content: contentResult.data }
|
|
}
|
|
} catch (e) {
|
|
console.warn(`Failed to fetch content for report ${report.key}`, e)
|
|
}
|
|
return null
|
|
})
|
|
|
|
const chunkContents = await Promise.all(contentPromises)
|
|
|
|
// 3. Parse each report's markdown content
|
|
// Rule: js-hoist-regexp - Regex patterns now in parseReportData function
|
|
for (const item of chunkContents) {
|
|
if (!item) continue
|
|
|
|
const { report, content } = item
|
|
const parsedData = parseReportData(report, content)
|
|
|
|
metricsList.push(parsedData)
|
|
}
|
|
}
|
|
|
|
setReportData(metricsList)
|
|
} catch (err: any) {
|
|
setError(err.message || '分析报告时发生错误')
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [isAdmin])
|
|
|
|
const clearData = useCallback(() => {
|
|
setReportData([])
|
|
setError(null)
|
|
}, [])
|
|
|
|
// Auto-load data when dialog opens
|
|
useEffect(() => {
|
|
if (isOpen && isAdmin) {
|
|
void loadAndAnalyzeReports()
|
|
} else {
|
|
clearData()
|
|
}
|
|
}, [isOpen, isAdmin, loadAndAnalyzeReports, clearData])
|
|
|
|
return {
|
|
isLoading,
|
|
error,
|
|
reportData,
|
|
loadAndAnalyzeReports,
|
|
clearData
|
|
}
|
|
}
|