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

Feature/volunteer-logs-page #92

Merged
merged 4 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions components/Logs/LogsDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { DashboardContainer } from '@/styles/volunteerDashboard.styles';
import { useEffect, useState } from 'react';
import {
VolunteerLogData,
VolunteerEventData,
} from 'bookem-shared/src/types/database';
import LogsTable from './LogsTable';
import LogsStats from './LogsStats';
import { Greeting, Header } from '@/styles/dashboard.styles';
import { Card } from 'antd';

type VolunteerLogWithEvent = VolunteerLogData & {
event: VolunteerEventData;
};

export type LogTableData = VolunteerLogWithEvent & {
eventName: string;
};

// unpack the eventName from the event object so that it can be used as a column in the table
const processLogDataForTable = (
logData: VolunteerLogWithEvent[]
): LogTableData[] => {
return logData.map(log => {
return {
...log,
eventName: log.event.name,
};
});
};

export default function LogsDashboard() {
const [logData, setLogData] = useState<LogTableData[]>([]);

useEffect(() => {
const fetchLogs = async () => {
const response = await fetch('/api/volunteer-logs');
const data: VolunteerLogWithEvent[] = await response.json();
// console.log("Logs data: ", data);
setLogData(processLogDataForTable(data));
};
fetchLogs();
}, []);

console.log('Log data: ', logData);

return (
<DashboardContainer>
<Greeting>Your Logs</Greeting>
<LogsStats logData={logData} />
{/* Table of logs */}
<Header>Log Details:</Header>
<Card
// gray background
style={{ backgroundColor: '#f0f2f5' }}>
<LogsTable logData={logData} />
</Card>
</DashboardContainer>
);
}
65 changes: 65 additions & 0 deletions components/Logs/LogsStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { LogTableData } from './LogsDashboard';
import { VolunteerStatsContainer } from '@/styles/volunteerDashboard.styles';
import {
Greeting,
StatsFlex,
FlexChild,
StatsNumber,
StatsDescription,
Header,
} from '@/styles/dashboard.styles';
import { VolunteerLogStatus } from 'bookem-shared/src/types/database';

// only count the approved logs
const calcTotalHours = (logData: LogTableData[]) => {
let totalHours = 0;
logData.forEach(log => {
if (log.status === VolunteerLogStatus.Approved) {
totalHours += log.hours;
}
});
return totalHours;
};

const calcTotalBooks = (logData: LogTableData[]) => {
let totalBooks = 0;
logData.forEach(log => {
if (log.status === VolunteerLogStatus.Approved) {
totalBooks += log.numBooks || 0;
}
});
return totalBooks;
};

// only count approved logs of events and only count unique events
const calcNumEvents = (logData: LogTableData[]) => {
const eventSet = new Set<string>();
logData.forEach(log => {
if (log.status === VolunteerLogStatus.Approved) {
eventSet.add(log.eventName);
}
});
return eventSet.size;
};

export default function LogsStats({ logData }) {
return (
<VolunteerStatsContainer>
<Header>Your accomplishments at a glance:</Header>
<StatsFlex>
<FlexChild>
<StatsNumber>{calcNumEvents(logData)}</StatsNumber>
<StatsDescription>Events helped</StatsDescription>
</FlexChild>
<FlexChild>
<StatsNumber>{calcTotalHours(logData)}</StatsNumber>
<StatsDescription>Hours volunteered</StatsDescription>
</FlexChild>
<FlexChild>
<StatsNumber>{calcTotalBooks(logData)}</StatsNumber>
<StatsDescription>Books distributed</StatsDescription>
</FlexChild>
</StatsFlex>
</VolunteerStatsContainer>
);
}
79 changes: 79 additions & 0 deletions components/Logs/LogsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Table, Tag } from 'antd';
import { LogTableData } from './LogsDashboard';
import { VolunteerLogStatus } from 'bookem-shared/src/types/database';
import Link from 'next/link';
// use antd link icon
import { LinkOutlined } from '@ant-design/icons';

export default function LogsTable({ logData }: { logData: LogTableData[] }) {
const columns = [
{
title: 'Event',
dataIndex: 'eventName',
key: 'event',
},
// add a see event link
{
title: 'Event Details',
dataIndex: 'event',
key: 'seeEvent',
render: (event: any) => {
return (
<Link href={`/event/${event._id}`}>
{/* light blue color text */}
<div style={{ color: '#1890ff' }}>
<LinkOutlined /> See event
</div>
</Link>
);
},
},

{
title: 'Atended on',
dataIndex: 'date',
key: 'date',
render: (date: string) => {
return new Date(date).toLocaleDateString();
},
},
{
title: 'Log submitted on',
dataIndex: 'createdAt',
key: 'createdAt',
render: (createdAt: string) => {
return new Date(createdAt).toLocaleDateString();
},
},
{
title: 'Hours',
dataIndex: 'hours',
key: 'hours',
},
{
title: 'Books',
dataIndex: 'numBooks',
key: 'numBooks',
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
// render the status with tags of different colors
render: (status: string) => {
let color = 'blue';
if (status === VolunteerLogStatus.Approved) {
color = 'green';
} else if (status === VolunteerLogStatus.Rejected) {
color = 'red';
}
return <Tag color={color}>{status}</Tag>;
},
},
];

return (
// allow the table to scroll horizontally
<Table dataSource={logData} columns={columns} scroll={{ x: true }} />
);
}
2 changes: 1 addition & 1 deletion pages/api/volunteer-logs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default async function handler(
// get all volunteerEvents from collection that match the user's Id
const volunteerLogs = await VolunteerLogs.find({
userId: usersId,
});
}).populate('event');

// return the result
res.status(200).json(volunteerLogs);
Expand Down
8 changes: 8 additions & 0 deletions pages/logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import LogsDashboard from '@/components/Logs/LogsDashboard';

export default function Logs() {
return <LogsDashboard />;
}

// perform automatic redirection to login page if user not logged in.
export { getServerSideProps } from '@/lib/getServerSideProps';
13 changes: 13 additions & 0 deletions public/sidebar/logs-black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions public/sidebar/logs-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions styles/logs.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components';

/**
* Container for volunteer dashboard
*/
export const DashboardContainer = styled.div`
height: fit-content;
width: 100%;
padding: 40px;
position: relative;
`;
9 changes: 9 additions & 0 deletions utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const AVAILABLE_ROUTES = {
HOME: '/',
VOLUNTEER: '/volunteer',
SETTINGS: '/settings',
LOGS: '/logs',
};

/**
Expand Down Expand Up @@ -75,6 +76,14 @@ export const SIDEBAR_ICON_PARAMS: SidebarIconParams[] = [
linkTo: AVAILABLE_ROUTES.HOME,
text: 'Home',
},
{
desktopDefaultSrc: '/sidebar/logs-white.svg',
mobileDefaultSrc: '/sidebar/logs-black.svg',
desktopHoveredSrc: '/sidebar/logs-black.svg',
mobileHoveredSrc: '/sidebar/logs-white.svg',
linkTo: '/logs',
text: 'Logs',
},
{
desktopDefaultSrc: '/sidebar/volunteer-white.png',
mobileDefaultSrc: '/sidebar/volunteer-black.png',
Expand Down
Loading