snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { Box } from '@rocket.chat/fuselage';
import { useEndpoint, type TranslationKey } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { ComponentPropsWithoutRef, MutableRefObject } from 'react';
import { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Chart from './Chart';
import { useChartContext } from './useChartContext';
import { useUpdateChartData } from './useUpdateChartData';
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
import { drawDoughnutChart } from '../../chartHandler';
const labels = ['Available', 'Away', 'Busy', 'Offline'];
const initialData = {
available: 0,
away: 0,
busy: 0,
offline: 0,
};
const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'doughnut'> | undefined, t: TFunction) =>
drawDoughnutChart(
canvas,
t('Agents'),
context,
labels.map((l) => t(l as TranslationKey)),
Object.values(initialData),
);
type AgentStatusChartsProps = {
departmentId: ILivechatDepartment['_id'];
} & ComponentPropsWithoutRef<typeof Box>;
const AgentStatusChart = ({ departmentId, ...props }: AgentStatusChartsProps) => {
const { t } = useTranslation();
const canvas: MutableRefObject<HTMLCanvasElement | null> = useRef(null);
const getAgentStatus = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/agents-status');
const { isSuccess, data: { offline = 0, available = 0, away = 0, busy = 0 } = initialData } = useQuery({
queryKey: omnichannelQueryKeys.analytics.agentsStatus(departmentId),
queryFn: () => getAgentStatus({ departmentId }),
gcTime: 0,
});
const context = useChartContext({
canvas,
init,
t,
});
const updateChartData = useUpdateChartData({
context,
canvas,
init,
t,
});
useEffect(() => {
if (!context) {
return;
}
if (!isSuccess) {
return;
}
updateChartData(t('Offline'), [offline]);
updateChartData(t('Available'), [available]);
updateChartData(t('Away'), [away]);
updateChartData(t('Busy'), [busy]);
}, [context, available, away, busy, offline, isSuccess, t, updateChartData]);
return <Chart canvasRef={canvas} {...props} />;
};
export default AgentStatusChart;

View File

@@ -0,0 +1,15 @@
import { Box } from '@rocket.chat/fuselage';
import type { MutableRefObject } from 'react';
export type ChartProps = { canvasRef: MutableRefObject<HTMLCanvasElement | null> };
const style = {
minHeight: '250px',
};
const Chart = ({ canvasRef, ...props }: ChartProps) => (
<Box padding='x20' height='x300' {...props}>
<canvas ref={canvasRef} style={style}></canvas>
</Box>
);
export default Chart;

View File

@@ -0,0 +1,98 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { Box } from '@rocket.chat/fuselage';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { ComponentPropsWithoutRef } from 'react';
import { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Chart from './Chart';
import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData';
import { getMomentCurrentLabel } from './getMomentCurrentLabel';
import { useChartContext } from './useChartContext';
import { useUpdateChartData } from './useUpdateChartData';
import { secondsToHHMMSS } from '../../../../../lib/utils/secondsToHHMMSS';
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
import { drawLineChart } from '../../chartHandler';
const [labels, initialData] = getMomentChartLabelsAndData();
const tooltipCallbacks = {
callbacks: {
title([ctx]: [chartjs.TooltipItem<'line'>]) {
const { dataset } = ctx;
return dataset.label;
},
label(ctx: chartjs.TooltipItem<'line'>) {
const { dataset, dataIndex } = ctx;
const item = dataset.data[dataIndex];
return `${dataset.label}: ${secondsToHHMMSS(typeof item === 'number' ? item : 0)}`;
},
},
};
const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'line'> | undefined, t: TFunction) =>
drawLineChart(canvas, context, [t('Avg_chat_duration'), t('Longest_chat_duration')], labels, [initialData, initialData.slice()], {
legends: true,
anim: true,
smallTicks: true,
displayColors: false,
tooltipCallbacks,
});
export type ChatDurationChartProps = {
departmentId: ILivechatDepartment['_id'];
dateRange: { start: string; end: string };
} & ComponentPropsWithoutRef<typeof Box>;
const ChatDurationChart = ({ departmentId, dateRange, ...props }: ChatDurationChartProps) => {
const { t } = useTranslation();
const canvas = useRef<HTMLCanvasElement | null>(null);
const getTimings = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/timings');
const { isSuccess, data } = useQuery({
queryKey: omnichannelQueryKeys.analytics.timings(departmentId, dateRange),
queryFn: () => getTimings({ departmentId, ...dateRange }),
gcTime: 0,
});
const context = useChartContext({
canvas,
init,
t,
});
const updateChartData = useUpdateChartData({
context,
canvas,
init,
t,
});
const {
chatDuration: { avg, longest },
} = data ?? {
chatDuration: {
avg: 0,
longest: 0,
},
};
useEffect(() => {
if (!context) {
return;
}
if (!isSuccess) {
return;
}
const label = getMomentCurrentLabel();
updateChartData(label, [avg, longest]);
}, [context, avg, longest, isSuccess, t, updateChartData]);
return <Chart canvasRef={canvas} {...props} />;
};
export default ChatDurationChart;

