consumption-distribution-chart.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import { useEffect, useMemo, useRef, useState } from 'react'
  2. import { VChart } from '@visactor/react-vchart'
  3. import { AreaChart, BarChart3, WalletCards } from 'lucide-react'
  4. import { useTranslation } from 'react-i18next'
  5. import type { TimeGranularity } from '@/lib/time'
  6. import { VCHART_OPTION } from '@/lib/vchart'
  7. import { useTheme } from '@/context/theme-provider'
  8. import { DEFAULT_TIME_GRANULARITY } from '@/features/dashboard/constants'
  9. import { processChartData } from '@/features/dashboard/lib'
  10. import type { QuotaDataItem } from '@/features/dashboard/types'
  11. let themeManagerPromise: Promise<
  12. (typeof import('@visactor/vchart'))['ThemeManager']
  13. > | null = null
  14. type DistributionChartType = 'bar' | 'area'
  15. interface ConsumptionDistributionChartProps {
  16. data: QuotaDataItem[]
  17. loading?: boolean
  18. timeGranularity?: TimeGranularity
  19. }
  20. const CHART_TYPES: Array<{
  21. value: DistributionChartType
  22. labelKey: string
  23. icon: typeof BarChart3
  24. }> = [
  25. { value: 'bar', labelKey: 'Bar Chart', icon: BarChart3 },
  26. { value: 'area', labelKey: 'Area Chart', icon: AreaChart },
  27. ]
  28. export function ConsumptionDistributionChart(
  29. props: ConsumptionDistributionChartProps
  30. ) {
  31. const { t } = useTranslation()
  32. const { resolvedTheme } = useTheme()
  33. const [chartType, setChartType] = useState<DistributionChartType>('bar')
  34. const [themeReady, setThemeReady] = useState(false)
  35. const themeManagerRef = useRef<
  36. (typeof import('@visactor/vchart'))['ThemeManager'] | null
  37. >(null)
  38. const timeGranularity = props.timeGranularity ?? DEFAULT_TIME_GRANULARITY
  39. useEffect(() => {
  40. const updateTheme = async () => {
  41. setThemeReady(false)
  42. if (!themeManagerPromise) {
  43. themeManagerPromise = import('@visactor/vchart').then(
  44. (m) => m.ThemeManager
  45. )
  46. }
  47. const ThemeManager = await themeManagerPromise
  48. themeManagerRef.current = ThemeManager
  49. ThemeManager.setCurrentTheme(resolvedTheme === 'dark' ? 'dark' : 'light')
  50. setThemeReady(true)
  51. }
  52. updateTheme()
  53. }, [resolvedTheme])
  54. const chartData = useMemo(
  55. () => processChartData(props.loading ? [] : props.data, timeGranularity, t),
  56. [props.data, props.loading, timeGranularity, t]
  57. )
  58. const spec = chartType === 'bar' ? chartData.spec_line : chartData.spec_area
  59. return (
  60. <div className='overflow-hidden rounded-lg border'>
  61. <div className='flex w-full flex-col gap-3 border-b px-4 py-3 sm:px-5 lg:flex-row lg:items-center lg:justify-between'>
  62. <div className='flex items-center gap-2'>
  63. <WalletCards className='text-muted-foreground/60 size-4' />
  64. <div className='text-sm font-semibold'>
  65. {t('Quota Distribution')}
  66. </div>
  67. <span className='text-muted-foreground text-xs'>
  68. {t('Total:')} {chartData.totalQuotaDisplay}
  69. </span>
  70. </div>
  71. <div className='bg-muted/60 inline-flex h-8 rounded-md border p-0.5'>
  72. {CHART_TYPES.map((item) => {
  73. const Icon = item.icon
  74. return (
  75. <button
  76. key={item.value}
  77. type='button'
  78. onClick={() => setChartType(item.value)}
  79. className={`inline-flex items-center gap-1.5 rounded-[5px] px-3 text-xs font-medium transition-colors ${
  80. chartType === item.value
  81. ? 'bg-background text-foreground shadow-sm'
  82. : 'text-muted-foreground hover:text-foreground'
  83. }`}
  84. >
  85. <Icon className='size-3.5' />
  86. {t(item.labelKey)}
  87. </button>
  88. )
  89. })}
  90. </div>
  91. </div>
  92. <div className='h-96 p-2'>
  93. {themeReady && spec && (
  94. <VChart
  95. key={`${chartType}-${resolvedTheme}`}
  96. spec={{
  97. ...spec,
  98. theme: resolvedTheme === 'dark' ? 'dark' : 'light',
  99. background: 'transparent',
  100. }}
  101. option={VCHART_OPTION}
  102. />
  103. )}
  104. </div>
  105. </div>
  106. )
  107. }