+
+
+
-
+
dispatch(setView("calendar"))}
+ />
);
}
diff --git a/src/features/Events/EventApi.ts b/src/features/Events/EventApi.ts
index 8263d89..25602ff 100644
--- a/src/features/Events/EventApi.ts
+++ b/src/features/Events/EventApi.ts
@@ -10,6 +10,7 @@ import {
import ICAL from "ical.js";
import moment from "moment-timezone";
import { detectDateTimeFormat } from "../../components/Event/utils/dateTimeHelpers";
+import { User } from "../../components/Attendees/PeopleSearch";
function resolveTimezoneId(tzid?: string): string | undefined {
if (!tzid) return undefined;
@@ -361,3 +362,41 @@ export async function importEventFromFile(id: string, calLink: string) {
});
return response;
}
+
+export async function searchEvent(
+ query: string,
+ filters: {
+ searchIn: string[];
+ keywords: string;
+ organizers: string[];
+ attendees: string[];
+ }
+) {
+ const { keywords, searchIn, organizers, attendees } = filters;
+
+ const reqParam: {
+ query: string;
+ calendars: { calendarId: string; userId: string }[];
+ organizers?: string[];
+ attendees?: string[];
+ } = {
+ query: !!keywords ? keywords : query,
+ calendars: searchIn.map((calId) => {
+ const [userId, calendarId] = calId.split("/");
+ return { calendarId, userId };
+ }),
+ };
+ if (organizers.length) {
+ reqParam.organizers = organizers;
+ }
+ if (attendees.length) {
+ reqParam.attendees = attendees;
+ }
+ const response = await api
+ .post("calendar/api/events/search?limit=30&offset=0", {
+ body: JSON.stringify(reqParam),
+ })
+ .json();
+
+ return response;
+}
diff --git a/src/features/Search/SearchResultsPage.tsx b/src/features/Search/SearchResultsPage.tsx
index 8a161d3..7e1e4d3 100644
--- a/src/features/Search/SearchResultsPage.tsx
+++ b/src/features/Search/SearchResultsPage.tsx
@@ -1,9 +1,99 @@
-import React from "react";
+import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
+import { useAppSelector } from "../../app/hooks";
+import logo from "../../static/noResult-logo.svg";
+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";
+
+const dateLocales = { en: enGB, fr: frLocale, ru: ruLocale, vi: viLocale };
export default function SearchResultsPage() {
+ const { t } = useI18n();
+ const { error, loading, hits, results } = useAppSelector(
+ (state) => state.searchResult
+ );
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ if (!hits) {
+ return (
+
+
{t("search.noResults")}
+

+
+ );
+ }
+
return (
-
- Search Results
-
+
+
{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 || "-"}
+
+
);
}
diff --git a/src/features/Search/SearchSlice.ts b/src/features/Search/SearchSlice.ts
new file mode 100644
index 0000000..f3449a3
--- /dev/null
+++ b/src/features/Search/SearchSlice.ts
@@ -0,0 +1,72 @@
+import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
+import { formatReduxError } from "../../utils/errorUtils";
+import { searchEvent } from "../Events/EventApi";
+
+export interface SearchResultsState {
+ hits: number;
+ results: Record[];
+ error: string | null;
+ loading: boolean;
+}
+
+const initialState: SearchResultsState = {
+ results: [],
+ hits: 0,
+ error: null,
+ loading: false,
+};
+
+export const searchEventsAsync = createAsyncThunk<
+ { hits: number; events: Record[] },
+ { search: string; filters: any },
+ { rejectValue: { message: string; status?: number } }
+>("events/searchEvents", async ({ search, filters }, { rejectWithValue }) => {
+ try {
+ const response = (await searchEvent(search, filters)) as Record<
+ string,
+ any
+ >;
+
+ return {
+ hits: Number(response._total_hits),
+ events: response._embedded?.events ?? [],
+ };
+ } catch (err: any) {
+ return rejectWithValue({
+ message: formatReduxError(err),
+ status: err.response?.status,
+ });
+ }
+});
+
+export const searchResultsSlice = createSlice({
+ name: "settings",
+ initialState,
+ reducers: {
+ setResults: (state, action: PayloadAction<[]>) => {
+ state.results = action.payload;
+ },
+ setHits: (state, action: PayloadAction) => {
+ state.hits = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(searchEventsAsync.pending, (state) => {
+ state.loading = true;
+ state.error = null; // reset error on new search
+ })
+ .addCase(searchEventsAsync.fulfilled, (state, action) => {
+ state.loading = false;
+ state.hits = action.payload.hits;
+ state.results = action.payload.events;
+ })
+ .addCase(searchEventsAsync.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload?.message || "Unknown error occurred";
+ });
+ },
+});
+
+export const { setResults, setHits } = searchResultsSlice.actions;
+export default searchResultsSlice.reducer;
diff --git a/src/locales/en.json b/src/locales/en.json
index 9f6a964..c562416 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -48,7 +48,22 @@
"all": "All",
"you": "You",
"select_timezone": "Select Timezone",
- "moreOptions": "More options"
+ "moreOptions": "More options",
+ "search": "Search"
+ },
+ "search": {
+ "searchIn": "Search in",
+ "filter": {
+ "allCalendar": "All calendars",
+ "myCalendar": "My calendars",
+ "sharedCalendars": "Shared calendars"
+ },
+ "keywords": "Keywords*",
+ "keywordsPlaceholder": "Enter keywords",
+ "organizers": "Organizers",
+ "participants": "Participants",
+ "noResults": "No results found",
+ "resultsTitle": "Search Results"
},
"calendarPopover": {
"tabs": {
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 1401859..1a12751 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -48,7 +48,22 @@
"all": "Tous",
"you": "Vous",
"select_timezone": "Sélectionner le fuseau horaire",
- "moreOptions": "Plus d'options"
+ "moreOptions": "Plus d'options",
+ "search": "Rechercher"
+ },
+ "search": {
+ "searchIn": "Rechercher dans",
+ "filter": {
+ "allCalendar": "Tous les calendriers",
+ "myCalendar": "Mes calendriers",
+ "sharedCalendars": "Calendriers partagés"
+ },
+ "keywords": "Mots-clés*",
+ "keywordsPlaceholder": "Saisissez des mots-clés",
+ "organizers": "Organisateurs",
+ "participants": "Participants",
+ "noResults": "Aucun résultat",
+ "resultsTitle": "Résultats de la recherche"
},
"calendarPopover": {
"tabs": {
diff --git a/src/locales/ru.json b/src/locales/ru.json
index 5b5725d..8b15eaa 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -48,7 +48,22 @@
"all": "Все",
"you": "Вы",
"select_timezone": "Выберите часовой пояс",
- "moreOptions": "Больше параметров"
+ "moreOptions": "Больше параметров",
+ "search": "Поиск"
+ },
+ "search": {
+ "searchIn": "Поиск в",
+ "filter": {
+ "allCalendar": "Все календари",
+ "myCalendar": "Мои календари",
+ "sharedCalendars": "Общие календари"
+ },
+ "keywords": "Ключевые слова*",
+ "keywordsPlaceholder": "Введите ключевые слова",
+ "organizers": "Организаторы",
+ "participants": "Участники",
+ "noResults": "Результаты не найдены",
+ "resultsTitle": "Результаты поиска"
},
"calendarPopover": {
"tabs": {
diff --git a/src/locales/vi.json b/src/locales/vi.json
index c21784a..bcfd1eb 100644
--- a/src/locales/vi.json
+++ b/src/locales/vi.json
@@ -48,7 +48,22 @@
"all": "Tất cả",
"you": "Bạn",
"select_timezone": "Chọn múi giờ",
- "moreOptions": "Thêm tùy chọn"
+ "moreOptions": "Thêm tùy chọn",
+ "search": "Tìm kiếm"
+ },
+ "search": {
+ "searchIn": "Tìm kiếm trong",
+ "filter": {
+ "allCalendar": "Tất cả lịch",
+ "myCalendar": "Lịch của tôi",
+ "sharedCalendars": "Lịch được chia sẻ"
+ },
+ "keywords": "Từ khóa*",
+ "keywordsPlaceholder": "Nhập từ khóa",
+ "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"
},
"calendarPopover": {
"tabs": {
diff --git a/src/static/noResult-logo.svg b/src/static/noResult-logo.svg
new file mode 100644
index 0000000..d8e928b
--- /dev/null
+++ b/src/static/noResult-logo.svg
@@ -0,0 +1,24 @@
+
\ No newline at end of file