import { Box, Button, Card, CardActions, CardContent, Divider, IconButton, InputAdornment, InputLabel, MenuItem, Popover, Select, Stack, TextField, Typography, } from "twake-mui"; import { type AutocompleteRenderInputParams } from "twake-mui"; import { useRef, useState, useEffect } from "react"; import HighlightOffIcon from "@mui/icons-material/HighlightOff"; import SearchIcon from "@mui/icons-material/Search"; import TuneIcon from "@mui/icons-material/Tune"; import { useI18n } from "twake-i18n"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { searchEventsAsync } from "../../features/Search/SearchSlice"; import { setView } from "../../features/Settings/SettingsSlice"; import { userAttendee } from "../../features/User/models/attendee"; import UserSearch from "../Attendees/AttendeeSearch"; import { CalendarItemList } from "../Calendar/CalendarItemList"; import { PeopleSearch, User } from "../Attendees/PeopleSearch"; import { createAttendee } from "../../features/User/models/attendee.mapper"; export default function SearchBar() { const { t } = useI18n(); const dispatch = useAppDispatch(); const calendars = Object.values( useAppSelector((state) => state.calendars.list) ); const userId = useAppSelector((state) => state.user.userData?.openpaasId); const personnalCalendars = userId ? calendars.filter((c) => c.id.split("/")[0] === userId) : []; const sharedCalendars = userId ? calendars.filter((c) => c.id.split("/")[0] !== userId) : calendars; const [search, setSearch] = useState(""); const [selectedContacts, setSelectedContacts] = useState([]); const [extended, setExtended] = useState(false); const [filters, setFilters] = useState({ searchIn: "my-calendars", keywords: "", organizers: [] as userAttendee[], attendees: [] as userAttendee[], }); const [anchorEl, setAnchorEl] = useState(null); const filterOpen = Boolean(anchorEl); const [filterError, setFilterError] = useState(false); const searchWidth = { xs: "10vw", sm: "20vw", md: "35vw", xl: "35vw", "@media (min-width: 2000px)": { width: "55vw", }, }; const inputRef = useRef(null); const containerRef = useRef(null); const shouldCollapseRef = useRef(false); type FilterField = "searchIn" | "keywords" | "organizers" | "attendees"; const handleFilterChange = ( field: FilterField, value: string | userAttendee[] ) => { setFilters((prev) => ({ ...prev, [field]: value })); if (field === "organizers") { setSelectedContacts( (value as userAttendee[]).map((a: userAttendee) => ({ displayName: a.cn ?? a.cal_address, email: a.cal_address || "", })) ); } }; function buildQuery( searchQuery: string, filters: { searchIn: string; keywords: string; organizers: userAttendee[]; attendees: userAttendee[]; } ) { const trimmedSearch = searchQuery.trim(); const trimmedKeywords = filters.keywords.trim(); // Block search if all search criteria are empty const hasSearchCriteria = trimmedSearch || trimmedKeywords || filters.organizers.length > 0 || filters.attendees.length > 0; if (!hasSearchCriteria) { return; } let searchInCalendars: string[]; if (filters.searchIn === "" || !filters.searchIn) { searchInCalendars = calendars.map((c) => c.id); } else if (filters.searchIn === "my-calendars") { searchInCalendars = personnalCalendars.map((c) => c.id); } else if (filters.searchIn === "shared-calendars") { searchInCalendars = sharedCalendars.map((c) => c.id); } else { searchInCalendars = [filters.searchIn]; } const cleanedFilters = { keywords: trimmedKeywords, organizers: filters.organizers.map((u) => u.cal_address), attendees: filters.attendees.map((u) => u.cal_address), searchIn: searchInCalendars, }; return { search: trimmedSearch, filters: cleanedFilters, }; } const handleClearFilters = () => { setFilters({ searchIn: "my-calendars", keywords: "", organizers: [] as userAttendee[], attendees: [] as userAttendee[], }); setAnchorEl(null); setFilterError(false); }; const handleContactSelect = (_event: any, contacts: User[]) => { setSelectedContacts(contacts); setSearch(""); if (contacts.length > 0) { handleSearch("", { ...filters, organizers: contacts.map((contact) => createAttendee({ cal_address: contact.email, cn: contact.displayName, }) ), }); } }; const handleSearch = async ( searchQuery: string, filters: { searchIn: string; keywords: string; organizers: userAttendee[]; attendees: userAttendee[]; } ) => { const cleanedQuery = buildQuery(searchQuery, filters); if (cleanedQuery) { dispatch(searchEventsAsync(cleanedQuery)); dispatch(setView("search")); setAnchorEl(null); } else { filterOpen && setFilterError(true); } }; useEffect(() => { function handleClickOutside(event: MouseEvent) { const target = event.target as Node; if (filterOpen) { return; } if ( containerRef.current?.contains(target) || inputRef.current?.contains(target) || (target as HTMLElement).closest(".MuiAutocomplete-popper") ) { return; } if (!search.trim() && selectedContacts.length === 0) { setExtended(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [filterOpen, search, selectedContacts]); return ( <> {!extended && ( setExtended(true)}> )} {extended && ( { handleContactSelect(event, users); }} objectTypes={["user", "contact"]} onToggleEventPreview={() => {}} customSlotProps={{ popper: { anchorEl: containerRef.current, placement: "bottom-start", sx: { minWidth: searchWidth, "& .MuiPaper-root": { width: "100%", }, }, modifiers: [ { name: "offset", options: { offset: [0, 8], }, }, ], }, }} customRenderInput={( params: AutocompleteRenderInputParams, query: string, setQuery: (value: string) => void ) => ( { inputRef.current = el; const ref = params.InputProps.ref; if (typeof ref === "function") { ref(el); } else if (ref && "current" in ref) { ( ref as React.MutableRefObject ).current = el; } }} onKeyDown={(e) => { if (e.key === "Enter") { handleSearch(query, filters); } }} onChange={(e) => { const value = e.target.value; setQuery(value); setSearch(value); }} variant="outlined" sx={{ borderRadius: "999px", "& .MuiInputBase-input": { padding: "12px 10px" }, animation: "scaleIn 0.25s ease-out", "@keyframes scaleIn": { from: { transform: "scaleX(0)", opacity: 0 }, to: { transform: "scaleX(1)", opacity: 1 }, }, transformOrigin: "right", "& .MuiOutlinedInput-root": { borderRadius: "999px", height: 40, padding: "0 10px", }, }} InputProps={{ ...params.InputProps, startAdornment: ( <> {params.InputProps.startAdornment} ), endAdornment: ( {(query || selectedContacts.length > 0) && ( { setQuery(""); setSearch(""); handleFilterChange("keywords", ""); setSelectedContacts([]); }} > )} e.preventDefault()} onClick={() => { setAnchorEl(containerRef.current); handleFilterChange("keywords", query); handleFilterChange( "organizers", selectedContacts.map((attendee: User) => createAttendee({ cal_address: attendee.email, cn: attendee.displayName, }) ) ); }} > ), }} /> )} /> )} { if ( !search.trim() && selectedContacts.length === 0 && shouldCollapseRef.current ) { setExtended(false); } shouldCollapseRef.current = false; }, }, }} > {t("search.searchIn")} {t("search.keywords")} { handleFilterChange("keywords", e.target.value); if (e.target.value.trim()) { setFilterError(false); } }} size="small" /> {t("search.organizers")} { handleFilterChange("organizers", users); if (users.length > 0) { setFilterError(false); } }} /> {t("search.participants")} { handleFilterChange("attendees", users); if (users.length > 0) { setFilterError(false); } }} /> ); }