Skip to content

Commit 8a43801

Browse files
author
ledouxm
committed
feat: implement search functionality for reports with SearchBar component
1 parent 779db49 commit 8a43801

3 files changed

Lines changed: 179 additions & 11 deletions

File tree

packages/frontend/src/components/MUIDsfr.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Box, styled } from "@mui/material";
1111
import { Link as RouterLink } from "@tanstack/react-router";
1212
import { Checkbox as DsfrCheckbox } from "@codegouvfr/react-dsfr/Checkbox";
1313
import { Summary as DsfrSummary } from "@codegouvfr/react-dsfr/Summary";
14+
import { SearchBar as DsfrSearchBar } from "@codegouvfr/react-dsfr/SearchBar";
1415

1516
export const Center = styled(Box)({
1617
display: "flex",
@@ -30,3 +31,4 @@ export const Checkbox = styled(DsfrCheckbox)();
3031
export const Summary = styled(DsfrSummary)();
3132
export const Table = styled(DsfrTable)();
3233
export const Pagination = styled(DsfrPagination)();
34+
export const SearchBar = styled(DsfrSearchBar)();

packages/frontend/src/features/report/ReportList.tsx

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
1-
import { Button, Center } from "#components/MUIDsfr.tsx";
1+
import { Button, Center, Input, SearchBar } from "#components/MUIDsfr.tsx";
22
import { Pagination } from "@codegouvfr/react-dsfr/Pagination";
33
import { Box, Stack } from "@mui/material";
44
import { chunk } from "pastable";
5-
import { useState } from "react";
5+
import { useRef, useState } from "react";
66
import welcomeImage from "../../assets/welcome.svg?url";
77
import { useLiveUser, useUser } from "../../contexts/AuthContext";
88
import { Report, StateReport } from "../../db/AppSchema";
99
import { useDbQuery } from "../../db/db";
1010
import { useIsDesktop } from "../../hooks/useIsDesktop";
1111
import { ReportListItem } from "./ReportListItem";
1212
import { getRouteApi } from "@tanstack/react-router";
13-
import { getReportQueries, getStateReportQueries } from "../useDocumentQueries";
13+
import {
14+
getReportQueries,
15+
getSearchReportQueries,
16+
getSearchStateReportQueries,
17+
getStateReportQueries,
18+
} from "../useDocumentQueries";
1419
import { StateReportListItem } from "../state-report/StateReportListItem";
1520
import { AppDocument } from "../../utils";
1621
import { DocumentTypeSelector } from "#components/DocumentTypeSelector.tsx";
1722
import { fr } from "@codegouvfr/react-dsfr";
1823
import { useStatus, useSyncStream } from "@powersync/react";
1924
import { 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

2130
export type ReportWithUser = Report & { createdByName: string | null };
2231
export type StateReportWithUser = StateReport & { createdByName: string | null };
2332

2433
const routeApi = getRouteApi("/");
2534

35+
const searchStore = createStore(
36+
{
37+
search: "",
38+
},
39+
{
40+
setSearch: (ctx, event: { search: string }) => ({ search: event.search }),
41+
},
42+
);
43+
2644
export 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+
241329
export 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

packages/frontend/src/features/useDocumentQueries.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,39 @@ export const getReportQueries = (
3636
return { baseQuery, countQuery };
3737
};
3838

39+
export const getSearchReportQueries = (
40+
search: string,
41+
scope: "my" | "all",
42+
user: { id: string; service_id: string | null },
43+
) => {
44+
let baseQuery = reportQueries.base.where((eb) =>
45+
eb.or([
46+
eb("title", "like", `%${search}%`),
47+
eb("redactedBy", "like", `%${search}%`),
48+
eb("applicantName", "like", `%${search}%`),
49+
eb("applicantAddress", "like", `%${search}%`),
50+
eb("city", "like", `%${search}%`),
51+
eb("zipCode", "like", `%${search}%`),
52+
]),
53+
);
54+
let countQuery = reportQueries.count.where((eb) =>
55+
eb.or([
56+
eb("title", "like", `%${search}%`),
57+
eb("redactedBy", "like", `%${search}%`),
58+
eb("applicantName", "like", `%${search}%`),
59+
eb("applicantAddress", "like", `%${search}%`),
60+
eb("city", "like", `%${search}%`),
61+
eb("zipCode", "like", `%${search}%`),
62+
]),
63+
);
64+
if (scope === "my") {
65+
baseQuery = baseQuery.where((eb) => eb.or([eb("createdBy", "=", user.id), eb("redactedById", "=", user.id)]));
66+
countQuery = countQuery.where((eb) => eb.or([eb("createdBy", "=", user.id), eb("redactedById", "=", user.id)]));
67+
}
68+
69+
return { baseQuery, countQuery };
70+
};
71+
3972
const stateReportQueries = {
4073
base: db
4174
.selectFrom("state_report")
@@ -70,3 +103,36 @@ export const getStateReportQueries = (
70103

71104
return { baseQuery, countQuery };
72105
};
106+
107+
export const getSearchStateReportQueries = (
108+
search: string,
109+
scope: "my" | "all",
110+
user: { id: string; service_id: string | null },
111+
) => {
112+
let baseQuery = stateReportQueries.base.where((eb) =>
113+
eb.or([
114+
eb("titre_edifice", "like", `%${search}%`),
115+
eb("redacted_by", "like", `%${search}%`),
116+
eb("commune", "like", `%${search}%`),
117+
eb("commune_historique", "like", `%${search}%`),
118+
eb("reference_pop", "like", `%${search}%`),
119+
eb("code_postal", "like", `%${search}%`),
120+
]),
121+
);
122+
let countQuery = stateReportQueries.count.where((eb) =>
123+
eb.or([
124+
eb("titre_edifice", "like", `%${search}%`),
125+
eb("redacted_by", "like", `%${search}%`),
126+
eb("commune", "like", `%${search}%`),
127+
eb("commune_historique", "like", `%${search}%`),
128+
eb("reference_pop", "like", `%${search}%`),
129+
eb("code_postal", "like", `%${search}%`),
130+
]),
131+
);
132+
if (scope === "my") {
133+
baseQuery = baseQuery.where("created_by", "=", user.id);
134+
countQuery = countQuery.where("created_by", "=", user.id);
135+
}
136+
137+
return { baseQuery, countQuery };
138+
};

0 commit comments

Comments
 (0)