Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"prettier": "^3.5.1",
"react": ">=19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.3.0",
"react-router": "^7.3.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rrule": "^2.8.1",
"ts-jest": "^29.2.5",
Expand Down
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Scheduler } from "./lib";
import { EVENTS } from "./events";
import { useRef } from "react";
import { SchedulerRef } from "./lib/types";
import { Link } from "react-router-dom";
import { Link } from "react-router";

function App() {
const calendarRef = useRef<SchedulerRef>(null);
Expand Down
2 changes: 1 addition & 1 deletion src/Page1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Scheduler } from "./lib";
import { EVENTS } from "./events";
import { useRef } from "react";
import { SchedulerRef } from "./lib/types";
import { Link } from "react-router-dom";
import { Link } from "react-router";

const events = EVENTS.slice(3, 6);

Expand Down
116 changes: 48 additions & 68 deletions src/events.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import { datetime, RRule } from "rrule";
import { ProcessedEvent } from "./lib/types";

const createDate = (
startHour: number,
startMinutes: number = 0,
days: number = 0,
months: number = 0
) => {
const date = new Date();
date.setHours(startHour);
date.setMinutes(startMinutes);
date.setDate(date.getDate() + days);
date.setMonth(date.getMonth() + months);

return date;
};

export const EVENTS: ProcessedEvent[] = [
{
event_id: 1,
title: "Event 1 (Disabled)",
subtitle: "This event is disabled",
start: new Date(new Date(new Date().setHours(9)).setMinutes(0)),
end: new Date(new Date(new Date().setHours(10)).setMinutes(0)),
start: createDate(9),
end: createDate(10),
disabled: true,
admin_id: [1, 2, 3, 4],
},
{
event_id: 2,
title: "Event 2",
subtitle: "This event is draggable",
start: new Date(new Date(new Date().setHours(10)).setMinutes(0)),
end: new Date(new Date(new Date().setHours(12)).setMinutes(0)),
start: createDate(10),
end: createDate(12),
admin_id: 2,
color: "#50b500",
agendaAvatar: "E",
Expand All @@ -25,21 +40,17 @@ export const EVENTS: ProcessedEvent[] = [
event_id: 3,
title: "Event 3",
subtitle: "This event is not editable",
start: new Date(new Date(new Date().setHours(11)).setMinutes(0)),
end: new Date(new Date(new Date().setHours(12)).setMinutes(0)),
start: createDate(11),
end: createDate(12),
admin_id: 1,
editable: false,
deletable: false,
},
{
event_id: 4,
title: "Event 4",
start: new Date(
new Date(new Date(new Date().setHours(9)).setMinutes(30)).setDate(new Date().getDate() - 2)
),
end: new Date(
new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate(new Date().getDate() - 2)
),
start: createDate(9, 30, -2),
end: createDate(11, 0, -2),
admin_id: [2, 3],
color: "#900000",
allDay: true,
Expand All @@ -48,23 +59,17 @@ export const EVENTS: ProcessedEvent[] = [
event_id: 5,
title: "Event 5",
subtitle: "This event is editable",
start: new Date(
new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() - 2)
),
end: new Date(
new Date(new Date(new Date().setHours(14)).setMinutes(0)).setDate(new Date().getDate() - 2)
),
start: createDate(10, 30, -2),
end: createDate(14, 0, -2),
admin_id: 2,
editable: true,
},
{
event_id: 6,
title: "Event 6",
subtitle: "This event is all day",
start: new Date(
new Date(new Date(new Date().setHours(20)).setMinutes(30)).setDate(new Date().getDate() - 3)
),
end: new Date(new Date(new Date().setHours(23)).setMinutes(0)),
start: createDate(20, 30, -3),
end: createDate(23),
admin_id: 2,
allDay: true,
sx: { color: "purple" },
Expand All @@ -73,12 +78,8 @@ export const EVENTS: ProcessedEvent[] = [
event_id: 7,
title: "Event 7 (Not draggable)",
subtitle: "This event is not draggable",
start: new Date(
new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() - 3)
),
end: new Date(
new Date(new Date(new Date().setHours(14)).setMinutes(30)).setDate(new Date().getDate() - 3)
),
start: createDate(10, 30, -3),
end: createDate(14, 30, -3),
admin_id: 1,
draggable: false,
color: "#8000cc",
Expand All @@ -87,66 +88,45 @@ export const EVENTS: ProcessedEvent[] = [
event_id: 8,
title: "Event 8",
subtitle: "This event has a custom color",
start: new Date(
new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() + 30)
),
end: new Date(
new Date(new Date(new Date().setHours(14)).setMinutes(30)).setDate(new Date().getDate() + 30)
),
start: createDate(10, 30, 30),
end: createDate(14, 30, 30),
admin_id: 1,
color: "#8000cc",
},
{
event_id: 9,
title: "Event 9",
subtitle: `This event is a recurring weekly until ${new Date(
new Date().setMonth(
new Date(
new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate(
new Date().getDate() + 1
)
).getMonth() + 1
)
).toDateString()}`,
start: new Date(
new Date(new Date(new Date().setHours(10)).setMinutes(0)).setDate(new Date().getDate() + 1)
),
end: new Date(
new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate(new Date().getDate() + 1)
),
subtitle: `This event is a recurring weekly until ${createDate(11, 0, 1, 1).toDateString()}`,
start: createDate(10, 0, 1),
end: createDate(11, 0, 1),
recurring: new RRule({
freq: RRule.WEEKLY,
dtstart: convertDateToRRuleDate(
new Date(
new Date(new Date(new Date().setHours(10)).setMinutes(0)).setDate(
new Date().getDate() - 20
)
)
),
until: new Date(
new Date().setMonth(
new Date(
new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate(
new Date().getDate() + 1
)
).getMonth() + 1
)
),
dtstart: convertDateToRRuleDate(createDate(11, 0, -20)),
until: createDate(11, 0, 1, 1),
}),
},
{
event_id: 10,
title: "Event 10",
subtitle: "This event is a recurring hourly 3 times",
start: new Date(new Date(new Date().setHours(14)).setMinutes(15)),
end: new Date(new Date(new Date().setHours(14)).setMinutes(45)),
start: createDate(14, 15),
end: createDate(14, 45),
recurring: new RRule({
freq: RRule.HOURLY,
count: 3,
dtstart: convertDateToRRuleDate(new Date(new Date(new Date().setHours(14)).setMinutes(15))),
dtstart: convertDateToRRuleDate(createDate(14, 15)),
}),
color: "#dc4552",
},
{
event_id: 11,
title: "Event 11",
subtitle: "This event is not resizable",
start: createDate(10, 30, -4),
end: createDate(12, 30, -4),
admin_id: 1,
resizable: false,
},
];

export const RESOURCES = [
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from "react";
import { CssBaseline, ThemeProvider, createTheme } from "@mui/material";
import { createRoot } from "react-dom/client";
import App from "./App";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { BrowserRouter, Route, Routes } from "react-router";
import Page1 from "./Page1";

const root = createRoot(document.getElementById("root") as HTMLElement);
Expand Down
48 changes: 40 additions & 8 deletions src/lib/components/events/EventItem.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
import { Fragment, MouseEvent, useCallback, useMemo, useState } from "react";
import { Typography, ButtonBase, useTheme } from "@mui/material";
import { Fragment, MouseEvent, useCallback, useMemo, useRef, useState } from "react";
import { Typography, ButtonBase, useTheme, Popper } from "@mui/material";
import { format } from "date-fns";
import { ProcessedEvent } from "../../types";
import ArrowRightRoundedIcon from "@mui/icons-material/ArrowRightRounded";
import ArrowLeftRoundedIcon from "@mui/icons-material/ArrowLeftRounded";
import { EventItemPaper } from "../../styles/styles";
import { DragHandle, EventItemPaper } from "../../styles/styles";
import { differenceInDaysOmitTime, getHourFormat } from "../../helpers/generals";
import useStore from "../../hooks/useStore";
import useDragAttributes from "../../hooks/useDragAttributes";
import EventItemPopover from "./EventItemPopover";
import useEventPermissions from "../../hooks/useEventPermissions";
import useResizeAttributes from "../../hooks/useResizeAttributes";

interface EventItemProps {
event: ProcessedEvent;
multiday?: boolean;
hasPrev?: boolean;
hasNext?: boolean;
showdate?: boolean;
minuteHeight?: number;
}

const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: EventItemProps) => {
const EventItem = ({
event,
multiday,
hasPrev,
hasNext,
showdate = true,
minuteHeight,
}: EventItemProps) => {
const { direction, locale, hourFormat, eventRenderer, onEventClick, view, disableViewer } =
useStore();
const dragHandleRef = useRef<HTMLDivElement>(null);
const [dragTime, setDragTime] = useState<Date>();
const onDragMove = useCallback((time: Date | undefined) => {
setDragTime(time);
}, []);

const dragProps = useDragAttributes(event);
const resizeProps = useResizeAttributes(event, minuteHeight, onDragMove);
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
const [deleteConfirm, setDeleteConfirm] = useState(false);
const theme = useTheme();
Expand All @@ -32,7 +48,11 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event
const PrevArrow = direction === "rtl" ? ArrowRightRoundedIcon : ArrowLeftRoundedIcon;
const hideDates = differenceInDaysOmitTime(event.start, event.end) <= 0 && event.allDay;

const { canDrag } = useEventPermissions(event);
const { canDrag, canResize } = useEventPermissions(event);
const resizable = useMemo(
() => !!canResize && !!minuteHeight && !multiday,
[canResize, minuteHeight, multiday]
);

const triggerViewer = useCallback(
(el?: MouseEvent<Element>) => {
Expand All @@ -53,6 +73,7 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event
return (
<EventItemPaper key={`${event.start.getTime()}_${event.end.getTime()}_${event.event_id}`}>
{custom}
<DragHandle {...resizeProps} draggable={resizable} ref={dragHandleRef} />
</EventItemPaper>
);
}
Expand Down Expand Up @@ -124,9 +145,7 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event
if (!disableViewer) {
triggerViewer(e);
}
if (typeof onEventClick === "function") {
onEventClick(event);
}
onEventClick?.(event);
}}
focusRipple
tabIndex={disableViewer ? -1 : 0}
Expand All @@ -137,6 +156,7 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event
{item}
</div>
</ButtonBase>
<DragHandle {...resizeProps} draggable={resizable} ref={dragHandleRef} />
</EventItemPaper>
);
}, [
Expand All @@ -159,6 +179,8 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event
hasNext,
NextArrow,
onEventClick,
resizable,
resizeProps,
]);

