Skip to content

Commit

Permalink
feat: create general statistic page (#378)
Browse files Browse the repository at this point in the history
* feat: initalize general statistic and create date range component

* feat: create member activity chart

* feat: change sidebar to have my statistics only at root pages and change general statistics to my statistics

* feat: rename file to myAnalyticspage

* fix: responsive design and filling ranges gaps

* feat: controlling number of bars depends on screen size
  • Loading branch information
LinaYahya authored Jun 28, 2024
1 parent ea16e39 commit 66cde10
Show file tree
Hide file tree
Showing 21 changed files with 1,384 additions and 1,091 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
"@emotion/cache": "11.11.0",
"@emotion/react": "11.11.4",
"@emotion/styled": "11.11.5",
"@graasp/query-client": "3.10.0",
"@graasp/query-client": "3.13.0",
"@graasp/sdk": "4.12.0",
"@graasp/translations": "1.28.0",
"@graasp/ui": "4.17.1",
"@mui/icons-material": "5.15.16",
"@mui/lab": "5.0.0-alpha.141",
"@mui/material": "5.15.16",
"@sentry/react": "7.113.0",
"date-fns": "3.6.0",
"google-map-react": "2.2.1",
"http-status-codes": "2.3.0",
"i18next": "23.11.3",
Expand All @@ -37,6 +38,7 @@
"lucide-react": "0.378.0",
"miragejs": "0.1.48",
"react": "18.3.1",
"react-date-range": "2.0.1",
"react-dom": "18.3.1",
"react-ga4": "2.1.0",
"react-i18next": "14.1.1",
Expand Down Expand Up @@ -97,6 +99,7 @@
"@types/lodash.groupby": "4.6.9",
"@types/lodash.truncate": "4.4.9",
"@types/node": "20.12.8",
"@types/react-date-range": "1.4.9",
"@typescript-eslint/eslint-plugin": "7.8.0",
"@typescript-eslint/parser": "7.8.0",
"@vitejs/plugin-react": "4.2.1",
Expand Down
14 changes: 14 additions & 0 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
EXPORT_ANALYTICS_PATH,
HOME_PATH,
ITEMS_ANALYTICS_PATH,
MY_ANALYTICS_PATH,
USERS_ANALYTICS_PATH,
buildItemPath,
} from '../config/paths';
Expand All @@ -27,6 +28,7 @@ import GeneralAnalyticsPage from './pages/Item/GeneralAnalyticsPage';
import ItemAnalyticPage from './pages/Item/ItemAnalyticPage';
import ItemPage from './pages/Item/ItemPage';
import UsersAnalyticPage from './pages/Item/UsersAnalyticPage';
import MyAnalyticsPage from './pages/MyAnalyticsPage';

