[#197] added missing infos
This commit is contained in:
committed by
Benoit TELLIER
parent
ed8bb41ca9
commit
6411ed636b
@@ -155,8 +155,10 @@ describe("Event Preview Display", () => {
|
|||||||
/>,
|
/>,
|
||||||
preloadedState
|
preloadedState
|
||||||
);
|
);
|
||||||
fireEvent.click(screen.getByTestId("MoreVertIcon"));
|
const moreButton = screen.getByTestId("MoreVertIcon");
|
||||||
expect(screen.queryByText("Delete event")).not.toBeInTheDocument();
|
if (moreButton) {
|
||||||
|
expect(screen.queryByText("Delete event")).not.toBeInTheDocument();
|
||||||
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
// Renders the personnal cal event
|
// Renders the personnal cal event
|
||||||
renderWithProviders(
|
renderWithProviders(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import dayGridPlugin from "@fullcalendar/daygrid";
|
|||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
import interactionPlugin from "@fullcalendar/interaction";
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
|
||||||
import ReactCalendar from "react-calendar";
|
|
||||||
import "./Calendar.styl";
|
import "./Calendar.styl";
|
||||||
import "./CustomCalendar.styl";
|
import "./CustomCalendar.styl";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
@@ -330,25 +329,39 @@ export default function CalendarApp({
|
|||||||
filteredTempEvents,
|
filteredTempEvents,
|
||||||
userId
|
userId
|
||||||
)}
|
)}
|
||||||
weekNumbers
|
weekNumbers={
|
||||||
|
currentView === "timeGridWeek" || currentView === "timeGridDay"
|
||||||
|
}
|
||||||
weekNumberFormat={{ week: "long" }}
|
weekNumberFormat={{ week: "long" }}
|
||||||
weekNumberContent={(arg) => {
|
weekNumberContent={(arg) => {
|
||||||
const showSelector =
|
|
||||||
currentView === "timeGridWeek" || currentView === "timeGridDay";
|
|
||||||
return (
|
return (
|
||||||
<div className="weekSelector">
|
<div className="weekSelector">
|
||||||
<div>{arg.text}</div>
|
<div>{arg.text}</div>
|
||||||
{showSelector && (
|
<TimezoneSelector
|
||||||
<TimezoneSelector
|
value={timezone}
|
||||||
value={timezone}
|
onChange={(newTimezone: string) =>
|
||||||
onChange={(newTimezone: string) =>
|
dispatch(setTimeZone(newTimezone))
|
||||||
dispatch(setTimeZone(newTimezone))
|
}
|
||||||
}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
dayCellContent={(arg) => {
|
||||||
|
const month = arg.date.toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
});
|
||||||
|
if (arg.view.type === "dayGridMonth") {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={`fc-daygrid-day-number ${
|
||||||
|
arg.isToday ? "current-date" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{arg.dayNumberText === "1" ? month : ""} {arg.dayNumberText}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
slotDuration={"00:30:00"}
|
slotDuration={"00:30:00"}
|
||||||
slotLabelInterval={"01:00:00"}
|
slotLabelInterval={"01:00:00"}
|
||||||
scrollTime="12:00:00"
|
scrollTime="12:00:00"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
|||||||
import { getDeltaInMilliseconds } from "../../../utils/dateUtils";
|
import { getDeltaInMilliseconds } from "../../../utils/dateUtils";
|
||||||
import {
|
import {
|
||||||
getCalendarDetailAsync,
|
getCalendarDetailAsync,
|
||||||
|
getEventAsync,
|
||||||
putEventAsync,
|
putEventAsync,
|
||||||
updateEventInstanceAsync,
|
updateEventInstanceAsync,
|
||||||
updateEventLocal,
|
updateEventLocal,
|
||||||
@@ -97,6 +98,21 @@ export const createEventHandlers = (props: EventHandlersProps) => {
|
|||||||
window.open(info.event.url);
|
window.open(info.event.url);
|
||||||
} else {
|
} else {
|
||||||
setOpenEventDisplay(true);
|
setOpenEventDisplay(true);
|
||||||
|
if (
|
||||||
|
calendars[info.event.extendedProps.calId] &&
|
||||||
|
calendars[info.event.extendedProps.calId].events[
|
||||||
|
info.event.extendedProps.uid
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
getEventAsync(
|
||||||
|
calendars[info.event.extendedProps.calId].events[
|
||||||
|
info.event.extendedProps.uid
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setEventDisplayedId(info.event.extendedProps.uid);
|
setEventDisplayedId(info.event.extendedProps.uid);
|
||||||
setEventDisplayedCalId(info.event.extendedProps.calId);
|
setEventDisplayedCalId(info.event.extendedProps.calId);
|
||||||
setEventDisplayedTemp(info.event._def.extendedProps.temp);
|
setEventDisplayedTemp(info.event._def.extendedProps.temp);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const useCalendarEventHandlers = (props: EventHandlersProps) => {
|
|||||||
props.setEventDisplayedId,
|
props.setEventDisplayedId,
|
||||||
props.setEventDisplayedCalId,
|
props.setEventDisplayedCalId,
|
||||||
props.setEventDisplayedTemp,
|
props.setEventDisplayedTemp,
|
||||||
|
props.calendars,
|
||||||
]),
|
]),
|
||||||
handleEventAllow: useCallback(eventHandlers.handleEventAllow, []),
|
handleEventAllow: useCallback(eventHandlers.handleEventAllow, []),
|
||||||
handleEventDrop: useCallback(eventHandlers.handleEventDrop, [
|
handleEventDrop: useCallback(eventHandlers.handleEventDrop, [
|
||||||
|
|||||||
@@ -10,15 +10,19 @@ import NotificationsNoneIcon from "@mui/icons-material/NotificationsNone";
|
|||||||
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
|
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
|
||||||
import SubjectIcon from "@mui/icons-material/Subject";
|
import SubjectIcon from "@mui/icons-material/Subject";
|
||||||
import VideocamIcon from "@mui/icons-material/Videocam";
|
import VideocamIcon from "@mui/icons-material/Videocam";
|
||||||
|
import RepeatIcon from "@mui/icons-material/Repeat";
|
||||||
|
import LockOutlineIcon from "@mui/icons-material/LockOutline";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
Chip,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import AvatarGroup from "@mui/material/AvatarGroup";
|
import AvatarGroup from "@mui/material/AvatarGroup";
|
||||||
@@ -40,6 +44,7 @@ import { renderAttendeeBadge } from "../../components/Event/utils/eventUtils";
|
|||||||
import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector";
|
import { getTimezoneOffset } from "../../components/Calendar/TimezoneSelector";
|
||||||
import { getCalendarRange } from "../../utils/dateUtils";
|
import { getCalendarRange } from "../../utils/dateUtils";
|
||||||
import { updateTempCalendar } from "../../components/Calendar/utils/calendarUtils";
|
import { updateTempCalendar } from "../../components/Calendar/utils/calendarUtils";
|
||||||
|
import { CalendarEvent } from "./EventsTypes";
|
||||||
export default function EventPreviewModal({
|
export default function EventPreviewModal({
|
||||||
eventId,
|
eventId,
|
||||||
calId,
|
calId,
|
||||||
@@ -68,6 +73,7 @@ export default function EventPreviewModal({
|
|||||||
const user = useAppSelector((state) => state.user);
|
const user = useAppSelector((state) => state.user);
|
||||||
|
|
||||||
const isRecurring = event?.uid?.includes("/");
|
const isRecurring = event?.uid?.includes("/");
|
||||||
|
const isOwn = calendar.ownerEmails?.includes(user.userData.email);
|
||||||
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
||||||
const [openFullDisplay, setOpenFullDisplay] = useState(false);
|
const [openFullDisplay, setOpenFullDisplay] = useState(false);
|
||||||
const [openUpdateModal, setOpenUpdateModal] = useState(false);
|
const [openUpdateModal, setOpenUpdateModal] = useState(false);
|
||||||
@@ -177,12 +183,14 @@ export default function EventPreviewModal({
|
|||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
<IconButton
|
{((event.class !== "PRIVATE" && !isOwn) || isOwn) && (
|
||||||
size="small"
|
<IconButton
|
||||||
onClick={(e) => setToggleActionMenu(e.currentTarget)}
|
size="small"
|
||||||
>
|
onClick={(e) => setToggleActionMenu(e.currentTarget)}
|
||||||
<MoreVertIcon />
|
>
|
||||||
</IconButton>
|
<MoreVertIcon />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
<Menu
|
<Menu
|
||||||
open={Boolean(toggleActionMenu)}
|
open={Boolean(toggleActionMenu)}
|
||||||
onClose={() => setToggleActionMenu(null)}
|
onClose={() => setToggleActionMenu(null)}
|
||||||
@@ -247,16 +255,44 @@ export default function EventPreviewModal({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
<Typography
|
<Box display="flex" flexDirection="row">
|
||||||
variant="inherit"
|
{event.class === "PRIVATE" &&
|
||||||
fontWeight="bold"
|
(isOwn ? (
|
||||||
style={{
|
<Tooltip
|
||||||
wordBreak: "break-word",
|
title={
|
||||||
}}
|
"Only you and attendees can see the details of this event."
|
||||||
gutterBottom
|
}
|
||||||
>
|
placement="top"
|
||||||
{event.title}
|
>
|
||||||
</Typography>
|
<LockOutlineIcon />
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<LockOutlineIcon />
|
||||||
|
))}
|
||||||
|
<Typography
|
||||||
|
variant="inherit"
|
||||||
|
fontWeight="bold"
|
||||||
|
style={{
|
||||||
|
wordBreak: "break-word",
|
||||||
|
}}
|
||||||
|
gutterBottom
|
||||||
|
>
|
||||||
|
{event.title}
|
||||||
|
</Typography>
|
||||||
|
{event.transp === "TRANSPARENT" && (
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
"Others see you as available during the time range of this event."
|
||||||
|
}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
icon={<CircleIcon color="success" fontSize="small" />}
|
||||||
|
label="Free"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
<Typography variant="body2" color="textSecondary" gutterBottom>
|
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||||
{formatDate(event.start, event.allday)}
|
{formatDate(event.start, event.allday)}
|
||||||
{event.end &&
|
{event.end &&
|
||||||
@@ -387,99 +423,140 @@ export default function EventPreviewModal({
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{/* Video */}
|
{((event.class !== "PRIVATE" && !isOwn) || isOwn) && (
|
||||||
{event.x_openpass_videoconference && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<VideocamIcon style={{ fontSize: 18 }} />}
|
|
||||||
content={
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={() => window.open(event.x_openpass_videoconference)}
|
|
||||||
>
|
|
||||||
Join the video conference
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* Attendees */}
|
|
||||||
{attendees?.length > 0 && (
|
|
||||||
<>
|
<>
|
||||||
<InfoRow
|
{/* Video */}
|
||||||
icon={<PeopleAltOutlinedIcon />}
|
{event.x_openpass_videoconference && (
|
||||||
content={
|
<InfoRow
|
||||||
<Box
|
icon={<VideocamIcon style={{ fontSize: 18 }} />}
|
||||||
style={{
|
content={
|
||||||
marginBottom: 1,
|
<Button
|
||||||
display: "flex",
|
variant="contained"
|
||||||
flexDirection: "row",
|
onClick={() =>
|
||||||
}}
|
window.open(event.x_openpass_videoconference)
|
||||||
>
|
}
|
||||||
<Box>
|
|
||||||
<Typography>{attendees.length} guests</Typography>
|
|
||||||
<Typography>
|
|
||||||
{
|
|
||||||
attendees.filter((a) => a.partstat === "ACCEPTED")
|
|
||||||
.length
|
|
||||||
}{" "}
|
|
||||||
yes,{" "}
|
|
||||||
{
|
|
||||||
attendees.filter((a) => a.partstat === "DECLINED")
|
|
||||||
.length
|
|
||||||
}{" "}
|
|
||||||
no
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
{!showAllAttendees && (
|
|
||||||
<AvatarGroup max={attendeeDisplayLimit}>
|
|
||||||
{organizer &&
|
|
||||||
renderAttendeeBadge(
|
|
||||||
organizer,
|
|
||||||
"org",
|
|
||||||
showAllAttendees,
|
|
||||||
true
|
|
||||||
)}
|
|
||||||
{visibleAttendees.map((a, idx) =>
|
|
||||||
renderAttendeeBadge(a, idx.toString(), showAllAttendees)
|
|
||||||
)}
|
|
||||||
</AvatarGroup>
|
|
||||||
)}
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="primary"
|
|
||||||
style={{ cursor: "pointer", marginTop: 0.5 }}
|
|
||||||
onClick={() => setShowAllAttendees(!showAllAttendees)}
|
|
||||||
>
|
>
|
||||||
{showAllAttendees ? "Show less" : `Show more `}
|
Join the video conference
|
||||||
</Typography>
|
</Button>
|
||||||
</Box>
|
}
|
||||||
}
|
/>
|
||||||
/>
|
)}
|
||||||
|
{/* Attendees */}
|
||||||
|
{attendees?.length > 0 && (
|
||||||
|
<>
|
||||||
|
<InfoRow
|
||||||
|
icon={<PeopleAltOutlinedIcon />}
|
||||||
|
content={
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
marginBottom: 1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography>{attendees.length} guests</Typography>
|
||||||
|
<Typography>
|
||||||
|
{
|
||||||
|
attendees.filter((a) => a.partstat === "ACCEPTED")
|
||||||
|
.length
|
||||||
|
}{" "}
|
||||||
|
yes,{" "}
|
||||||
|
{
|
||||||
|
attendees.filter((a) => a.partstat === "DECLINED")
|
||||||
|
.length
|
||||||
|
}{" "}
|
||||||
|
no
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
{!showAllAttendees && (
|
||||||
|
<AvatarGroup max={attendeeDisplayLimit}>
|
||||||
|
{organizer &&
|
||||||
|
renderAttendeeBadge(
|
||||||
|
organizer,
|
||||||
|
"org",
|
||||||
|
showAllAttendees,
|
||||||
|
true
|
||||||
|
)}
|
||||||
|
{visibleAttendees.map((a, idx) =>
|
||||||
|
renderAttendeeBadge(
|
||||||
|
a,
|
||||||
|
idx.toString(),
|
||||||
|
showAllAttendees
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</AvatarGroup>
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
style={{ cursor: "pointer", marginTop: 0.5 }}
|
||||||
|
onClick={() => setShowAllAttendees(!showAllAttendees)}
|
||||||
|
>
|
||||||
|
{showAllAttendees ? "Show less" : `Show more `}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{showAllAttendees &&
|
||||||
|
organizer &&
|
||||||
|
renderAttendeeBadge(organizer, "org", showAllAttendees, true)}
|
||||||
|
{showAllAttendees &&
|
||||||
|
visibleAttendees.map((a, idx) =>
|
||||||
|
renderAttendeeBadge(a, idx.toString(), showAllAttendees)
|
||||||
|
)}
|
||||||
|
{/* Location */}
|
||||||
|
{event.location && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<LocationOnOutlinedIcon />}
|
||||||
|
text={event.location}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Description */}
|
||||||
|
{event.description && (
|
||||||
|
<InfoRow icon={<SubjectIcon />} text={event.description} />
|
||||||
|
)}
|
||||||
|
{/* ALARM */}
|
||||||
|
{event.alarm && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<NotificationsNoneIcon />}
|
||||||
|
text={`${event.alarm.trigger} before by ${event.alarm.action}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Repetition */}
|
||||||
|
{event.repetition && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<RepeatIcon />}
|
||||||
|
text={makeRecurrenceString(event)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{showAllAttendees &&
|
{event.class === "PRIVATE" && !isOwn && (
|
||||||
organizer &&
|
<Box
|
||||||
renderAttendeeBadge(organizer, "org", showAllAttendees, true)}
|
sx={{
|
||||||
{showAllAttendees &&
|
backgroundColor: "#F3F4F6",
|
||||||
visibleAttendees.map((a, idx) =>
|
height: 48,
|
||||||
renderAttendeeBadge(a, idx.toString(), showAllAttendees)
|
borderRadius: "8px",
|
||||||
)}
|
gap: "16px",
|
||||||
{/* Location */}
|
paddingTop: "16px",
|
||||||
{event.location && (
|
paddingBottom: " 16px",
|
||||||
<InfoRow icon={<LocationOnOutlinedIcon />} text={event.location} />
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
fontFamily={"Inter"}
|
||||||
|
fontWeight={500}
|
||||||
|
fontSize={"12px"}
|
||||||
|
lineHeight={"16px"}
|
||||||
|
letterSpacing={"0.5px"}
|
||||||
|
textAlign={"center"}
|
||||||
|
>
|
||||||
|
This is a private event. Details are hidden.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
{/* Description */}
|
|
||||||
{event.description && (
|
|
||||||
<InfoRow icon={<SubjectIcon />} text={event.description} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Description */}
|
|
||||||
{event.alarm && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<NotificationsNoneIcon />}
|
|
||||||
text={`${event.alarm.trigger} before by ${event.alarm.action}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Error */}
|
{/* Error */}
|
||||||
{event.error && (
|
{event.error && (
|
||||||
<InfoRow
|
<InfoRow
|
||||||
@@ -488,7 +565,6 @@ export default function EventPreviewModal({
|
|||||||
error
|
error
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Calendar color dot */}
|
{/* Calendar color dot */}
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
@@ -508,7 +584,6 @@ export default function EventPreviewModal({
|
|||||||
/>
|
/>
|
||||||
<Typography variant="body2">{calendar.name}</Typography>
|
<Typography variant="body2">{calendar.name}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{currentUserAttendee && !event.repetition && (
|
{currentUserAttendee && !event.repetition && (
|
||||||
<>
|
<>
|
||||||
<Divider variant="fullWidth" />
|
<Divider variant="fullWidth" />
|
||||||
@@ -549,6 +624,33 @@ export default function EventPreviewModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeRecurrenceString(event: CalendarEvent): string | undefined {
|
||||||
|
if (!event.repetition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const recur = [`Reccurent Event · ${event.repetition.freq}`];
|
||||||
|
const recurType: Record<string, string> = {
|
||||||
|
daily: "days",
|
||||||
|
monthly: "months",
|
||||||
|
yearly: "years",
|
||||||
|
};
|
||||||
|
if (event.repetition.byday) {
|
||||||
|
recur.push(`on ${event.repetition.byday.join(", ")}`);
|
||||||
|
}
|
||||||
|
if (event.repetition.interval && event.repetition.interval > 1) {
|
||||||
|
recur.push(
|
||||||
|
`every ${event.repetition.interval} ${recurType[event.repetition.freq]}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (event.repetition.occurrences) {
|
||||||
|
recur.push(`for ${event.repetition.occurrences} occurences`);
|
||||||
|
}
|
||||||
|
if (event.repetition.endDate) {
|
||||||
|
recur.push(`until ${event.repetition.endDate}`);
|
||||||
|
}
|
||||||
|
return recur.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(date: Date | string, allday?: boolean) {
|
function formatDate(date: Date | string, allday?: boolean) {
|
||||||
if (allday) {
|
if (allday) {
|
||||||
return new Date(date).toLocaleDateString(undefined, {
|
return new Date(date).toLocaleDateString(undefined, {
|
||||||
|
|||||||
@@ -94,7 +94,11 @@ export function parseCalendarEvent(
|
|||||||
case "rrule":
|
case "rrule":
|
||||||
event.repetition = { freq: value.freq.toLowerCase() };
|
event.repetition = { freq: value.freq.toLowerCase() };
|
||||||
if (value.byday) {
|
if (value.byday) {
|
||||||
event.repetition.byday = value.byday;
|
if (typeof value.byday === "string") {
|
||||||
|
event.repetition.byday = [value.byday];
|
||||||
|
} else {
|
||||||
|
event.repetition.byday = value.byday;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (value.until) {
|
if (value.until) {
|
||||||
event.repetition.endDate = value.until;
|
event.repetition.endDate = value.until;
|
||||||
|
|||||||
Reference in New Issue
Block a user