1- import { Button , Center } from "#components/MUIDsfr.tsx" ;
1+ import { Button , Center , Input , SearchBar } from "#components/MUIDsfr.tsx" ;
22import { Pagination } from "@codegouvfr/react-dsfr/Pagination" ;
33import { Box , Stack } from "@mui/material" ;
44import { chunk } from "pastable" ;
5- import { useState } from "react" ;
5+ import { useRef , useState } from "react" ;
66import welcomeImage from "../../assets/welcome.svg?url" ;
77import { useLiveUser , useUser } from "../../contexts/AuthContext" ;
88import { Report , StateReport } from "../../db/AppSchema" ;
99import { useDbQuery } from "../../db/db" ;
1010import { useIsDesktop } from "../../hooks/useIsDesktop" ;
1111import { ReportListItem } from "./ReportListItem" ;
1212import { getRouteApi } from "@tanstack/react-router" ;
13- import { getReportQueries , getStateReportQueries } from "../useDocumentQueries" ;
13+ import {
14+ getReportQueries ,
15+ getSearchReportQueries ,
16+ getSearchStateReportQueries ,
17+ getStateReportQueries ,
18+ } from "../useDocumentQueries" ;
1419import { StateReportListItem } from "../state-report/StateReportListItem" ;
1520import { AppDocument } from "../../utils" ;
1621import { DocumentTypeSelector } from "#components/DocumentTypeSelector.tsx" ;
1722import { fr } from "@codegouvfr/react-dsfr" ;
1823import { useStatus , useSyncStream } from "@powersync/react" ;
1924import { Spinner } from "#components/Spinner.tsx" ;
25+ import { createStore } from "@xstate/store" ;
26+ import { useSelector } from "@xstate/store/react" ;
27+ import { Flex } from "#components/ui/Flex.tsx" ;
28+ import { cx } from "@codegouvfr/react-dsfr/tools/cx" ;
2029
2130export type ReportWithUser = Report & { createdByName : string | null } ;
2231export type StateReportWithUser = StateReport & { createdByName : string | null } ;
2332
2433const routeApi = getRouteApi ( "/" ) ;
2534
35+ const searchStore = createStore (
36+ {
37+ search : "" ,
38+ } ,
39+ {
40+ setSearch : ( ctx , event : { search : string } ) => ( { search : event . search } ) ,
41+ } ,
42+ ) ;
43+
2644export const MyReports = ( ) => {
2745 const [ page , setPage ] = useState ( 0 ) ;
2846 const document = routeApi . useSearch ( ) . document ;
2947
30- const { baseQuery, countQuery } = useRightQueries ( { page, document, scope : "my" } ) ;
48+ const search = useSelector ( searchStore , ( state ) => state . context . search ) ;
49+
50+ const { baseQuery, countQuery } = useRightQueries ( { page, document, scope : "my" , search } ) ;
3151 const reports = baseQuery . data ;
3252
3353 const reportsCount = countQuery . data ?. [ 0 ] ?. count as number ;
@@ -72,7 +92,9 @@ export const AllReports = () => {
7292 const [ page , setPage ] = useState ( 0 ) ;
7393 const document = routeApi . useSearch ( ) . document ;
7494
75- const { baseQuery, countQuery } = useRightQueries ( { page, document, scope : "all" } ) ;
95+ const search = useSelector ( searchStore , ( state ) => state . context . search ) ;
96+
97+ const { baseQuery, countQuery } = useRightQueries ( { page, document, scope : "all" , search } ) ;
7698 const reports = baseQuery . data ;
7799 const reportsCount = countQuery . data ?. [ 0 ] ?. count as number ;
78100
@@ -117,12 +139,26 @@ const useRightQueries = <Document extends AppDocument>({
117139 page,
118140 document,
119141 scope,
142+ search,
120143} : {
121144 page : number ;
122145 document : Document ;
123146 scope : "my" | "all" ;
147+ search ?: string ;
124148} ) => {
125149 const user = useUser ( ) ! ;
150+
151+ if ( search ?. length ) {
152+ const queries =
153+ document === "compte-rendus"
154+ ? getSearchReportQueries ( search ! , scope , user )
155+ : getSearchStateReportQueries ( search ! , scope , user ) ;
156+ return {
157+ baseQuery : useDbQuery ( queries . baseQuery as any ) ,
158+ countQuery : useDbQuery ( queries . countQuery ) ,
159+ } ;
160+ }
161+
126162 if ( document === "compte-rendus" ) {
127163 const queries = getReportQueries ( scope , page , user ) ;
128164 return { baseQuery : useDbQuery ( queries . baseQuery ) , countQuery : useDbQuery ( queries . countQuery ) } ;
@@ -161,16 +197,28 @@ export const ReportList = ({
161197 onClick ?: ( ) => void ;
162198 hideEmpty ?: boolean ;
163199} ) => {
164- const error = reports . length === 0 ? < NoReport /> : null ;
200+ const search = useSelector ( searchStore , ( state ) => state . context . search ) ;
201+ const error = reports . length === 0 && ! search ? < NoReport /> : null ;
165202 const isDesktop = useIsDesktop ( ) ;
166203 const columns = reports . length < 6 ? [ reports ] : chunk ( reports , Math . ceil ( reports . length / 2 ) ) ;
167204
168205 return (
169206 < Stack component = "div" width = "100%" mt = { { xs : "20px" , lg : "30px" } } px = "16px" >
170- < Center mb = "40px" >
171- < Box width = "926px" >
207+ < Center
208+ mb = "40px"
209+ width = "100%"
210+ maxWidth = { { xs : "100%" , lg : "calc(800px + 126px)" } }
211+ alignSelf = "center"
212+ gap = "16px"
213+ flexDirection = { { xs : "column" , lg : "row" } }
214+ >
215+ < Box maxWidth = "800px" width = { { xs : "100%" , lg : "unset" } } >
172216 < DocumentTypeSelector />
173217 </ Box >
218+
219+ < Box width = "100%" ml = { { xs : "0" , lg : "24px" } } >
220+ < AppSearchBar />
221+ </ Box >
174222 </ Center >
175223 { ! hideEmpty && error ? (
176224 error
@@ -238,6 +286,46 @@ export const ReportList = ({
238286 ) ;
239287} ;
240288
289+ const AppSearchBar = ( ) => {
290+ const search = useSelector ( searchStore , ( state ) => state . context . search ) ;
291+ const setSearch = ( search : string ) => searchStore . send ( { type : "setSearch" , search } ) ;
292+
293+ const searchInputRef = useRef < HTMLInputElement > ( null ) ;
294+
295+ return (
296+ < Flex
297+ sx = { {
298+ ".fr-search-bar" : { width : "100%" } ,
299+ ".fr-search-bar > div" : { width : "100%" } ,
300+ } }
301+ >
302+ < SearchBar
303+ renderInput = { ( params ) => (
304+ < div >
305+ < input
306+ id = { params . id }
307+ className = { cx ( params . className ) }
308+ placeholder = { params . placeholder }
309+ type = { params . type }
310+ value = { search }
311+ ref = { searchInputRef }
312+ style = { { width : "100%" } }
313+ onChange = { ( e ) => setSearch ( e . target . value ) }
314+ onKeyDown = { ( event ) => {
315+ if ( event . key === "Escape" ) {
316+ if ( searchInputRef . current !== null ) {
317+ searchInputRef . current . blur ( ) ;
318+ }
319+ }
320+ } }
321+ />
322+ </ div >
323+ ) }
324+ />
325+ </ Flex >
326+ ) ;
327+ } ;
328+
241329export const StateReportList = ( {
242330 reports,
243331 page,
@@ -255,16 +343,28 @@ export const StateReportList = ({
255343 onClick ?: ( ) => void ;
256344 hideEmpty ?: boolean ;
257345} ) => {
258- const error = reports . length === 0 ? < NoReport /> : null ;
346+ const search = useSelector ( searchStore , ( state ) => state . context . search ) ;
347+
348+ const error = reports . length === 0 && ! search ? < NoReport /> : null ;
259349 const isDesktop = useIsDesktop ( ) ;
260350 const columns = reports . length < 6 ? [ reports ] : chunk ( reports , Math . ceil ( reports . length / 2 ) ) ;
261351
262352 return (
263353 < Stack component = "div" width = "100%" mt = { { xs : "20px" , lg : "30px" } } px = "16px" >
264- < Center mb = "40px" >
265- < Box width = "926px" >
354+ < Center
355+ mb = "40px"
356+ width = "100%"
357+ maxWidth = { { xs : "100%" , lg : "calc(800px + 126px)" } }
358+ alignSelf = "center"
359+ gap = "16px"
360+ flexDirection = { { xs : "column" , lg : "row" } }
361+ >
362+ < Box maxWidth = "800px" width = { { xs : "100%" , lg : "unset" } } >
266363 < DocumentTypeSelector />
267364 </ Box >
365+ < Box width = "100%" ml = { { xs : "0" , lg : "24px" } } >
366+ < AppSearchBar />
367+ </ Box >
268368 </ Center >
269369 { ! hideEmpty && error ? (
270370 error
0 commit comments