UNPKG

@pagamio/frontend-commons-lib

Version:

Pagamio library for Frontend reusable components like the form engine and table container

195 lines (194 loc) 10.4 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import ReactECharts from 'echarts-for-react'; import { useMemo, useState } from 'react'; import Card from '../components/CardWrapper'; import ChartDetailsModal from '../components/ChartDetailsModal'; import ChartWrapper from '../components/ChartWrapper'; import { useChartData } from '../hooks/useChartData'; import { DashboardPaths, defaultColors, getPieChartData } from '../utils'; import { createDistributionMetricOptions } from '../utils/chartOptions'; import { createTooltipFormatter, processTooltipFields } from '../utils/tooltipUtils'; const useMetricData = (url, query) => { const { data = [], error, isEmpty, loading, refresh } = useChartData(url, query); return { data, error, isEmpty, loading, refresh }; }; const sumByKey = (array, key) => { return array.reduce((total, item) => { return total + (item[key] ?? 0); // fallback to 0 if key is missing }, 0); }; const useItemMetricState = (url, queryData) => { const { data, error, isEmpty, loading, refresh } = useMetricData(url, queryData.query); const firstResult = Array.isArray(data) && data.length > 0 ? data[0] : {}; const totalNumberOfItems = sumByKey(data, queryData.valueKey); return { value: totalNumberOfItems ?? 0, previousValue: queryData.previousValueKey ? (firstResult[queryData.previousValueKey] ?? 0) : undefined, change: firstResult[queryData.changeKey] ?? 0, title: queryData.title, format: queryData.format ?? '', isEmpty, loading, error, currency: firstResult?.currency, refresh: () => refresh().catch(() => { }), }; }; const useMetricState = (url, queryData) => { const { data, error, isEmpty, loading, refresh } = useMetricData(url, queryData.query); const valueMetricData = data; return { value: (valueMetricData && valueMetricData[queryData.valueKey]) ?? 0, previousValue: queryData.previousValueKey ? (valueMetricData?.[queryData.previousValueKey] ?? 0) : undefined, change: (valueMetricData && valueMetricData[queryData.changeKey]) ?? 0, title: queryData.title, format: queryData.format ?? '', isEmpty, loading, error, currency: (typeof valueMetricData === 'object' && valueMetricData?.additionalData?.currency) ?? undefined, refresh: () => refresh().catch(() => { }), }; }; const usePieChartOptions = (data, config) => { const tooltipFormatterFn = useMemo(() => { const currency = data?.[0]?.currency; return createTooltipFormatter(config.tooltipValueFormat, config.tooltipTitle ?? 'Value', config.tooltipUnit ?? '', config.tooltipFields.map((field) => ({ toolTipkey: field.toolTipkey ?? '', valueKey: field.valueKey ?? '', nameKey: field.nameKey ?? '', label: field.label ?? '', suffix: field.suffix ?? '', format: field.format ?? 'number', formatter: field.formatter ?? ((value) => String(value)), })), data, currency, config.currencyDisplaySymbol); }, [config, data]); return useMemo(() => { const chartData = data.length > 0 ? getPieChartData(data, config.nameKey, config.valueKey, config.otherKey) : undefined; return { tooltip: { trigger: 'item', formatter: tooltipFormatterFn, ...config.chartToolTip, }, legend: { orient: 'horizontal', top: 0, left: 'center', padding: [0, 0, 20, 0], type: 'plain', textStyle: { fontSize: 12 }, }, color: config.colors, series: [ { name: config.title, type: 'pie', radius: ['0%', '65%'], center: ['50%', '60%'], data: chartData?.seriesData ?? [], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)', }, }, }, ], grid: { containLabel: true }, ...config.options, }; }, [data, config, tooltipFormatterFn]); }; const PieChart = ({ url = DashboardPaths.QUERY, metricDetailData, nameKey, valueKey, query, otherKey, options = {}, title, colors = defaultColors, tooltipValueFormat, tooltipTitle = 'Value', tooltipUnit = '%', tooltipAdditionalFields = [], currencyDisplaySymbol, chartToolTip, showDetailsModal = true, ...props }) => { const [isModalOpen, setIsModalOpen] = useState(false); const statisticsUrl = metricDetailData?.statisticsUrl ?? DashboardPaths.METRICS; // Base chart data const { data: chartMetricData = [], error, isEmpty, loading, refresh } = useMetricData(url, query); // Full metric data for expanded view const fullMetricState = useMetricData(url, { ...query, limit: undefined }); // Statistics data const itemMetrics = useItemMetricState(url, metricDetailData.itemStatisticsQuery); const valueMetrics = useMetricState(statisticsUrl, metricDetailData.valueMetricsQuery); const averageMetrics = useMetricState(statisticsUrl, metricDetailData.averageMetricsQuery); // Distribution data const distributionState = useMetricData(url, metricDetailData.distributionMetricsQuery.query); const processedTooltipFields = useMemo(() => processTooltipFields(tooltipAdditionalFields), [tooltipAdditionalFields]); const processedDistributionTooltipFields = useMemo(() => processTooltipFields(metricDetailData.distributionChartTooltip.tooltipAdditionalFields), [metricDetailData.distributionChartTooltip.tooltipAdditionalFields]); const baseChartConfig = { tooltipValueFormat, tooltipTitle, tooltipUnit, tooltipFields: processedTooltipFields, currencyDisplaySymbol, colors, nameKey, valueKey, otherKey, chartToolTip: { trigger: 'item', ...chartToolTip, }, options, }; // Chart options for different views const chartOptions = usePieChartOptions(chartMetricData, { ...baseChartConfig, title }); const fullChartOptions = usePieChartOptions(fullMetricState.data, baseChartConfig); const topFiveChartOptions = usePieChartOptions(fullMetricState.data?.slice(0, 5) ?? [], { ...baseChartConfig, tooltipTitle: metricDetailData.topFiveChartTitle, }); const bottomFiveChartOptions = usePieChartOptions((fullMetricState.data?.slice(-5) ?? []).reverse(), { ...baseChartConfig, tooltipTitle: metricDetailData.bottomFiveChartTitle, }); const { topFiveItems, bottomFiveItems } = useMemo(() => { if (!fullMetricState.data?.length) { return { topFiveItems: {}, bottomFiveItems: {}, }; } return { topFiveItems: topFiveChartOptions, bottomFiveItems: bottomFiveChartOptions, }; }, [fullMetricState.data, topFiveChartOptions, bottomFiveChartOptions]); const distributionMetricData = useMemo(() => { return createDistributionMetricOptions({ distributionChartTooltip: metricDetailData.distributionChartTooltip, distributionData: distributionState.data ?? [], processedTooltipFields: processedDistributionTooltipFields, xAxisDataValueKey: metricDetailData.distributionMetricsQuery.xAxisDataValueKey, seriesDataValueKey: metricDetailData.distributionMetricsQuery.seriesDataValueKey, }); }, [ distributionState.data, metricDetailData.distributionMetricsQuery.seriesDataValueKey, metricDetailData.distributionMetricsQuery.xAxisDataValueKey, metricDetailData.distributionChartTooltip, processedDistributionTooltipFields, ]); return (_jsxs(_Fragment, { children: [_jsx(Card, { title: title, ...props, showDetailsModal: showDetailsModal, onOpenDetails: showDetailsModal && chartMetricData ? () => setIsModalOpen(true) : undefined, children: _jsx(ChartWrapper, { loading: loading, error: error, isEmpty: isEmpty, onRetry: refresh, children: _jsx("div", { children: _jsx(ReactECharts, { option: chartOptions, style: { height: '350px' } }) }) }) }), showDetailsModal && (_jsx(ChartDetailsModal, { title: metricDetailData.title, isOpen: isModalOpen, onClose: () => setIsModalOpen(false), data: fullMetricState.data, columns: metricDetailData.dataGridColumns, error: fullMetricState.error, isEmpty: fullMetricState.isEmpty, loading: fullMetricState.loading, detailsTableTitle: metricDetailData.detailsTableTitle, refresh: fullMetricState.refresh, renderDetailsChart: fullMetricState.data?.length <= 40, topFivePerformingChartTitle: metricDetailData.topFiveChartTitle, bottomFivePerformingChartTitle: metricDetailData.bottomFiveChartTitle, top5PerformingItems: topFiveItems, bottom5NonPerformingItems: bottomFiveItems, chartData: fullChartOptions, valueKey: metricDetailData.valueKey, dataGridQuery: query, searchKey: metricDetailData.dataGridSearchKey, searchInputPlaceHolder: metricDetailData.dataSearchInputPlaceHolder, itemMetricsData: { ...itemMetrics, error: itemMetrics.error, }, valueMetricsData: { ...valueMetrics, error: valueMetrics.error, }, averageMetricsData: { ...averageMetrics, error: averageMetrics.error, }, distributionMetricsData: { data: distributionMetricData, title: metricDetailData.distributionMetricsQuery.title, isEmpty: distributionState.isEmpty, loading: distributionState.loading, error: distributionState.error, refresh: () => distributionState.refresh().catch(() => { }), chartToolTip: metricDetailData.distributionMetricsQuery.chartToolTip, } }))] })); }; export default PieChart;