diff --git a/src/components/Attendees/PeopleSearch.tsx b/src/components/Attendees/PeopleSearch.tsx index 110fa27..3939630 100644 --- a/src/components/Attendees/PeopleSearch.tsx +++ b/src/components/Attendees/PeopleSearch.tsx @@ -102,8 +102,8 @@ export function PeopleSearch({ {...params} error={!!error} helperText={error} - placeholder="Search user" - label="Search user" + placeholder="Start typing a name or email" + label="Start typing a name or email" onKeyDown={(e) => { if (e.key === "Enter" && onToggleEventPreview) { e.preventDefault(); diff --git a/src/components/Dialog/ResponsiveDialog.tsx b/src/components/Dialog/ResponsiveDialog.tsx index 21bcd2d..fefb9d9 100644 --- a/src/components/Dialog/ResponsiveDialog.tsx +++ b/src/components/Dialog/ResponsiveDialog.tsx @@ -122,6 +122,11 @@ function ResponsiveDialog({ }; const baseContentSx: SxProps = { + width: "100%", + padding: isExpanded ? "16px" : undefined, + }; + + const contentWrapperSx: SxProps = { maxWidth: isExpanded ? expandedContentMaxWidth : "100%", margin: isExpanded ? "0 auto" : "0", width: "100%", @@ -160,7 +165,13 @@ function ResponsiveDialog({ ]} {...dialogContentProps} > - {children} + {isExpanded ? ( + + {children} + + ) : ( + {children} + )} {actions && {actions}} diff --git a/src/components/Event/EventRepeat.tsx b/src/components/Event/EventRepeat.tsx index df065f9..5bf6011 100644 --- a/src/components/Event/EventRepeat.tsx +++ b/src/components/Event/EventRepeat.tsx @@ -1,6 +1,5 @@ import { FormControl, - InputLabel, Select, SelectChangeEvent, MenuItem, @@ -28,7 +27,6 @@ export default function RepeatEvent({ setRepetition: Function; isOwn?: boolean; }) { - const repetitionValues = ["day", "week", "month", "year"]; const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]; const day = new Date(eventStart); @@ -43,9 +41,11 @@ export default function RepeatEvent({ // keep endOption in sync if repetition changes from parent useEffect(() => { - if (!endOption) { - setEndOption(getEndOption()); + const newEndOption = getEndOption(); + if (endOption !== newEndOption) { + setEndOption(newEndOption); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [repetition.occurrences, repetition.endDate]); const handleDayChange = (day: string) => { @@ -57,37 +57,11 @@ export default function RepeatEvent({ }; return ( - - Repetition - - - {repetition.freq && ( - + + {/* Interval */} - Interval: + Repeat every - - { - repetitionValues[ - repetitionValues.findIndex((el) => el === repetition.freq) - ] - } - + + + {/* Weekly selection */} {repetition.freq === "weekly" && ( - On days: + Repeat on: {days.map((day) => ( @@ -136,7 +125,7 @@ export default function RepeatEvent({ {/* End options */} - + End: - )} - + ); } diff --git a/src/features/Events/EventModal.tsx b/src/features/Events/EventModal.tsx index 9ecf1a6..10b2e55 100644 --- a/src/features/Events/EventModal.tsx +++ b/src/features/Events/EventModal.tsx @@ -11,8 +11,15 @@ import { SelectChangeEvent, TextField, Typography, + ToggleButtonGroup, + ToggleButton, } from "@mui/material"; -import React, { useEffect, useState } from "react"; +import { + Description as DescriptionIcon, + Public as PublicIcon, + Lock as LockIcon, +} from "@mui/icons-material"; +import React, { useEffect, useState, useMemo } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import AttendeeSelector from "../../components/Attendees/AttendeeSearch"; import { ResponsiveDialog } from "../../components/Dialog"; @@ -22,6 +29,7 @@ import { userAttendee } from "../User/userDataTypes"; import { CalendarEvent, RepetitionObject } from "./EventsTypes"; import { createSelector } from "@reduxjs/toolkit"; import RepeatEvent from "../../components/Event/EventRepeat"; +import { TIMEZONES } from "../../utils/timezone-data"; // Helper component for field with label const FieldWithLabel = React.memo( @@ -112,7 +120,50 @@ function EventPopover({ const userPersonnalCalendars: Calendars[] = useAppSelector( selectPersonnalCalendars ); + + // Helper function to resolve timezone aliases + const resolveTimezone = (tzName: string): string => { + if (TIMEZONES.zones[tzName]) { + return tzName; + } + if (TIMEZONES.aliases[tzName]) { + return TIMEZONES.aliases[tzName].aliasTo; + } + return tzName; + }; + + const timezoneList = useMemo(() => { + const zones = Object.keys(TIMEZONES.zones).sort(); + const browserTz = resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone); + + const getTimezoneOffset = (tzName: string): string => { + const resolvedTz = resolveTimezone(tzName); + const tzData = TIMEZONES.zones[resolvedTz]; + if (!tzData) return ""; + + const icsMatch = tzData.ics.match(/TZOFFSETTO:([+-]\d{4})/); + if (!icsMatch) return ""; + + const offset = icsMatch[1]; + const hours = parseInt(offset.slice(0, 3)); + const minutes = parseInt(offset.slice(3)); + + if (minutes === 0) { + return `UTC${hours >= 0 ? '+' : ''}${hours}`; + } + return `UTC${hours >= 0 ? '+' : ''}${hours}:${Math.abs(minutes).toString().padStart(2, '0')}`; + }; + + return { zones, browserTz, getTimezoneOffset }; + }, []); + const [showMore, setShowMore] = useState(false); + const [showDescription, setShowDescription] = useState( + event?.description ? true : false + ); + const [showRepeat, setShowRepeat] = useState( + event?.repetition?.freq ? true : false + ); const [title, setTitle] = useState(event?.title ?? ""); @@ -137,8 +188,10 @@ function EventPopover({ const [alarm, setAlarm] = useState(event?.alarm?.trigger ?? ""); const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC"); const [busy, setBusy] = useState(event?.transp ?? "OPAQUE"); - - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const [important, setImportant] = useState(false); + const [timezone, setTimezone] = useState( + event?.timezone ? resolveTimezone(event.timezone) : timezoneList.browserTz + ); useEffect(() => { if (selectedRange) { @@ -160,11 +213,15 @@ function EventPopover({ onClose({}, "backdropClick"); // Reset state setShowMore(false); + setShowDescription(false); + setShowRepeat(false); setTitle(""); setDescription(""); setAttendees([]); setLocation(""); setCalendarid(0); + setImportant(false); + setTimezone(timezoneList.browserTz); }; const handleSave = async () => { @@ -210,11 +267,15 @@ function EventPopover({ // Reset state setShowMore(false); + setShowDescription(false); + setShowRepeat(false); setTitle(""); setDescription(""); setAttendees([]); setLocation(""); setCalendarid(0); + setImportant(false); + setTimezone(timezoneList.browserTz); // Save to API in background dispatch( @@ -254,46 +315,47 @@ function EventPopover({ setTitle(e.target.value)} size="small" margin="dense" /> - - setDescription(e.target.value)} - size="small" - margin="dense" - multiline - rows={2} - /> - - - - {!showMore && ( - Calendar - )} - - + Add description + + + {showDescription && ( + + setDescription(e.target.value)} + size="small" + margin="dense" + multiline + rows={2} + /> + + )} + @@ -355,7 +417,17 @@ function EventPopover({ - + + setImportant(!important)} + /> + } + label="Mark as important" + sx={{ padding: "0 8px 0 0" }} + /> + { + setShowRepeat(!showRepeat); + if (showRepeat) { + setRepetition({} as RepetitionObject); + } else { + setRepetition({ + freq: "daily", + interval: 1, + } as RepetitionObject); + } + }} + /> + } + label="Repeat" + /> + + + - + + {showRepeat && ( + + + + )} + + setLocation(e.target.value)} size="small" margin="dense" /> + + + {!showMore && ( + Calendar + )} + + + + {/* Extended options */} {showMore && ( <> - - - - + - setEventClass(e.target.value) - } - > - Public - Show time only - Private - - - - +