From 325d33caefe36c699224687ed04e80d415876639 Mon Sep 17 00:00:00 2001 From: Camille Moussu <66134347+Eriikah@users.noreply.github.com> Date: Tue, 2 Dec 2025 15:26:00 +0100 Subject: [PATCH] [#361] Improve search result page (#375) * [#361] added back button * [#361] greatly improved looks of results * [#361] added popup on click * [#361] removed classnames with \!important replaced with style in component --------- Co-authored-by: Camille Moussu --- .../Search/SearchResultsPage.test.tsx | 1 - src/App.styl | 1 + src/components/Menubar/EventSearchBar.tsx | 20 +- src/features/Calendars/CalendarSlice.ts | 2 +- src/features/Search/SearchResultsPage.tsx | 349 ++++++++++++++---- src/features/Search/searchResult.styl | 31 ++ src/locales/en.json | 5 +- src/locales/fr.json | 3 +- src/locales/ru.json | 3 +- src/locales/vi.json | 3 +- 10 files changed, 328 insertions(+), 90 deletions(-) create mode 100644 src/features/Search/searchResult.styl diff --git a/__test__/features/Search/SearchResultsPage.test.tsx b/__test__/features/Search/SearchResultsPage.test.tsx index d8ce076..6eefce2 100644 --- a/__test__/features/Search/SearchResultsPage.test.tsx +++ b/__test__/features/Search/SearchResultsPage.test.tsx @@ -199,7 +199,6 @@ describe("SearchResultsPage", () => { }); expect(screen.getByText("Untitled Event")).toBeInTheDocument(); - expect(screen.getByText("-")).toBeInTheDocument(); }); it("should format all-day events correctly", () => { diff --git a/src/App.styl b/src/App.styl index 88ed990..6d98d04 100644 --- a/src/App.styl +++ b/src/App.styl @@ -15,3 +15,4 @@ .App-link color #61dafb + diff --git a/src/components/Menubar/EventSearchBar.tsx b/src/components/Menubar/EventSearchBar.tsx index f978f15..274efda 100644 --- a/src/components/Menubar/EventSearchBar.tsx +++ b/src/components/Menubar/EventSearchBar.tsx @@ -8,7 +8,6 @@ import { IconButton, InputAdornment, InputLabel, - ListSubheader, MenuItem, Popover, Select, @@ -137,9 +136,17 @@ export default function SearchBar() { {extended && ( { + onBlur={(e) => { + const next = e.relatedTarget as HTMLElement | null; + if ( + next instanceof Node && + searchBoxRef.current?.contains(next) + ) { + return; + } if (!search.trim()) { setExtended(false); } @@ -184,7 +191,12 @@ export default function SearchBar() { {search && ( - setSearch("")}> + { + setSearch(""); + handleFilterChange("keywords", ""); + }} + > @@ -335,7 +347,7 @@ export default function SearchBar() { - handleFilterChange("participants", users) + handleFilterChange("attendees", users) } /> diff --git a/src/features/Calendars/CalendarSlice.ts b/src/features/Calendars/CalendarSlice.ts index 309a97a..74c8835 100644 --- a/src/features/Calendars/CalendarSlice.ts +++ b/src/features/Calendars/CalendarSlice.ts @@ -684,7 +684,7 @@ const CalendarSlice = createSlice({ action.payload.event; state.list[action.payload.calendarUid].events[ action.payload.event.uid - ].URL = `dav/calendars/${action.payload.calendarUid}/${ + ].URL = `/calendars/${action.payload.calendarUid}/${ action.payload.event.uid.split("/")[0] }.isc`; }, diff --git a/src/features/Search/SearchResultsPage.tsx b/src/features/Search/SearchResultsPage.tsx index 7e1e4d3..f4f8fc7 100644 --- a/src/features/Search/SearchResultsPage.tsx +++ b/src/features/Search/SearchResultsPage.tsx @@ -1,99 +1,290 @@ -import { useI18n } from "cozy-ui/transpiled/react/providers/I18n"; -import { useAppSelector } from "../../app/hooks"; -import logo from "../../static/noResult-logo.svg"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import SquareRoundedIcon from "@mui/icons-material/SquareRounded"; +import { Box, IconButton, Stack, Typography } from "@mui/material"; import CircularProgress from "@mui/material/CircularProgress"; -import { Box, Card, CardContent, Typography, Chip, Stack } from "@mui/material"; -import { format } from "date-fns"; -import { - enGB, - fr as frLocale, - ru as ruLocale, - vi as viLocale, -} from "date-fns/locale"; +import { useI18n } from "cozy-ui/transpiled/react/providers/I18n"; +import { useState } from "react"; +import { useAppDispatch, useAppSelector } from "../../app/hooks"; +import { AppDispatch } from "../../app/store"; +import logo from "../../static/noResult-logo.svg"; +import { getEventAsync } from "../Calendars/CalendarSlice"; +import EventPreviewModal from "../Events/EventDisplayPreview"; +import { CalendarEvent } from "../Events/EventsTypes"; -const dateLocales = { en: enGB, fr: frLocale, ru: ruLocale, vi: viLocale }; +import { setView } from "../Settings/SettingsSlice"; +import "./searchResult.styl"; + +const styles = { + M3BodyLarge: { + fontFamily: "Roboto", + fontWeight: 400, + fontStyle: "normal", + fontSize: "22px", + lineHeight: "28px", + letterSpacing: "0%", + color: "#243B55", + }, + M3BodyMedium1: { + fontFamily: "Inter", + fontWeight: 400, + fontStyle: "normal", + fontSize: "16px", + lineHeight: "24px", + letterSpacing: "-0.15px", + color: "#243B55", + }, + M3BodyMedium: { + fontFamily: "Roboto", + fontWeight: 400, + fontStyle: "normal", + fontSize: "14px", + lineHeight: "20px", + letterSpacing: "0.25px", + verticalAlign: "middle", + color: "#8C9CAF", + }, + M3BodyMedium3: { + fontFamily: "Inter", + fontWeight: 400, + fontSize: "14px", + lineHeight: "20px", + letterSpacing: "0.25px", + verticalAlign: "middle", + color: "#8C9CAF", + }, + M3TitleMedium: { + fontFamily: "Roboto", + fontWeight: 500, + fontStyle: "medium", + fontSize: "16px", + lineHeight: "24px", + letterSpacing: "0.15px", + textAlign: "center", + verticalAlign: "middle", + color: "#243B55", + }, +}; export default function SearchResultsPage() { const { t } = useI18n(); + const dispatch = useAppDispatch(); const { error, loading, hits, results } = useAppSelector( (state) => state.searchResult ); + let layout; + if (loading) { - return ( -
- -
+ layout = ( + + + ); - } - - if (error) { - return ( -
-
{error}
-
+ } else if (error) { + layout = ( + + {error} + ); - } - - if (!hits) { - return ( -
-

{t("search.noResults")}

+ } else if (!hits) { + layout = ( + {t("search.noResults")} -
+ + {t("search.noResults")} + + + {t("search.noResultsSubtitle")} + + + ); + } else { + layout = ( + + + {results?.map((r: any, idx: number) => ( + + ))} + + ); } return ( -
-

{t("search.resultsTitle")}

- - {results?.map((r: any, idx: number) => ( - - ))} - -
- ); -} - -function ResultItem({ eventData }: { eventData: Record }) { - const { lang } = useI18n(); - const locale = dateLocales[lang as keyof typeof dateLocales] || enGB; - - const startDate = new Date(eventData.start); - - const formatDateTime = (date: Date) => { - if (eventData.allDay) { - return format(date, "PPP", { locale }); - } - return format(date, "PPP p", { locale }); - }; - - return ( - - - {formatDateTime(startDate)} - - - - {eventData.summary || "Untitled Event"} - - - - {eventData.organizer?.cn || eventData.organizer?.email || "-"} - + + + + dispatch(setView("calendar"))} + aria-label={t("settings.back")} + > + + + {t("search.resultsTitle")} + + + {layout} ); } + +function ResultItem({ + eventData, + dispatch, +}: { + eventData: Record; + dispatch: AppDispatch; +}) { + const { t } = useI18n(); + const startDate = new Date(eventData.data.start); + const endDate = eventData.data.end ? new Date(eventData.data.end) : startDate; + const timeZone = useAppSelector((state) => state.calendars.timeZone); + const calendar = useAppSelector( + (state) => + state.calendars.list[ + `${eventData.data.userId}/${eventData.data.calendarId}` + ] + ); + const calendarColor = calendar?.color?.light; + + const [openPreview, setOpenPreview] = useState(false); + + const handleOpenResult = async (eventData: Record) => { + if (calendar) { + const event = { + URL: eventData._links.self.href, + calId: calendar.id, + uid: eventData.data.uid, + start: eventData.data.start, + end: eventData.data.end, + allday: eventData.data.allDay, + attendee: eventData.data.attendees, + class: eventData.data.class, + description: eventData.data.description, + stamp: eventData.data.dtstamp, + location: eventData.data.location, + organizer: eventData.data.organizer, + title: eventData.data.summary, + timezone: timeZone, + } as CalendarEvent; + await dispatch(getEventAsync(event)); + setOpenPreview(true); + } + }; + + return ( + <> + handleOpenResult(eventData)} + > + + {startDate.toLocaleDateString(t("locale"), { + day: "2-digit", + month: "short", + timeZone, + })} + {startDate.toDateString() !== endDate.toDateString() && ( + <> + {" - "} + {endDate.toLocaleDateString(t("locale"), { + day: "2-digit", + month: "short", + timeZone, + })} + + )} + + + {!eventData.data.allDay && ( + <> + {startDate.toLocaleTimeString(t("locale"), { + hour: "2-digit", + minute: "2-digit", + timeZone, + })} + - + {endDate.toLocaleTimeString(t("locale"), { + hour: "2-digit", + minute: "2-digit", + timeZone, + })} + + )} + + + + + {eventData.data.summary || t("event.untitled")} + + + {eventData.data.organizer?.cn || + eventData.data.organizer?.email || + ""} + + {eventData.data?.location && ( + + {eventData.data?.location ?? ""} + + )} + + {eventData.data?.description?.replace(/\n/g, " ") ?? ""} + + + {calendar && calendar.events[eventData.data.uid] && ( + setOpenPreview(false)} + /> + )} + + ); +} diff --git a/src/features/Search/searchResult.styl b/src/features/Search/searchResult.styl new file mode 100644 index 0000000..d4db79a --- /dev/null +++ b/src/features/Search/searchResult.styl @@ -0,0 +1,31 @@ +.search-layout + flex-grow 1 + display flex + flex-direction column + height 100% + overflow-y auto + border-radius 16px + background #fff + + .noResults, + .loading, + .error + height: 90% + align-content center + align-items center + bottom 50% + +.back-button + display flex + align-content flex-start + align-items center + + +.search-result-content-header + display flex + align-items center + padding 16px 24px + gap 16px + +.search-result-content-body + flex-grow 1 diff --git a/src/locales/en.json b/src/locales/en.json index c562416..d6ae4d1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -62,8 +62,9 @@ "keywordsPlaceholder": "Enter keywords", "organizers": "Organizers", "participants": "Participants", - "noResults": "No results found", - "resultsTitle": "Search Results" + "noResults": "No events found", + "resultsTitle": "Search Results", + "noResultsSubtitle": "Try adjusting your filters or search terms." }, "calendarPopover": { "tabs": { diff --git a/src/locales/fr.json b/src/locales/fr.json index 1a12751..37ee3cb 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -63,7 +63,8 @@ "organizers": "Organisateurs", "participants": "Participants", "noResults": "Aucun résultat", - "resultsTitle": "Résultats de la recherche" + "resultsTitle": "Résultats de la recherche", + "noResultsSubtitle": "Essayez de changer vos filtres ou termes recherchés." }, "calendarPopover": { "tabs": { diff --git a/src/locales/ru.json b/src/locales/ru.json index 8b15eaa..35d77fc 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -63,7 +63,8 @@ "organizers": "Организаторы", "participants": "Участники", "noResults": "Результаты не найдены", - "resultsTitle": "Результаты поиска" + "resultsTitle": "Результаты поиска", + "noResultsSubtitle": "Попробуйте изменить фильтры или условия поиска." }, "calendarPopover": { "tabs": { diff --git a/src/locales/vi.json b/src/locales/vi.json index bcfd1eb..f9aafbc 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -63,7 +63,8 @@ "organizers": "Người tổ chức", "participants": "Người tham gia", "noResults": "Không tìm thấy kết quả", - "resultsTitle": "Kết quả tìm kiếm" + "resultsTitle": "Kết quả tìm kiếm", + "noResultsSubtitle": "Hãy thử điều chỉnh bộ lọc hoặc thuật ngữ tìm kiếm của bạn." }, "calendarPopover": { "tabs": {