Compare commits

...

2 Commits

Author SHA1 Message Date
15aa98e9db refactor(home): increase dropzone area
All checks were successful
Open PR on Staging / audit_and_check (pull_request) Successful in 32s
2024-08-09 11:29:00 +02:00
93b2477839 feat(home): add search param to address bar and sync the state with navigation 2024-08-09 10:58:30 +02:00
3 changed files with 165 additions and 139 deletions

View File

@ -30,6 +30,7 @@
border: 1px solid rgba(0, 0, 0, 0.137); border: 1px solid rgba(0, 0, 0, 0.137);
padding: 5px; padding: 5px;
cursor: pointer; cursor: pointer;
-webkit-user-select: none;
user-select: none; user-select: none;
&.selected { &.selected {
@ -42,15 +43,15 @@
border-color: #01aaad79; border-color: #01aaad79;
} }
} }
} }
} }
} }
.pdfImageWrapper { .pdfImageWrapper {
position: relative; position: relative;
-webkit-user-select: none;
user-select: none; user-select: none;
&.drawing { &.drawing {
cursor: crosshair; cursor: crosshair;
} }
@ -94,7 +95,7 @@
background-color: #fff; background-color: #fff;
border: 1px solid rgb(160, 160, 160); border: 1px solid rgb(160, 160, 160);
border-radius: 50%; border-radius: 50%;
color: #E74C3C; color: #e74c3c;
font-size: 10px; font-size: 10px;
cursor: pointer; cursor: pointer;
} }
@ -110,4 +111,4 @@
background: #fff; background: #fff;
padding: 5px 0; padding: 5px 0;
} }
} }

View File

