Skip to content

Commit 9c7dcc1

Browse files
rahmanunversamuelreichert
authored andcommitted
feat(calendar-web): add events, action variables, refactor
1 parent a97bf0d commit 9c7dcc1

File tree

6 files changed

+1431
-1647
lines changed

6 files changed

+1431
-1647
lines changed

packages/pluggableWidgets/calendar-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"devDependencies": {
5454
"@mendix/automation-utils": "workspace:*",
5555
"@mendix/eslint-config-web-widgets": "workspace:*",
56-
"@mendix/pluggable-widgets-tools": "10.16.0",
56+
"@mendix/pluggable-widgets-tools": "10.21.0",
5757
"@mendix/prettier-config-web-widgets": "workspace:*",
5858
"@mendix/run-e2e": "workspace:^*",
5959
"@mendix/widget-plugin-component-kit": "workspace:*",
Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,17 @@
11
import classnames from "classnames";
2-
import * as dateFns from "date-fns";
32
import { ReactElement, createElement } from "react";
4-
import { Calendar, dateFnsLocalizer, ViewsProps } from "react-big-calendar";
5-
import "react-big-calendar/lib/css/react-big-calendar.css";
3+
import { DnDCalendar, extractCalendarProps } from "./utils/calendar-utils";
64
import { CalendarContainerProps } from "../typings/CalendarProps";
75
import { constructWrapperStyle } from "./utils/utils";
86

9-
const localizer = dateFnsLocalizer({
10-
format: dateFns.format,
11-
parse: dateFns.parse,
12-
startOfWeek: dateFns.startOfWeek,
13-
getDay: dateFns.getDay,
14-
locales: {}
15-
});
16-
17-
interface CalEvent {
18-
title: string;
19-
start: Date;
20-
end: Date;
21-
allDay: boolean;
22-
color?: string;
23-
}
24-
257
export default function MxCalendar(props: CalendarContainerProps): ReactElement {
268
const { class: className } = props;
279
const wrapperStyle = constructWrapperStyle(props);
28-
29-
const items = props.databaseDataSource?.items ?? [];
30-
31-
const events: CalEvent[] = items.map(item => {
32-
const title =
33-
props.titleType === "attribute" && props.titleAttribute
34-
? (props.titleAttribute.get(item).value ?? "")
35-
: props.titleType === "expression" && props.titleExpression
36-
? String(props.titleExpression.get(item) ?? "")
37-
: "Untitled Event";
38-
39-
const start = props.startAttribute?.get(item).value ?? new Date();
40-
const end = props.endAttribute?.get(item).value ?? start;
41-
const allDay = props.allDayAttribute?.get(item).value ?? false;
42-
const color = props.eventColor?.get(item).value;
43-
44-
return { title, start, end, allDay, color };
45-
});
46-
47-
const viewsOption: ViewsProps<CalEvent, object> =
48-
props.view === "standard" ? ["month", "week", "day"] : ["month", "week", "work_week", "day", "agenda"];
49-
50-
const eventPropGetter = (event: CalEvent) => ({
51-
style: event.color ? { backgroundColor: event.color } : undefined
52-
});
10+
const calendarProps = extractCalendarProps(props);
5311

5412
return (
5513
<div className={classnames("widget-calendar", className)} style={wrapperStyle}>
56-
<Calendar<CalEvent>
57-
localizer={localizer}
58-
events={events}
59-
defaultView={props.defaultView}
60-
startAccessor={event => event.start}
61-
endAccessor={event => event.end}
62-
views={viewsOption}
63-
titleAccessor={event => event.title}
64-
allDayAccessor={event => event.allDay}
65-
eventPropGetter={eventPropGetter}
66-
/>
14+
<DnDCalendar {...calendarProps} />
6715
</div>
6816
);
6917
}

packages/pluggableWidgets/calendar-web/src/Calendar.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,41 @@
113113
</property>
114114
<property key="onClickEvent" type="action" required="false">
115115
<caption>On click action</caption>
116+
<actionVariables>
117+
<actionVariable key="startDate" type="DateTime" caption="Event start date" />
118+
<actionVariable key="endDate" type="DateTime" caption="Event end date" />
119+
<actionVariable key="title" type="String" caption="Event title" />
120+
</actionVariables>
116121
<description />
117122
</property>
118123
<property key="onCreateEvent" type="action" required="false">
119124
<caption>On create action</caption>
125+
<actionVariables>
126+
<actionVariable key="startDate" type="DateTime" caption="New event start date" />
127+
<actionVariable key="endDate" type="DateTime" caption="New event end date" />
128+
<actionVariable key="allDay" type="Boolean" caption="All day flag" />
129+
</actionVariables>
120130
<description>The create event is triggered when a time slot is selected, and the 'Enable create' property is set to 'true'</description>
121131
</property>
122132
<property key="onChange" type="action" required="false">
123133
<caption>On change action</caption>
134+
<actionVariables>
135+
<actionVariable key="oldStart" type="DateTime" caption="Old start date" />
136+
<actionVariable key="oldEnd" type="DateTime" caption="Old end date" />
137+
<actionVariable key="newStart" type="DateTime" caption="New start date" />
138+
<actionVariable key="newEnd" type="DateTime" caption="New end date" />
139+
</actionVariables>
124140
<description>The change event is triggered on moving/dragging an item or changing the start or end time of by resizing an item</description>
125141
</property>
142+
<property key="onRangeChange" type="action" required="false">
143+
<caption>On view range change</caption>
144+
<description>Triggered when the calendar view range (start/end) changes</description>
145+
<actionVariables>
146+
<actionVariable key="rangeStart" type="DateTime" caption="View range start" />
147+
<actionVariable key="rangeEnd" type="DateTime" caption="View range end" />
148+
<actionVariable key="currentView" type="String" caption="Current view" />
149+
</actionVariables>
150+
</property>
126151
</propertyGroup>
127152
<propertyGroup caption="Dimensions">
128153
<property key="widthUnit" type="enumeration" defaultValue="percentage">
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Calendar, dateFnsLocalizer, ViewsProps } from "react-big-calendar";
2+
import withDragAndDrop from "react-big-calendar/lib/addons/dragAndDrop";
3+
import "react-big-calendar/lib/addons/dragAndDrop/styles.css";
4+
import "react-big-calendar/lib/css/react-big-calendar.css";
5+
6+
import * as dateFns from "date-fns";
7+
import { CalendarContainerProps } from "../../typings/CalendarProps";
8+
9+
// Define the event shape
10+
export interface CalEvent {
11+
title: string;
12+
start: Date;
13+
end: Date;
14+
allDay: boolean;
15+
color?: string;
16+
}
17+
18+
// Configure date-fns localizer
19+
const localizer = dateFnsLocalizer({
20+
format: dateFns.format,
21+
parse: dateFns.parse,
22+
startOfWeek: dateFns.startOfWeek,
23+
getDay: dateFns.getDay,
24+
locales: {}
25+
});
26+
27+
export const DnDCalendar = withDragAndDrop(Calendar);
28+
29+
function getViewRange(view: string, date: Date) {
30+
switch (view) {
31+
case "month":
32+
return { start: dateFns.startOfMonth(date), end: dateFns.endOfMonth(date) };
33+
case "week":
34+
return { start: dateFns.startOfWeek(date), end: dateFns.endOfWeek(date) };
35+
case "work_week": {
36+
const start = dateFns.startOfWeek(date);
37+
return { start, end: dateFns.addDays(start, 4) };
38+
}
39+
case "day":
40+
return { start: date, end: date };
41+
default:
42+
return { start: date, end: date };
43+
}
44+
}
45+
46+
export function extractCalendarProps(props: CalendarContainerProps) {
47+
const items = props.databaseDataSource?.items ?? [];
48+
const events: CalEvent[] = items.map(item => {
49+
const title =
50+
props.titleType === "attribute" && props.titleAttribute
51+
? (props.titleAttribute.get(item).value ?? "")
52+
: props.titleType === "expression" && props.titleExpression
53+
? String(props.titleExpression.get(item) ?? "")
54+
: "Untitled Event";
55+
const start = props.startAttribute?.get(item).value ?? new Date();
56+
const end = props.endAttribute?.get(item).value ?? start;
57+
const allDay = props.allDayAttribute?.get(item).value ?? false;
58+
const color = props.eventColor?.get(item).value;
59+
return { title, start, end, allDay, color };
60+
});
61+
62+
const viewsOption: ViewsProps<CalEvent, object> =
63+
props.view === "standard" ? ["month", "week", "day"] : ["month", "week", "work_week", "day", "agenda"];
64+
65+
const eventPropGetter = (event: CalEvent) => ({
66+
style: event.color ? { backgroundColor: event.color } : undefined
67+
});
68+
69+
const handleSelectEvent = (event: CalEvent) => {
70+
if (props.onClickEvent?.canExecute) {
71+
props.onClickEvent.execute({
72+
startDate: event.start,
73+
endDate: event.end,
74+
title: event.title
75+
});
76+
}
77+
};
78+
79+
const handleSelectSlot = (slotInfo: { start: Date; end: Date; action: string }) => {
80+
if (props.enableCreate && props.onCreateEvent?.canExecute) {
81+
props.onCreateEvent.execute({
82+
startDate: slotInfo.start,
83+
endDate: slotInfo.end,
84+
allDay: slotInfo.action === "select"
85+
});
86+
}
87+
};
88+
89+
const handleEventDropOrResize = ({ event, start, end }: { event: CalEvent; start: Date; end: Date }) => {
90+
if (props.onChange?.canExecute) {
91+
props.onChange.execute({
92+
oldStart: event.start,
93+
oldEnd: event.end,
94+
newStart: start,
95+
newEnd: end
96+
});
97+
}
98+
};
99+
100+
const handleRangeChange = (date: Date, view: string) => {
101+
if (props.onRangeChange?.canExecute) {
102+
const { start, end } = getViewRange(view, date);
103+
props.onRangeChange.execute({
104+
rangeStart: start,
105+
rangeEnd: end,
106+
currentView: view
107+
});
108+
}
109+
};
110+
111+
return {
112+
localizer,
113+
events,
114+
defaultView: props.defaultView,
115+
startAccessor: (event: unknown) => (event as CalEvent).start,
116+
endAccessor: (event: unknown) => (event as CalEvent).end,
117+
selectable: props.enableCreate,
118+
resizable: props.editable !== "never",
119+
onSelectEvent: handleSelectEvent,
120+
onSelectSlot: handleSelectSlot,
121+
onEventDrop: handleEventDropOrResize,
122+
onEventResize: handleEventDropOrResize,
123+
onNavigate: handleRangeChange,
124+
views: viewsOption,
125+
titleAccessor: (event: unknown) => (event as CalEvent).title,
126+
allDayAccessor: (event: unknown) => (event as CalEvent).allDay,
127+
eventPropGetter
128+
};
129+
}