return (
Expand All @@ -167,6 +189,16 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event

{/* Viewer */}
<EventItemPopover anchorEl={anchorEl} event={event} onTriggerViewer={triggerViewer} />
<Popper
open={!!dragTime}
role="tooltip"
anchorEl={dragHandleRef.current}
sx={{ pointerEvents: "none" }}
>
<Typography variant="caption">
{dragTime ? format(dragTime, hFormat, { locale }) : null}
</Typography>
</Popper>
</Fragment>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/events/TodayEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const TodayEvents = ({
: "",
}}
>
<EventItem event={event} />
<EventItem event={event} minuteHeight={minuteHeight} />
</div>
);
})}
Expand Down
16 changes: 14 additions & 2 deletions src/lib/components/week/WeekTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
filterMultiDaySlot,
filterTodayEvents,
getHourFormat,
preventDragEvent,
} from "../../helpers/generals";
import { MULTI_DAY_EVENT_HEIGHT } from "../../helpers/constants";
import { DefaultResource, ProcessedEvent } from "../../types";
Expand Down Expand Up @@ -119,7 +120,13 @@ const WeekTable = ({
overflowX: "hidden",
}}
>
<EventItem event={event} hasPrev={hasPrev} hasNext={hasNext} multiday />
<EventItem
event={event}
hasPrev={hasPrev}
hasNext={hasNext}
multiday
minuteHeight={minutesHeight}
/>
</div>
);
});
Expand Down Expand Up @@ -170,7 +177,12 @@ const WeekTable = ({
const end = addMinutes(start, step);
const field = resourceFields.idField;
return (
<span key={ii} className={`rs__cell ${isToday(date) ? "rs__today_cell" : ""}`}>
<span
key={ii}
className={`rs__cell ${isToday(date) ? "rs__today_cell" : ""}`}
onDragEnter={preventDragEvent}
onDragOver={preventDragEvent}
>
{/* Events of each day - run once on the top hour column */}
{i === 0 && (
<TodayEvents
Expand Down
Loading