UNPKG

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
/** * 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 }; };