@ -1,7 +1,7 @@
import { Button, TextField } from '@mui/material' import { Button, TextField } from '@mui/material'
import JSZip from 'jszip' import JSZip from 'jszip'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useAppSelector } from '../../hooks' import { useAppSelector } from '../../hooks'
import { appPrivateRoutes, appPublicRoutes } from '../../routes' import { appPrivateRoutes, appPublicRoutes } from '../../routes'
@ -40,6 +40,15 @@ type Sort = (typeof SORT_BY)[number]['value']
export const HomePage = () => { export const HomePage = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
const q = searchParams.get('q') ?? ''
useEffect(() => {
const searchInput = document.getElementById('q') as HTMLInputElement | null
if (searchInput) {
searchInput.value = q
}
}, [q])
const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) const [sigits, setSigits] = useState<{ [key: string]: Meta }>({})
const [parsedSigits, setParsedSigits] = useState<{ const [parsedSigits, setParsedSigits] = useState<{
@ -118,131 +127,143 @@ export const HomePage = () => {
[navigate] [navigate]
) )
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }) const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
onDrop,
noClick: true
})
const [search, setSearch] = useState('')
const [filter, setFilter] = useState<Filter>('Show all') const [filter, setFilter] = useState<Filter>('Show all')
const [sort, setSort] = useState<Sort>('desc') const [sort, setSort] = useState<Sort>('desc')
return ( return (
<Container className={styles.container}> <div {...getRootProps()}>
<div className={styles.header}> <Container className={styles.container}>
<div className={styles.filters}> <div className={styles.header}>
<Select <div className={styles.filters}>
name={'filter-select'} <Select
value={filter} name={'filter-select'}
setValue={setFilter} value={filter}
options={FILTERS.map((f) => { setValue={setFilter}
return { options={FILTERS.map((f) => {
label: f, return {
value: f label: f,
} value: f
})}
/>
<Select
name={'sort-select'}
value={sort}
setValue={setSort}
options={SORT_BY.map((s) => {
return { ...s }
})}
/>
</div>
<div className={styles.actionButtons}>
<form
className={styles.search}
onSubmit={(e) => {
e.preventDefault()
const searchInput = e.currentTarget.elements.namedItem(
'q'
) as HTMLInputElement
setSearch(searchInput.value)
}}
>
<TextField
name="q"
placeholder="Search"
size="small"
type="search"
onChange={(e) => {
// Handle the case when users click native search input's clear or x
if (e.currentTarget.value === '') {
setSearch(e.currentTarget.value)
} }
}} })}
sx={{
width: '100%',
fontSize: '16px',
borderTopLeftRadius: 'var(----mui-shape-borderRadius)',
borderBottomLeftRadius: 'var(----mui-shape-borderRadius)',
'& .MuiInputBase-root': {
borderTopRightRadius: 0,
borderBottomRightRadius: 0
},
'& .MuiInputBase-input': {
padding: '7px 14px'
},
'& .MuiOutlinedInput-notchedOutline': {
display: 'none'
}
}}
/> />
<Button <Select
type="submit" name={'sort-select'}
sx={{ value={sort}
minWidth: '44px', setValue={setSort}
padding: '11.5px 12px', options={SORT_BY.map((s) => {
borderTopLeftRadius: 0, return { ...s }
borderBottomLeftRadius: 0 })}
/>
</div>
<div className={styles.actionButtons}>
<form
className={styles.search}
onSubmit={(e) => {
e.preventDefault()
const searchInput = e.currentTarget.elements.namedItem(
'q'
) as HTMLInputElement
searchParams.set('q', searchInput.value)
setSearchParams(searchParams)
}} }}
variant={'contained'}
> >
<FontAwesomeIcon icon={faSearch} /> <TextField
</Button> id="q"
</form> name="q"
placeholder="Search"
size="small"
type="search"
defaultValue={q}
onChange={(e) => {
// Handle the case when users click native search input's clear or x
if (e.currentTarget.value === '') {
searchParams.delete('q')
setSearchParams(searchParams)
}
}}
sx={{
width: '100%',
fontSize: '16px',
borderTopLeftRadius: 'var(----mui-shape-borderRadius)',
borderBottomLeftRadius: 'var(----mui-shape-borderRadius)',
'& .MuiInputBase-root': {
borderTopRightRadius: 0,
borderBottomRightRadius: 0
},
'& .MuiInputBase-input': {
padding: '7px 14px'
},
'& .MuiOutlinedInput-notchedOutline': {
display: 'none'
}
}}
/>
<Button
type="submit"
sx={{
minWidth: '44px',
padding: '11.5px 12px',
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0
}}
variant={'contained'}
aria-label="Submit Search"
>
<FontAwesomeIcon icon={faSearch} />
</Button>
</form>
</div>
</div> </div>
</div> <div
<div className={styles.dropzone}> className={`${styles.dropzone} ${isDragActive ? styles.isDragActive : ''}`}
<div {...getRootProps()}> onClick={open}
<input {...getInputProps()} /> >
{isDragActive ? ( <div>
<p>Drop the files here ...</p> <input {...getInputProps()} />
) : ( {isDragActive ? (
<p>Click or drag files to upload!</p> <p>Drop the files here ...</p>
)} ) : (
<p>Click or drag files to upload!</p>
)}
</div>
</div> </div>
</div> <div className={styles.submissions}>
<div className={styles.submissions}> {Object.keys(parsedSigits)
{Object.keys(parsedSigits) .filter((s) => {
.filter((s) => { const { title, signedStatus } = parsedSigits[s]
const { title, signedStatus } = parsedSigits[s] const isMatch = title?.toLowerCase().includes(q.toLowerCase())
const isMatch = title?.toLowerCase().includes(search.toLowerCase()) switch (filter) {
switch (filter) { case 'Completed':
case 'Completed': return signedStatus === SignedStatus.Complete && isMatch
return signedStatus === SignedStatus.Complete && isMatch case 'In-progress':
case 'In-progress': return signedStatus === SignedStatus.Partial && isMatch
return signedStatus === SignedStatus.Partial && isMatch case 'Show all':
case 'Show all': return isMatch
return isMatch default:
default: console.error('Filter case not handled.')
console.error('Filter case not handled.') }
} })
}) .sort((a, b) => {
.sort((a, b) => { const x = parsedSigits[a].createdAt ?? 0
const x = parsedSigits[a].createdAt ?? 0 const y = parsedSigits[b].createdAt ?? 0
const y = parsedSigits[b].createdAt ?? 0 return sort === 'desc' ? y - x : x - y
return sort === 'desc' ? y - x : x - y })
}) .map((key) => (
.map((key) => ( <DisplaySigit
<DisplaySigit key={`sigit-${key}`}
key={`sigit-${key}`} parsedMeta={parsedSigits[key]}
parsedMeta={parsedSigits[key]} meta={sigits[key]}
meta={sigits[key]} profiles={profiles}
profiles={profiles} setProfiles={setProfiles}
setProfiles={setProfiles} />
/> ))}
))} </div>
</div> </Container>
</Container> </div>
) )
} }

View File

@ -53,31 +53,35 @@
} }
.dropzone { .dropzone {
position: relative;
font-size: 16px;
background-color: $overlay-background-color; background-color: $overlay-background-color;
height: 250px; height: 250px;
transition: padding ease 0.2s; color: rgba(0, 0, 0, 0.25);
padding: 15px; display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
&:hover { &::before {
padding: 10px; content: '';
position: absolute;
> div { transition:
background: rgba(0, 0, 0, 0.15); background-color ease 0.2s,
} inset ease 0.2s;
}
> div {
transition: background-color ease 0.2s;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.1);
color: rgba(0, 0, 0, 0.25);
height: 100%;
border-radius: 2px; border-radius: 2px;
border: dashed 3px rgba(0, 0, 0, 0.1); border: dashed 3px rgba(0, 0, 0, 0.1);
font-size: 16px; inset: 15px;
}
&.isDragActive,
&:hover {
&::before {
inset: 10px;
background: rgba(0, 0, 0, 0.15);
}
} }
} }