Skip to content

Commit

Permalink
Add tabs to judging page (#509)
Browse files Browse the repository at this point in the history
* improve the readablity for judging page; create assigned and all teams tab; fix the bug in organizer's scoreboard

* fix the bugs in model and database; add form to all teams tag in judging page

* fix bug in confirm judging sessions api

* fix bug in types/database.ts

* fix bugs of changing database and model in script
  • Loading branch information
JihengLi authored Sep 25, 2024
1 parent 2a9d199 commit 3f633fe
Show file tree
Hide file tree
Showing 18 changed files with 708 additions and 373 deletions.
17 changes: 4 additions & 13 deletions components/Organizer/JudgingTab/Scoreboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ export default function Scoreboard(props: AllScoresProps) {
setSearchText('');
};

const handleSearch = (
selectedKeys: string[],
confirm: (param?: FilterConfirmProps) => void,
dataIndex: string,
closeDropDown: boolean
) => {
confirm({ closeDropdown: closeDropDown });
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};

const handleReset = (clearFilters: () => void) => {
clearFilters();
setSearchText('');
Expand All @@ -67,9 +56,11 @@ export default function Scoreboard(props: AllScoresProps) {
value={selectedKeys[0]}
onChange={e => {
setSelectedKeys(e.target.value ? [e.target.value] : []);
handleSearch(selectedKeys as string[], confirm, dataIndex, false);
confirm({ closeDropdown: false });
setSearchText(e.target.value);
setSearchedColumn(dataIndex);
}}
onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex, true)}
onPressEnter={() => confirm({ closeDropdown: true })}
style={{ marginBottom: 8, display: 'block' }}
/>
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { Space, Table, Collapse, Tag, Switch, Button, notification, Upload, Spin, theme, Radio } from 'antd';
import React, { useContext, useMemo, useState } from 'react';
import { Space, Table, Tag } from 'antd';
import React, { useMemo, useState } from 'react';
import { DateTime } from 'luxon';
import Link from 'next/link';
import { JudgingSessionData } from '../../types/database';
import { User } from 'next-auth';
import { ThemeContext, getAccentColor } from '../../theme/themeProvider';
import { JudgingSessionData } from '../../../types/database';

interface ScheduleProps {
data: JudgingSessionData[];
Expand Down Expand Up @@ -181,101 +178,3 @@ export default function OrganizerSchedule(props: ScheduleProps) {
</div>
);
}

export function JudgeSchedule({ data, handleChange }: ScheduleProps) {
const [isJudged, setIsJudged] = useState(false);
const { accentColor, baseTheme } = useContext(ThemeContext);

const columns = [
{
title: 'Table',
dataIndex: 'table',
key: 'table',
width: '10%',
render: (locationNum: number) => locationNum,
},
{
title: 'Project',
dataIndex: 'project',
key: 'project',
width: '40%',
render: ({ name, link }: { name: string; link: URL }) => (
<>
<td>{name}</td>
<Link href={link} passHref>
<a
style={{ color: getAccentColor(accentColor, baseTheme), textDecoration: 'underline' }}
target="_blank">
Devpost
</a>
</Link>
</>
),
},
{
title: 'Team Members',
dataIndex: 'teamMembers',
key: 'teamMembers',
width: '40%',
render: (members: User[]) => members.map(member => <Tag key={member.id}>{member.name}</Tag>),
},
{
title: 'Judgement State',
dataIndex: 'haveJudged',
key: 'haveJudged',
width: '10%',
render: (haveJudged: []) => <Tag>{haveJudged ? 'Judged' : 'Without Judgement'}</Tag>,
},
];

const dataSource = data
.filter(item => (isJudged ? item.haveJudged : !item.haveJudged))
.map(item => ({
table: item.team.locationNum,
project: { name: item.team.name, link: new URL(item.team.devpost) },
teamMembers: item.team.members,
teamId: item.team._id,
haveJudged: item.haveJudged,
}));

const handleRowClick = (record: any) => {
handleChange(record.teamId);
};

return (
<Table
locale={{
emptyText: (
<div style={{ paddingTop: '50px', paddingBottom: '50px' }}>
<h3>
{data.length == 0
? 'Stay tuned! You will see your teams that you will judge soon!'
: isJudged
? "You haven't started judging yet."
: "Hurraaaaarrgh! You're off duty!"}
</h3>
</div>
),
}}
dataSource={dataSource}
columns={columns}
pagination={false}
bordered
scroll={{ x: true }}
summary={_ => (
<Table.Summary fixed={true}>
<Table.Summary.Row>
<Table.Summary.Cell index={0} colSpan={6}>
<Button type="primary" onClick={() => setIsJudged(!isJudged)} style={{ marginLeft: 8 }}>
{isJudged ? 'Judged' : 'Without Judgement'}
</Button>
</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
)}
onRow={record => ({
onClick: () => handleRowClick(record),
})}
/>
);
}
2 changes: 1 addition & 1 deletion components/Organizer/ScheduleTab/ScheduleTab.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button, InputNumber, Slider, Row, Col } from 'antd';
import { SetStateAction, useContext, useEffect, useState } from 'react';
import { matchTeams, handleConfirmSchedule } from '../../../utils/organizer-utils';
import OrganizerSchedule, { generateTimes } from '../../judges/schedule';
import OrganizerSchedule, { generateTimes } from './OrganizerSchedule';
import { ResponseError, JudgingSessionData, UserData, TeamData, HackathonSettingsData } from '../../../types/database';
import Title from 'antd/lib/typography/Title';
import { RequestType, useCustomSWR } from '../../../utils/request-utils';
Expand Down
148 changes: 148 additions & 0 deletions components/judges/AllTeamsTab/AllTeamsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { FilterConfirmProps, FilterValue, SorterResult } from 'antd/es/table/interface';
import { useContext, useRef, useState } from 'react';
import { TeamData } from '../../../types/database';
import { Button, Input, InputRef, Table } from 'antd';
import { getAccentColor, ThemeContext } from '../../../theme/themeProvider';
import { SearchOutlined } from '@ant-design/icons';
import Highlighter from 'react-highlight-words';
import Link from 'next/link';