const App = (): JSX.Element => {
const { data: currentMember, isLoading } = hooks.useCurrentMember();
Expand Down Expand Up @@ -60,6 +62,10 @@ const App = (): JSX.Element => {
ItemPage,
withAuthorizationProps,
);
const MyAnalyticsWithAuth = withAuthorization(
MyAnalyticsPage,
withAuthorizationProps,
);

return (
<Routes>
Expand All @@ -73,6 +79,14 @@ const App = (): JSX.Element => {
</PageWrapper>
}
/>
<Route
path={MY_ANALYTICS_PATH}
element={
<PageWrapper>
<MyAnalyticsWithAuth />
</PageWrapper>
}
/>

<Route
path={buildItemPath()}
Expand Down
67 changes: 67 additions & 0 deletions src/components/common/DateRange.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useContext, useState } from 'react';
import { DateRangePicker, defaultStaticRanges } from 'react-date-range';
import 'react-date-range/dist/styles.css';
import 'react-date-range/dist/theme/default.css';

import { Box, Popover, TextField } from '@mui/material';

import { format } from 'date-fns';

import i18n, { locales, useAnalyticsTranslation } from '@/config/i18n';

import { MyAnalyticsDateRangeDataContext } from '../context/MyAnalyticsDateRangeContext';

const DateRange = (): JSX.Element => {
const { t } = useAnalyticsTranslation();
const { dateRange, setDateRange } = useContext(
MyAnalyticsDateRangeDataContext,
);
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const open = Boolean(anchorEl);

const handleClose = () => {
setAnchorEl(null);
};

const formattedStartDate = format(dateRange.startDate, 'MMM d, yyyy');
const formattedEndDate = format(dateRange.endDate, 'MMM d, yyyy');
const inputValue = `${formattedStartDate} - ${formattedEndDate}`;

const defaultStaticRangesTranslatedLabels = defaultStaticRanges.map((r) => ({
...r,
label: t(r.label as string),
}));

return (
<Box margin={{ sm: 'auto', md: 'unset' }}>
<TextField
required
label={t('RANGE_DATE_PICKER_INPUT_LABEL')}
value={inputValue}
onClick={(event) => {
setAnchorEl(event.currentTarget);
}}
sx={{ minWidth: '240px' }}
/>
<Popover
onClose={handleClose}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<DateRangePicker
onChange={(item) => setDateRange({ ...dateRange, ...item.selection })}
maxDate={new Date()}
ranges={[dateRange]}
locale={locales[i18n.language]}
staticRanges={defaultStaticRangesTranslatedLabels}
/>
</Popover>
</Box>
);
};

export default DateRange;
38 changes: 38 additions & 0 deletions src/components/common/MyAnalyticCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import { Box, Stack, Typography, styled } from '@mui/material';

type Props = {
title: string;
stat: number;
};

const StyledCardBox = styled(Stack)(({ theme }) => ({
padding: theme.spacing(2),
backgroundColor: 'rgba(228, 224, 228, 0.61)',
borderRadius: theme.spacing(2),
color: '#808080',
flex: 1,
justifyContent: 'space-between',
}));

const MyAnalyticsCard = ({ title, stat }: Props): JSX.Element => {
return (
<StyledCardBox>
<Typography fontWeight={700}>{title}</Typography>
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
fontWeight: 900,
color: '#7F82CD',
}}
>
{stat}
</Box>
</StyledCardBox>
);
};

export default MyAnalyticsCard;
65 changes: 65 additions & 0 deletions src/components/context/MyAnalyticsDateRangeContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { createContext, useMemo, useState } from 'react';

import { addDays, intervalToDuration } from 'date-fns';

import { DateRange, GroupByInterval } from '@/config/type';

const defaultValue: {
dateRange: DateRange;
setDateRange: (view: DateRange) => void;
groupInterval: GroupByInterval;
} = {
dateRange: {
startDate: addDays(new Date(), -30),
endDate: new Date(),
key: 'selection',
},
setDateRange: () => {
// do nothing
},
groupInterval: GroupByInterval.Week,
};

export const MyAnalyticsDateRangeDataContext = createContext(defaultValue);

const MyAnalyticsDateRangeProvider = ({
children,
}: {
children: JSX.Element;
}): JSX.Element => {
const [dateRange, setDateRange] = useState({
startDate: addDays(new Date(), -30),
endDate: new Date(),
key: 'selection',
});

const { months, days, years } = intervalToDuration({
start: dateRange.startDate,
end: dateRange.endDate,
});

const groupInterval =
years && years >= 1
? GroupByInterval.Year
: months && months > 2
? GroupByInterval.Month
: days && days < 8
? GroupByInterval.Day
: GroupByInterval.Week;

const value = useMemo(
() => ({
dateRange,
setDateRange,
groupInterval,
}),
[dateRange, setDateRange, groupInterval],
);
return (
<MyAnalyticsDateRangeDataContext.Provider value={value}>
{children}
</MyAnalyticsDateRangeDataContext.Provider>
);
};

export default MyAnalyticsDateRangeProvider;
2 changes: 1 addition & 1 deletion src/components/custom/LinkMenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Link, useLocation } from 'react-router-dom';
import { MenuItem } from '@graasp/ui';

