diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index 0b6a19f1d1..9baabd38aa 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -3,6 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { VFC } from 'react'; import { UsersChart } from './UsersChart/UsersChart'; import { FlagsChart } from './FlagsChart/FlagsChart'; +import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/useExecutiveSummary'; import { UserStats } from './UserStats/UserStats'; const StyledGrid = styled(Box)(({ theme }) => ({ @@ -13,6 +14,8 @@ const StyledGrid = styled(Box)(({ theme }) => ({ })); export const ExecutiveDashboard: VFC = () => { + const { executiveDashboardData, loading, error } = useExecutiveDashboard(); + return ( <> ({ paddingBottom: theme.spacing(4) })}> @@ -22,14 +25,17 @@ export const ExecutiveDashboard: VFC = () => { Dashboard } - // subtitle='Succesfully synchronized: 01 Sep 2023 - 07:05:07' /> - {/* Dashboard */} - - + + Stats + ); diff --git a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx index a855b1e118..6dc058366a 100644 --- a/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/FlagsChart/FlagsChartComponent.tsx @@ -1,4 +1,4 @@ -import { type VFC } from 'react'; +import { useMemo, type VFC } from 'react'; import { Chart as ChartJS, CategoryScale, @@ -18,40 +18,31 @@ import { type ILocationSettings, } from 'hooks/useLocationSettings'; import { formatDateYMD } from 'utils/formatDate'; -import { mockData as usersMockData } from '../UsersChart/UsersChartComponent'; +import { ExecutiveSummarySchema } from 'openapi'; -type Data = { - date: string | Date; - total?: number; - active?: number; - archived?: number; -}[]; - -const mockData: Data = usersMockData.map((item) => ({ - ...item, - archived: item.inactive, -})); - -const createData = (theme: Theme) => ({ - labels: mockData.map((item) => item.date), +const createData = ( + theme: Theme, + flagsTrends: ExecutiveSummarySchema['flagsTrends'] = [], +) => ({ + labels: flagsTrends.map((item) => item.date), datasets: [ { label: 'Total flags', - data: mockData.map((item) => item.total), + data: flagsTrends.map((item) => item.total), borderColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main, fill: true, }, { label: 'Archived flags', - data: mockData.map((item) => item.archived), + data: flagsTrends.map((item) => item.archived), borderColor: theme.palette.error.main, backgroundColor: theme.palette.error.main, fill: true, }, { label: 'Active flags', - data: mockData.map((item) => item.active), + data: flagsTrends.map((item) => item.active), borderColor: theme.palette.success.main, backgroundColor: theme.palette.success.main, fill: true, @@ -124,10 +115,19 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) => }, }) as const; -const FlagsChartComponent: VFC = () => { +interface IFlagsChartComponentProps { + flagsTrends: ExecutiveSummarySchema['flagsTrends']; +} + +const FlagsChartComponent: VFC = ({ + flagsTrends, +}) => { const theme = useTheme(); const { locationSettings } = useLocationSettings(); - const data = createData(theme); + const data = useMemo( + () => createData(theme, flagsTrends), + [theme, flagsTrends], + ); const options = createOptions(theme, locationSettings); return ( diff --git a/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx b/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx index ad39e0f6cd..b2493621ad 100644 --- a/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx +++ b/frontend/src/component/executiveDashboard/UsersChart/UsersChartComponent.tsx @@ -1,4 +1,4 @@ -import { type VFC } from 'react'; +import { useMemo, type VFC } from 'react'; import { Chart as ChartJS, CategoryScale, @@ -18,316 +18,7 @@ import { type ILocationSettings, } from 'hooks/useLocationSettings'; import { formatDateYMD } from 'utils/formatDate'; - -type Data = { - date: string | Date; - total?: number; - active?: number; - inactive?: number; -}[]; - -export const mockData: Data = [ - { - date: '2023-01-21', - }, - { - date: '2023-01-28', - }, - { - date: '2023-02-04', - }, - { - date: '2023-02-11', - }, - { - date: '2023-02-18', - }, - { - date: '2023-02-25', - }, - { - date: '2023-03-04', - }, - { - date: '2023-03-11', - }, - { - date: '2023-03-18', - }, - { - date: '2023-03-25', - }, - { - date: '2023-04-01', - }, - { - date: '2023-04-08', - }, - { - date: '2023-04-15', - }, - { - date: '2023-04-22', - total: 43, - active: 0, - inactive: 0, - }, - { - date: '2023-04-29', - total: 54, - active: 54, - inactive: 0, - }, - { - date: '2023-05-06', - total: 63, - active: 63, - inactive: 0, - }, - { - date: '2023-05-13', - total: 81, - active: 81, - inactive: 0, - }, - { - date: '2023-05-20', - total: 80, - active: 80, - inactive: 0, - }, - { - date: '2023-05-27', - total: 95, - active: 95, - inactive: 0, - }, - { - date: '2023-06-03', - total: 108, - active: 108, - inactive: 0, - }, - { - date: '2023-06-10', - total: 101, - active: 101, - inactive: 0, - }, - { - date: '2023-06-17', - total: 104, - active: 104, - inactive: 0, - }, - { - date: '2023-06-24', - total: 114, - active: 114, - inactive: 0, - }, - { - date: '2023-07-01', - total: 108, - active: 106, - inactive: 2, - }, - { - date: '2023-07-08', - total: 103, - active: 102, - inactive: 1, - }, - { - date: '2023-07-15', - total: 106, - active: 105, - inactive: 1, - }, - { - date: '2023-07-22', - total: 112, - active: 106, - inactive: 6, - }, - { - date: '2023-07-29', - total: 113, - active: 107, - inactive: 6, - }, - { - date: '2023-08-05', - total: 109, - active: 98, - inactive: 11, - }, - { - date: '2023-08-12', - total: 110, - active: 96, - inactive: 14, - }, - { - date: '2023-08-19', - total: 127, - active: 111, - inactive: 16, - }, - { - date: '2023-08-26', - total: 140, - active: 124, - inactive: 16, - }, - { - date: '2023-09-02', - total: 150, - active: 130, - inactive: 20, - }, - { - date: '2023-09-09', - total: 168, - active: 148, - inactive: 20, - }, - { - date: '2023-09-16', - total: 171, - active: 154, - inactive: 17, - }, - { - date: '2023-09-23', - total: 190, - active: 174, - inactive: 16, - }, - { - date: '2023-09-30', - total: 186, - active: 169, - inactive: 17, - }, - { - date: '2023-10-07', - total: 188, - active: 173, - inactive: 15, - }, - { - date: '2023-10-14', - total: 181, - active: 166, - inactive: 15, - }, - { - date: '2023-10-21', - total: 192, - active: 177, - inactive: 15, - }, - { - date: '2023-10-28', - total: 183, - active: 164, - inactive: 19, - }, - { - date: '2023-11-04', - total: 200, - active: 180, - inactive: 20, - }, - { - date: '2023-11-11', - total: 212, - active: 189, - inactive: 23, - }, - { - date: '2023-11-18', - total: 204, - active: 177, - inactive: 27, - }, - { - date: '2023-11-25', - total: 200, - active: 173, - inactive: 27, - }, - { - date: '2023-12-02', - total: 200, - active: 175, - inactive: 25, - }, - { - date: '2023-12-09', - total: 200, - active: 176, - inactive: 24, - }, - { - date: '2023-12-16', - total: 215, - active: 186, - inactive: 29, - }, - { - date: '2023-12-23', - total: 221, - active: 195, - inactive: 26, - }, - { - date: '2023-12-30', - total: 214, - active: 184, - inactive: 30, - }, - { - date: '2024-01-06', - total: 204, - active: 173, - inactive: 31, - }, - { - date: '2024-01-13', - total: 215, - active: 181, - inactive: 34, - }, -]; - -const createData = (theme: Theme) => ({ - labels: mockData.map((item) => item.date), - datasets: [ - { - label: 'Total users', - data: mockData.map((item) => item.total), - borderColor: theme.palette.primary.main, - backgroundColor: theme.palette.primary.main, - fill: true, - }, - { - label: 'Inactive users', - data: mockData.map((item) => item.inactive), - borderColor: theme.palette.error.main, - backgroundColor: theme.palette.error.main, - fill: true, - }, - { - label: 'Active users', - data: mockData.map((item) => item.active), - borderColor: theme.palette.success.main, - backgroundColor: theme.palette.success.main, - fill: true, - }, - ], -}); +import { ExecutiveSummarySchema } from 'openapi'; const createOptions = (theme: Theme, locationSettings: ILocationSettings) => ({ @@ -380,10 +71,50 @@ const createOptions = (theme: Theme, locationSettings: ILocationSettings) => }, }) as const; -const UsersChartComponent: VFC = () => { +interface IUsersChartComponentProps { + userTrends: ExecutiveSummarySchema['userTrends']; +} + +const createData = ( + theme: Theme, + userTrends: ExecutiveSummarySchema['userTrends'], +) => ({ + labels: userTrends.map((item) => item.date), + datasets: [ + { + label: 'Total users', + data: userTrends.map((item) => item.total), + borderColor: theme.palette.primary.main, + backgroundColor: theme.palette.primary.main, + fill: true, + }, + { + label: 'Inactive users', + data: userTrends.map((item) => item.inactive), + borderColor: theme.palette.error.main, + backgroundColor: theme.palette.error.main, + fill: true, + }, + { + label: 'Active users', + data: userTrends.map((item) => item.active), + borderColor: theme.palette.success.main, + backgroundColor: theme.palette.success.main, + fill: true, + }, + ], +}); + +const UsersChartComponent: VFC = ({ + userTrends, +}) => { const theme = useTheme(); const { locationSettings } = useLocationSettings(); - const data = createData(theme); + const data = useMemo( + () => createData(theme, userTrends), + [theme, userTrends], + ); + const options = createOptions(theme, locationSettings); return ( diff --git a/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts new file mode 100644 index 0000000000..74b9780d70 --- /dev/null +++ b/frontend/src/hooks/api/getters/useExecutiveSummary/useExecutiveSummary.ts @@ -0,0 +1,43 @@ +import useSWR, { mutate, SWRConfiguration } from 'swr'; +import { useCallback } from 'react'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import { ExecutiveSummarySchema } from 'openapi'; + +interface IUseExecutiveDashboardDataOutput { + executiveDashboardData: ExecutiveSummarySchema | undefined; + refetchExecutiveDashboard: () => void; + loading: boolean; + error?: Error; +} + +export const useExecutiveDashboard = ( + options?: SWRConfiguration, +): IUseExecutiveDashboardDataOutput => { + const path = formatApiPath('api/admin/dashboard/executive'); + + const { data, error } = useSWR( + path, + fetchExecutiveDashboard, + options, + ); + + const refetchExecutiveDashboard = useCallback(() => { + mutate(path).catch(console.warn); + }, [path]); + + return { + executiveDashboardData: data, + refetchExecutiveDashboard, + loading: !error && !data, + error, + }; +}; + +const fetchExecutiveDashboard = ( + path: string, +): Promise => { + return fetch(path) + .then(handleErrorResponses('Executive Dashboard Data')) + .then((res) => res.json()); +};