Skip to content

Commit

Permalink
Merge pull request #118 from RnDAO/feat/active-member-breakdown
Browse files Browse the repository at this point in the history
Feat/active member breakdown
  • Loading branch information
mehdi-torabiv authored Jun 30, 2023
2 parents c3c28cd + 16c6388 commit 50bbf64
Show file tree
Hide file tree
Showing 15 changed files with 1,313 additions and 4 deletions.
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

0 comments on commit 50bbf64

Please sign in to comment.