Implement global search feature with context, UI components, and routing updates
This commit is contained in:
@@ -411,14 +411,29 @@ const MyChores = () => {
|
||||
}
|
||||
}, [searchInputFocus])
|
||||
|
||||
// A global-search result can hand a query back to the task list as a scoped filter.
|
||||
useEffect(() => {
|
||||
const query = searchParams.get('search')
|
||||
if (query !== null) {
|
||||
setSearchTerm(query.toLowerCase())
|
||||
setSelectedCalendarDate(null)
|
||||
clearActiveFilter()
|
||||
clearQuickFilters()
|
||||
}
|
||||
// The setters above are intentionally applied only when URL search params change.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams])
|
||||
|
||||
// Read and apply project from URL parameters
|
||||
useEffect(() => {
|
||||
if (!projects.length) return
|
||||
|
||||
const projectIdFromUrl = searchParams.get('project')
|
||||
|
||||
if (projectIdFromUrl && projectIdFromUrl !== selectedProject?.id) {
|
||||
const project = projectsWithDefault.find(p => p.id === projectIdFromUrl)
|
||||
if (projectIdFromUrl && projectIdFromUrl !== String(selectedProject?.id)) {
|
||||
const project = projectsWithDefault.find(
|
||||
p => String(p.id) === projectIdFromUrl,
|
||||
)
|
||||
if (project) {
|
||||
setSelectedProjectWithCache(project)
|
||||
}
|
||||
@@ -759,6 +774,11 @@ const MyChores = () => {
|
||||
setFilteredChores(selectedProject ? projectFilteredChores : chores)
|
||||
setSearchInputFocus(0)
|
||||
setSelectedCalendarDate(null)
|
||||
if (searchParams.has('search')) {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.delete('search')
|
||||
setSearchParams(params, { replace: true })
|
||||
}
|
||||
}
|
||||
|
||||
const setSelectedChoreSectionWithCache = value => {
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
import { CancelRounded } from '@mui/icons-material'
|
||||
import { CancelRounded, SearchRounded } from '@mui/icons-material'
|
||||
import { Box, Input } from '@mui/joy'
|
||||
|
||||
import KeyboardShortcutHint from '../../../components/common/KeyboardShortcutHint'
|
||||
import { useGlobalSearch } from '../../../search/GlobalSearchContext'
|
||||
|
||||
const SearchBar = ({
|
||||
value,
|
||||
inputRef,
|
||||
onChange,
|
||||
onClose,
|
||||
onFocus,
|
||||
showKeyboardShortcuts,
|
||||
inputRef,
|
||||
value,
|
||||
}) => {
|
||||
const { openSearch } = useGlobalSearch()
|
||||
const handleOpen = () => {
|
||||
onFocus?.()
|
||||
openSearch(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
slotProps={{ input: { ref: inputRef } }}
|
||||
placeholder='Search'
|
||||
slotProps={{ input: { ref: inputRef, readOnly: true } }}
|
||||
placeholder='Search Donetick'
|
||||
value={value}
|
||||
onFocus={onFocus}
|
||||
onFocus={handleOpen}
|
||||
onMouseDown={event => {
|
||||
event.preventDefault()
|
||||
handleOpen()
|
||||
}}
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 1,
|
||||
@@ -24,17 +36,29 @@ const SearchBar = ({
|
||||
height: 24,
|
||||
borderColor: 'text.disabled',
|
||||
padding: 1,
|
||||
cursor: 'pointer',
|
||||
'& input': { cursor: 'pointer' },
|
||||
}}
|
||||
onChange={onChange}
|
||||
startDecorator={
|
||||
<KeyboardShortcutHint shortcut='F' show={showKeyboardShortcuts} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<SearchRounded sx={{ fontSize: 18, color: 'text.secondary' }} />
|
||||
<KeyboardShortcutHint shortcut='F' show={showKeyboardShortcuts} />
|
||||
</Box>
|
||||
}
|
||||
endDecorator={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
{value && (
|
||||
<>
|
||||
<KeyboardShortcutHint shortcut='X' show={showKeyboardShortcuts} />
|
||||
<CancelRounded onClick={onClose} />
|
||||
<CancelRounded
|
||||
aria-label='Clear task search'
|
||||
onMouseDown={event => event.stopPropagation()}
|
||||
onClick={event => {
|
||||
event.stopPropagation()
|
||||
onClose()
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ListAlt,
|
||||
Logout,
|
||||
MenuRounded,
|
||||
SearchRounded,
|
||||
SettingsOutlined,
|
||||
Toll,
|
||||
Widgets,
|
||||
@@ -23,30 +24,36 @@ import {
|
||||
ListItemDecorator,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
|
||||
import { version } from '../../../package.json'
|
||||
import UserProfileAvatar from '../../components/UserProfileAvatar'
|
||||
import Z_INDEX from '../../constants/zIndex'
|
||||
import { useLocalization } from '../../contexts/LocalizationContext'
|
||||
import { useResource } from '../../queries/ResourceQueries'
|
||||
import { useGlobalSearch } from '../../search/GlobalSearchContext'
|
||||
import { apiClient } from '../../utils/ApiClient'
|
||||
import NavBarLink from './NavBarLink'
|
||||
import SyncStatusIndicator from './SyncStatusIndicator'
|
||||
|
||||
import Z_INDEX from '../../constants/zIndex'
|
||||
import { useResource } from '../../queries/ResourceQueries'
|
||||
import { apiClient } from '../../utils/ApiClient'
|
||||
|
||||
const publicPages = ['/landing', '/privacy', '/terms']
|
||||
const NavBar = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const { isRTL } = useLocalization()
|
||||
const { data: resource } = useResource()
|
||||
const { openSearch } = useGlobalSearch()
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [drawerOpen, setDrawerOpen] = useState(false)
|
||||
|
||||
const links = [
|
||||
{
|
||||
label: t('navigation.search'),
|
||||
icon: <SearchRounded />,
|
||||
onClick: () => openSearch(),
|
||||
},
|
||||
{
|
||||
to: '/chores',
|
||||
label: t('navigation.allTasks'),
|
||||
@@ -105,6 +112,22 @@ const NavBar = () => {
|
||||
<MenuRounded />
|
||||
</IconButton>
|
||||
)
|
||||
if (location.pathname === '/search') {
|
||||
return (
|
||||
<IconButton
|
||||
size='md'
|
||||
variant='plain'
|
||||
onClick={() => {
|
||||
if (window.history.state?.idx > 0) navigate(-1)
|
||||
else navigate('/chores', { replace: true })
|
||||
}}
|
||||
aria-label='Back from search'
|
||||
title={t('back')}
|
||||
>
|
||||
<ArrowBack />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
return menuRounded
|
||||
}
|
||||
|
||||
@@ -7,13 +7,12 @@ import {
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
const NavBarLink = ({ link }) => {
|
||||
const { to, icon, label } = link
|
||||
const { to, icon, label, onClick } = link
|
||||
return (
|
||||
<ListItem>
|
||||
<ListItemButton
|
||||
key={to}
|
||||
component={Link}
|
||||
to={to}
|
||||
{...(onClick ? { onClick } : { component: Link, to })}
|
||||
variant='plain'
|
||||
color='neutral'
|
||||
sx={{
|
||||
|
||||
Reference in New Issue
Block a user