View File

@@ -0,0 +1,85 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { Box } from '@rocket.chat/fuselage';
import { useEndpoint, type TranslationKey } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { ComponentPropsWithoutRef, MutableRefObject } from 'react';
import { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Chart from './Chart';
import { useChartContext } from './useChartContext';
import { useUpdateChartData } from './useUpdateChartData';
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
import { drawDoughnutChart } from '../../chartHandler';
const labels = ['Open', 'Queued', 'On_Hold_Chats', 'Closed'];
const initialData = {
open: 0,
queued: 0,
onhold: 0,
closed: 0,
};
const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'doughnut'> | undefined, t: TFunction) =>
drawDoughnutChart(
canvas,
t('Chats'),
context,
labels.map((l) => t(l as TranslationKey)),
Object.values(initialData),
);
export type ChatsChartProps = {
departmentId: ILivechatDepartment['_id'];
dateRange: { start: string; end: string };
} & ComponentPropsWithoutRef<typeof Box>;
const ChatsChart = ({ departmentId, dateRange, ...props }: ChatsChartProps) => {
const { t } = useTranslation();
const canvas: MutableRefObject<HTMLCanvasElement | null> = useRef(null);
const getChats = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/chats');
const { isSuccess, data } = useQuery({
queryKey: omnichannelQueryKeys.analytics.chats(departmentId, dateRange),
queryFn: () => getChats({ departmentId, ...dateRange }),
gcTime: 0,
});
const context = useChartContext({
canvas,
init,
t,
});
const updateChartData = useUpdateChartData({
context,
canvas,
init,
t,
});
const { open, queued, closed, onhold } = data ?? initialData;
useEffect(() => {
if (!context) {
return;
}
if (!isSuccess) {
return;
}
updateChartData(t('Open'), [open]);
updateChartData(t('Closed'), [closed]);
updateChartData(t('On_Hold_Chats'), [onhold]);
updateChartData(t('Queued'), [queued]);
}, [context, closed, open, queued, onhold, isSuccess, t, updateChartData]);
return <Chart canvasRef={canvas} {...props} />;
};
export default ChatsChart;

View File

@@ -0,0 +1,72 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { Box } from '@rocket.chat/fuselage';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { ComponentPropsWithoutRef } from 'react';
import { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Chart from './Chart';
import { useChartContext } from './useChartContext';
import { useUpdateChartData } from './useUpdateChartData';
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
import { drawLineChart, resetChart } from '../../chartHandler';
const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'line'> | undefined, t: TFunction) =>
drawLineChart(canvas, context, [t('Open'), t('Closed'), t('On_Hold_Chats')], [], [[], []], {
legends: true,
anim: true,
smallTicks: true,
});
export type ChatsPerAgentChartProps = {
departmentId: ILivechatDepartment['_id'];
dateRange: { start: string; end: string };
} & ComponentPropsWithoutRef<typeof Box>;
const ChatsPerAgentChart = ({ departmentId, dateRange, ...props }: ChatsPerAgentChartProps) => {
const { t } = useTranslation();
const canvas = useRef<HTMLCanvasElement | null>(null);
const getChatsPerAgent = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/chats-per-agent');
const { isSuccess, data } = useQuery({
queryKey: omnichannelQueryKeys.analytics.chatsPerAgent(departmentId, dateRange),
queryFn: () => getChatsPerAgent({ departmentId, ...dateRange }),
select: ({ success: _, ...data }) => Object.entries(data),
gcTime: 0,
});
const context = useChartContext({
canvas,
init,
t,
});
const updateChartData = useUpdateChartData({
context,
canvas,
init,
t,
});
useEffect(() => {
if (!context) return;
if (!isSuccess) {
return;
}
resetChart(context);
data.forEach(([name, value]) => {
updateChartData(name, [value.open, value.closed, value.onhold]);
});
}, [context, data, isSuccess, t, updateChartData]);
return <Chart canvasRef={canvas} {...props} />;
};
export default ChatsPerAgentChart;

