From 7caa3c9d8071199fa81be93d8c2b59a490531871 Mon Sep 17 00:00:00 2001 From: Yun Kai Peng Date: Sat, 1 Aug 2020 11:49:07 -0400 Subject: [PATCH 1/2] refactor: extract page elements and use hooks for search --- src/features/Search/Filters.tsx | 98 ++++---- src/features/Search/HackerSelect.tsx | 74 +++---- src/features/Search/Search.tsx | 319 ++++++++++++--------------- src/pages/Admin/Search.tsx | 10 +- src/pages/Sponsor/Search.tsx | 9 +- 5 files changed, 241 insertions(+), 269 deletions(-) diff --git a/src/features/Search/Filters.tsx b/src/features/Search/Filters.tsx index 55ebec18b..18cc9508c 100644 --- a/src/features/Search/Filters.tsx +++ b/src/features/Search/Filters.tsx @@ -24,47 +24,26 @@ interface IFilterProps { loading: boolean; } -class FilterComponent extends React.Component { - constructor(props: IFilterProps) { - super(props); - this.handleSubmit = this.handleSubmit.bind(this); - this.renderFormik = this.renderFormik.bind(this); - this.resetForm = this.resetForm.bind(this); - } - public render() { - return ( - - - - ); - } - private parseInitialValues(initFilters: ISearchParameter[]) { +const FilterComponent: React.FC = (props) => { + const parseInitialValues = (initFilters: ISearchParameter[]) => { const initVals = { - school: this.searchParam2List('application.general.school', initFilters), - gradYear: this.searchParam2List( + school: searchParam2List('application.general.school', initFilters), + gradYear: searchParam2List( 'application.general.graduationYear', initFilters ), - degree: this.searchParam2List('application.general.degree', initFilters), - status: this.searchParam2List('status', initFilters), - skills: this.searchParam2List( - 'application.shortAnswer.skills', - initFilters - ), - jobInterest: this.searchParam2List( + degree: searchParam2List('application.general.degree', initFilters), + status: searchParam2List('status', initFilters), + skills: searchParam2List('application.shortAnswer.skills', initFilters), + jobInterest: searchParam2List( 'application.general.jobInterest', initFilters ), }; return initVals; - } + }; - private renderFormik(fp: FormikProps) { + const renderFormik = (fp: FormikProps) => { return (
{ - )} - {account && isSponsor(account) && ( - - )} - - - - - - - - - - - - - - - - - - ); - } - public async componentDidMount() { - const account = (await Account.getSelf()).data.data; - this.setState({ account }); + if (isSponsor(account)) { + const sponsor = (await Sponsor.getSelf()).data.data; + setSponsor(sponsor); + } + await triggerSearch(); + })(); + }, []); - if (isSponsor(account)) { - const sponsor = (await Sponsor.getSelf()).data.data; - this.setState({ sponsor }); - } - await this.triggerSearch(); - } - private getSearchFromQuery(): ISearchParameter[] { + const getSearchFromQuery = () => { const search = getValueFromQuery('q'); if (!search) { return []; @@ -167,14 +79,14 @@ class SearchContainer extends React.Component<{}, ISearchState> { } catch (e) { return []; } - } + }; - private getSearchBarFromQuery(): string { + const getSearchBarFromQuery = () => { const search = getValueFromQuery('searchBar'); return search ? decodeURIComponent(search) : ''; - } + }; - private downloadData(): void { + const downloadData = () => { const headers = [ { label: CONSTANTS.FIRST_NAME_LABEL, key: 'accountId.firstName' }, { label: CONSTANTS.LAST_NAME_LABEL, key: 'accountId.lastName' }, @@ -195,10 +107,7 @@ class SearchContainer extends React.Component<{}, ISearchState> { }, ]; // Return all fields for admin, and only subset for sponsors - if ( - this.state.account && - this.state.account.accountType === UserType.STAFF - ) { + if (account && account.accountType === UserType.STAFF) { headers.push({ label: 'Resume', key: 'application.general.URL.resume' }); headers.push({ label: 'Github', key: 'application.general.URL.github' }); headers.push({ @@ -264,7 +173,7 @@ class SearchContainer extends React.Component<{}, ISearchState> { tempHeaders.push(header.label); }); const csvData: string[] = [tempHeaders.join('\t')]; - this.filter().forEach((result) => { + filter().forEach((result) => { if (result.selected) { const row: string[] = []; headers.forEach((header) => { @@ -281,11 +190,10 @@ class SearchContainer extends React.Component<{}, ISearchState> { } }); fileDownload(csvData.join('\n'), 'hackerData.tsv', 'text/tsv'); - } + }; - private async triggerSearch(): Promise { - this.setState({ loading: true }); - const { model, query } = this.state; + const triggerSearch = async (): Promise => { + setLoading(true); try { const response = await Search.search(model, query, { expand: true, @@ -297,32 +205,31 @@ class SearchContainer extends React.Component<{}, ISearchState> { hacker: v, })) : []; - this.setState({ results: tableData, loading: false }); + setResults(tableData); + setLoading(false); } catch (e) { ValidationErrorGenerator(e.data); - this.setState({ loading: false }); + setLoading(false); } - } - private onResetForm() { - this.setState({ query: [] }); - this.updateQueryURL([], this.state.searchBar); - } + }; + const onResetForm = () => { + setQuery([]); + updateQueryURL([], searchBar); + }; - private onFilterChange(newFilters: ISearchParameter[]) { - this.setState({ - query: newFilters, - }); - this.updateQueryURL(newFilters, this.state.searchBar); - this.triggerSearch(); - } + const onFilterChange = (newFilters: ISearchParameter[]) => { + setQuery(newFilters); + updateQueryURL(newFilters, searchBar); + triggerSearch(); + }; - private onSearchBarChanged(e: any) { + const onSearchBarChanged = (e: any) => { const searchBar = e.target.value; - this.setState({ searchBar }); - this.updateQueryURL(this.state.query, searchBar); - } + setSearchBar(searchBar); + updateQueryURL(query, searchBar); + }; - private updateQueryURL(filters: ISearchParameter[], searchBar: string) { + const updateQueryURL = (filters: ISearchParameter[], searchBar: string) => { const newSearch = `?q=${encodeURIComponent( JSON.stringify(filters) )}&searchBar=${encodeURIComponent(searchBar)}`; @@ -331,11 +238,10 @@ class SearchContainer extends React.Component<{}, ISearchState> { '', window.location.href.split('?')[0] + newSearch ); - } + }; - private filter() { - const { sponsor, viewSaved, results } = this.state; - const searchBar = this.state.searchBar.toLowerCase(); + const filter = () => { + const currSearchBar = searchBar.toLowerCase(); return results.filter(({ hacker }) => { const { accountId } = hacker; let foundAcct; @@ -345,29 +251,31 @@ class SearchContainer extends React.Component<{}, ISearchState> { account.lastName }`.toLowerCase(); foundAcct = - fullName.includes(searchBar) || - account.email.toLowerCase().includes(searchBar) || - account.phoneNumber.toString().includes(searchBar) || - account.gender.toLowerCase().includes(searchBar) || - (account._id && account._id.includes(searchBar)); + fullName.includes(currSearchBar) || + account.email.toLowerCase().includes(currSearchBar) || + account.phoneNumber.toString().includes(currSearchBar) || + account.gender.toLowerCase().includes(currSearchBar) || + (account._id && account._id.includes(currSearchBar)); } else { - foundAcct = accountId.includes(searchBar); + foundAcct = accountId.includes(currSearchBar); } const foundHacker = - hacker.id.includes(searchBar) || - hacker.application.general.school.includes(searchBar) || - hacker.application.general.degree.includes(searchBar) || - hacker.application.general.fieldOfStudy.includes(searchBar) || + hacker.id.includes(currSearchBar) || + hacker.application.general.school.includes(currSearchBar) || + hacker.application.general.degree.includes(currSearchBar) || + hacker.application.general.fieldOfStudy.includes(currSearchBar) || hacker.application.general.graduationYear .toString() - .includes(searchBar) || - hacker.application.general.jobInterest.includes(searchBar) || - hacker.status.includes(searchBar) || - hacker.application.shortAnswer.question1.includes(searchBar) || - hacker.application.shortAnswer.question2.includes(searchBar) || - hacker.application.accommodation.shirtSize.includes(searchBar) || + .includes(currSearchBar) || + hacker.application.general.jobInterest.includes(currSearchBar) || + hacker.status.includes(currSearchBar) || + hacker.application.shortAnswer.question1.includes(currSearchBar) || + hacker.application.shortAnswer.question2.includes(currSearchBar) || + hacker.application.accommodation.shirtSize.includes(currSearchBar) || (hacker.application.shortAnswer.skills && - hacker.application.shortAnswer.skills.toString().includes(searchBar)); + hacker.application.shortAnswer.skills + .toString() + .includes(currSearchBar)); const isSavedBySponsorIfToggled = !viewSaved || @@ -375,16 +283,83 @@ class SearchContainer extends React.Component<{}, ISearchState> { return (foundAcct || foundHacker) && isSavedBySponsorIfToggled; }); - } + }; - private toggleSaved = async () => { + const toggleSaved = async () => { // Resets the sponsor if they made changes to their saved hackers const sponsor = (await Sponsor.getSelf()).data.data; - const { viewSaved } = this.state; if (sponsor) { - this.setState({ sponsor, viewSaved: !viewSaved }); + // this.setState({ sponsor, viewSaved: !viewSaved }); + setSponsor(sponsor); + setViewSaved(!viewSaved); } }; -} + + return ( + + + + +

+ Search +

+
+ + + +

+ Hackers +

+
+ + + + + {account && account.accountType === UserType.STAFF && ( + + )} + {account && isSponsor(account) && ( + + )} + + +
+
+
+
+ + + + + + + + + + +
+ ); +}; export default withContext(WithToasterContainer(SearchContainer)); diff --git a/src/pages/Admin/Search.tsx b/src/pages/Admin/Search.tsx index 44637771c..70e1db5a7 100644 --- a/src/pages/Admin/Search.tsx +++ b/src/pages/Admin/Search.tsx @@ -1,9 +1,15 @@ import React from 'react'; import SearchContainer from '../../features/Search/Search'; - +import Helmet from 'react-helmet'; +import { HACKATHON_NAME } from '../../config'; const AdminSearchPage: React.FC = () => ( - +
+ + Search | {HACKATHON_NAME} + + +
); export default AdminSearchPage; diff --git a/src/pages/Sponsor/Search.tsx b/src/pages/Sponsor/Search.tsx index 3548bdba9..295d8e73a 100644 --- a/src/pages/Sponsor/Search.tsx +++ b/src/pages/Sponsor/Search.tsx @@ -1,9 +1,16 @@ import React from 'react'; import SearchContainer from '../../features/Search/Search'; +import Helmet from 'react-helmet'; +import { HACKATHON_NAME } from '../../config'; const SponsorSearchPage: React.FC = () => ( - +
+ + Search | {HACKATHON_NAME} + + +
); export default SponsorSearchPage; From d912470253e078894eee50c93d1b3cb1de97fdab Mon Sep 17 00:00:00 2001 From: Yun Kai Peng Date: Fri, 7 Aug 2020 20:29:52 -0400 Subject: [PATCH 2/2] refactor: add comments to search and hackerselect --- src/features/Search/HackerSelect.tsx | 6 ++++++ src/features/Search/Search.tsx | 17 ++++++++++++++--- src/pages/Admin/Search.tsx | 2 +- src/pages/Sponsor/Search.tsx | 3 +-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/features/Search/HackerSelect.tsx b/src/features/Search/HackerSelect.tsx index 60bb411f5..bb9343df7 100644 --- a/src/features/Search/HackerSelect.tsx +++ b/src/features/Search/HackerSelect.tsx @@ -7,11 +7,15 @@ interface IProps { hackerId: string; } +// push the selected hacker to the list of selected hacker for the search const HackerSelect: React.FC = (props) => { + // react state.context is assigned to the variable "contet" const context = useContext(NomineeContext); + // state to only change one hacker selection state at a time const [isChanging, setIsChanging] = useState(false); + // function to push selected hacker to the search list const handleChange = async (event: React.ChangeEvent) => { const isChecked = event.target.checked; const { hackerId } = props; @@ -21,9 +25,11 @@ const HackerSelect: React.FC = (props) => { } if (context) { + // push them to the selected list if (isChecked) { context.nominees.push(hackerId); } else { + // remove selected hacker from list context.nominees = context.nominees.filter( (n: string) => n !== hackerId ); diff --git a/src/features/Search/Search.tsx b/src/features/Search/Search.tsx index b93640c31..04b8a8ee1 100644 --- a/src/features/Search/Search.tsx +++ b/src/features/Search/Search.tsx @@ -20,8 +20,8 @@ import theme from '../../shared/Styles/theme'; import { getNestedAttr, getValueFromQuery, isSponsor } from '../../util'; import withContext from '../../shared/HOC/withContext'; -import { FilterComponent } from '../../features/Search/Filters'; -import { ResultsTable } from '../../features/Search/ResultsTable'; +import { FilterComponent } from './Filters'; +import { ResultsTable } from './ResultsTable'; interface IResult { /** @@ -33,15 +33,23 @@ interface IResult { } const SearchContainer: React.FC = () => { + // search bar for hacker const [model] = useState('hacker'); + // query tused to find const [query, setQuery] = useState([]); + // response from query const [results, setResults] = useState([]); + // text in search bar const [searchBar, setSearchBar] = useState(''); + // state of loading const [loading, setLoading] = useState(false); + // state of is saved const [viewSaved, setViewSaved] = useState(false); + // determine if it as an admin/sponsor it used by admin or a sponsor const [account, setAccount] = useState(null); const [sponsor, setSponsor] = useState(null); + // same as componentDidMount, checks if it is an admin or sponsor after component mounted useEffect(() => { (async () => { setQuery(getSearchFromQuery()); @@ -57,6 +65,7 @@ const SearchContainer: React.FC = () => { })(); }, []); + // return the parameters of the search const getSearchFromQuery = () => { const search = getValueFromQuery('q'); if (!search) { @@ -86,6 +95,7 @@ const SearchContainer: React.FC = () => { return search ? decodeURIComponent(search) : ''; }; + // function to download the candidates file const downloadData = () => { const headers = [ { label: CONSTANTS.FIRST_NAME_LABEL, key: 'accountId.firstName' }, @@ -192,6 +202,7 @@ const SearchContainer: React.FC = () => { fileDownload(csvData.join('\n'), 'hackerData.tsv', 'text/tsv'); }; + // function to search based on query and type of user (hacker/admin/sponsor) const triggerSearch = async (): Promise => { setLoading(true); try { @@ -240,6 +251,7 @@ const SearchContainer: React.FC = () => { ); }; + // filter to read the search bar to retrieve useful info const filter = () => { const currSearchBar = searchBar.toLowerCase(); return results.filter(({ hacker }) => { @@ -289,7 +301,6 @@ const SearchContainer: React.FC = () => { // Resets the sponsor if they made changes to their saved hackers const sponsor = (await Sponsor.getSelf()).data.data; if (sponsor) { - // this.setState({ sponsor, viewSaved: !viewSaved }); setSponsor(sponsor); setViewSaved(!viewSaved); } diff --git a/src/pages/Admin/Search.tsx b/src/pages/Admin/Search.tsx index 70e1db5a7..5ddd44341 100644 --- a/src/pages/Admin/Search.tsx +++ b/src/pages/Admin/Search.tsx @@ -1,7 +1,7 @@ import React from 'react'; +import Helmet from 'react-helmet'; import SearchContainer from '../../features/Search/Search'; -import Helmet from 'react-helmet'; import { HACKATHON_NAME } from '../../config'; const AdminSearchPage: React.FC = () => (
diff --git a/src/pages/Sponsor/Search.tsx b/src/pages/Sponsor/Search.tsx index 295d8e73a..cd38c456d 100644 --- a/src/pages/Sponsor/Search.tsx +++ b/src/pages/Sponsor/Search.tsx @@ -1,9 +1,8 @@ import React from 'react'; +import Helmet from 'react-helmet'; import SearchContainer from '../../features/Search/Search'; -import Helmet from 'react-helmet'; import { HACKATHON_NAME } from '../../config'; - const SponsorSearchPage: React.FC = () => (