diff --git a/webclient/package.json b/webclient/package.json index 9ee9267..9ee5948 100644 --- a/webclient/package.json +++ b/webclient/package.json @@ -6,11 +6,13 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "abort-controller": "^3.0.0", "blueimp-load-image": "^5.13.0", "darkmode-js": "~1.5.5", "lodash": "^4.17.19", "moment": "~2.24.0", "moment-timezone": "~0.5.28", + "query-string": "^7.0.1", "react": "^16.13.1", "react-datetime": "~2.16.3", "react-dom": "^16.13.1", diff --git a/webclient/src/Members.js b/webclient/src/Members.js index 8ab8424..47a21c5 100644 --- a/webclient/src/Members.js +++ b/webclient/src/Members.js @@ -1,11 +1,13 @@ import React, { useState, useEffect, useReducer } from 'react'; -import { BrowserRouter as Router, Switch, Route, Link, useParams, useLocation } from 'react-router-dom'; +import { BrowserRouter as Router, Switch, Route, Link, useParams, useLocation, useHistory } from 'react-router-dom'; import './light.css'; import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Input, Item, Menu, Message, Segment, Table } from 'semantic-ui-react'; import { statusColor, isAdmin, isInstructor, BasicTable, staticUrl, requester } from './utils.js'; import { NotFound, PleaseLogin } from './Misc.js'; import { AdminMemberInfo, AdminMemberPause, AdminMemberForm, AdminMemberCards, AdminMemberTraining, AdminMemberCertifications } from './AdminMembers.js'; import { AdminMemberTransactions } from './AdminTransactions.js'; +import queryString from 'query-string'; +import AbortController from 'abort-controller'; const memberSorts = { recently_vetted: 'Recently Vetted', @@ -59,38 +61,52 @@ export function MembersDropdown(props) { ); }; -let searchCache = ''; let numShowCache = 20; export function Members(props) { + const history = useHistory(); const qs = useLocation().search; const params = new URLSearchParams(qs); const sort = params.get('sort') || 'recently_vetted'; + const search = params.get('q') || ''; const [response, setResponse] = useState(false); const [numShow, setNumShow] = useState(numShowCache); - const searchDefault = {seq: 0, q: searchCache}; - const [search, setSearch] = useState(searchDefault); + const [controller, setController] = useState(false); const { token } = props; - useEffect(() => { - setResponse(false); - searchCache = search.q; - search.sort = sort; - requester('/search/', 'POST', token, search) - .then(res => { - if (!search.seq || res.seq > response.seq) { - setResponse(res); - } - }) - .catch(err => { - console.log(err); - }); - }, [search, sort]); + const doSearch = (q) => { + console.log('doing search', q); + if (q.length) { + const qs = queryString.stringify({ 'q': q }); + history.replace('/members?' + qs); + } else { + history.replace('/members'); + } + }; + + const handleChange = (event) => { + const q = event.target.value; + doSearch(q); + }; useEffect(() => { - setSearch({seq: 0, q: ''}); - }, [sort]); + if (controller) { + controller.abort(); + } + const ctl = new AbortController(); + setController(ctl); + const signal = ctl.signal; + + const data = {q: search, sort: sort}; + requester('/search/', 'POST', token, data, signal) + .then(res => { + setResponse(res); + }) + .catch(err => { + ; + }); + }, [search, sort]); return ( @@ -100,16 +116,16 @@ export function Members(props) { setSearch({seq: parseInt(e.timeStamp), q: v.value})} + value={search} + onChange={handleChange} aria-label='search products' style={{ marginRight: '0.5rem' }} /> - {search.q.length ? + {search.length ?