Add a date selector to the search function (#494)
* add date picker * lint * add date picker * lint * refine * fix/finish the date picker UI part * finish query parameter passing & incremental cleanup * fix/finish the date picker UI part * finish query parameter passing & incremental cleanup * fix bad merge * fix linting errors * wrap for mobile * add date picker * lint * add date picker * lint * refine * fix/finish the date picker UI part * finish query parameter passing & incremental cleanup * fix/finish the date picker UI part * finish query parameter passing & incremental cleanup * fix bad merge * fix linting errors * wrap for mobile * merge glitch? * enhance a little --------- Co-authored-by: rleed <rleed1@pm.me> Co-authored-by: keyan <keyan.kousha+huumn@gmail.com> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
b3aee502a0
commit
247744a83c
|
@ -79,7 +79,7 @@ export default {
|
|||
items
|
||||
}
|
||||
},
|
||||
search: async (parent, { q: query, sub, cursor, sort, what, when }, { me, models, search }) => {
|
||||
search: async (parent, { q: query, sub, cursor, sort, what, when, from: whenFrom, to: whenTo }, { me, models, search }) => {
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
let sitems
|
||||
|
||||
|
@ -199,6 +199,16 @@ export default {
|
|||
break
|
||||
}
|
||||
|
||||
const whenRange = when === 'custom'
|
||||
? {
|
||||
gte: new Date(whenFrom),
|
||||
lte: new Date(Math.min(new Date(whenTo), decodedCursor.time))
|
||||
}
|
||||
: {
|
||||
lte: decodedCursor.time,
|
||||
gte: whenGte
|
||||
}
|
||||
|
||||
try {
|
||||
sitems = await search.search({
|
||||
index: 'item',
|
||||
|
@ -232,10 +242,7 @@ export default {
|
|||
{
|
||||
range:
|
||||
{
|
||||
createdAt: {
|
||||
lte: decodedCursor.time,
|
||||
gte: whenGte
|
||||
}
|
||||
createdAt: whenRange
|
||||
}
|
||||
},
|
||||
{ range: { wvotes: { gt: -1 * ITEM_FILTER_THRESHOLD } } }
|
||||
|
|
|
@ -7,7 +7,7 @@ export default gql`
|
|||
pageTitleAndUnshorted(url: String!): TitleUnshorted
|
||||
dupes(url: String!): [Item!]
|
||||
related(cursor: String, title: String, id: ID, minMatch: String, limit: Int): Items
|
||||
search(q: String, sub: String, cursor: String, what: String, sort: String, when: String): Items
|
||||
search(q: String, sub: String, cursor: String, what: String, sort: String, when: String, from: String, to: String): Items
|
||||
auctionPosition(sub: String, id: ID, bid: Int!): Int!
|
||||
itemRepetition(parentId: ID): Int!
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import TextareaAutosize from 'react-textarea-autosize'
|
|||
import { useToast } from './toast'
|
||||
import { useInvoiceable } from './invoice'
|
||||
import { numWithUnits } from '../lib/format'
|
||||
import ReactDatePicker from 'react-datepicker'
|
||||
import 'react-datepicker/dist/react-datepicker.css'
|
||||
|
||||
export function SubmitButton ({
|
||||
children, variant, value, onClick, disabled, cost, ...props
|
||||
|
@ -589,3 +591,29 @@ export function Select ({ label, items, groupClassName, onChange, noForm, overri
|
|||
</FormGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export function DatePicker ({ fromName, toName, noForm, onMount, ...props }) {
|
||||
const formik = noForm ? null : useFormikContext()
|
||||
const onChangeHandler = props.onChange
|
||||
const [,, fromHelpers] = noForm ? [{}, {}, {}] : useField({ ...props, name: fromName })
|
||||
const [,, toHelpers] = noForm ? [{}, {}, {}] : useField({ ...props, name: toName })
|
||||
|
||||
useEffect(() => {
|
||||
if (onMount) {
|
||||
const [from, to] = onMount()
|
||||
fromHelpers.setValue(from)
|
||||
toHelpers.setValue(to)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ReactDatePicker
|
||||
{...props}
|
||||
onChange={([from, to], e) => {
|
||||
fromHelpers.setValue(from?.toISOString())
|
||||
toHelpers.setValue(to?.toISOString())
|
||||
onChangeHandler(formik, [from, to], e)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import Container from 'react-bootstrap/Container'
|
|||
import styles from './search.module.css'
|
||||
import SearchIcon from '../svgs/search-line.svg'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Form, Input, Select, SubmitButton } from './form'
|
||||
import { Form, Input, Select, DatePicker, SubmitButton } from './form'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export default function Search ({ sub }) {
|
||||
|
@ -35,6 +35,7 @@ export default function Search ({ sub }) {
|
|||
if (values.what === '' || values.what === 'all') delete values.what
|
||||
if (values.sort === '' || values.sort === 'zaprank') delete values.sort
|
||||
if (values.when === '' || values.when === 'forever') delete values.when
|
||||
if (values.when !== 'custom') { delete values.from; delete values.to }
|
||||
await router.push({
|
||||
pathname: prefix + '/search',
|
||||
query: values
|
||||
|
@ -46,13 +47,20 @@ export default function Search ({ sub }) {
|
|||
const what = router.pathname.startsWith('/stackers') ? 'stackers' : router.query.what || 'all'
|
||||
const sort = router.query.sort || 'zaprank'
|
||||
const when = router.query.when || 'forever'
|
||||
const from = router.query.from || new Date().toISOString()
|
||||
const to = router.query.to || new Date().toISOString()
|
||||
|
||||
const [datePicker, setDatePicker] = useState(when === 'custom')
|
||||
// The following state is needed for the date picker (and driven by the date picker).
|
||||
// Substituting router.query or formik values would cause network lag and/or timezone issues.
|
||||
const [range, setRange] = useState({ start: new Date(from), end: new Date(to) })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.searchSection}>
|
||||
<Container className={`px-md-0 ${styles.searchContainer} ${filter ? styles.leaveRoom : ''}`}>
|
||||
<Container className={`px-md-0 ${styles.searchContainer}`}>
|
||||
<Form
|
||||
initial={{ q, what, sort, when }}
|
||||
initial={{ q, what, sort, when, from, to }}
|
||||
onSubmit={search}
|
||||
>
|
||||
<div className={`${styles.active} my-3`}>
|
||||
|
@ -74,37 +82,61 @@ export default function Search ({ sub }) {
|
|||
</SubmitButton>
|
||||
</div>
|
||||
{filter &&
|
||||
<div className='text-muted fw-bold d-flex align-items-center'>
|
||||
<Select
|
||||
groupClassName='me-2 mb-0'
|
||||
onChange={(formik, e) => search({ ...formik?.values, what: e.target.value })}
|
||||
name='what'
|
||||
size='sm'
|
||||
overrideValue={what}
|
||||
items={['all', 'posts', 'comments', 'stackers']}
|
||||
/>
|
||||
{what !== 'stackers' &&
|
||||
<>
|
||||
by
|
||||
<Select
|
||||
groupClassName='mx-2 mb-0'
|
||||
onChange={(formik, e) => search({ ...formik?.values, sort: e.target.value })}
|
||||
name='sort'
|
||||
size='sm'
|
||||
overrideValue={sort}
|
||||
items={['zaprank', 'match', 'recent', 'comments', 'sats']}
|
||||
/>
|
||||
for
|
||||
<Select
|
||||
groupClassName='mb-0 ms-2'
|
||||
onChange={(formik, e) => search({ ...formik?.values, when: e.target.value })}
|
||||
name='when'
|
||||
size='sm'
|
||||
overrideValue={when}
|
||||
items={['forever', 'day', 'week', 'month', 'year']}
|
||||
/>
|
||||
|
||||
</>}
|
||||
<div className='text-muted fw-bold d-flex align-items-center flex-wrap pb-2'>
|
||||
<div className='text-muted fw-bold d-flex align-items-center pb-2'>
|
||||
<Select
|
||||
groupClassName='me-2 mb-0'
|
||||
onChange={(formik, e) => search({ ...formik?.values, what: e.target.value })}
|
||||
name='what'
|
||||
size='sm'
|
||||
overrideValue={what}
|
||||
items={['all', 'posts', 'comments', 'stackers']}
|
||||
/>
|
||||
{what !== 'stackers' &&
|
||||
<>
|
||||
by
|
||||
<Select
|
||||
groupClassName='mx-2 mb-0'
|
||||
onChange={(formik, e) => search({ ...formik?.values, sort: e.target.value })}
|
||||
name='sort'
|
||||
size='sm'
|
||||
overrideValue={sort}
|
||||
items={['zaprank', 'match', 'recent', 'comments', 'sats']}
|
||||
/>
|
||||
for
|
||||
<Select
|
||||
groupClassName='mb-0 mx-2'
|
||||
onChange={(formik, e) => {
|
||||
search({ ...formik?.values, when: e.target.value, from: from || new Date().toISOString(), to: to || new Date().toISOString() })
|
||||
setDatePicker(e.target.value === 'custom')
|
||||
if (e.target.value === 'custom') setRange({ start: new Date(), end: new Date() })
|
||||
}}
|
||||
name='when'
|
||||
size='sm'
|
||||
overrideValue={when}
|
||||
items={['custom', 'forever', 'day', 'week', 'month', 'year']}
|
||||
/>
|
||||
</>}
|
||||
</div>
|
||||
{datePicker &&
|
||||
<DatePicker
|
||||
fromName='from' toName='to'
|
||||
className='form-control p-0 px-2 mb-2 text-center'
|
||||
onMount={() => {
|
||||
setRange({ start: new Date(from), end: new Date(to) })
|
||||
return [from, to]
|
||||
}}
|
||||
onChange={(formik, [start, end], e) => {
|
||||
setRange({ start, end })
|
||||
search({ ...formik?.values, from: start && start.toISOString(), to: end && end.toISOString() })
|
||||
}}
|
||||
selected={range.start}
|
||||
startDate={range.start} endDate={range.end}
|
||||
selectsRange
|
||||
dateFormat='MM/dd/yy'
|
||||
maxDate={new Date()}
|
||||
minDate={new Date('2021-05-01')}
|
||||
/>}
|
||||
</div>}
|
||||
</Form>
|
||||
</Container>
|
||||
|
|
|
@ -7,14 +7,9 @@
|
|||
}
|
||||
|
||||
.searchContainer {
|
||||
height: 88px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.leaveRoom {
|
||||
height: 130px !important;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
|
|
@ -48,11 +48,11 @@ export const SUB_ITEMS = gql`
|
|||
export const SUB_SEARCH = gql`
|
||||
${SUB_FIELDS}
|
||||
${ITEM_FULL_FIELDS}
|
||||
query SubSearch($sub: String, $q: String, $cursor: String, $sort: String, $what: String, $when: String) {
|
||||
query SubSearch($sub: String, $q: String, $cursor: String, $sort: String, $what: String, $when: String, $from: String, $to: String) {
|
||||
sub(name: $sub) {
|
||||
...SubFields
|
||||
}
|
||||
search(sub: $sub, q: $q, cursor: $cursor, sort: $sort, what: $what, when: $when) {
|
||||
search(sub: $sub, q: $q, cursor: $cursor, sort: $sort, what: $what, when: $when, from: $from, to: $to) {
|
||||
cursor
|
||||
items {
|
||||
...ItemFullFields
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"react-avatar-editor": "^13.0.0",
|
||||
"react-bootstrap": "^2.8.0",
|
||||
"react-countdown": "^2.3.5",
|
||||
"react-datepicker": "^4.18.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-longpressable": "^1.1.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
|
@ -5971,9 +5972,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
|
@ -14489,6 +14493,23 @@
|
|||
"react-dom": ">= 15"
|
||||
}
|
||||
},
|
||||
"node_modules/react-datepicker": {
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.18.0.tgz",
|
||||
"integrity": "sha512-0MYt3HmLbHVk1sw4v+RCbLAVg5TA3jWP7RyjZbo53PC+SEi+pjdgc92lB53ai/ENZaTOhbXmgni9GzvMrorMAw==",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.30.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-onclickoutside": "^6.13.0",
|
||||
"react-popper": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17 || ^18",
|
||||
"react-dom": "^16.9.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
@ -14599,6 +14620,38 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/react-onclickoutside": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
|
||||
"integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.5.x || ^16.x || ^17.x || ^18.x",
|
||||
"react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-popper": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
|
||||
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
|
||||
"dependencies": {
|
||||
"react-fast-compare": "^3.0.1",
|
||||
"warning": "^4.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.0.0",
|
||||
"react": "^16.8.0 || ^17 || ^18",
|
||||
"react-dom": "^16.8.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-popper/node_modules/react-fast-compare": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
|
||||
},
|
||||
"node_modules/react-resize-detector": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
|
||||
|
@ -22658,9 +22711,12 @@
|
|||
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA=="
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.29.3",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.21.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
|
@ -28271,6 +28327,19 @@
|
|||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-datepicker": {
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.18.0.tgz",
|
||||
"integrity": "sha512-0MYt3HmLbHVk1sw4v+RCbLAVg5TA3jWP7RyjZbo53PC+SEi+pjdgc92lB53ai/ENZaTOhbXmgni9GzvMrorMAw==",
|
||||
"requires": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.30.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-onclickoutside": "^6.13.0",
|
||||
"react-popper": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
|
@ -28356,6 +28425,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react-onclickoutside": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz",
|
||||
"integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A=="
|
||||
},
|
||||
"react-popper": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
|
||||
"integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
|
||||
"requires": {
|
||||
"react-fast-compare": "^3.0.1",
|
||||
"warning": "^4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-fast-compare": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-resize-detector": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"react-avatar-editor": "^13.0.0",
|
||||
"react-bootstrap": "^2.8.0",
|
||||
"react-countdown": "^2.3.5",
|
||||
"react-datepicker": "^4.18.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-longpressable": "^1.1.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
|
|
Loading…
Reference in New Issue