interface Props {
disabled: boolean;
disabled?: boolean;
to: string;
text: string;
icon: ReactElement;
Expand Down
49 changes: 49 additions & 0 deletions src/components/custom/MemberGeneralStatisticsCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

import { Grid } from '@mui/material';

import { Action, ActionTriggers } from '@graasp/sdk';

import { useAnalyticsTranslation } from '@/config/i18n';

import MyAnalyticsCard from '../common/MyAnalyticCard';

type Props = {
actionsGroupedByTypes: { [key: string]: Action[] };
};
const MemberGeneralStatisticsCards = ({
actionsGroupedByTypes,
}: Props): JSX.Element => {
const { t } = useAnalyticsTranslation();

return (
<Grid container spacing={2} p={2}>
<Grid item md={3} lg={2} sx={{ display: 'flex' }}>
<MyAnalyticsCard
title={t('GENERAL_STATISTIC_ITEMS_CREATED')}
stat={actionsGroupedByTypes[ActionTriggers.Create]?.length ?? 0}
/>
</Grid>
<Grid item md={3} lg={2} sx={{ display: 'flex' }}>
<MyAnalyticsCard
title={t('GENERAL_STATISTIC_LIKED_ITEMS')}
stat={actionsGroupedByTypes[ActionTriggers.ItemLike]?.length ?? 0}
/>
</Grid>
<Grid item md={3} lg={2} sx={{ display: 'flex' }}>
<MyAnalyticsCard
title={t('GENERAL_STATISTIC_DOWNLOADED_ITEMS')}
stat={actionsGroupedByTypes[ActionTriggers.ItemDownload]?.length ?? 0}
/>
</Grid>
<Grid item md={3} lg={2} sx={{ display: 'flex' }}>
<MyAnalyticsCard
title={t('GENERAL_STATISTIC_CHAT_CREATED')}
stat={actionsGroupedByTypes[ActionTriggers.ChatCreate]?.length ?? 0}
/>
</Grid>
</Grid>
);
};

export default MemberGeneralStatisticsCards;
24 changes: 20 additions & 4 deletions src/components/layout/Navigator.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { useLocation, useMatch, useNavigate } from 'react-router-dom';
import { Link, useLocation, useMatch, useNavigate } from 'react-router-dom';

import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
import { IconButton } from '@mui/material';
import { IconButton, Typography, styled } from '@mui/material';

import { Navigation } from '@graasp/ui';

import { useAnalyticsTranslation } from '@/config/i18n';

import { NAVIGATOR_BACKGROUND_COLOR } from '../../config/constants';
import { HOME_PATH, buildItemPath } from '../../config/paths';
import {
HOME_PATH,
MY_ANALYTICS_PATH,
buildItemPath,
} from '../../config/paths';
import { hooks } from '../../config/queryClient';
import {
BREADCRUMBS_NAVIGATOR_ID,
Expand All @@ -18,7 +24,12 @@ import {

const { useItem, useParents, useCurrentMember, useChildren } = hooks;

const NavigationLink = styled(Link)(({ theme }) => ({
textDecoration: 'none',
color: theme.palette.text.primary,
}));
const Navigator = (): JSX.Element | null => {
const { t } = useAnalyticsTranslation();
const match = useMatch(buildItemPath());
const { pathname } = useLocation();
const itemId = match?.params?.itemId;
Expand Down Expand Up @@ -50,11 +61,16 @@ const Navigator = (): JSX.Element | null => {
<HomeOutlinedIcon />
</IconButton>
<ArrowForwardIosIcon sx={{ m: 2 }} fontSize="inherit" />
{pathname === MY_ANALYTICS_PATH && (
<NavigationLink to={MY_ANALYTICS_PATH}>
<Typography>{t('TAB_MY_ANALYTICS')}</Typography>
</NavigationLink>
)}
</>
);
};

if (!item && pathname !== HOME_PATH) {
if (!item && pathname !== MY_ANALYTICS_PATH && pathname !== HOME_PATH) {
return null;
}

Expand Down
2 changes: 0 additions & 2 deletions src/components/layout/PageWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ const PageWrapper = ({ children }: { children: JSX.Element }): JSX.Element => {
/>
}
LinkComponent={LinkComponent}
// to close the drawer at home and shared pages, So user won't move to item routes
open={pathname !== HOME_PATH}
>
<Box height="100%" display="flex" flexGrow={1} flexDirection="column">
{!isRoot && (
Expand Down
Loading

0 comments on commit 66cde10

Please sign in to comment.