packages/pluggableWidgets/calendar-web/typings/CalendarProps.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @author Mendix Widgets Framework Team
55
*/
66
import { CSSProperties } from "react";
7-
import { ActionValue, EditableValue, ListValue, ListAttributeValue, ListExpressionValue } from "mendix";
7+
import { ActionValue, EditableValue, ListValue, Option, ListAttributeValue, ListExpressionValue } from "mendix";
88

99
export type TitleTypeEnum = "attribute" | "expression";
1010

@@ -43,9 +43,10 @@ export interface CalendarContainerProps {
4343
defaultView: DefaultViewEnum;
4444
startDateAttribute?: EditableValue<Date>;
4545
eventDataAttribute?: EditableValue<string>;
46-
onClickEvent?: ActionValue;
47-
onCreateEvent?: ActionValue;
48-
onChange?: ActionValue;
46+
onClickEvent?: ActionValue<{ startDate: Option<Date>; endDate: Option<Date>; title: Option<string> }>;
47+
onCreateEvent?: ActionValue<{ startDate: Option<Date>; endDate: Option<Date>; allDay: Option<boolean> }>;
48+
onChange?: ActionValue<{ oldStart: Option<Date>; oldEnd: Option<Date>; newStart: Option<Date>; newEnd: Option<Date> }>;
49+
onRangeChange?: ActionValue<{ rangeStart: Option<Date>; rangeEnd: Option<Date>; currentView: Option<string> }>;
4950
widthUnit: WidthUnitEnum;
5051
width: number;
5152
heightUnit: HeightUnitEnum;
@@ -85,6 +86,7 @@ export interface CalendarPreviewProps {
8586
onClickEvent: {} | null;
8687
onCreateEvent: {} | null;
8788
onChange: {} | null;
89+
onRangeChange: {} | null;
8890
widthUnit: WidthUnitEnum;
8991
width: number | null;
9092
heightUnit: HeightUnitEnum;

0 commit comments

Comments
 (0)