View File

@@ -0,0 +1,74 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { Box } from '@rocket.chat/fuselage';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { ComponentPropsWithoutRef } from 'react';
import { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Chart from './Chart';
import { useChartContext } from './useChartContext';
import { useUpdateChartData } from './useUpdateChartData';
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
import { drawLineChart, resetChart } from '../../chartHandler';
const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'line'> | undefined, t: TFunction) =>
drawLineChart(canvas, context, [t('Open'), t('Closed')], [], [[], []], {
legends: true,
anim: true,
smallTicks: true,
});
export type ChatsPerDepartmentChartProps = {
departmentId: ILivechatDepartment['_id'];
dateRange: { start: string; end: string };
} & Omit<ComponentPropsWithoutRef<typeof Box>, 'data'>;
const ChatsPerDepartmentChart = ({ departmentId, dateRange, ...props }: ChatsPerDepartmentChartProps) => {
const { t } = useTranslation();
const canvas = useRef<HTMLCanvasElement | null>(null);
const getChatsPerDepartment = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/chats-per-department');
const { isSuccess, data } = useQuery({
queryKey: omnichannelQueryKeys.analytics.chatsPerDepartment(departmentId, dateRange),
queryFn: () => getChatsPerDepartment({ departmentId, ...dateRange }),
select: ({ success: _, ...data }) => Object.entries(data),
gcTime: 0,
});
const context = useChartContext({
canvas,
init,
t,
});
const updateChartData = useUpdateChartData({
context,
canvas,
init,
t,
});
useEffect(() => {
if (!context) {
return;
}
if (!isSuccess) {
return;
}
resetChart(context);
data.forEach(([name, value]) => {
updateChartData(name, [value.open, value.closed]);
});
}, [context, data, isSuccess, t, updateChartData]);
return <Chart canvasRef={canvas} {...props} />;
};
export default ChatsPerDepartmentChart;

View File