interface AllTeamsProps {
teamsData: TeamData[];
teamId: string;
handleTeamChange: (teamId: string) => void;
}

export const AllTeamsForm = ({ teamsData, teamId, handleTeamChange }: AllTeamsProps) => {
const [filteredInfo, setFilteredInfo] = useState<Record<string, FilterValue | null>>({});
const [sortedInfo, setSortedInfo] = useState<SorterResult<TeamData>>({});
const [searchedColumn, setSearchedColumn] = useState('');
const [searchText, setSearchText] = useState('');
const searchInput = useRef<InputRef>(null);
const { accentColor, baseTheme } = useContext(ThemeContext);

const handleChange = (pagination: any, filters: any, sorter: any) => {
setSortedInfo(sorter as SorterResult<TeamData>);
setFilteredInfo(filters);
};

const handleReset = (clearFilters: () => void) => {
clearFilters();
setSearchText('');
};

const getColumnSearchProps = (dataIndex: string) => ({
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}: {
setSelectedKeys: (selectedKeys: React.Key[]) => void;
selectedKeys: React.Key[];
confirm: (param?: FilterConfirmProps) => void;
clearFilters: () => void;
}) => (
<div style={{ padding: 8 }}>
<Input
ref={searchInput}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => {
setSelectedKeys(e.target.value ? [e.target.value] : []);
confirm({ closeDropdown: false });
setSearchText(e.target.value);
setSearchedColumn(dataIndex);
}}
onPressEnter={() => confirm({ closeDropdown: true })}
style={{ marginBottom: 8, display: 'block' }}
/>
<Button
onClick={() => {
clearFilters && handleReset(clearFilters);
confirm({ closeDropdown: false });
}}
style={{ width: '100%' }}>
Reset
</Button>
</div>
),
filterIcon: (filtered: boolean) => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value: string | number | boolean, record: any): boolean => {
const recordValue = dataIndex in record ? record[dataIndex] : record.application?.[dataIndex];
if (recordValue === undefined || recordValue === null) {
return false;
}
return recordValue.toString().toLowerCase().includes(value.toString().toLowerCase());
},
filteredValue:
(dataIndex in filteredInfo ? filteredInfo[dataIndex] : filteredInfo['application.' + dataIndex]) || null,
onFilterDropdownOpenChange: (open: boolean) => {
if (open) {
setTimeout(() => searchInput.current?.select(), 100);
}
},
render: (text: string) =>
searchedColumn === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text?.toString() ?? ''}
/>
) : (
text
),
});

const columns = [
{
title: 'Table',
dataIndex: 'locationNum',
key: 'locationNum',
width: '20%',
sorter: (a: any, b: any) => a.locationNum - b.locationNum,
sortOrder: sortedInfo.columnKey === 'locationNum' ? sortedInfo.order : null,
},
{
title: 'Team',
dataIndex: 'name',
key: 'name',
width: '40%',
...getColumnSearchProps('name'),
},
{
title: 'Devpost',
dataIndex: 'devpost',
key: 'devpost',
width: '40%',
render: (link: URL) => {
return (
<>
<Link href={link} passHref>
<a
style={{ color: getAccentColor(accentColor, baseTheme), textDecoration: 'underline' }}
target="_blank">
{link}
</a>
</Link>
</>
);
},
},
];

return (
<>
<Table
dataSource={teamsData}
columns={columns}
onChange={handleChange}
sortDirections={['descend', 'ascend']}
onRow={record => ({
onClick: () => handleTeamChange(teamId !== String(record._id) ? String(record._id) : ''),
})}
/>
</>
);
};
Loading

0 comments on commit 3f633fe

Please sign in to comment.