11import { useEffect , useMemo , useState } from "react"
22
33import { ContextInfo } from "components/ui/context-info"
4- import { Input } from "components/ui/input"
4+ import { FilterInput } from "components/ui/filter- input"
55import { PageRange , Pagination } from "components/ui/pagination"
6+ import { groupedCategories } from "data/categories"
67import { timeEntriesData , TimeEntry } from "data/time-entries"
7- import { CategorySelect } from "features/components/category-select"
88import {
99 CheckedStateProvider ,
1010 TimeEntriesBulkActions ,
@@ -14,8 +14,9 @@ import {
1414import { useObjectState } from "hooks/use-object-state"
1515import { useAtomValue } from "lib/yaasl"
1616import { cn } from "utils/cn"
17+ import { dateHelpers } from "utils/date-helpers"
1718import { fuzzyFilter } from "utils/fuzzy-filter"
18- import { hstack , vstack } from "utils/styles"
19+ import { vstack } from "utils/styles"
1920
2021const pageSizes = [ 10 , 15 , 20 , 25 , 30 ] as const satisfies number [ ]
2122const 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+
6573export 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
0 commit comments