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

Add NFC point features + Improve Hacker Portal #365

Merged
merged 43 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
cc759b0
Change text color
JiashuHarryHuang Sep 9, 2023
183ccc1
Add NFC point fields and display it in organizer table
JiashuHarryHuang Sep 9, 2023
73fba25
Add a save button
JiashuHarryHuang Sep 9, 2023
f6bffea
Add on change event binding to each text box and move the columns ins…
JiashuHarryHuang Sep 9, 2023
5a95ec2
New endpoint for event save changes
JiashuHarryHuang Sep 12, 2023
14cd8ab
Update EventData type with nfcPoints
JiashuHarryHuang Sep 12, 2023
4c3cd67
Finish logic for frontend save changes
JiashuHarryHuang Sep 12, 2023
f51b418
Successfully bind nfc points
JiashuHarryHuang Sep 14, 2023
0985224
Add some comments
JiashuHarryHuang Sep 14, 2023
8b7c066
Clean up stuffs
JiashuHarryHuang Sep 14, 2023
b5e38fd
Finish setting up hackathon start date environment variable
JiashuHarryHuang Sep 16, 2023
147ba76
Styled
JiashuHarryHuang Sep 16, 2023
f63934e
Finish updating NFC points while checking in user
JiashuHarryHuang Sep 16, 2023
f7e46ad
Display User's current NFC points
JiashuHarryHuang Sep 16, 2023
a74e218
Isolate Registration Logo out as a component and removed it from hack…
JiashuHarryHuang Sep 23, 2023
05c78ce
Finish drawing the layout
JiashuHarryHuang Sep 23, 2023
d25eb38
Add logo
JiashuHarryHuang Sep 30, 2023
fd93631
Header done for first half
JiashuHarryHuang Sep 30, 2023
469b117
Small adjustment
JiashuHarryHuang Sep 30, 2023
20b2d7f
Add mobile responsiveness and a big box wrapping entire portal
JiashuHarryHuang Sep 30, 2023
483737c
Basic structure for middle box
JiashuHarryHuang Oct 1, 2023
e28bd7e
Run Prettier
JiashuHarryHuang Oct 1, 2023
939e94a
Header mostly done
JiashuHarryHuang Oct 3, 2023
dce4e3f
Run Prettier
JiashuHarryHuang Oct 3, 2023
6669cce
Removed hacking started environment variable
JiashuHarryHuang Oct 4, 2023
949a41b
fix codeql vuln and refactor api
zineanteoh Oct 6, 2023
87a0a3e
remove package-lock
zineanteoh Oct 6, 2023
c7cbdb0
add todo comment
zineanteoh Oct 6, 2023
61a922b
remove unused variable
zineanteoh Oct 6, 2023
35909f5
Remove the moment()
JiashuHarryHuang Oct 7, 2023
e5ba5bb
Merge branch 'main' into hacker-nfc-team-features
JiashuHarryHuang Oct 7, 2023
0c3b8a7
Successfully get the settings
JiashuHarryHuang Oct 7, 2023
c1e69f1
Add the curDate
JiashuHarryHuang Oct 7, 2023
9916383
Merge branch 'main' into hacker-nfc-team-features
JiashuHarryHuang Oct 7, 2023
a3e4c4c
Use Date.parse to parse string
JiashuHarryHuang Oct 7, 2023
7148ed5
Merge branch 'main' into hacker-nfc-team-features
JiashuHarryHuang Oct 11, 2023
485976b
Conditionally render hacker portal
JiashuHarryHuang Oct 11, 2023
7576465
Use bulk write to optimize bulk update
JiashuHarryHuang Oct 11, 2023
30f105b
fix yarn.lock
zineanteoh Oct 11, 2023
d9fa449
remove unused class
zineanteoh Oct 11, 2023
2fa787e
remove unused class
zineanteoh Oct 11, 2023
4f9439a
add dev condition, fix css, fix codeql, add TODOs
zineanteoh Oct 11, 2023
f122604
refactor codebase relating to antd notification
zineanteoh Oct 11, 2023
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
224 changes: 140 additions & 84 deletions components/Organizer/EventsTab/EventsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,108 @@
import { Button, Input, InputRef, Modal, notification, Table } from 'antd';
import { Button, Input, InputRef, Modal, notification, Table, InputNumber } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { useEffect, useRef, useState } from 'react';
import { EventCountData, EventData } from '../../../types/database';
import { RequestType, useCustomSWR } from '../../../utils/request-utils';
import { mutate } from 'swr';
import { ObjectId } from 'mongoose';
import { handleSubmitFailure, handleSubmitSuccess } from '../../../lib/helpers';

