@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
JavaScript
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;