@@ -0,0 +1,102 @@
import type { ILivechatDepartment } from '@rocket.chat/core-typings';
import type { Box } from '@rocket.chat/fuselage';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { ComponentPropsWithoutRef } from 'react';
import { useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Chart from './Chart';
import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData';
import { getMomentCurrentLabel } from './getMomentCurrentLabel';
import { useChartContext } from './useChartContext';
import { useUpdateChartData } from './useUpdateChartData';
import { secondsToHHMMSS } from '../../../../../lib/utils/secondsToHHMMSS';
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
import { drawLineChart } from '../../chartHandler';
const [labels, initialData] = getMomentChartLabelsAndData();
const tooltipCallbacks = {
callbacks: {
title([ctx]: [chartjs.TooltipItem<'line'>]) {
const { dataset } = ctx;
return dataset.label;
},
label(ctx: chartjs.TooltipItem<'line'>) {
const { dataset, dataIndex } = ctx;
const item = dataset.data[dataIndex];
return `${dataset.label}: ${secondsToHHMMSS(typeof item === 'number' ? item : 0)}`;
},
},
};
const init = (canvas: HTMLCanvasElement, context: chartjs.Chart<'line'> | undefined, t: TFunction) =>
drawLineChart(
canvas,
context,
[t('Avg_reaction_time'), t('Longest_reaction_time'), t('Avg_response_time'), t('Longest_response_time')],
labels,
[initialData, initialData.slice(), initialData.slice(), initialData.slice()],
{ legends: true, anim: true, smallTicks: true, displayColors: false, tooltipCallbacks },
);
export type ResponseTimesChartProps = {
departmentId: ILivechatDepartment['_id'];
dateRange: { start: string; end: string };
} & Omit<ComponentPropsWithoutRef<typeof Box>, 'data'>;
const ResponseTimesChart = ({ departmentId, dateRange, ...props }: ResponseTimesChartProps) => {
const { t } = useTranslation();
const canvas = useRef<HTMLCanvasElement | null>(null);
const getTimings = useEndpoint('GET', '/v1/livechat/analytics/dashboards/charts/timings');
const {
isSuccess,
data: { reaction: { avg: reactionAvg, longest: reactionLongest }, response: { avg: responseAvg, longest: responseLongest } } = {
reaction: {
avg: 0,
longest: 0,
},
response: {
avg: 0,
longest: 0,
},
},
} = useQuery({
queryKey: omnichannelQueryKeys.analytics.timings(departmentId, dateRange),
queryFn: () => getTimings({ departmentId, ...dateRange }),
gcTime: 0,
});
const context = useChartContext({
canvas,
init,
t,
});
const updateChartData = useUpdateChartData({
context,
canvas,
init,
t,
});
useEffect(() => {
if (!context) {
return;
}
if (!isSuccess) {
return;
}
const label = getMomentCurrentLabel();
updateChartData(label, [reactionAvg, reactionLongest, responseAvg, responseLongest]);
}, [context, reactionAvg, reactionLongest, responseAvg, responseLongest, isSuccess, t, updateChartData]);
return <Chart canvasRef={canvas} {...props} />;
};
export default ResponseTimesChart;

View File

@@ -0,0 +1,23 @@
import { getMomentChartLabelsAndData } from './getMomentChartLabelsAndData';
const expectedTimingLabels = [
'12AM-1AM',
'1AM-2AM',
'2AM-3AM',
'3AM-4AM',
'4AM-5AM',
'5AM-6AM',
'6AM-7AM',
'7AM-8AM',
'8AM-9AM',
'9AM-10AM',
'10AM-11AM',
'11AM-12PM',
];
describe('getMomentChartLabelsAndData', () => {
it('should create timing labels from midnight to noon', () => {
const [timingLabels] = getMomentChartLabelsAndData(12 * 60 * 60 * 1000);
expect(timingLabels).toStrictEqual(expectedTimingLabels);
});
});

View File

@@ -0,0 +1,17 @@
import { startOfDay, addHours, format } from 'date-fns';
export const getMomentChartLabelsAndData = (timestamp = Date.now()) => {
const timingLabels: string[] = [];
const initData: number[] = [];
const now = new Date(timestamp);
let m = startOfDay(timestamp);
while (m < now) {
const n = addHours(m, 1);
timingLabels.push(`${format(m, 'ha')}-${format(n, 'ha')}`);
initData.push(0);
m = n;
}
return [timingLabels, initData] as const;
};

View File

@@ -0,0 +1,8 @@
import { getMomentCurrentLabel } from './getMomentCurrentLabel';
describe('getMomentCurrentLabel', () => {
it('should create timing labels from midnight to noon', () => {
const label = getMomentCurrentLabel(12 * 60 * 60 * 1000);
expect(label).toStrictEqual('12PM-1PM');
});
});

View File

@@ -0,0 +1,8 @@
import { format, addHours } from 'date-fns';
export const getMomentCurrentLabel = (timestamp = Date.now()) => {
const m = new Date(timestamp);
const n = addHours(m, 1);
return `${format(m, 'ha')}-${format(n, 'ha')}`;
};

View File

@@ -0,0 +1,44 @@
import type { Chart, ChartType } from 'chart.js';
import type { TFunction } from 'i18next';
import type { MutableRefObject } from 'react';
import { useEffect, useState } from 'react';
type UseChartContextProps<TChart> = {
canvas: MutableRefObject<HTMLCanvasElement | null>;
init: (canvas: HTMLCanvasElement, context: TChart | undefined, t: TFunction) => Promise<TChart>;
t: TFunction;
};
export const useChartContext = <TChartType extends ChartType>({ canvas, init, t }: UseChartContextProps<Chart<TChartType>>) => {
const [context, setContext] = useState<Chart<TChartType>>();
useEffect(() => {
let chart: Chart<TChartType> | undefined;
let unmounted = false;
const initializeChart = async () => {
if (!canvas.current) {
return;
}
chart = await init(canvas.current, undefined, t);
if (unmounted) {
chart?.destroy();
return;
}
setContext(chart);
};
void initializeChart();
return () => {
unmounted = true;
chart?.destroy();
setContext(undefined);
};
}, [canvas, init, t]);
return context;
};

View File

@@ -0,0 +1,32 @@
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import type * as chartjs from 'chart.js';
import type { TFunction } from 'i18next';
import type { MutableRefObject } from 'react';
import { updateChart } from '../../chartHandler';
type UseUpdateChartDataOptions<TChart> = {
context: TChart | undefined;
canvas: MutableRefObject<HTMLCanvasElement | null>;
init: (canvas: HTMLCanvasElement, context: TChart | undefined, t: TFunction) => Promise<TChart>;
t: TFunction;
};
export function useUpdateChartData<TChartType extends chartjs.ChartType>({
canvas: canvasRef,
context,
init,
t,
}: UseUpdateChartDataOptions<chartjs.Chart<TChartType>>) {
return useStableCallback(async (label: string, data: number[]) => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const chartContext = context ?? (await init(canvas, undefined, t));
await updateChart(chartContext, label, data);
});
}