interface EventDisplay extends EventData {
setCurEvent: (open: EventDisplay) => void;
}

const columns: ColumnsType<EventDisplay> = [
{
title: 'Day',
dataIndex: 'startTime',
key: 'day',
render: (startTime: string) => {
let date = new Date(startTime).toDateString();
// TODO use a date format string instead for clarity
date = date.substring(0, date.length - 5);
return date;
export default function Events() {
const [curEvent, setCurEvent] = useState<EventDisplay | null>(null);
const [events, setEvents] = useState<EventDisplay[]>([]);
const [nfcId, setNfcId] = useState<string>('');
const [loading, setLoading] = useState(false);
const [showSaveButton, setShowSaveButton] = useState(false);

const columns: ColumnsType<EventDisplay> = [
{
title: 'Day',
dataIndex: 'startTime',
key: 'day',
render: (startTime: string) => {
let date = new Date(startTime).toDateString();
// TODO use a date format string instead for clarity
date = date.substring(0, date.length - 5);
return date;
},
width: '15%',
},
width: '15%',
},
{
title: 'Time',
dataIndex: 'startTime',
key: 'time',
render: (startTime: string, record: EventDisplay) => {
// TODO use a date format string instead for clarity
const start = new Date(startTime);
const end = new Date(record.endTime.toString());
const startHours = start.getHours() % 12 || 12;
const startMinutes = start.getMinutes();
const endHours = end.getHours() % 12 || 12;
const endMinutes = end.getMinutes();
const startAmPm = start.getHours() >= 12 ? 'PM' : 'AM';
const endAmPm = end.getHours() >= 12 ? 'PM' : 'AM';
return (
<span>
{startHours}:{startMinutes < 10 ? `0${startMinutes}` : startMinutes} {startAmPm} - {endHours}:
{endMinutes < 10 ? `0${endMinutes}` : endMinutes} {endAmPm}
</span>
);
{
title: 'Time',
dataIndex: 'startTime',
key: 'time',
render: (startTime: string, record: EventDisplay) => {
// TODO use a date format string instead for clarity
const start = new Date(startTime);
const end = new Date(record.endTime.toString());
const startHours = start.getHours() % 12 || 12;
const startMinutes = start.getMinutes();
const endHours = end.getHours() % 12 || 12;
const endMinutes = end.getMinutes();
const startAmPm = start.getHours() >= 12 ? 'PM' : 'AM';
const endAmPm = end.getHours() >= 12 ? 'PM' : 'AM';
return (
<span>
{startHours}:{startMinutes < 10 ? `0${startMinutes}` : startMinutes} {startAmPm} - {endHours}:
{endMinutes < 10 ? `0${endMinutes}` : endMinutes} {endAmPm}
</span>
);
},
sorter: (a: EventDisplay, b: EventDisplay) => {
const aStart = new Date(a.startTime.toString());
const bStart = new Date(b.startTime.toString());
return aStart.getTime() - bStart.getTime();
},
sortOrder: 'ascend',
width: '15%',
},
sorter: (a: EventDisplay, b: EventDisplay) => {
const aStart = new Date(a.startTime.toString());
const bStart = new Date(b.startTime.toString());
return aStart.getTime() - bStart.getTime();
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '30%',
},
sortOrder: 'ascend',
width: '15%',
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '40%',
},
{
title: 'Count',
dataIndex: 'count',
key: 'count',
width: '10%',
render: (count: number) => {
return <span>{count ? count : 0}</span>;
{
title: 'NFC Points',
dataIndex: 'nfcPoints',
key: 'nfcPoints',
width: '10%',
render: (nfcPoints: number, record: EventDisplay, index: number) => {
return (
<>
<InputNumber
defaultValue={nfcPoints ? nfcPoints : 0}
onChange={(newNfcPoints: number | null) =>
handleNFCPointChanges(record._id, newNfcPoints || 0)
}
/>
</>
);
},
},
},
{
title: 'Check In',
dataIndex: 'checkIn',
key: 'checkIn',
render: (_: any, record: EventDisplay) => {
return <Button onClick={() => record.setCurEvent(record)}>Check In</Button>;
{
title: 'Count',
dataIndex: 'count',
key: 'count',
width: '10%',
render: (count: number) => {
return <span>{count ? count : 0}</span>;
},
},
width: '20%',
},
];

const EventsTab = () => {
const [curEvent, setCurEvent] = useState<EventDisplay | null>(null);
const [events, setEvents] = useState<EventDisplay[]>([]);
const [nfcId, setNfcId] = useState<string>('');
const [loading, setLoading] = useState(false);
{
title: 'Check In',
dataIndex: 'checkIn',
key: 'checkIn',
render: (_: any, record: EventDisplay) => {
return <Button onClick={() => record.setCurEvent(record)}>Check In</Button>;
},
width: '20%',
},
];

const input = useRef<InputRef>(null);

Expand Down Expand Up @@ -131,6 +152,19 @@ const EventsTab = () => {
.finally(() => setLoading(false));
};

const handleNFCPointChanges = (eventId: ObjectId, nfcPoints: number) => {
// Deep copy and update array
const newEvents = JSON.parse(JSON.stringify(events));
newEvents.map((event: EventDisplay) => {
if (event._id === eventId) event.nfcPoints = nfcPoints;
return event;
});
setEvents(newEvents);

// Show the save button
setShowSaveButton(true);
};

const handleCheckIn = async () => {
const response = await fetch('/api/event-checkin', {
method: 'POST',
Expand All @@ -140,24 +174,36 @@ const EventsTab = () => {
body: JSON.stringify({
nfcId,
eventId: curEvent?._id,
nfcPoints: curEvent?.nfcPoints,
}),
});
if (response.ok) {
notification['success']({
message: `Successfully checked in!`,
placement: 'bottomRight',
});
handleSubmitSuccess('Successfully checked in!');
} else {
notification['error']({
message: 'Failed to check-in hacker',
description: await response.text(),
placement: 'bottomRight',
});
handleSubmitFailure('Failed to check in user!');
}
setNfcId('');
refreshData();
};

const handleSaveChanges = async () => {
// Send POST request
const response = await fetch('/api/event-save-changes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(events),
});

// Display the success/failure messages
if (response.ok) {
handleSubmitSuccess('Successfully saved changes!');
} else {
handleSubmitFailure('Failed to save changes!');
}
};

const handleCancel = () => {
setCurEvent(null);
setNfcId('');
Expand All @@ -169,11 +215,23 @@ const EventsTab = () => {
refreshData();
};

const ButtonBoxStyle = {
display: 'flex',
justifyContent: 'space-between',
};
return (
<>
<Button loading={loading} onClick={syncCalendar}>
Sync Calendar Events
</Button>
<div style={ButtonBoxStyle}>
<Button loading={loading} onClick={syncCalendar}>
Sync Calendar Events
</Button>
{showSaveButton && (
<Button loading={loading} onClick={handleSaveChanges}>
Save Changes
</Button>
)}
</div>

<br />
<br />
<Table sticky bordered dataSource={events} columns={columns} />
Expand All @@ -197,6 +255,4 @@ const EventsTab = () => {
</Modal>
</>
);
};

export default EventsTab;
}
Loading
Loading