pagamio-frontend-commons-lib
Version:
Pagamio library for Frontend reusable components like the form engine and table container
247 lines (246 loc) • 10.3 kB
JavaScript
/**
* A generic utility to build x-axis values and series data for a stacked bar chart in ECharts.
*
* @param data - An array of objects of any shape (T).
* @param xAxisDataValueKey - The string key in each object representing x-axis data (e.g. "channel", "status").
* @param seriesDataNameKey - The string key in each object representing the series name (e.g. "productName").
* @param seriesDataValueKey - The string key in each object representing the numeric value (e.g. "amount").
*
* @returns An object containing:
* - xAxisValues: A string[] of unique x-axis values (in order).
* - seriesData: An array of SeriesItem (with "name", "type", "stack", and the numeric data array).
*/
export function getBarChartData(data, xAxisDataValueKey, seriesDataNameKey, seriesDataValueKey) {
// 1. Extract unique x-axis values (in order of appearance).
const xAxis = [];
for (const item of data) {
const xVal = String(item[xAxisDataValueKey] ?? '');
if (!xAxis.includes(xVal)) {
xAxis.push(xVal);
}
}
// 2. Extract unique series names (in order of appearance).
const seriesNames = [];
for (const item of data) {
const seriesName = String(item[seriesDataNameKey] ?? '');
if (!seriesNames.includes(seriesName)) {
seriesNames.push(seriesName);
}
}
// 3. Create a mapping: seriesName -> number[] (index matches xAxis order).
const productAmountMap = {};
seriesNames.forEach((name) => {
productAmountMap[name] = Array(xAxis.length).fill(0);
});
// 4. Populate the mapping using the data.
for (const item of data) {
const xVal = String(item[xAxisDataValueKey] ?? '');
const seriesName = String(item[seriesDataNameKey] ?? '');
const amount = Number(item[seriesDataValueKey] ?? 0);
const xIndex = xAxis.indexOf(xVal);
if (xIndex !== -1 && productAmountMap[seriesName]) {
productAmountMap[seriesName][xIndex] = amount;
}
}
// 5. Construct the series data array for ECharts.
const seriesData = seriesNames.map((name) => ({
name,
type: 'bar',
stack: 'total',
data: productAmountMap[name],
}));
return {
xAxisValues: xAxis,
seriesData,
};
}
/**
* Flattens your data (if it happens to be an array of arrays)
* and maps it into an xAxis array of names and a seriesData
* array of { name, value } objects.
*
* @param data The raw data from the backend, e.g. [ [ { amount: 60, productName: 'Electronics' }, ... ] ]
* If data is just a single array (e.g. [ { amount: 60, productName: 'Electronics' }, ... ]),
* this function will still work (no harm in calling `flat()`).
* @param nameKey The key in each object that maps to the category name (used in xAxis and the `name` field).
* @param valueKey The key in each object that maps to the numeric value (used in `value`).
*
* @returns An object containing:
* - xAxis: string[] (e.g. ["Electronics", "Fashion", "Home & Garden"])
* - seriesData: Array<{ name: string; value: number }>
* (e.g. [{ name: "Electronics", value: 60 }, ...])
*/
export function getPieChartData(data, // can be an array of objects OR an array of arrays of objects
nameKey, // e.g. 'productName'
valueKey, // e.g. 'amount'
otherKey) {
// Flatten in case the response is nested, e.g. [ [ { ...}, { ...} ] ]
const flattened = Array.isArray(data[0]) ? data.flat() : data;
const getOtherKeyValue = (item) => {
let result = 0;
if (otherKey) {
let convertedValue;
if (typeof item[otherKey] === 'number') {
convertedValue = Number(item[otherKey]);
}
else {
convertedValue = String(item[otherKey]);
}
result = convertedValue;
}
return result;
};
// Build xAxis and seriesData
const xAxis = flattened.map((item) => String(item[nameKey]));
const seriesData = flattened.map((item) => ({
name: String(item[nameKey]),
value: Number(item[valueKey]) || 0,
otherValue: getOtherKeyValue(item),
}));
return { xAxis, seriesData };
}
/**
* Flattens your data (if it happens to be an array of arrays)
* and maps it into an xAxis array of names and a seriesData
* array of { name, value } objects.
*
* @param data The raw data from the backend, e.g. [ [ { amount: 60, productName: 'Electronics' }, ... ] ]
* If data is just a single array (e.g. [ { amount: 60, productName: 'Electronics' }, ... ]),
* this function will still work (no harm in calling `flat()`).
* @param nameKey The key in each object that maps to the category name (used in xAxis and the `name` field).
* @param valueKey The key in each object that maps to the numeric value (used in `value`).
*
* @returns An object containing:
* - seriesData: Array<{ name: string; value: number }>
* (e.g. [{ name: "Electronics", value: 60 }, ...])
*/
export function getSeriesData(data, // can be an array of objects OR an array of arrays of objects
nameKey, // e.g. 'productName'
valueKey) {
// Flatten in case the response is nested, e.g. [ [ { ...}, { ...} ] ]
const flattened = Array.isArray(data[0]) ? data.flat() : data;
// Build seriesData
const seriesData = flattened.map((item) => ({
name: String(item[nameKey]),
value: Number(item[valueKey]) || 0,
}));
return { seriesData };
}
/**
* Flattens your data (if it happens to be an array of arrays)
* and maps it into a select options array. Handles null/undefined values safely.
*
* @param data The raw data from the backend.
* @param labelKey The key in each object that maps to the label in the options.
* @param valueKey Optional key for the value in the options. If not provided, labelKey will be used for both label and value.
* @returns An object containing optionsData array with { label, value } pairs.
*/
export function getFilterOptionsData(data, labelKey, valueKey) {
// Handle empty or null data
if (!data || !data.length) {
return { optionsData: [] };
}
// Flatten in case the response is nested, e.g. [ [ { ...}, { ...} ] ]
const flattened = Array.isArray(data[0]) ? data.flat() : data;
// Use a Set to track unique labels and values
const uniqueLabels = new Set();
const uniqueValues = new Set();
// Build optionsData, ensuring uniqueness and handling null values
const optionsData = flattened.reduce((acc, item) => {
// Skip null/undefined items
if (!item)
return acc;
const rawLabelValue = item[labelKey];
// Skip if the labelKey value is null/undefined
if (rawLabelValue == null)
return acc;
const label = String(rawLabelValue);
const value = valueKey && item[valueKey] != null ? String(item[valueKey]) : label;
// Check if the label or value is already in the Set
if (!uniqueLabels.has(label) && !uniqueValues.has(value)) {
uniqueLabels.add(label);
uniqueValues.add(value);
acc.push({ label, value });
}
return acc;
}, []);
return { optionsData };
}
/**
* Flattens your data (if it happens to be an array of arrays)
* and maps it into a select options array.
*/
export function generateSelectOptions(data, labelKey, valueKey) {
// Flatten in case the response is nested, e.g. [ [ { ...}, { ...} ] ]
const flattened = Array.isArray(data[0]) ? data.flat() : data;
// Use a Set to track unique labels and values
const uniqueLabels = new Set();
const uniqueValues = new Set();
// Build optionsData, ensuring uniqueness
const optionsData = flattened.reduce((acc, item) => {
const label = String(item[labelKey]);
const value = String(item[valueKey]);
// Check if the label or value is already in the Set
if (!uniqueLabels.has(label) && !uniqueValues.has(value)) {
uniqueLabels.add(label);
uniqueValues.add(value);
acc.push({ label, value });
}
return acc;
}, []);
return { optionsData };
}
export const formatValue = (value, format, options) => {
const { currency = 'ZAR', locale = 'en-ZA' } = options || {};
switch (format) {
case 'currency':
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
minimumFractionDigits: 2,
}).format(value);
case 'number':
return new Intl.NumberFormat(locale, {
style: 'decimal',
minimumFractionDigits: 0,
}).format(value);
default:
return value.toLocaleString();
}
};
export const transformColumns = (columns) => {
return columns.map((col) => ({
accessorKey: col.accessor,
header: col.header,
enableCopy: col.enableCopy,
formattedCellElement: col.formattedCellElement ? col.formattedCellElement : undefined,
}));
};
/**
* Formats an array of objects into DataPoint format required for Distribution Chart
* @param data Array of objects to format
* @param valueKey Key in the object to use as the value
* @param nameKey Key in the object to use as the name
* @returns Array of DataPoint objects
*/
export function formatToDataPoints(data, valueKey, nameKey) {
return data.map((item) => ({
value: Number(item[valueKey]) || 0, // Convert to number, default to 0 if invalid
name: String(item[nameKey] || ''), // Convert to string, default to empty string if undefined
}));
}
export const formatDonutChartData = (data, nameKey, valueKey) => {
if (!data?.length)
return { legendData: [], seriesData: [] };
// Extract the legend data (product names)
const legendData = data.map((item) => item[nameKey]);
// Format series data for the donut chart
const seriesData = data.map((item) => {
const dataPoint = {
name: item[nameKey],
value: item[valueKey],
};
return dataPoint;
});
return { legendData, seriesData };
};