add basic vite integration and remove unused helpers

This commit is contained in:
Nate Kelley 2025-07-23 08:52:47 -06:00
parent 9889f1403c
commit 957c38c0ac
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
12 changed files with 258 additions and 425 deletions

View File

@ -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",

View File

@ -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,
},
},
};

View File

@ -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());
};

View File

@ -1,5 +1,3 @@
import React from 'react';
export const getBrowserLanguage = (full = false) => {
if (!navigator) return 'en';

View File

@ -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)[];

View File

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

View File

@ -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 });

View File

@ -0,0 +1 @@
export const isServer = typeof window === 'undefined';

View File

@ -0,0 +1 @@
@import './tailwind.css';

View File

@ -0,0 +1 @@
@import "tailwindcss";

View File

@ -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()],
});

View File

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