mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
feat: licensed users chart (#8844)
Currently showing 2 lines, because backend is not sorting the data. 
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 { Alert, Button, styled, Typography } from '@mui/material';
|
||||||
import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { DynamicSidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
|
import { LicensedUsersChart } from './LicensedUsersChart';
|
||||||
|
import { useLicensedUsers } from 'hooks/useLicensedUsers';
|
||||||
const ModalContentContainer = styled('section')(({ theme }) => ({
|
const ModalContentContainer = styled('section')(({ theme }) => ({
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
maxWidth: 700,
|
maxWidth: 700,
|
||||||
@ -74,6 +76,7 @@ export const LicensedUsersSidebar = ({
|
|||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
}: LicensedUsersSidebarProps) => {
|
}: LicensedUsersSidebarProps) => {
|
||||||
|
const { data } = useLicensedUsers();
|
||||||
return (
|
return (
|
||||||
<DynamicSidebarModal
|
<DynamicSidebarModal
|
||||||
open={open}
|
open={open}
|
||||||
@ -94,7 +97,10 @@ export const LicensedUsersSidebar = ({
|
|||||||
<RowHeader>Last 30 days</RowHeader>
|
<RowHeader>Last 30 days</RowHeader>
|
||||||
<InfoRow>
|
<InfoRow>
|
||||||
<LicenceBox>
|
<LicenceBox>
|
||||||
<Typography fontWeight='bold'>11/25</Typography>
|
<Typography fontWeight='bold'>
|
||||||
|
{data.licensedUsers.current}/
|
||||||
|
{data.seatCount}
|
||||||
|
</Typography>
|
||||||
<Typography variant='body2'>
|
<Typography variant='body2'>
|
||||||
Used seats last 30 days
|
Used seats last 30 days
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -108,7 +114,9 @@ export const LicensedUsersSidebar = ({
|
|||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<RowHeader>Last year</RowHeader>
|
<RowHeader>Last year</RowHeader>
|
||||||
<div>this will be great grid</div>
|
<LicensedUsersChart
|
||||||
|
licensedUsers={data.licensedUsers.history}
|
||||||
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</WidgetContainer>
|
</WidgetContainer>
|
||||||
<CloseRow>
|
<CloseRow>
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import { createOptions } from './createChartOptions';
|
import { createOptions } from './createChartOptions';
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
|
||||||
const StyledContainer = styled('div')(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
position: 'relative',
|
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<{
|
const LineChartComponent: FC<{
|
||||||
data: ChartData<'line', unknown>;
|
data: ChartData<'line', unknown>;
|
||||||
aspectRatio?: number;
|
aspectRatio?: number;
|
||||||
@ -100,16 +105,18 @@ const LineChartComponent: FC<{
|
|||||||
const { locationSettings } = useLocationSettings();
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() => ({
|
() =>
|
||||||
...createOptions(
|
mergeAll([
|
||||||
theme,
|
createOptions(
|
||||||
locationSettings,
|
theme,
|
||||||
setTooltip,
|
locationSettings,
|
||||||
Boolean(cover),
|
setTooltip,
|
||||||
),
|
Boolean(cover),
|
||||||
...overrideOptions,
|
),
|
||||||
}),
|
overrideOptions ?? {},
|
||||||
|
]),
|
||||||
[theme, locationSettings, overrideOptions, cover],
|
[theme, locationSettings, overrideOptions, cover],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,13 +3,14 @@ import type { ILocationSettings } from 'hooks/useLocationSettings';
|
|||||||
import type { TooltipState } from './ChartTooltip/ChartTooltip';
|
import type { TooltipState } from './ChartTooltip/ChartTooltip';
|
||||||
import { createTooltip } from './createTooltip';
|
import { createTooltip } from './createTooltip';
|
||||||
import { legendOptions } from './legendOptions';
|
import { legendOptions } from './legendOptions';
|
||||||
|
import type { ChartOptions } from 'chart.js';
|
||||||
|
|
||||||
export const createOptions = (
|
export const createOptions = (
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
locationSettings: ILocationSettings,
|
locationSettings: ILocationSettings,
|
||||||
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
|
setTooltip: React.Dispatch<React.SetStateAction<TooltipState | null>>,
|
||||||
isPlaceholder?: boolean,
|
isPlaceholder?: boolean,
|
||||||
) =>
|
): ChartOptions<'line'> =>
|
||||||
({
|
({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
...(isPlaceholder
|
...(isPlaceholder
|
||||||
@ -27,10 +28,6 @@ export const createOptions = (
|
|||||||
tooltip: {
|
tooltip: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
position: 'nearest',
|
position: 'nearest',
|
||||||
interaction: {
|
|
||||||
axis: 'xy',
|
|
||||||
mode: 'nearest',
|
|
||||||
},
|
|
||||||
external: createTooltip(setTooltip),
|
external: createTooltip(setTooltip),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -46,8 +43,6 @@ export const createOptions = (
|
|||||||
hitRadius: 15,
|
hitRadius: 15,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// cubicInterpolationMode: 'monotone',
|
|
||||||
tension: 0.1,
|
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
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