mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: licensed users chart (#8844)
Currently showing 2 lines, because backend is not sorting the data. ![image](https://github.com/user-attachments/assets/905001fb-2020-45b2-a1f4-ba497b594e61)
This commit is contained in:
parent
f985cb1deb
commit
9a269e3597
@ -0,0 +1,54 @@
|
||||
import type { FC } from 'react';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
import type { LicensedUsersSchema } from 'openapi';
|
||||
import { LineChart } from 'component/insights/components/LineChart/LineChart';
|
||||
import { useTheme } from '@mui/material';
|
||||
|
||||
interface ILicensedUsersChartProps {
|
||||
licensedUsers: LicensedUsersSchema['licensedUsers']['history'];
|
||||
}
|
||||
|
||||
export const LicensedUsersChart: FC<ILicensedUsersChartProps> = ({
|
||||
licensedUsers,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const data = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Licensed users',
|
||||
data: licensedUsers,
|
||||
borderColor: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
return (
|
||||
<LineChart
|
||||
data={data}
|
||||
overrideOptions={{
|
||||
parsing: {
|
||||
yAxisKey: 'count',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
time: {
|
||||
unit: 'month',
|
||||
tooltipFormat: 'MMM yyyy',
|
||||
displayFormats: {
|
||||
month: 'MMM',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,6 +1,8 @@
|
||||
import { Alert, Button, styled, Typography } from '@mui/material';
|
||||
import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import type React from 'react';
|
||||
import { LicensedUsersChart } from './LicensedUsersChart';
|
||||
import { useLicensedUsers } from 'hooks/useLicensedUsers';
|
||||
const ModalContentContainer = styled('section')(({ theme }) => ({
|
||||
minHeight: '100vh',
|
||||
maxWidth: 700,
|
||||
@ -74,6 +76,7 @@ export const LicensedUsersSidebar = ({
|
||||
open,
|
||||
close,
|
||||
}: LicensedUsersSidebarProps) => {
|
||||
const { data } = useLicensedUsers();
|
||||
return (
|
||||
<DynamicSidebarModal
|
||||
open={open}
|
||||
@ -94,7 +97,10 @@ export const LicensedUsersSidebar = ({
|
||||
<RowHeader>Last 30 days</RowHeader>
|
||||
<InfoRow>
|
||||
<LicenceBox>
|
||||
<Typography fontWeight='bold'>11/25</Typography>
|
||||
<Typography fontWeight='bold'>
|
||||
{data.licensedUsers.current}/
|
||||
{data.seatCount}
|
||||
</Typography>
|
||||
<Typography variant='body2'>
|
||||
Used seats last 30 days
|
||||
</Typography>
|
||||
@ -108,7 +114,9 @@ export const LicensedUsersSidebar = ({
|
||||
</Row>
|
||||
<Row>
|
||||
<RowHeader>Last year</RowHeader>
|
||||
<div>this will be great grid</div>
|
||||
<LicensedUsersChart
|
||||
licensedUsers={data.licensedUsers.history}
|
||||
/>
|
||||
</Row>
|
||||
</WidgetContainer>
|
||||
<CloseRow>
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { styled } from '@mui/material';
|
||||
import { createOptions } from './createChartOptions';
|
||||
import merge from 'deepmerge';
|
||||
|
||||
const StyledContainer = styled('div')(({ theme }) => ({
|
||||
position: 'relative',
|
||||
@ -81,6 +82,10 @@ const customHighlightPlugin = {
|
||||
},
|
||||
};
|
||||
|
||||
function mergeAll<T>(objects: Partial<T>[]): T {
|
||||
return merge.all<T>(objects.filter((i) => i));
|
||||
}
|
||||
|
||||
const LineChartComponent: FC<{
|
||||
data: ChartData<'line', unknown>;
|
||||
aspectRatio?: number;
|
||||
@ -100,16 +105,18 @@ const LineChartComponent: FC<{
|
||||
const { locationSettings } = useLocationSettings();
|
||||
|
||||
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
...createOptions(
|
||||
theme,
|
||||
locationSettings,
|
||||
setTooltip,
|
||||
Boolean(cover),
|
||||
),
|
||||
...overrideOptions,
|
||||
}),
|
||||
() =>
|
||||
mergeAll([
|
||||
createOptions(
|
||||
theme,
|
||||
locationSettings,
|
||||
setTooltip,
|
||||
Boolean(cover),
|
||||
),
|
||||
overrideOptions ?? {},
|
||||
]),
|
||||
[theme, locationSettings, overrideOptions, cover],
|
||||
);
|
||||
|
||||
|
@ -3,13 +3,14 @@ import type { ILocationSettings } from 'hooks/useLocationSettings';
|
||||
import type { TooltipState } from './ChartTooltip/ChartTooltip';
|
||||
import { createTooltip } from './createTooltip';
|
||||
import { legendOptions } from './legendOptions';
|
||||
import type { ChartOptions } from 'chart.js';
|
||||
|
||||
export const createOptions = (
|
||||
theme: Theme,
|
||||
locationSettings: ILocationSettings,
|
||||
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
|
||||
isPlaceholder?: boolean,
|
||||
) =>
|
||||
): ChartOptions<'line'> =>
|
||||
({
|
||||
responsive: true,
|
||||
...(isPlaceholder
|
||||
@ -27,10 +28,6 @@ export const createOptions = (
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
interaction: {
|
||||
axis: 'xy',
|
||||
mode: 'nearest',
|
||||
},
|
||||
external: createTooltip(setTooltip),
|
||||
},
|
||||
},
|
||||
@ -46,8 +43,6 @@ export const createOptions = (
|
||||
hitRadius: 15,
|
||||
},
|
||||
},
|
||||
// cubicInterpolationMode: 'monotone',
|
||||
tension: 0.1,
|
||||
color: theme.palette.text.secondary,
|
||||
scales: {
|
||||
y: {
|
||||
|
22
frontend/src/hooks/useLicensedUsers.ts
Normal file
22
frontend/src/hooks/useLicensedUsers.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { LicensedUsersSchema } from '../openapi';
|
||||
import { useApiGetter, fetcher } from './api/getters/useApiGetter/useApiGetter';
|
||||
import { formatApiPath } from '../utils/formatPath';
|
||||
|
||||
const path = `api/admin/licensed-users`;
|
||||
|
||||
const placeholderData: LicensedUsersSchema = {
|
||||
seatCount: 0,
|
||||
licensedUsers: {
|
||||
current: 0,
|
||||
history: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const useLicensedUsers = () => {
|
||||
const { data, refetch, loading, error } = useApiGetter<LicensedUsersSchema>(
|
||||
formatApiPath(path),
|
||||
() => fetcher(formatApiPath(path), 'Licensed users'),
|
||||
);
|
||||
|
||||
return { data: data || placeholderData, refetch, loading, error };
|
||||
};
|
Loading…
Reference in New Issue
Block a user