Skip to content

Commit

Permalink
fix the bugs in model and database; add form to all teams tag in judg…
Browse files Browse the repository at this point in the history
…ing page
  • Loading branch information
JihengLi committed Sep 24, 2024
1 parent 6d499a4 commit 1455b04
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 139 deletions.
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) : ''),
})}
/>
</>
);
};
220 changes: 90 additions & 130 deletions components/judges/AllTeamsTab/AllTeamsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
import useSWR from 'swr';
import useSWR, { useSWRConfig } from 'swr';
import { ResponseError, TeamData } from '../../../types/database';
import { Button, Input, InputRef, Table } from 'antd';
import Link from 'next/link';
import { getAccentColor, ThemeContext } from '../../../theme/themeProvider';
import { useContext, useRef, useState } from 'react';
import { FilterConfirmProps, FilterValue, SorterResult } from 'antd/es/table/interface';
import { SearchOutlined } from '@ant-design/icons';
import Highlighter from 'react-highlight-words';
import { AllTeamsForm } from './AllTeamsForm';
import { Dispatch, SetStateAction, useState } from 'react';
import { JudgingFormFields } from '../../../types/client';
import JudgingForm from '../AssignedTab/JudgingForm';
import { ScopedMutator } from 'swr/dist/types';
import { handleSubmitFailure, handleSubmitSuccess } from '../../../lib/helpers';

async function handleSubmit(
formData: JudgingFormFields,
mutate: ScopedMutator,
teamId: string,
isNewForm: boolean,
setIsNewForm: React.Dispatch<React.SetStateAction<boolean>>
) {
const res = await fetch(`/api/judging-form?id=${teamId}`, {
method: isNewForm ? 'POST' : 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});

if (res.ok) {
mutate('/api/teams');
mutate('/api/judging-form');
handleSubmitSuccess(`Successfully ${isNewForm ? 'submitted' : 'updated'}!`);
setIsNewForm(false);
} else {
handleSubmitFailure(await res.text());
}
}

export const AllTeamsTab = () => {
const [teamID, setTeamID] = useState('');
const [isNewForm, setIsNewForm] = useState(false);
const { mutate } = useSWRConfig();

// Get data for all teams
const { data: teamsData, error: teamsError } = useSWR('/api/teams-all', async url => {
const res = await fetch(url, { method: 'GET' });
Expand All @@ -20,137 +48,69 @@ export const AllTeamsTab = () => {
return (await res.json()) as TeamData[];
});

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);
// Get data for form component, formData will be false if teamId is not yet set.
const { data: formData, error: formError } = useSWR(
() => (teamID ? ['/api/judging-form', teamID] : null),
async (url: any, id: any) => {
const res = await fetch(`${url}?id=${id}`, { method: 'GET' });
if (!res.ok) {
if (res.status === 404) {
const emptyJudgeForm: JudgingFormFields = {
technicalAbility: 0,
creativity: 0,
utility: 0,
presentation: 0,
wowFactor: 0,
comments: '',
feedback: '',
};
setIsNewForm(true);
return emptyJudgeForm;
}
const error = new Error('Failed to get form information.') as ResponseError;
error.status = res.status;
throw error;
}
setIsNewForm(false);
return (await res.json()) as JudgingFormFields;
}
);

const teams = teamsData?.map(x => ({ ...x, key: x._id })) || ([] as TeamData[]);
const currentTeam = teams.find(team => String(team._id) === teamID);

const handleChange = (pagination: any, filters: any, sorter: any) => {
setSortedInfo(sorter as SorterResult<TeamData>);
setFilteredInfo(filters);
const handleTeamChange: Dispatch<SetStateAction<string>> = e => {
setTeamID(e);
setTimeout(() => {
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
}, 200);
};

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 (
<>
{teamsError ? (
<div>{teamsError ? (teamsError as ResponseError).message : 'Failed to get data.'}</div>
<div>{(teamsError as ResponseError).message || 'Failed to get data.'}</div>
) : (
<Table
dataSource={teams}
columns={columns}
onChange={handleChange}
sortDirections={['descend', 'ascend']}
/>
<>
<AllTeamsForm teamsData={teams} handleTeamChange={handleTeamChange} teamId={teamID} />
{formData && (
<>
<p
style={{
fontSize: '1.5rem',
fontWeight: 'bold',
color: 'black',
}}>
{currentTeam?.name}
</p>
<JudgingForm
formData={formData}
isNewForm={isNewForm}
onSubmit={formData => handleSubmit(formData, mutate, teamID, isNewForm, setIsNewForm)}
/>
</>
)}
</>
)}
</>
);
Expand Down
Loading

0 comments on commit 1455b04

Please sign in to comment.