mirror of https://github.com/buster-so/buster.git
add basic vite integration and remove unused helpers
This commit is contained in:
parent
9889f1403c
commit
957c38c0ac
|
@ -5,14 +5,7 @@
|
|||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./*": {
|
||||
"types": "./dist/*.d.ts",
|
||||
"default": "./dist/*.js"
|
||||
}
|
||||
"styles": "./src/styles/styles.css"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "tsx scripts/validate-env.ts",
|
||||
|
@ -29,6 +22,9 @@
|
|||
"@buster/server-shared": "workspace:*",
|
||||
"@buster/typescript-config": "workspace:*",
|
||||
"@buster/vitest-config": "workspace:*",
|
||||
"@tailwindcss/postcss": "4.1.11",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"chart.js": "4.5.0",
|
||||
"chartjs-adapter-dayjs-4": "^1.0.4",
|
||||
"chartjs-plugin-annotation": "^3.1.0",
|
||||
|
@ -41,7 +37,8 @@
|
|||
"lodash": "^4.17.21",
|
||||
"react": "^18.0.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "4.1.11"
|
||||
"tailwindcss": "4.1.11",
|
||||
"vite": "^7.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.17.20",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import { isServer } from '@tanstack/react-query';
|
||||
import { truncateText } from '@/lib/text';
|
||||
import { isServer } from '@/lib/window';
|
||||
import { DEFAULT_CHART_THEME } from '@buster/server-shared/metrics';
|
||||
import {
|
||||
ArcElement,
|
||||
BarController,
|
||||
|
@ -11,9 +13,9 @@ import {
|
|||
Colors,
|
||||
DoughnutController,
|
||||
Legend,
|
||||
LinearScale,
|
||||
LineController,
|
||||
LineElement,
|
||||
LinearScale,
|
||||
LogarithmicScale,
|
||||
PieController,
|
||||
PointElement,
|
||||
|
@ -21,13 +23,11 @@ import {
|
|||
TimeScale,
|
||||
TimeSeriesScale,
|
||||
Title,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
} from 'chart.js';
|
||||
import ChartJsAnnotationPlugin from 'chartjs-plugin-annotation';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
import ChartDeferred from 'chartjs-plugin-deferred';
|
||||
import { DEFAULT_CHART_THEME } from '@buster/server-shared/metrics';
|
||||
import { truncateText } from '@/lib/text';
|
||||
import { ChartMountedPlugin } from './core/plugins';
|
||||
import ChartTrendlinePlugin from './core/plugins/chartjs-plugin-trendlines';
|
||||
import './core/plugins/chartjs-plugin-dayjs';
|
||||
|
@ -85,7 +85,7 @@ ChartJS.defaults.font = {
|
|||
...ChartJS.defaults.font,
|
||||
family: chartJSThemefontFamily,
|
||||
size: 11,
|
||||
weight: 'normal'
|
||||
weight: 'normal',
|
||||
};
|
||||
|
||||
[
|
||||
|
@ -93,21 +93,21 @@ ChartJS.defaults.font = {
|
|||
ChartJS.defaults.scales.linear,
|
||||
ChartJS.defaults.scales.logarithmic,
|
||||
ChartJS.defaults.scales.time,
|
||||
ChartJS.defaults.scales.timeseries
|
||||
ChartJS.defaults.scales.timeseries,
|
||||
].forEach((scale) => {
|
||||
scale.title = {
|
||||
...scale.title,
|
||||
font: {
|
||||
...ChartJS.defaults.font,
|
||||
size: 10
|
||||
}
|
||||
size: 10,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
[
|
||||
ChartJS.defaults.scales.category,
|
||||
ChartJS.defaults.scales.time,
|
||||
ChartJS.defaults.scales.timeseries
|
||||
ChartJS.defaults.scales.timeseries,
|
||||
].forEach((scale) => {
|
||||
scale.ticks.showLabelBackdrop = true;
|
||||
scale.ticks.z = 10;
|
||||
|
@ -115,14 +115,14 @@ ChartJS.defaults.font = {
|
|||
scale.ticks.backdropColor = chartJSThemebackgroundColor;
|
||||
scale.ticks.autoSkipPadding = 4;
|
||||
scale.ticks.align = 'center';
|
||||
scale.ticks.callback = function (value, index, values) {
|
||||
scale.ticks.callback = function (value) {
|
||||
return truncateText(this.getLabelForValue(value as number), 18);
|
||||
};
|
||||
});
|
||||
for (const scale of [
|
||||
ChartJS.defaults.scales.category,
|
||||
ChartJS.defaults.scales.linear,
|
||||
ChartJS.defaults.scales.logarithmic
|
||||
ChartJS.defaults.scales.logarithmic,
|
||||
]) {
|
||||
scale.ticks.z = 0; //this used to be a 100, but I changed it for datalabels sake
|
||||
scale.ticks.backdropColor = chartJSThemebackgroundColor;
|
||||
|
@ -136,13 +136,13 @@ export const DEFAULT_CHART_LAYOUT = {
|
|||
top: 14,
|
||||
bottom: 4,
|
||||
left: 10,
|
||||
right: 10
|
||||
}
|
||||
right: 10,
|
||||
},
|
||||
};
|
||||
|
||||
ChartJS.defaults.layout = {
|
||||
...ChartJS.defaults.layout,
|
||||
...DEFAULT_CHART_LAYOUT
|
||||
...DEFAULT_CHART_LAYOUT,
|
||||
};
|
||||
ChartJS.defaults.normalized = true;
|
||||
|
||||
|
@ -150,7 +150,7 @@ ChartJS.defaults.plugins = {
|
|||
...ChartJS.defaults.plugins,
|
||||
legend: {
|
||||
...ChartJS.defaults.plugins.legend,
|
||||
display: false
|
||||
display: false,
|
||||
},
|
||||
datalabels: {
|
||||
...ChartJS.defaults.plugins.datalabels,
|
||||
|
@ -159,12 +159,12 @@ ChartJS.defaults.plugins = {
|
|||
font: {
|
||||
weight: 'normal',
|
||||
size: 10,
|
||||
family: chartJSThemefontFamily
|
||||
}
|
||||
family: chartJSThemefontFamily,
|
||||
},
|
||||
},
|
||||
tooltipHoverBar: {
|
||||
isDarkMode: false
|
||||
}
|
||||
isDarkMode: false,
|
||||
},
|
||||
};
|
||||
|
||||
//PIE SPECIFIC
|
||||
|
@ -173,7 +173,7 @@ ChartJS.overrides.pie = {
|
|||
hoverBorderColor: 'white',
|
||||
layout: {
|
||||
autoPadding: true,
|
||||
padding: 35
|
||||
padding: 35,
|
||||
},
|
||||
elements: {
|
||||
...ChartJS.overrides.pie?.elements,
|
||||
|
@ -183,9 +183,9 @@ ChartJS.overrides.pie = {
|
|||
borderRadius: 5,
|
||||
borderWidth: 2.5,
|
||||
borderAlign: 'center',
|
||||
borderJoinStyle: 'round'
|
||||
}
|
||||
}
|
||||
borderJoinStyle: 'round',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
//BAR SPECIFIC
|
||||
|
@ -195,9 +195,9 @@ ChartJS.overrides.bar = {
|
|||
...ChartJS.overrides.bar?.elements,
|
||||
bar: {
|
||||
...ChartJS.overrides.bar?.elements?.bar,
|
||||
borderRadius: 4
|
||||
}
|
||||
}
|
||||
borderRadius: 4,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
//LINE SPECIFIC
|
||||
|
@ -207,7 +207,7 @@ ChartJS.overrides.line = {
|
|||
...ChartJS.overrides.line?.elements,
|
||||
line: {
|
||||
...ChartJS.overrides.line?.elements?.line,
|
||||
borderWidth: 2
|
||||
}
|
||||
}
|
||||
borderWidth: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,7 +15,6 @@ import isDate from 'lodash/isDate';
|
|||
import lodashIsNaN from 'lodash/isNaN';
|
||||
import isNumber from 'lodash/isNumber';
|
||||
import isString from 'lodash/isString';
|
||||
import { getBrowserLanguage } from './language';
|
||||
import { isNumeric } from './numbers';
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
@ -25,106 +24,23 @@ dayjs.extend(utc);
|
|||
dayjs.extend(timezone);
|
||||
dayjs.extend(quarterOfYear);
|
||||
|
||||
export const getNow = () => dayjs();
|
||||
export const createDayjsDate = (date: string | Date) => dayjs(date);
|
||||
|
||||
const KEY_DATE_FORMATS = [
|
||||
'date',
|
||||
'year',
|
||||
'month',
|
||||
'day',
|
||||
'timestamp',
|
||||
'time_stamp',
|
||||
'created_at',
|
||||
'_month',
|
||||
];
|
||||
const KEY_DATE_INCLUDE_FORMATS = ['_month', '_year'];
|
||||
const VALID_DATE_FORMATS = [
|
||||
'YYYY-MM-DD',
|
||||
'YYYY-MM',
|
||||
'YYYY-MM-DDTHH:mm:ss',
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
'YYYY-MM-DDTHH:mm:ss.sssZ',
|
||||
'YYYY-MM-DDTHH:mm:ssZ',
|
||||
'YYYY-MM-DD HH:mm:ssZ',
|
||||
'YYYY-MM-DD HH:mm:ss.sssZ',
|
||||
'YYYY-MM-DD HH:mm:ss.sss',
|
||||
'YYYY-MM-DDTHH:mm:ss.sss',
|
||||
'YYYY-MM-DDTHH:mm:ssZ',
|
||||
'YYYY-MM-DDTHH:mm:ss',
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
'YYYY-MM-DD HH:mm',
|
||||
'YYYY-MM-DDTHH:mm',
|
||||
'YYYY-MM-DD HH',
|
||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
'YYYY-MM-DDTHH:mm:ss.SSS',
|
||||
'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
||||
'YYYY-MM-DD HH:mm:ss.SSSZ',
|
||||
'YYYY-MM-DDTHH:mm:ss.SSS',
|
||||
'MM DD YYYY',
|
||||
'M D YYYY',
|
||||
'MMM D, YYYY',
|
||||
'MMMM D, YYYY',
|
||||
];
|
||||
|
||||
export const numberDateFallback = (
|
||||
date: string | number | Date,
|
||||
dateKey?: string,
|
||||
convertNumberTo?: ColumnLabelFormat['convertNumberTo']
|
||||
) => {
|
||||
if (convertNumberTo === 'day_of_week' && isNumber(date) && valueIsValidDayOfWeek(date, dateKey)) {
|
||||
return dayjs().day(Number(date)).startOf('day');
|
||||
}
|
||||
if (valueIsValidMonth(date, dateKey) || convertNumberTo === 'month_of_year') {
|
||||
return dayjs()
|
||||
.month(Number(date) - 1)
|
||||
.startOf('month')
|
||||
.startOf('day');
|
||||
}
|
||||
|
||||
const numericDate = Number(date);
|
||||
if (!lodashIsNaN(numericDate)) {
|
||||
// Check for 13 digit timestamp (milliseconds)
|
||||
if (String(numericDate).length === 13) {
|
||||
return dayjs(numericDate);
|
||||
}
|
||||
// Check for 10 digit timestamp (seconds)
|
||||
if (String(numericDate).length === 10) {
|
||||
return dayjs.unix(numericDate);
|
||||
}
|
||||
}
|
||||
|
||||
return String(date);
|
||||
};
|
||||
|
||||
const extractDateForFormatting = (
|
||||
date: string | number | Date,
|
||||
dateKey?: string,
|
||||
convertNumberTo?: ColumnLabelFormat['convertNumberTo']
|
||||
) => {
|
||||
if (isString(date)) return date;
|
||||
if (isNumber(date)) return numberDateFallback(date, dateKey, convertNumberTo);
|
||||
if (isDate(date)) return new Date(date);
|
||||
return String(date);
|
||||
};
|
||||
|
||||
export const formatDate = ({
|
||||
date,
|
||||
format,
|
||||
isUTC = false,
|
||||
dateKey,
|
||||
ignoreValidation = true,
|
||||
convertNumberTo = null,
|
||||
}: {
|
||||
date: string | number | Date;
|
||||
format: string;
|
||||
isUTC?: boolean;
|
||||
dateKey?: string;
|
||||
ignoreValidation?: boolean;
|
||||
convertNumberTo?: ColumnLabelFormat['convertNumberTo'];
|
||||
}): string => {
|
||||
try {
|
||||
const myDate = extractDateForFormatting(date, dateKey, convertNumberTo);
|
||||
const myDate = extractDateForFormatting(date, convertNumberTo);
|
||||
|
||||
const valid = ignoreValidation
|
||||
? true
|
||||
|
@ -171,77 +87,98 @@ export const formatDate = ({
|
|||
}
|
||||
};
|
||||
|
||||
export const isDateSame = ({
|
||||
date,
|
||||
compareDate,
|
||||
interval = 'day',
|
||||
}: {
|
||||
date: string | number | dayjs.Dayjs | Date;
|
||||
compareDate: string | number | dayjs.Dayjs | Date;
|
||||
interval?: dayjs.OpUnitType;
|
||||
}) => {
|
||||
return dayjs(date).isSame(compareDate, interval);
|
||||
const numberDateFallback = (
|
||||
date: string | number | Date,
|
||||
convertNumberTo?: ColumnLabelFormat['convertNumberTo']
|
||||
) => {
|
||||
if (convertNumberTo === 'day_of_week' && isNumber(date) && valueIsValidDayOfWeek(date)) {
|
||||
return dayjs().day(Number(date)).startOf('day');
|
||||
}
|
||||
if (valueIsValidMonth(date) || convertNumberTo === 'month_of_year') {
|
||||
return dayjs()
|
||||
.month(Number(date) - 1)
|
||||
.startOf('month')
|
||||
.startOf('day');
|
||||
}
|
||||
|
||||
const numericDate = Number(date);
|
||||
if (!lodashIsNaN(numericDate)) {
|
||||
// Check for 13 digit timestamp (milliseconds)
|
||||
if (String(numericDate).length === 13) {
|
||||
return dayjs(numericDate);
|
||||
}
|
||||
// Check for 10 digit timestamp (seconds)
|
||||
if (String(numericDate).length === 10) {
|
||||
return dayjs.unix(numericDate);
|
||||
}
|
||||
}
|
||||
|
||||
return String(date);
|
||||
};
|
||||
|
||||
export const isDateBefore = ({
|
||||
date,
|
||||
compareDate,
|
||||
interval = 'day',
|
||||
}: {
|
||||
date: string | number | dayjs.Dayjs | Date;
|
||||
compareDate: string | number | dayjs.Dayjs | Date;
|
||||
interval?: dayjs.OpUnitType;
|
||||
}) => {
|
||||
return dayjs(date).isBefore(compareDate, interval);
|
||||
const extractDateForFormatting = (
|
||||
date: string | number | Date,
|
||||
|
||||
convertNumberTo?: ColumnLabelFormat['convertNumberTo']
|
||||
) => {
|
||||
if (isString(date)) return date;
|
||||
if (isNumber(date)) return numberDateFallback(date, convertNumberTo);
|
||||
if (isDate(date)) return new Date(date);
|
||||
return String(date);
|
||||
};
|
||||
|
||||
export const isDateAfter = ({
|
||||
date,
|
||||
compareDate,
|
||||
interval = 'day',
|
||||
}: {
|
||||
date: string | number | dayjs.Dayjs | Date;
|
||||
compareDate: string | number | dayjs.Dayjs | Date;
|
||||
interval?: dayjs.OpUnitType;
|
||||
}) => {
|
||||
return dayjs(date).isAfter(compareDate, interval);
|
||||
};
|
||||
|
||||
export const timeFromNow = (date: string | Date, relative = false) => {
|
||||
return dayjs(new Date(date)).fromNow(relative);
|
||||
};
|
||||
|
||||
export const valueIsValidMonth = (value: string | number | Date | undefined, key?: string) => {
|
||||
const valueIsValidMonth = (value: string | number | Date | undefined) => {
|
||||
if (value === undefined || value === null) return false;
|
||||
const month = Number(value);
|
||||
return (month > 0 && month <= 12) || isKeyLikelyDate(key);
|
||||
return month > 0 && month <= 12;
|
||||
};
|
||||
|
||||
const valueIsValidDayOfWeek = (value: string | number | Date | undefined, key?: string) => {
|
||||
const valueIsValidDayOfWeek = (value: string | number | Date | undefined) => {
|
||||
if (value === undefined || value === null) return false;
|
||||
const day = Number(value);
|
||||
return (day >= 0 && day <= 7) || isKeyLikelyDate(key);
|
||||
return day >= 0 && day <= 7;
|
||||
};
|
||||
|
||||
const isKeyLikelyDate = (dateKey?: string) => {
|
||||
return (
|
||||
KEY_DATE_FORMATS.some((v) => v === dateKey) ||
|
||||
KEY_DATE_INCLUDE_FORMATS.some((v) => dateKey?.toLowerCase().endsWith(v))
|
||||
);
|
||||
};
|
||||
// const VALID_DATE_FORMATS = [
|
||||
// 'YYYY-MM-DD',
|
||||
// 'YYYY-MM',
|
||||
// 'YYYY-MM-DDTHH:mm:ss',
|
||||
// 'YYYY-MM-DD HH:mm:ss',
|
||||
// 'YYYY-MM-DDTHH:mm:ss.sssZ',
|
||||
// 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
// 'YYYY-MM-DD HH:mm:ssZ',
|
||||
// 'YYYY-MM-DD HH:mm:ss.sssZ',
|
||||
// 'YYYY-MM-DD HH:mm:ss.sss',
|
||||
// 'YYYY-MM-DDTHH:mm:ss.sss',
|
||||
// 'YYYY-MM-DDTHH:mm:ssZ',
|
||||
// 'YYYY-MM-DDTHH:mm:ss',
|
||||
// 'YYYY-MM-DD HH:mm:ss',
|
||||
// 'YYYY-MM-DD HH:mm',
|
||||
// 'YYYY-MM-DDTHH:mm',
|
||||
// 'YYYY-MM-DD HH',
|
||||
// 'YYYY-MM-DD HH:mm:ss.SSS',
|
||||
// 'YYYY-MM-DDTHH:mm:ss.SSS',
|
||||
// 'YYYY-MM-DDTHH:mm:ss.SSSZ',
|
||||
// 'YYYY-MM-DD HH:mm:ss.SSSZ',
|
||||
// 'YYYY-MM-DDTHH:mm:ss.SSS',
|
||||
// 'MM DD YYYY',
|
||||
// 'M D YYYY',
|
||||
// 'MMM D, YYYY',
|
||||
// 'MMMM D, YYYY',
|
||||
// ];
|
||||
|
||||
export const isDateValid = ({
|
||||
const isDateValid = ({
|
||||
date,
|
||||
dateKey,
|
||||
|
||||
useNumbersAsDateKey = true,
|
||||
}: {
|
||||
date: string | number | Date | undefined;
|
||||
dateKey?: string;
|
||||
|
||||
useNumbersAsDateKey?: boolean;
|
||||
}) => {
|
||||
if (isDate(date)) return true;
|
||||
if (useNumbersAsDateKey && dateKey && isNumeric(date as string) && isKeyLikelyDate(dateKey)) {
|
||||
return valueIsValidMonth(date || '', dateKey);
|
||||
if (useNumbersAsDateKey && isNumeric(date as string)) {
|
||||
return valueIsValidMonth(date || '');
|
||||
}
|
||||
if (!date || isNumber(date)) return false;
|
||||
const hyphenCount = (date.match(/-/g) || []).length;
|
||||
|
@ -260,114 +197,7 @@ export const isDateValid = ({
|
|||
|
||||
if (filter) return filter && dayjs(date).isValid();
|
||||
|
||||
return VALID_DATE_FORMATS.some((format) => dayjs(date, format, true).isValid());
|
||||
};
|
||||
|
||||
export const keysWithDate = (
|
||||
data: Record<string, string | number | null>[] = [],
|
||||
params?: {
|
||||
parseable?: boolean;
|
||||
includeFormats?: boolean;
|
||||
absoluteFormats?: boolean;
|
||||
}
|
||||
): string[] => {
|
||||
const uniqueKeys = Object.keys(data[0] || {});
|
||||
const { parseable, includeFormats = true, absoluteFormats = true } = params || {};
|
||||
|
||||
if (parseable) {
|
||||
return uniqueKeys.filter((key) => {
|
||||
const value = data?.[0]?.[key];
|
||||
return isDateValid({
|
||||
date: String(value),
|
||||
dateKey: key,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return uniqueKeys
|
||||
.filter((key) => {
|
||||
const value = data?.[0]?.[key];
|
||||
|
||||
return (
|
||||
(absoluteFormats && KEY_DATE_FORMATS.some((keyDate) => keyDate === key.toLowerCase())) ||
|
||||
isDateValid({
|
||||
date: String(value),
|
||||
dateKey: key,
|
||||
}) ||
|
||||
(includeFormats &&
|
||||
KEY_DATE_INCLUDE_FORMATS.some((format) => key.toLowerCase().endsWith(format)))
|
||||
);
|
||||
})
|
||||
.sort((v) => {
|
||||
if (v.toLowerCase().includes('year')) return 1;
|
||||
return -1;
|
||||
});
|
||||
};
|
||||
|
||||
export const formatTime = (date: string | Date, format: string, isUTC: boolean) => {
|
||||
return isUTC ? dayjs.utc(date).format(format) : dayjs(date).format(format || 'h:mm A');
|
||||
};
|
||||
|
||||
export enum DEFAULT_TIME_ENCODE_FORMATS {
|
||||
MONTHS = 'month',
|
||||
YEARS = 'year',
|
||||
DAYS = 'day',
|
||||
SECONDS = 'second',
|
||||
HOURS = 'hour',
|
||||
MONTHS_ONLY = 'month_only',
|
||||
NO_FORMAT = 'no_format',
|
||||
MILLISECONDS = 'millisecond',
|
||||
WEEK = 'week',
|
||||
MINUTES = 'minute',
|
||||
}
|
||||
|
||||
let loadedLocales: string[] = [];
|
||||
export const setNewDateLocale = async (locale: string) => {
|
||||
if (!locale) return;
|
||||
let _locale: string = locale;
|
||||
|
||||
const loadAndSet = async (_locale: string) => {
|
||||
try {
|
||||
await import(`dayjs/locale/${_locale}.js`);
|
||||
loadedLocales = [...loadedLocales, _locale];
|
||||
} catch (_error) {
|
||||
//
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (!loadedLocales.includes(_locale)) {
|
||||
if (_locale === 'en') _locale = getBrowserLanguage(true).toLocaleLowerCase();
|
||||
loadAndSet(_locale);
|
||||
}
|
||||
} catch {
|
||||
try {
|
||||
_locale = locale;
|
||||
loadAndSet(_locale);
|
||||
} catch (error) {
|
||||
console.error(`Error loading locale ${_locale}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
dayjs.locale(_locale);
|
||||
};
|
||||
|
||||
export const getBestDateFormat = (minDate: dayjs.Dayjs, maxDate: dayjs.Dayjs) => {
|
||||
const diffInDays = maxDate.diff(minDate, 'days');
|
||||
const diffInMonths = maxDate.diff(minDate, 'months');
|
||||
const diffInYears = maxDate.diff(minDate, 'years');
|
||||
|
||||
if (diffInDays <= 1) {
|
||||
return 'h:mmA'; // 1/1 2:30 PM
|
||||
}
|
||||
if (diffInDays <= 31) {
|
||||
return 'MMM D'; // Jan 1
|
||||
}
|
||||
if (diffInMonths <= 12) {
|
||||
return 'MMM YYYY'; // Jan 2024
|
||||
}
|
||||
if (diffInYears <= 1) {
|
||||
return 'MMM YYYY'; // Jan 2024
|
||||
}
|
||||
return 'YYYY'; // 2024
|
||||
return true;
|
||||
|
||||
// return VALID_DATE_FORMATS.some((format) => dayjs(date, format, true).isValid());
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import React from 'react';
|
||||
|
||||
export const getBrowserLanguage = (full = false) => {
|
||||
if (!navigator) return 'en';
|
||||
|
||||
|
|
|
@ -77,77 +77,6 @@ export class JsonDataFrameOperationsSingle {
|
|||
}
|
||||
}
|
||||
|
||||
export class DataFrameOperations {
|
||||
private data: [...(number | string)[]][];
|
||||
private columnIndex: number;
|
||||
|
||||
constructor(data: [...(number | string)[]][], columnIndex = 1) {
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Data should be an array.');
|
||||
}
|
||||
this.data = data;
|
||||
this.columnIndex = columnIndex;
|
||||
}
|
||||
|
||||
first(): number {
|
||||
return this.data[0]?.[this.columnIndex] as number;
|
||||
}
|
||||
|
||||
current(): number {
|
||||
return this.data[this.data.length - 1]?.[this.columnIndex] as number;
|
||||
}
|
||||
|
||||
last(): number {
|
||||
return this.data[this.data.length - 1]?.[this.columnIndex] as number;
|
||||
}
|
||||
|
||||
count(): number {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
sum(): number {
|
||||
return this.data.reduce((acc, item) => {
|
||||
const value = item[this.columnIndex];
|
||||
return acc + (Number.isNaN(value as number) ? 0 : Number(value));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
average(): number {
|
||||
const validEntries = this.data.filter(
|
||||
(item) => !Number.isNaN(item[this.columnIndex] as number)
|
||||
);
|
||||
return validEntries.length === 0 ? 0 : this.sum() / validEntries.length;
|
||||
}
|
||||
|
||||
min(): number {
|
||||
return Math.min(
|
||||
...this.data
|
||||
.map((item) => Number(item[this.columnIndex]))
|
||||
.filter((value) => !Number.isNaN(value))
|
||||
);
|
||||
}
|
||||
|
||||
max(): number {
|
||||
return Math.max(
|
||||
...this.data
|
||||
.map((item) => Number(item[this.columnIndex]))
|
||||
.filter((value) => !Number.isNaN(value))
|
||||
);
|
||||
}
|
||||
|
||||
median(): number {
|
||||
const sortedValues = this.data
|
||||
.map((item) => Number(item[this.columnIndex]))
|
||||
.filter((value) => !Number.isNaN(value))
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const middle = Math.floor(sortedValues.length / 2);
|
||||
return sortedValues.length % 2 !== 0
|
||||
? (sortedValues[middle] as number)
|
||||
: ((sortedValues[middle - 1] as number) + (sortedValues[middle] as number)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrayOperations {
|
||||
private data: number[] | string[] | (number | string)[];
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ export const formatNumber = (
|
|||
options?.minDecimals,
|
||||
options?.maximumFractionDigits,
|
||||
options?.maximumDecimals,
|
||||
options?.maximumSignificantDigits
|
||||
options?.maximumSignificantDigits,
|
||||
].filter(isNumber)
|
||||
);
|
||||
|
||||
|
@ -68,7 +68,7 @@ export const formatNumber = (
|
|||
compactDisplay: 'short',
|
||||
style: options?.currency ? 'currency' : 'decimal',
|
||||
currency: options?.currency,
|
||||
useGrouping: options?.useGrouping !== false
|
||||
useGrouping: options?.useGrouping !== false,
|
||||
});
|
||||
|
||||
return formatter.format(Number(processedValue));
|
||||
|
@ -105,7 +105,7 @@ export const formatNumber = (
|
|||
* countTotalDigits(1000000) // returns 7
|
||||
* countTotalDigits(-123.4) // returns 4 (ignores sign)
|
||||
*/
|
||||
export const countTotalDigits = (num: number, roundToDecimals: number = 2): number => {
|
||||
export const countTotalDigits = (num: number, roundToDecimals = 2): number => {
|
||||
const roundedNum = roundNumber(num, 0, roundToDecimals);
|
||||
const str = Math.abs(roundedNum).toString();
|
||||
return str.replace('.', '').length;
|
||||
|
|
|
@ -1,65 +1,11 @@
|
|||
import isNumber from 'lodash/isNumber';
|
||||
import truncate from 'lodash/truncate';
|
||||
|
||||
export const inputHasText = (input: unknown): boolean => {
|
||||
if (typeof input !== 'string') {
|
||||
return false;
|
||||
}
|
||||
const trimmedInput = input.trim();
|
||||
return trimmedInput.length > 0;
|
||||
};
|
||||
|
||||
const capitalizeFirstLetter = (str: string): string => {
|
||||
if (!str) return '';
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
};
|
||||
|
||||
export const getFirstTwoCapitalizedLetters = (input: string) => {
|
||||
const capitalizedLetters = input
|
||||
.replace('@', '')
|
||||
.replace(/[^A-Za-z]/g, '') // Remove non-alphabetic characters
|
||||
.match(/[A-Z]/g); // Find all uppercase letters
|
||||
|
||||
if (capitalizedLetters && capitalizedLetters.length < 2) {
|
||||
return input
|
||||
.replace('@', '')
|
||||
.replace(/[^A-Za-z]/g, '')
|
||||
.slice(0, 2)
|
||||
.toUpperCase();
|
||||
}
|
||||
if (capitalizedLetters && capitalizedLetters.length >= 2) {
|
||||
return capitalizedLetters.slice(0, 2).filter(Boolean).join('');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const removeAllSpaces = (str?: string) => {
|
||||
return str ? str.replace(/\s/g, '') : '';
|
||||
};
|
||||
|
||||
export const getInitials = (value: string | null | undefined): string => {
|
||||
if (!value) return '';
|
||||
|
||||
// Split by spaces or other common separators and filter out empty strings
|
||||
const words = value.trim().split(/\s+/).filter(Boolean);
|
||||
|
||||
// If we have multiple words, use the first letter of the first two words
|
||||
if (words.length >= 2) {
|
||||
return words
|
||||
.slice(0, 2)
|
||||
.map((word) => word.charAt(0).toUpperCase())
|
||||
.join('');
|
||||
}
|
||||
|
||||
// If we have only one word, split by capital letters
|
||||
const capitalizedLetters = value.match(/[A-Z]/g);
|
||||
|
||||
if (capitalizedLetters && capitalizedLetters.length >= 2) {
|
||||
return capitalizedLetters.slice(0, 2).filter(Boolean).join('');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const makeHumanReadble = (input: string | number | undefined | null): string => {
|
||||
if (!input && !isNumber(input)) {
|
||||
return '';
|
||||
|
@ -96,16 +42,6 @@ export const makeHumanReadble = (input: string | number | undefined | null): str
|
|||
return convertedString;
|
||||
};
|
||||
|
||||
export const calculateTextWidth = (text: string, font: string): number => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) return 0;
|
||||
context.font = font;
|
||||
const width = context.measureText(text).width;
|
||||
canvas.remove();
|
||||
return width;
|
||||
};
|
||||
|
||||
export const truncateText = (text: string, characters: number) => {
|
||||
if (text.length <= characters) return text;
|
||||
return truncate(text, { length: characters });
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const isServer = typeof window === 'undefined';
|
|
@ -0,0 +1 @@
|
|||
@import './tailwind.css';
|
|
@ -0,0 +1 @@
|
|||
@import "tailwindcss";
|
|
@ -0,0 +1,7 @@
|
|||
import tailwindcss from '@tailwindcss/vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
});
|
133
pnpm-lock.yaml
133
pnpm-lock.yaml
|
@ -964,6 +964,15 @@ importers:
|
|||
'@buster/vitest-config':
|
||||
specifier: workspace:*
|
||||
version: link:../vitest-config
|
||||
'@tailwindcss/postcss':
|
||||
specifier: 4.1.11
|
||||
version: 4.1.11
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.7.0
|
||||
version: 4.7.0(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))
|
||||
chart.js:
|
||||
specifier: 4.5.0
|
||||
version: 4.5.0
|
||||
|
@ -1003,6 +1012,9 @@ importers:
|
|||
tailwindcss:
|
||||
specifier: 4.1.11
|
||||
version: 4.1.11
|
||||
vite:
|
||||
specifier: ^7.0.5
|
||||
version: 7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
|
||||
devDependencies:
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.20
|
||||
|
@ -1879,6 +1891,18 @@ packages:
|
|||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.27.1':
|
||||
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.27.1':
|
||||
resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx@7.27.1':
|
||||
resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -4252,6 +4276,9 @@ packages:
|
|||
'@radix-ui/rect@1.1.1':
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.44.2':
|
||||
resolution: {integrity: sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==}
|
||||
cpu: [arm]
|
||||
|
@ -4999,6 +5026,11 @@ packages:
|
|||
'@tailwindcss/postcss@4.1.11':
|
||||
resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==}
|
||||
|
||||
'@tailwindcss/vite@4.1.11':
|
||||
resolution: {integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==}
|
||||
peerDependencies:
|
||||
vite: ^5.2.0 || ^6 || ^7
|
||||
|
||||
'@tanstack/form-core@1.14.2':
|
||||
resolution: {integrity: sha512-II/hbNxhnf5Sjhpz6i1wILGE1CHPN0OzYwz82aDGuDKchGPIdBaZcR00aduPGrBwqqo2XN9O6L/GfIvImPrlsQ==}
|
||||
|
||||
|
@ -5559,6 +5591,12 @@ packages:
|
|||
'@aws-sdk/credential-provider-web-identity':
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-react@4.7.0':
|
||||
resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
|
||||
'@vitest/coverage-v8@3.2.4':
|
||||
resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
|
||||
peerDependencies:
|
||||
|
@ -9877,6 +9915,10 @@ packages:
|
|||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-refresh@0.17.0:
|
||||
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
react-remove-scroll-bar@2.3.8:
|
||||
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -11244,6 +11286,46 @@ packages:
|
|||
yaml:
|
||||
optional: true
|
||||
|
||||
vite@7.0.5:
|
||||
resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.0
|
||||
lightningcss: ^1.21.0
|
||||
sass: ^1.70.0
|
||||
sass-embedded: ^1.70.0
|
||||
stylus: '>=0.54.8'
|
||||
sugarss: ^5.0.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@3.2.4:
|
||||
resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
|
@ -13056,6 +13138,16 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
|
||||
'@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0)':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
|
@ -15712,6 +15804,8 @@ snapshots:
|
|||
|
||||
'@radix-ui/rect@1.1.1': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.44.2':
|
||||
optional: true
|
||||
|
||||
|
@ -16754,6 +16848,13 @@ snapshots:
|
|||
postcss: 8.5.6
|
||||
tailwindcss: 4.1.11
|
||||
|
||||
'@tailwindcss/vite@4.1.11(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.11
|
||||
'@tailwindcss/oxide': 4.1.11
|
||||
tailwindcss: 4.1.11
|
||||
vite: 7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
|
||||
|
||||
'@tanstack/form-core@1.14.2':
|
||||
dependencies:
|
||||
'@tanstack/store': 0.7.2
|
||||
|
@ -17406,6 +17507,18 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@aws-sdk/credential-provider-web-identity': 3.840.0
|
||||
|
||||
'@vitejs/plugin-react@4.7.0(vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0)
|
||||
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
|
@ -22419,6 +22532,8 @@ snapshots:
|
|||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
react-refresh@0.17.0: {}
|
||||
|
||||
react-remove-scroll-bar@2.3.8(@types/react@18.3.23)(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
@ -24032,6 +24147,24 @@ snapshots:
|
|||
tsx: 4.20.3
|
||||
yaml: 2.8.0
|
||||
|
||||
vite@7.0.5(@types/node@24.0.10)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.5
|
||||
fdir: 6.4.6(picomatch@4.0.2)
|
||||
picomatch: 4.0.2
|
||||
postcss: 8.5.6
|
||||
rollup: 4.44.2
|
||||
tinyglobby: 0.2.14
|
||||
optionalDependencies:
|
||||
'@types/node': 24.0.10
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.4.2
|
||||
lightningcss: 1.30.1
|
||||
sass: 1.89.2
|
||||
terser: 5.43.1
|
||||
tsx: 4.20.3
|
||||
yaml: 2.8.0
|
||||
|
||||
vitest@3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@20.19.4)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.10.4(@types/node@20.19.4)(typescript@5.8.3))(sass@1.89.2)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.2
|
||||
|
|
Loading…
Reference in New Issue