Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/active member breakdown #118

Merged
merged 15 commits into from
Jun 30, 2023
5 changes: 2 additions & 3 deletions src/components/layouts/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ type items = {
icon: any;
};

import polygon from '../../assets/svg/polygon.svg';

import { conf } from '../../configs/index';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

// import the icons you need
Expand Down Expand Up @@ -122,7 +121,7 @@ const Sidebar = () => {
<div className="w-10 h-10 mb-2 mx-auto">
{guildId && guildInfoByDiscord.icon ? (
<Image
src={`https://cdn.discordapp.com/icons/${guildId}/${guildInfoByDiscord.icon}`}
src={`${conf.DISCORD_CDN}icons/${guildId}/${guildInfoByDiscord.icon}`}
width="100"
height="100"
alt={guildInfoByDiscord.name ? guildInfoByDiscord.name : ''}
Expand Down
3 changes: 2 additions & 1 deletion src/components/layouts/xs/SidebarXs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { MdKeyboardBackspace } from 'react-icons/md';
import useAppStore from '../../../store/useStore';
import { StorageService } from '../../../services/StorageService';
import { IUser } from '../../../utils/types';
import { conf } from '../../../configs';

const Sidebar = () => {
const { guildInfoByDiscord } = useAppStore();
Expand Down Expand Up @@ -112,7 +113,7 @@ const Sidebar = () => {
<div className="w-10 h-10 mx-auto">
{guildId && guildInfoByDiscord.icon ? (
<Image
src={`https://cdn.discordapp.com/icons/${guildId}/${guildInfoByDiscord.icon}`}
src={`${conf.DISCORD_CDN}icons/${guildId}/${guildInfoByDiscord.icon}`}
width="100"
height="100"
alt={guildInfoByDiscord.name ? guildInfoByDiscord.name : ''}
Expand Down
4 changes: 4 additions & 0 deletions src/components/pages/statistics/ActiveMembersComposition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FiCalendar } from 'react-icons/fi';
import RangeSelect from '../../global/RangeSelect';
import { SeriesData, StatisticsProps } from '../../../utils/interfaces';
import { communityActiveDates } from '../../../lib/data/dateRangeValues';
import ActiveMemberBreakdown from './memberBreakdowns/activeMembers/ActiveMemberBreakdown';

export interface ActiveMembersComposition {
activePeriod: number;
Expand Down Expand Up @@ -59,6 +60,7 @@ export default function ActiveMembersComposition({
handleDateRange,
}: ActiveMembersComposition) {
const { activeMembers } = useAppStore();

const [options, setOptions] = useState(defaultOptions);
const [statistics, setStatistics] = useState<StatisticsProps[]>([]);

Expand Down Expand Up @@ -208,6 +210,8 @@ export default function ActiveMembersComposition({
<StatisticalData statistics={[...statistics]} />
</div>

<ActiveMemberBreakdown />

<div className="w-full">
<div className="flex flex-col space-y-2 md:space-y-0 md:flex-row justify-between items-center pb-4">
<h3 className="text-xl font-medium text-lite-black">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import CustomDialogDetail from './CustomDialogDetail';
import { activityCompositionOptions } from '../../../../utils/interfaces';

const mockRowDetail = {
discordId: '123',
avatar: 'avatar.png',
username: 'John Doe',
roles: [
{ id: 1, name: 'Role 1', color: '#ff0000' },
{ id: 2, name: 'Role 2', color: '#00ff00' },
],
activityComposition: ['Composition 1', 'Composition 2'],
};

const mockOptions: activityCompositionOptions[] = [
{ name: 'Option 1', value: 'option1', color: '#0000ff' },
{ name: 'Option 2', value: 'option2', color: '#ffff00' },
];

test('renders the dialog with all states', () => {
// Render the dialog with open state
render(
<CustomDialogDetail
open={true}
rowDetail={mockRowDetail}
onClose={jest.fn()}
options={mockOptions}
/>
);

// Check if the close button is rendered
const closeButton = screen.getByTestId('close-modal-icon');
expect(closeButton).toBeInTheDocument();

// Check if the user avatar is rendered
const avatar = screen.getByAltText('User Avatar');
expect(avatar).toBeInTheDocument();

// Check if the username is rendered
const username = screen.getByText('John Doe');
expect(username).toBeInTheDocument();

// Check if the roles are rendered
const roleElements = screen.getAllByTestId('role');
expect(roleElements.length).toBe(2);

// Check if the activity compositions are rendered
const compositionElements = screen.getAllByTestId('activity-composition');
expect(compositionElements.length).toBe(2);

// Render the dialog with closed state
render(
<CustomDialogDetail
open={false}
rowDetail={mockRowDetail}
onClose={jest.fn()}
options={mockOptions}
/>
);

// Check if the dialog is not rendered
const dialog = screen.queryByRole('MuiDialog-paper');
expect(dialog).toBeNull();
});

test('calls the onClose callback when the close button is clicked', () => {
const onClose = jest.fn();
render(
<CustomDialogDetail
open={true}
rowDetail={mockRowDetail}
onClose={onClose}
options={mockOptions}
/>
);

// Click the close button
const closeButton = screen.getByTestId('close-modal-icon');
fireEvent.click(closeButton);

// Check if the onClose callback is called
expect(onClose).toHaveBeenCalled();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React from 'react';
import { Dialog, DialogProps } from '@mui/material';
import { IoClose } from 'react-icons/io5';
import { conf } from '../../../../configs';
import { Avatar } from '@mui/material';
import { activityCompositionOptions } from '../../../../utils/interfaces';

interface CustomDialogDetailProps extends DialogProps {
open: boolean;
rowDetail: any;
options: activityCompositionOptions[];
onClose: () => void;
}

const CustomDialogDetail: React.FC<CustomDialogDetailProps> = ({
open,
rowDetail,
onClose,
options,
...props
}) => {
const handleClose = () => {
onClose();
};

return (
<Dialog open={open} onClose={onClose} {...props}>
<div className="pb-8 pt-6 px-5 space-y-6">
<IoClose
data-testid="close-modal-icon"
size={40}
onClick={handleClose}
className="cursor-pointer float-right"
/>
<div className="py-6 px-8 space-y-6">
<div className="flex flex-row items-center">
<Avatar
src={`${conf.DISCORD_CDN}avatars/${rowDetail?.discordId}/${rowDetail?.avatar}.png`}
alt="User Avatar"
/>
<span className="ml-2 font-semibold text-base">
{rowDetail?.username}
</span>
</div>
<div>
<p className="text-xs pb-2 font-semibold">Roles:</p>
<div className="flex flex-row flex-wrap items-center first:ml-0 ml-1">
{rowDetail?.roles.map((role: any) => (
<div
key={role.id}
className="flex flex-row flex-wrap"
style={{ whiteSpace: 'nowrap' }}
data-testid="role"
>
<span
className="bg-white p-1 px-2 rounded-[4px] border border-[#D1D1D1] text-xs"
style={{
display: 'flex',
alignItems: 'center',
backgroundColor:
role.color !== 0
? `#${role.color.toString(16).padStart(6, '0')}`
: '#96A5A6',
}}
>
<span
className="w-2 h-2 rounded-full mr-1"
style={{
backgroundColor:
role.color !== 0
? `#${role.color.toString(16).padStart(6, '0')}`
: '#96A5A6',
flexShrink: 0,
}}
/>
{role.name}
</span>
</div>
))}
</div>
</div>
<div>
<p className="text-xs pb-2 font-semibold">Activity composition:</p>
<div className="flex flex-row flex-wrap">
{rowDetail && rowDetail?.activityComposition.length > 0 ? (
rowDetail?.activityComposition.map((composition: any) => {
const matchedOption = options.find(
(option) => option.name === composition
);
const backgroundColor = matchedOption
? matchedOption.color
: '#96A5A6';

return (
<div
key={composition}
className="flex flex-row flex-wrap items-center first:ml-0 ml-1"
data-testid="activity-composition"
>
<span
className="bg-white p-1 px-2 mb-2 rounded-[4px] border border-[#D1D1D1] text-xs flex items-center"
style={{
backgroundColor: backgroundColor,
display: 'flex',
alignItems: 'center',
}}
>
<span
className="w-2 h-2 rounded-full mr-2"
style={{
backgroundColor: backgroundColor,
flexShrink: 0,
}}
/>
{composition}
</span>
</div>
);
})
) : (
<div className="flex flex-row flex-wrap items-center">
<span
className="bg-white p-1 px-2 rounded-[4px] border border-[#D1D1D1] text-xs flex items-center"
style={{
backgroundColor: '#96A5A6',
display: 'flex',
alignItems: 'center',
}}
>
<span
className="w-2 h-2 rounded-full mr-2"
style={{
backgroundColor: '#96A5A6',
flexShrink: 0,
}}
/>
other{' '}
</span>
</div>
)}
</div>
</div>
</div>
</div>
</Dialog>
);
};

export default CustomDialogDetail;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import CustomPagination from './CustomPagination';

describe('CustomPagination', () => {
const mockTotalItems = 100;
const mockItemsPerPage = 10;
const mockCurrentPage = 1;
const mockOnChangePage = jest.fn();

it('renders the component', () => {
render(
<CustomPagination
totalItems={mockTotalItems}
itemsPerPage={mockItemsPerPage}
currentPage={mockCurrentPage}
onChangePage={mockOnChangePage}
/>
);

expect(screen.getByRole('navigation')).toBeInTheDocument();
expect(screen.getByText('1')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
});

it('triggers onChangePage when a page is clicked', () => {
render(
<CustomPagination
totalItems={mockTotalItems}
itemsPerPage={mockItemsPerPage}
currentPage={mockCurrentPage}
onChangePage={mockOnChangePage}
/>
);

fireEvent.click(screen.getByText('2'));
expect(mockOnChangePage).toHaveBeenCalledWith(2);
});

it('does not trigger onChangePage when the current page is clicked', () => {
render(
<CustomPagination
totalItems={mockTotalItems}
itemsPerPage={mockItemsPerPage}
currentPage={mockCurrentPage}
onChangePage={mockOnChangePage}
/>
);

fireEvent.click(screen.getByText('1'));
expect(mockOnChangePage).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from 'react';
import { Pagination, PaginationItem } from '@mui/material';

interface PaginationProps {
totalItems: number;
itemsPerPage: number;
currentPage: number;
onChangePage: (page: number) => void;
}

const CustomPagination: React.FC<PaginationProps> = ({
totalItems,
itemsPerPage,
currentPage,
onChangePage,
}) => {
const totalPages = Math.ceil(totalItems / itemsPerPage);

const handleChangePage = (page: number) => {
if (page !== currentPage) {
onChangePage(page);
}
};

return (
<Pagination
shape="rounded"
count={totalPages}
page={currentPage}
onChange={(event, page) => handleChangePage(page)}
renderItem={(item) => <PaginationItem component="button" {...item} />}
/>
);
};

export default CustomPagination;
Loading