Skip to content

Commit 61dd1a9

Browse files
committed
feat(search-route): Use text filter input instead of separate inputs
1 parent 6a9fea7 commit 61dd1a9

2 files changed

Lines changed: 76 additions & 22 deletions

File tree

src/app/routes/search/search-route.tsx

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { useEffect, useMemo, useState } from "react"
22

33
import { ContextInfo } from "components/ui/context-info"
4-
import { Input } from "components/ui/input"
4+
import { FilterInput } from "components/ui/filter-input"
55
import { PageRange, Pagination } from "components/ui/pagination"
6+
import { groupedCategories } from "data/categories"
67
import { timeEntriesData, TimeEntry } from "data/time-entries"
7-
import { CategorySelect } from "features/components/category-select"
88
import {
99
CheckedStateProvider,
1010
TimeEntriesBulkActions,
@@ -14,8 +14,9 @@ import {
1414
import { useObjectState } from "hooks/use-object-state"
1515
import { useAtomValue } from "lib/yaasl"
1616
import { cn } from "utils/cn"
17+
import { dateHelpers } from "utils/date-helpers"
1718
import { fuzzyFilter } from "utils/fuzzy-filter"
18-
import { hstack, vstack } from "utils/styles"
19+
import { vstack } from "utils/styles"
1920

2021
const pageSizes = [10, 15, 20, 25, 30] as const satisfies number[]
2122
const initialPageSize = 15 as const
@@ -62,49 +63,96 @@ const sortLatestTop = (a: TimeEntry, b: TimeEntry) => {
6263
return stampB.localeCompare(stampA)
6364
}
6465

66+
interface Filters {
67+
description?: string
68+
category?: string
69+
fromDate?: string
70+
toDate?: string
71+
}
72+
6573
export const SearchRoute = () => {
6674
const raw = useAtomValue(timeEntriesData)
6775
const allFlat = useMemo(
6876
() => Object.values(raw).flat().sort(sortLatestTop),
6977
[raw]
7078
)
79+
const groups = useAtomValue(groupedCategories)
80+
const categories = useMemo(
81+
() =>
82+
Object.fromEntries([
83+
["", "No category"],
84+
...groups.flatMap(({ name: groupName, categories }) =>
85+
categories.map(
86+
({ id, name }) =>
87+
[id, [groupName, name].filter(Boolean).join(" - ")] as const
88+
)
89+
),
90+
]),
91+
[groups]
92+
)
7193

72-
const [filter, setFilter] = useObjectState<Partial<TimeEntry>>({})
94+
const [filter, setFilter] = useObjectState<Filters>({})
7395

7496
const filtered = useMemo(() => {
7597
let filtered = allFlat
7698

77-
if (filter.categoryId) {
78-
filtered = filtered.filter(
79-
({ categoryId }) => categoryId === filter.categoryId
80-
)
99+
if (filter.category) {
100+
filtered = fuzzyFilter({
101+
items: filtered,
102+
filter: filter.category ?? "",
103+
getFilterValue: item => categories[item.categoryId ?? ""] ?? "",
104+
})
81105
}
82106

83107
if (filter.description) {
84108
filtered = fuzzyFilter({
85-
items: allFlat,
109+
items: filtered,
86110
filter: filter.description,
87111
getFilterValue: item => item.description,
88112
})
89113
}
90114

115+
if (filter.fromDate || filter.toDate) {
116+
filtered = filtered.filter(({ date }) =>
117+
dateHelpers.isInRange(date, filter.fromDate, filter.toDate)
118+
)
119+
}
120+
91121
return filtered
92-
}, [allFlat, filter.description, filter.categoryId])
122+
}, [
123+
allFlat,
124+
filter.category,
125+
filter.description,
126+
filter.fromDate,
127+
filter.toDate,
128+
categories,
129+
])
93130

94131
return (
95132
<div className={cn(vstack({}), "h-full px-10 pt-6")}>
96-
<div className={cn(hstack({ gap: 2 }), "mb-4")}>
97-
<Input
98-
type="text"
99-
placeholder="Description"
100-
value={filter.description ?? ""}
101-
onChange={description => setFilter({ description })}
102-
className="flex-1"
103-
/>
104-
<CategorySelect
105-
value={filter.categoryId ?? ""}
106-
onChange={categoryId => setFilter({ categoryId })}
107-
className="min-w-45"
133+
<div className="mb-4">
134+
<FilterInput
135+
className="block"
136+
placeholder="Search for time entries"
137+
onChange={({ text, tags }) => {
138+
setFilter({
139+
description: text,
140+
category: tags.category,
141+
fromDate: tags.from,
142+
toDate: tags.to,
143+
})
144+
}}
145+
tagConfigs={{
146+
category: { validate: Boolean },
147+
to: {
148+
validate: dateHelpers.isValid,
149+
format: value => dateHelpers.stringify(dateHelpers.parse(value)),
150+
},
151+
from: {
152+
validate: dateHelpers.isValid,
153+
format: value => dateHelpers.stringify(dateHelpers.parse(value)),
154+
},
155+
}}
108156
/>
109157
</div>
110158

src/utils/date-helpers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export interface ParsedDate {
99
day: number
1010
}
1111

12+
const isValid = (date: Date | string) => {
13+
const dateObj = new Date(date)
14+
return !Number.isNaN(dateObj.valueOf())
15+
}
16+
1217
const parse = (date: Date | string) => {
1318
const dateObj = new Date(date)
1419
return {
@@ -37,6 +42,7 @@ const isInRange = (date: string, start?: string, end?: string) => {
3742
}
3843

3944
export const dateHelpers = {
45+
isValid,
4046
today,
4147
parse,
4248
stringify,

0 commit comments

Comments
 (0)