diff --git a/src/App.tsx b/src/App.tsx index 449cb1c..441f905 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,12 @@ import { Suspense } from "react"; import { Route, Routes } from "react-router-dom"; import { HistoryRouter as Router } from "redux-first-history/rr6"; -import { Menubar } from "./components/Menubar/Menubar"; import { CallbackResume } from "./features/User/LoginCallback"; import { history } from "./app/store"; import "./App.css"; import { Loading } from "./components/Loading/Loading"; import HandleLogin from "./features/User/HandleLogin"; -import CalendarApp from "./components/Calendar/Calendar"; +import CalendarLayout from "./components/Calendar/CalendarLayout"; import { Error } from "./components/Error/Error"; function App() { return ( @@ -15,15 +14,7 @@ function App() { } /> - - - - - } - /> + } /> } /> } /> diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index ba735f0..f0393ce 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -7,15 +7,13 @@ import { CalendarApi, DateSelectArg } from "@fullcalendar/core"; import ReactCalendar from "react-calendar"; import "./Calendar.css"; import "./CustomCalendar.css"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import EventPopover from "../../features/Events/EventModal"; -import CalendarPopover from "../../features/Calendars/CalendarModal"; import { CalendarEvent } from "../../features/Events/EventsTypes"; import CalendarSelection from "./CalendarSelection"; import { getCalendarDetailAsync, - getCalendarsListAsync, getEventAsync, putEventAsync, updateEventLocal, @@ -40,12 +38,15 @@ import { userAttendee } from "../../features/User/userDataTypes"; import { TempCalendarsInput } from "./TempCalendarsInput"; import Button from "@mui/material/Button"; -export default function CalendarApp() { - const calendarRef = useRef(null); +interface CalendarAppProps { + calendarRef: React.RefObject; + onDateChange?: (date: Date) => void; +} + +export default function CalendarApp({ calendarRef, onDateChange }: CalendarAppProps) { const [selectedDate, setSelectedDate] = useState(new Date()); const [selectedMiniDate, setSelectedMiniDate] = useState(new Date()); const tokens = useAppSelector((state) => state.user.tokens); - const user = useAppSelector((state) => state.user.userData); const dispatch = useAppDispatch(); if (!tokens) { @@ -115,15 +116,20 @@ export default function CalendarApp() { ); useEffect(() => { - updateCalsDetails( - selectedCalendars, - pending, - calendars, - rangeKey, - dispatch, - calendarRange - ); - }, [rangeKey, selectedCalendars]); + selectedCalendars.forEach((id) => { + if (!pending && rangeKey) { + dispatch( + getCalendarDetailAsync({ + calId: id, + match: { + start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), + end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), + }, + }) + ); + } + }); + }, [rangeKey, selectedCalendars, pending, dispatch, calendarRange.start, calendarRange.end]); useEffect(() => { updateCalsDetails( @@ -327,29 +333,7 @@ export default function CalendarApp() { height={"100%"} select={handleDateSelect} nowIndicator - customButtons={{ - refresh: { - text: "↻", - click: async () => { - await dispatch(getCalendarsListAsync()); - selectedCalendars.forEach((id) => { - if (!pending && rangeKey) { - dispatch( - getCalendarDetailAsync({ - calId: id, - match: { - start: formatDateToYYYYMMDDTHHMMSS( - calendarRange.start - ), - end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), - }, - }) - ); - } - }); - }, - }, - }} + headerToolbar={false} views={{ timeGridWeek: { titleFormat: { month: "long", year: "numeric" } }, }} @@ -372,15 +356,21 @@ export default function CalendarApp() { hour12: false, }} datesSet={(arg) => { + // Get the current date from calendar API to ensure consistency + const calendarCurrentDate = calendarRef.current?.getDate() || new Date(arg.start); + if (arg.view.type === "dayGridMonth") { setSelectedDate(new Date(arg.start)); - const midTimestamp = - (arg.start.getTime() + arg.end.getTime()) / 2; - setSelectedMiniDate(new Date(midTimestamp)); + setSelectedMiniDate(calendarCurrentDate); } else { setSelectedDate(new Date(arg.start)); setSelectedMiniDate(new Date(arg.start)); } + + // Always use the calendar's current date for consistency + if (onDateChange) { + onDateChange(calendarCurrentDate); + } }} dayHeaderContent={(arg) => { const date = arg.date.getDate(); @@ -695,11 +685,6 @@ export default function CalendarApp() { putEventAsync({ cal: calendars[newEvent.calId], newEvent }) ); }} - headerToolbar={{ - left: "title", - center: "prev,today,next", - right: "refresh,dayGridMonth,timeGridWeek,timeGridDay", - }} eventContent={(arg) => { const event = arg.event; if ( diff --git a/src/components/Calendar/CalendarLayout.tsx b/src/components/Calendar/CalendarLayout.tsx new file mode 100644 index 0000000..ff92293 --- /dev/null +++ b/src/components/Calendar/CalendarLayout.tsx @@ -0,0 +1,64 @@ +import React, { useRef, useState } from "react"; +import { Menubar, MenubarProps } from "../Menubar/Menubar"; +import CalendarApp from "./Calendar"; +import { useAppDispatch } from "../../app/hooks"; +import { + getCalendarDetailAsync, + getCalendarsListAsync, +} from "../../features/Calendars/CalendarSlice"; +import { + formatDateToYYYYMMDDTHHMMSS, + getCalendarRange, +} from "../../utils/dateUtils"; +import { useAppSelector } from "../../app/hooks"; + +export default function CalendarLayout() { + const calendarRef = useRef(null); + const dispatch = useAppDispatch(); + const selectedCalendars = useAppSelector((state) => state.calendars.list); + const userId = useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; + const [currentDate, setCurrentDate] = useState(new Date()); + + const handleRefresh = async () => { + await dispatch(getCalendarsListAsync()); + + // Get current calendar range + if (calendarRef.current) { + const view = calendarRef.current.getApi().view; + const calendarRange = getCalendarRange(view.activeStart); + + // Refresh events for selected calendars + Object.keys(selectedCalendars).forEach((id) => { + if (id.split("/")[0] === userId) { + dispatch( + getCalendarDetailAsync({ + calId: id, + match: { + start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), + end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), + }, + }) + ); + } + }); + } + }; + + const handleDateChange = (date: Date) => { + setCurrentDate(date); + }; + + const menubarProps: MenubarProps = { + calendarRef, + onRefresh: handleRefresh, + currentDate, + onDateChange: handleDateChange, + }; + + return ( +
+ + +
+ ); +} diff --git a/src/components/Menubar/Menubar.css b/src/components/Menubar/Menubar.css index 2fcffb0..a3f4c82 100644 --- a/src/components/Menubar/Menubar.css +++ b/src/components/Menubar/Menubar.css @@ -93,3 +93,8 @@ font-size: 14px; text-align: center; } + +.left-menu { + display: flex; + align-items: center; +} diff --git a/src/components/Menubar/Menubar.tsx b/src/components/Menubar/Menubar.tsx index 3850f71..8207b79 100644 --- a/src/components/Menubar/Menubar.tsx +++ b/src/components/Menubar/Menubar.tsx @@ -1,11 +1,25 @@ import React, { useState } from "react"; import logo from "../../static/images/calendar.svg"; import AppsIcon from "@mui/icons-material/Apps"; +import RefreshIcon from "@mui/icons-material/Refresh"; +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import "./Menubar.css"; import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { stringToColor } from "../../features/Events/EventDisplay"; -import { Avatar, IconButton, Popover } from "@mui/material"; +import { + Avatar, + IconButton, + Popover, + ButtonGroup, + Button, + Select, + MenuItem, + FormControl, + Typography +} from "@mui/material"; import { push } from "redux-first-history"; +import { CalendarApi } from "@fullcalendar/core"; export type AppIconProps = { name: string; @@ -13,10 +27,18 @@ export type AppIconProps = { icon: string; }; -export function Menubar() { +export type MenubarProps = { + calendarRef: React.RefObject; + onRefresh: () => void; + currentDate: Date; + onDateChange?: (date: Date) => void; +}; + +export function Menubar({ calendarRef, onRefresh, currentDate, onDateChange }: MenubarProps) { const user = useAppSelector((state) => state.user.userData); const applist: AppIconProps[] = (window as any).appList ?? []; const [anchorEl, setAnchorEl] = useState(null); + const [currentView, setCurrentView] = useState("timeGridWeek"); const dispatch = useAppDispatch(); if (!user) { @@ -30,37 +52,112 @@ export function Menubar() { setAnchorEl(null); }; + const handleNavigation = (action: 'prev' | 'next' | 'today') => { + if (!calendarRef.current) return; + + switch (action) { + case 'prev': + calendarRef.current.prev(); + break; + case 'next': + calendarRef.current.next(); + break; + case 'today': + calendarRef.current.today(); + break; + } + + // Notify parent about date change after navigation + if (onDateChange) { + const newDate = calendarRef.current.getDate(); + onDateChange(newDate); + } + }; + + const handleViewChange = (view: string) => { + if (!calendarRef.current) return; + setCurrentView(view); + calendarRef.current.changeView(view); + + // Notify parent about date change after view change + if (onDateChange) { + const newDate = calendarRef.current.getDate(); + onDateChange(newDate); + } + }; + + const handleRefresh = () => { + onRefresh(); + }; + + const open = Boolean(anchorEl); return ( <>
- -
-

big search bar

-
+
+ + +
+ + + + + +
-
- {applist.length > 0 && ( - - - - )} - - {user?.name && user?.family_name - ? `${user.name[0]}${user.family_name[0]}` - : (user?.email?.[0] ?? "")} - +
+ + {currentDate.toLocaleDateString('en-US', { + month: 'long', + year: 'numeric' + })} + +
+
+
+
+ + + + + {applist.length > 0 && ( + + + + )} + + + {user?.name && user?.family_name + ? `${user.name[0]}${user.family_name[0]}` + : (user?.email?.[0] ?? "")} + +
- + Calendar

Twake Calendar @@ -102,6 +199,7 @@ function AppIcon({ prop }: { prop: AppIconProps }) {