client/app/bundles/course/leaderboard/components/tables/LeaderboardTable.tsx
import { FC, memo, useEffect, useState } from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import { Avatar, AvatarGroup, Box, Tooltip } from '@mui/material';
import { TableColumns } from 'types/components/DataTable';
import {
GroupLeaderboardAchievement,
GroupLeaderboardPoints,
LeaderboardAchievement,
LeaderboardPoints,
} from 'types/course/leaderboard';
import { getAchievementBadgeUrl } from 'course/helper/achievements';
import DataTable from 'lib/components/core/layouts/DataTable';
import Link from 'lib/components/core/Link';
import { getAchievementURL, getCourseUserURL } from 'lib/helpers/url-builders';
import { getCourseId } from 'lib/helpers/url-helpers';
import useMedia from 'lib/hooks/useMedia';
import { LeaderboardTableType } from '../../types';
interface Props {
data:
| LeaderboardPoints[]
| LeaderboardAchievement[]
| GroupLeaderboardPoints[]
| GroupLeaderboardAchievement[];
id: LeaderboardTableType;
}
const translations = defineMessages({
titlePoints: {
id: 'course.leaderboard.LeaderboardTable.titlePoints',
defaultMessage: 'By Experience Points',
},
titleAchievements: {
id: 'course.leaderboard.LeaderboardTable.titleAchievements',
defaultMessage: 'By Achievements',
},
average: {
id: 'course.leaderboard.LeaderboardTable.average',
defaultMessage: 'Average',
},
experience: {
id: 'course.leaderboard.LeaderboardTable.experience',
defaultMessage: 'Experience',
},
achievements: {
id: 'course.leaderboard.LeaderboardTable.achievements',
defaultMessage: 'Achievements',
},
});
const styles = {
title: {
flexDirection: 'column',
textAlign: 'center',
fontSize: 20,
},
link: {
textDecoration: 'none',
},
avatarGroup: {
justifyContent: 'left',
'& .MuiAvatar-root': {
width: 40,
height: 40,
marginLeft: '0.1px',
},
},
avatar: {
maxWidth: '250px',
wordWrap: 'break-word',
display: 'flex',
alignItems: 'center',
minWidth: '150px',
},
};
const LeaderboardTable: FC<Props> = (props: Props) => {
const { data, id: tableType } = props;
const tabletView = useMedia.MinWidth('sm');
const phoneView = useMedia.MinWidth('xs');
const [maxAvatars, setMaxAvatars] = useState(6);
useEffect(() => {
if (phoneView) {
setMaxAvatars(2);
} else if (tabletView) {
setMaxAvatars(4);
} else {
setMaxAvatars(6);
}
}, [phoneView, tabletView]);
const columns: TableColumns[] = [
{
name: 'id',
label: 'Rank',
options: {
filter: false,
sort: false,
setCellHeaderProps: () => ({
style: { padding: '16px', textAlign: 'center' },
}),
setCellProps: () => ({
style: { textAlign: 'center', maxWidth: '50px' },
}),
customBodyRenderLite: (dataIndex) => dataIndex + 1,
},
},
];
const addIndividual = (): void => {
const individualData = data as
| LeaderboardPoints[]
| LeaderboardAchievement[];
columns.push({
name: 'name',
label: 'Name',
options: {
filter: false,
sort: false,
setCellProps: () => ({
style: { width: '100%' },
}),
setCellHeaderProps: () => ({
style: { padding: '0px' },
}),
customBodyRenderLite: (dataIndex) => (
<Box
className="course_user"
id={`course_user_${individualData[dataIndex].id}`}
sx={styles.avatar}
>
<Avatar
alt={individualData[dataIndex].name}
component={Link}
marginRight={1}
src={individualData[dataIndex].imageUrl}
to={getCourseUserURL(getCourseId(), individualData[dataIndex].id)}
underline="none"
/>
<Link
to={getCourseUserURL(getCourseId(), individualData[dataIndex].id)}
underline="hover"
>
{individualData[dataIndex].name}
</Link>
</Box>
),
},
});
};
const addPoints = (): void => {
const pointData = data as LeaderboardPoints[];
columns.push(
{
name: 'level',
label: 'Level',
options: {
filter: false,
sort: false,
setCellHeaderProps: () => ({
style: { padding: '0px', textAlign: 'center' },
}),
setCellProps: () => ({
style: { textAlign: 'center' },
}),
customBodyRenderLite: (dataIndex) => pointData[dataIndex].level,
},
},
{
name: 'experience',
label: 'Experience',
options: {
filter: false,
sort: false,
setCellHeaderProps: () => ({
style: { padding: '16px', textAlign: 'center' },
}),
setCellProps: () => ({
style: { textAlign: 'center' },
}),
customBodyRenderLite: (dataIndex) => pointData[dataIndex].experience,
},
},
);
};
const addAchievements = (): void => {
const achievementData = data as LeaderboardAchievement[];
columns.push({
name: 'achievements',
label: 'Achievements',
options: {
filter: false,
sort: false,
alignLeft: true,
justifyCenter: true,
setCellHeaderProps: () => ({
style: { padding: '0px 16px 0px 1px' },
}),
setCellProps: () => ({
style: { padding: '0px 16px 0px 0px' },
}),
customBodyRenderLite: (dataIndex) => (
<AvatarGroup
componentsProps={{
additionalAvatar: {
onClick: (): void => {
window.location.href = getCourseUserURL(
getCourseId(),
achievementData[dataIndex].id,
);
},
sx: { cursor: 'pointer' },
},
}}
max={maxAvatars}
sx={styles.avatarGroup}
total={achievementData[dataIndex].achievementCount}
>
{achievementData[dataIndex].achievements.map((achievement) => {
return (
<Tooltip key={achievement.id} title={achievement.title}>
<Avatar
alt={achievement.badge.name}
className="achievement"
component={Link}
id={`achievement_${achievement.id}`}
src={getAchievementBadgeUrl(achievement.badge.url, true)}
to={getAchievementURL(getCourseId(), achievement.id)}
underline="none"
/>
</Tooltip>
);
})}
</AvatarGroup>
),
},
});
};
const addGroup = (): void => {
const groupData = data as
| GroupLeaderboardPoints[]
| GroupLeaderboardAchievement[];
columns.push(
{
name: 'name',
label: 'Name',
options: {
filter: false,
sort: false,
setCellHeaderProps: () => ({
style: { padding: '0px 16px 0px 1px', minWidth: '80px' },
}),
setCellProps: () => ({
style: { padding: '0px 16px 0px 0px', minWidth: '80px' },
}),
customBodyRenderLite: (dataIndex) => (
<Box className="group" id={`group_${groupData[dataIndex].id}`}>
{groupData[dataIndex].name}
</Box>
),
},
},
{
name: 'members',
label: 'Members',
options: {
filter: false,
sort: false,
setCellHeaderProps: () => ({
style: { padding: '0px 16px 0px 1px', width: '100%' },
}),
setCellProps: () => ({
style: { padding: '0px 16px 0px 0px', width: '100%' },
}),
customBodyRenderLite: (dataIndex) => (
<AvatarGroup
max={maxAvatars}
sx={styles.avatarGroup}
total={groupData[dataIndex].group.length}
>
{groupData[dataIndex].group.map((user) => (
<Tooltip key={user.id} title={user.name}>
<Avatar
alt={user.name}
component={Link}
src={user.imageUrl}
to={getCourseUserURL(getCourseId(), user.id)}
underline="none"
/>
</Tooltip>
))}
</AvatarGroup>
),
},
},
);
};
const addAverageExperience = (): void => {
const groupPointData = data as GroupLeaderboardPoints[];
columns.push({
name: 'points',
label: 'Average Experience',
options: {
filter: false,
sort: false,
alignCenter: true,
justifyCenter: true,
customHeadLabelRender: () => (
<>
<div>
<FormattedMessage {...translations.average} />
</div>
<div>
<FormattedMessage {...translations.experience} />
</div>
</>
),
customBodyRenderLite: (_dataIndex) =>
groupPointData[_dataIndex].averageExperiencePoints.toFixed(2),
},
});
};
const addAverageAchievements = (): void => {
const groupAchievementData = data as GroupLeaderboardAchievement[];
columns.push({
name: 'achievements',
label: 'Average Achievements',
options: {
filter: false,
sort: false,
alignCenter: true,
justifyCenter: true,
customHeadLabelRender: () => (
<>
<div>
<FormattedMessage {...translations.average} />
</div>
<div>
<FormattedMessage {...translations.achievements} />
</div>
</>
),
customBodyRenderLite: (_dataIndex) =>
groupAchievementData[_dataIndex].averageAchievementCount.toFixed(2),
},
});
};
const updateColumns = (): void => {
switch (tableType) {
case LeaderboardTableType.LeaderboardPoints:
addIndividual();
addPoints();
break;
case LeaderboardTableType.LeaderboardAchievement:
addIndividual();
addAchievements();
break;
case LeaderboardTableType.GroupLeaderboardPoints:
addGroup();
addAverageExperience();
break;
case LeaderboardTableType.GroupLeaderboardAchievement:
addGroup();
addAverageAchievements();
break;
default:
break;
}
};
const options = {
download: false,
filter: false,
pagination: false,
print: false,
search: false,
selectableRows: 'none',
viewColumns: false,
};
const title = (
<Box sx={styles.title}>
{tableType === LeaderboardTableType.LeaderboardPoints ||
tableType === LeaderboardTableType.GroupLeaderboardPoints ? (
<FormattedMessage {...translations.titlePoints} />
) : (
<FormattedMessage {...translations.titleAchievements} />
)}
</Box>
);
// Update columns based on table type
updateColumns();
return (
<DataTable
columns={columns}
data={data}
options={options}
title={title}
titleAlignCenter
titleGrid
withMargin
/>
);
};
export default memo(
LeaderboardTable,
(prevProps, nextProps) => prevProps.data.length === nextProps.data.length,
);