@isma91/react-scheduler 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +29 -0
- package/.github/workflows/tests.yml +35 -0
- package/.gitignore +32 -0
- package/.husky/pre-commit +2 -0
- package/.prettierignore +1 -0
- package/.prettierrc.json +7 -0
- package/.yarnrc.yml +1 -0
- package/LICENSE +24 -0
- package/README.md +172 -0
- package/dist/LICENSE +24 -0
- package/dist/README.md +172 -0
- package/dist/SchedulerComponent.d.ts +3 -0
- package/dist/components/common/Cell.d.ts +13 -0
- package/dist/components/common/LocaleArrow.d.ts +8 -0
- package/dist/components/common/ResourceHeader.d.ts +6 -0
- package/dist/components/common/Tabs.d.ts +16 -0
- package/dist/components/common/TodayTypo.d.ts +8 -0
- package/dist/components/common/WithResources.d.ts +6 -0
- package/dist/components/events/Actions.d.ts +8 -0
- package/dist/components/events/AgendaEventsList.d.ts +7 -0
- package/dist/components/events/CurrentTimeBar.d.ts +11 -0
- package/dist/components/events/EmptyAgenda.d.ts +2 -0
- package/dist/components/events/EventItem.d.ts +10 -0
- package/dist/components/events/EventItemPopover.d.ts +9 -0
- package/dist/components/events/MonthEvents.d.ts +13 -0
- package/dist/components/events/TodayEvents.d.ts +16 -0
- package/dist/components/hoc/DateProvider.d.ts +5 -0
- package/dist/components/inputs/DatePicker.d.ts +14 -0
- package/dist/components/inputs/Input.d.ts +19 -0
- package/dist/components/inputs/SelectInput.d.ts +22 -0
- package/dist/components/month/MonthTable.d.ts +8 -0
- package/dist/components/nav/DayDateBtn.d.ts +6 -0
- package/dist/components/nav/MonthDateBtn.d.ts +6 -0
- package/dist/components/nav/Navigation.d.ts +3 -0
- package/dist/components/nav/WeekDateBtn.d.ts +8 -0
- package/dist/components/week/WeekTable.d.ts +11 -0
- package/dist/helpers/constants.d.ts +4 -0
- package/dist/helpers/generals.d.ts +78 -0
- package/dist/hooks/useArrowDisable.d.ts +5 -0
- package/dist/hooks/useCellAttributes.d.ts +18 -0
- package/dist/hooks/useDragAttributes.d.ts +10 -0
- package/dist/hooks/useEventPermissions.d.ts +7 -0
- package/dist/hooks/useStore.d.ts +2 -0
- package/dist/hooks/useSyncScroll.d.ts +8 -0
- package/dist/hooks/useWindowResize.d.ts +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2853 -0
- package/dist/package.json +65 -0
- package/dist/positionManger/context.d.ts +14 -0
- package/dist/positionManger/provider.d.ts +5 -0
- package/dist/positionManger/usePosition.d.ts +4 -0
- package/dist/store/context.d.ts +2 -0
- package/dist/store/default.d.ts +245 -0
- package/dist/store/provider.d.ts +7 -0
- package/dist/store/types.d.ts +27 -0
- package/dist/styles/styles.d.ts +30 -0
- package/dist/types.d.ts +372 -0
- package/dist/views/Day.d.ts +2 -0
- package/dist/views/DayAgenda.d.ts +7 -0
- package/dist/views/Editor.d.ts +11 -0
- package/dist/views/Month.d.ts +2 -0
- package/dist/views/MonthAgenda.d.ts +7 -0
- package/dist/views/Week.d.ts +2 -0
- package/dist/views/WeekAgenda.d.ts +8 -0
- package/eslint.config.js +79 -0
- package/index.html +41 -0
- package/jest.config.ts +194 -0
- package/package.json +137 -0
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/scripts/post-pack.js +34 -0
- package/src/App.tsx +25 -0
- package/src/Page1.tsx +67 -0
- package/src/events.tsx +227 -0
- package/src/index.tsx +21 -0
- package/src/lib/SchedulerComponent.tsx +78 -0
- package/src/lib/__tests__/index.test.tsx +24 -0
- package/src/lib/components/common/Cell.tsx +52 -0
- package/src/lib/components/common/LocaleArrow.tsx +38 -0
- package/src/lib/components/common/ResourceHeader.tsx +73 -0
- package/src/lib/components/common/Tabs.tsx +119 -0
- package/src/lib/components/common/TodayTypo.tsx +44 -0
- package/src/lib/components/common/WithResources.tsx +98 -0
- package/src/lib/components/events/Actions.tsx +65 -0
- package/src/lib/components/events/AgendaEventsList.tsx +115 -0
- package/src/lib/components/events/CurrentTimeBar.tsx +59 -0
- package/src/lib/components/events/EmptyAgenda.tsx +27 -0
- package/src/lib/components/events/EventItem.tsx +180 -0
- package/src/lib/components/events/EventItemPopover.tsx +179 -0
- package/src/lib/components/events/MonthEvents.tsx +141 -0
- package/src/lib/components/events/TodayEvents.tsx +99 -0
- package/src/lib/components/hoc/DateProvider.tsx +19 -0
- package/src/lib/components/inputs/DatePicker.tsx +95 -0
- package/src/lib/components/inputs/Input.tsx +113 -0
- package/src/lib/components/inputs/SelectInput.tsx +164 -0
- package/src/lib/components/month/MonthTable.tsx +207 -0
- package/src/lib/components/nav/DayDateBtn.tsx +77 -0
- package/src/lib/components/nav/MonthDateBtn.tsx +80 -0
- package/src/lib/components/nav/Navigation.tsx +201 -0
- package/src/lib/components/nav/WeekDateBtn.tsx +89 -0
- package/src/lib/components/week/WeekTable.tsx +229 -0
- package/src/lib/helpers/constants.ts +4 -0
- package/src/lib/helpers/generals.tsx +354 -0
- package/src/lib/hooks/useArrowDisable.ts +26 -0
- package/src/lib/hooks/useCellAttributes.ts +67 -0
- package/src/lib/hooks/useDragAttributes.ts +31 -0
- package/src/lib/hooks/useEventPermissions.ts +42 -0
- package/src/lib/hooks/useStore.ts +8 -0
- package/src/lib/hooks/useSyncScroll.ts +31 -0
- package/src/lib/hooks/useWindowResize.ts +37 -0
- package/src/lib/index.tsx +14 -0
- package/src/lib/positionManger/context.ts +14 -0
- package/src/lib/positionManger/provider.tsx +113 -0
- package/src/lib/positionManger/usePosition.ts +8 -0
- package/src/lib/store/context.ts +5 -0
- package/src/lib/store/default.ts +157 -0
- package/src/lib/store/provider.tsx +211 -0
- package/src/lib/store/types.ts +33 -0
- package/src/lib/styles/styles.ts +256 -0
- package/src/lib/types.ts +423 -0
- package/src/lib/views/Day.tsx +265 -0
- package/src/lib/views/DayAgenda.tsx +57 -0
- package/src/lib/views/Editor.tsx +258 -0
- package/src/lib/views/Month.tsx +82 -0
- package/src/lib/views/MonthAgenda.tsx +84 -0
- package/src/lib/views/Week.tsx +92 -0
- package/src/lib/views/WeekAgenda.tsx +81 -0
- package/src/vite-env.d.ts +3 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +27 -0
- package/vite.config.js +40 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addDays,
|
|
3
|
+
addMilliseconds,
|
|
4
|
+
addMinutes,
|
|
5
|
+
addSeconds,
|
|
6
|
+
differenceInDays,
|
|
7
|
+
differenceInMilliseconds,
|
|
8
|
+
endOfDay,
|
|
9
|
+
format,
|
|
10
|
+
isAfter,
|
|
11
|
+
isBefore,
|
|
12
|
+
isSameDay,
|
|
13
|
+
isWithinInterval,
|
|
14
|
+
startOfDay,
|
|
15
|
+
subMinutes,
|
|
16
|
+
} from "date-fns";
|
|
17
|
+
import { View } from "../components/nav/Navigation";
|
|
18
|
+
import {
|
|
19
|
+
DefaultResource,
|
|
20
|
+
FieldProps,
|
|
21
|
+
ProcessedEvent,
|
|
22
|
+
ResourceFields,
|
|
23
|
+
SchedulerProps,
|
|
24
|
+
} from "../types";
|
|
25
|
+
import { StateEvent } from "../views/Editor";
|
|
26
|
+
|
|
27
|
+
export const getOneView = (state: Partial<SchedulerProps>): View => {
|
|
28
|
+
if (state.month) {
|
|
29
|
+
return "month";
|
|
30
|
+
} else if (state.week) {
|
|
31
|
+
return "week";
|
|
32
|
+
} else if (state.day) {
|
|
33
|
+
return "day";
|
|
34
|
+
}
|
|
35
|
+
throw new Error("No views were selected");
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const getAvailableViews = (state: SchedulerProps) => {
|
|
39
|
+
const views: View[] = [];
|
|
40
|
+
if (state.month) {
|
|
41
|
+
views.push("month");
|
|
42
|
+
}
|
|
43
|
+
if (state.week) {
|
|
44
|
+
views.push("week");
|
|
45
|
+
}
|
|
46
|
+
if (state.day) {
|
|
47
|
+
views.push("day");
|
|
48
|
+
}
|
|
49
|
+
return views;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const arraytizeFieldVal = (field: FieldProps, val: any, event?: StateEvent) => {
|
|
53
|
+
const arrytize = field.config?.multiple && !Array.isArray(event?.[field.name] || field.default);
|
|
54
|
+
const value = arrytize ? (val ? [val] : []) : val;
|
|
55
|
+
const validity = arrytize ? value.length : value;
|
|
56
|
+
return { value, validity };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const getResourcedEvents = (
|
|
60
|
+
events: ProcessedEvent[],
|
|
61
|
+
resource: DefaultResource,
|
|
62
|
+
resourceFields: ResourceFields,
|
|
63
|
+
fields: FieldProps[]
|
|
64
|
+
): ProcessedEvent[] => {
|
|
65
|
+
const keyName = resourceFields.idField;
|
|
66
|
+
const resourceField = fields.find((f) => f.name === keyName);
|
|
67
|
+
const isMultiple = !!resourceField?.config?.multiple;
|
|
68
|
+
|
|
69
|
+
const resourcedEvents = [];
|
|
70
|
+
|
|
71
|
+
for (const event of events) {
|
|
72
|
+
// Handle single select & multiple select accordingly
|
|
73
|
+
const arrytize = isMultiple && !Array.isArray(event[keyName]);
|
|
74
|
+
const eventVal = arrytize ? [event[keyName]] : event[keyName];
|
|
75
|
+
|
|
76
|
+
const isThisResource =
|
|
77
|
+
isMultiple || Array.isArray(eventVal)
|
|
78
|
+
? eventVal.includes(resource[keyName])
|
|
79
|
+
: eventVal === resource[keyName];
|
|
80
|
+
|
|
81
|
+
if (isThisResource) {
|
|
82
|
+
resourcedEvents.push({
|
|
83
|
+
...event,
|
|
84
|
+
color: event.color || resource[resourceFields.colorField || ""],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return resourcedEvents;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const traversCrossingEvents = (
|
|
93
|
+
todayEvents: ProcessedEvent[],
|
|
94
|
+
event: ProcessedEvent
|
|
95
|
+
): ProcessedEvent[] => {
|
|
96
|
+
return todayEvents.filter(
|
|
97
|
+
(e) =>
|
|
98
|
+
e.event_id !== event.event_id &&
|
|
99
|
+
(isWithinInterval(addMinutes(event.start, 1), {
|
|
100
|
+
start: e.start,
|
|
101
|
+
end: e.end,
|
|
102
|
+
}) ||
|
|
103
|
+
isWithinInterval(addMinutes(event.end, -1), {
|
|
104
|
+
start: e.start,
|
|
105
|
+
end: e.end,
|
|
106
|
+
}) ||
|
|
107
|
+
isWithinInterval(addMinutes(e.start, 1), {
|
|
108
|
+
start: event.start,
|
|
109
|
+
end: event.end,
|
|
110
|
+
}) ||
|
|
111
|
+
isWithinInterval(addMinutes(e.end, -1), {
|
|
112
|
+
start: event.start,
|
|
113
|
+
end: event.end,
|
|
114
|
+
}))
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const calcMinuteHeight = (cellHeight: number, step: number) => {
|
|
119
|
+
return Math.ceil(cellHeight) / step;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const calcCellHeight = (tableHeight: number, hoursLength: number) => {
|
|
123
|
+
return Math.max(tableHeight / hoursLength, 60);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const differenceInDaysOmitTime = (start: Date, end: Date) => {
|
|
127
|
+
return differenceInDays(endOfDay(addSeconds(end, -1)), startOfDay(start));
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const convertRRuleDateToDate = (rruleDate: Date) => {
|
|
131
|
+
return new Date(
|
|
132
|
+
rruleDate.getUTCFullYear(),
|
|
133
|
+
rruleDate.getUTCMonth(),
|
|
134
|
+
rruleDate.getUTCDate(),
|
|
135
|
+
rruleDate.getUTCHours(),
|
|
136
|
+
rruleDate.getUTCMinutes()
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const getRecurrencesForDate = (event: ProcessedEvent, today: Date, timeZone?: string) => {
|
|
141
|
+
const duration = differenceInMilliseconds(event.end, event.start);
|
|
142
|
+
if (event.recurring) {
|
|
143
|
+
return event.recurring
|
|
144
|
+
?.between(addDays(today, -1), addDays(today, 1), true)
|
|
145
|
+
.map((d: Date, index: number) => {
|
|
146
|
+
const start = convertRRuleDateToDate(d);
|
|
147
|
+
return {
|
|
148
|
+
...event,
|
|
149
|
+
recurrenceId: index,
|
|
150
|
+
start: start,
|
|
151
|
+
end: addMilliseconds(start, duration),
|
|
152
|
+
};
|
|
153
|
+
})
|
|
154
|
+
.map((event) => convertEventTimeZone(event, timeZone));
|
|
155
|
+
}
|
|
156
|
+
return [convertEventTimeZone(event, timeZone)];
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const filterTodayEvents = (
|
|
160
|
+
events: ProcessedEvent[],
|
|
161
|
+
today: Date,
|
|
162
|
+
timeZone?: string,
|
|
163
|
+
forceInlineMultiDay?: boolean
|
|
164
|
+
): ProcessedEvent[] => {
|
|
165
|
+
const list: ProcessedEvent[] = [];
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < events.length; i++) {
|
|
168
|
+
for (const rec of getRecurrencesForDate(events[i], today, timeZone)) {
|
|
169
|
+
const isSingleDayEvent =
|
|
170
|
+
!rec.allDay && isSameDay(today, rec.start) && !differenceInDaysOmitTime(rec.start, rec.end);
|
|
171
|
+
|
|
172
|
+
if (isSingleDayEvent) {
|
|
173
|
+
list.push(rec);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (forceInlineMultiDay && !rec.allDay && differenceInDaysOmitTime(rec.start, rec.end) > 0) {
|
|
178
|
+
const dayStart = startOfDay(today);
|
|
179
|
+
const dayEnd = endOfDay(today);
|
|
180
|
+
|
|
181
|
+
const eventOverlapsToday =
|
|
182
|
+
isWithinInterval(today, { start: startOfDay(rec.start), end: endOfDay(rec.end) }) ||
|
|
183
|
+
isWithinInterval(rec.start, { start: dayStart, end: dayEnd }) ||
|
|
184
|
+
isWithinInterval(rec.end, { start: dayStart, end: dayEnd });
|
|
185
|
+
|
|
186
|
+
if (eventOverlapsToday) {
|
|
187
|
+
const segmentStart = isBefore(rec.start, dayStart) ? dayStart : rec.start;
|
|
188
|
+
const segmentEnd = isAfter(rec.end, dayEnd) ? dayEnd : rec.end;
|
|
189
|
+
|
|
190
|
+
list.push({
|
|
191
|
+
...rec,
|
|
192
|
+
start: segmentStart,
|
|
193
|
+
end: segmentEnd,
|
|
194
|
+
_originalStart: rec.start,
|
|
195
|
+
_originalEnd: rec.end,
|
|
196
|
+
_hasPrev: isBefore(rec.start, dayStart),
|
|
197
|
+
_hasNext: isAfter(rec.end, dayEnd),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Sort by the length est event
|
|
205
|
+
return sortEventsByTheLengthest(list);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const filterTodayAgendaEvents = (events: ProcessedEvent[], today: Date) => {
|
|
209
|
+
const list: ProcessedEvent[] = events.filter((ev) =>
|
|
210
|
+
isWithinInterval(today, {
|
|
211
|
+
start: startOfDay(ev.start),
|
|
212
|
+
end: endOfDay(subMinutes(ev.end, 1)),
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return sortEventsByTheEarliest(list);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const sortEventsByTheLengthest = (events: ProcessedEvent[]) => {
|
|
220
|
+
return events.sort((a, b) => {
|
|
221
|
+
const aDiff = a.end.getTime() - a.start.getTime();
|
|
222
|
+
const bDiff = b.end.getTime() - b.start.getTime();
|
|
223
|
+
return bDiff - aDiff;
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const sortEventsByTheEarliest = (events: ProcessedEvent[]) => {
|
|
228
|
+
return events.sort((a, b) => {
|
|
229
|
+
const isMulti = a.allDay || differenceInDaysOmitTime(a.start, a.end) > 0;
|
|
230
|
+
return isMulti ? -1 : a.start.getTime() - b.start.getTime();
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const filterMultiDaySlot = (
|
|
235
|
+
events: ProcessedEvent[],
|
|
236
|
+
date: Date | Date[],
|
|
237
|
+
timeZone?: string,
|
|
238
|
+
lengthOnly?: boolean,
|
|
239
|
+
forceInlineMultiDay?: boolean
|
|
240
|
+
) => {
|
|
241
|
+
const isMultiDates = Array.isArray(date);
|
|
242
|
+
const list: ProcessedEvent[] = [];
|
|
243
|
+
const multiPerDay: Record<string, ProcessedEvent[]> = {};
|
|
244
|
+
for (let i = 0; i < events.length; i++) {
|
|
245
|
+
const event = convertEventTimeZone(events[i], timeZone);
|
|
246
|
+
const isMultiDay = differenceInDaysOmitTime(event.start, event.end) > 0;
|
|
247
|
+
const shouldForceInline = forceInlineMultiDay && !event.allDay && isMultiDay;
|
|
248
|
+
let withinSlot = event.allDay || (isMultiDay && !shouldForceInline);
|
|
249
|
+
if (!withinSlot) continue;
|
|
250
|
+
if (isMultiDates) {
|
|
251
|
+
withinSlot = date.some((weekday) =>
|
|
252
|
+
isWithinInterval(weekday, {
|
|
253
|
+
start: startOfDay(event.start),
|
|
254
|
+
end: endOfDay(event.end),
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
} else {
|
|
258
|
+
withinSlot = isWithinInterval(date, {
|
|
259
|
+
start: startOfDay(event.start),
|
|
260
|
+
end: endOfDay(event.end),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (withinSlot) {
|
|
265
|
+
list.push(event);
|
|
266
|
+
if (isMultiDates) {
|
|
267
|
+
for (const d of date) {
|
|
268
|
+
const start = format(d, "yyyy-MM-dd");
|
|
269
|
+
if (isWithinInterval(d, { start: startOfDay(event.start), end: endOfDay(event.end) })) {
|
|
270
|
+
multiPerDay[start] = (multiPerDay[start] || []).concat(event);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
const start = format(event.start, "yyyy-MM-dd");
|
|
275
|
+
multiPerDay[start] = (multiPerDay[start] || []).concat(event);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (isMultiDates && lengthOnly) {
|
|
281
|
+
return Object.values(multiPerDay).sort((a, b) => b.length - a.length)?.[0] || [];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return list;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const convertEventTimeZone = (event: ProcessedEvent, timeZone?: string) => {
|
|
288
|
+
return {
|
|
289
|
+
...event,
|
|
290
|
+
start: getTimeZonedDate(event.start, timeZone),
|
|
291
|
+
end: getTimeZonedDate(event.end, timeZone),
|
|
292
|
+
convertedTz: true,
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const getTimeZonedDate = (date: Date, timeZone?: string) => {
|
|
297
|
+
return new Date(
|
|
298
|
+
new Intl.DateTimeFormat("en-US", {
|
|
299
|
+
dateStyle: "short",
|
|
300
|
+
timeStyle: "medium",
|
|
301
|
+
timeZone,
|
|
302
|
+
}).format(date)
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Performs the reverse of getTimeZonedDate, IE: the given date is assumed
|
|
308
|
+
* to already be in the provided timeZone and is reverted to the local
|
|
309
|
+
* browser's timeZone.
|
|
310
|
+
* @param date The date to convert.
|
|
311
|
+
* @param timeZone The timeZone to convert from.
|
|
312
|
+
* @returns A new date reverted from the given timeZone to local time.
|
|
313
|
+
*/
|
|
314
|
+
export const revertTimeZonedDate = (date: Date, timeZone?: string) => {
|
|
315
|
+
if (!timeZone) {
|
|
316
|
+
return date;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// This always gets the offset between the local computer's time
|
|
320
|
+
// and UTC. It has nothing to do with the value of the date object,
|
|
321
|
+
// despite being an instance method.
|
|
322
|
+
const localOffset = -date.getTimezoneOffset();
|
|
323
|
+
const desiredOffset = getTimezoneOffset(timeZone);
|
|
324
|
+
const diff = localOffset - desiredOffset;
|
|
325
|
+
return new Date(date.getTime() + diff * 60 * 1000);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
export const isTimeZonedToday = ({
|
|
329
|
+
dateLeft,
|
|
330
|
+
dateRight,
|
|
331
|
+
timeZone,
|
|
332
|
+
}: {
|
|
333
|
+
dateLeft: Date;
|
|
334
|
+
dateRight?: Date;
|
|
335
|
+
timeZone?: string;
|
|
336
|
+
}) => {
|
|
337
|
+
return isSameDay(dateLeft, getTimeZonedDate(dateRight || new Date(), timeZone));
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export const getHourFormat = (hourFormat: "12" | "24") => {
|
|
341
|
+
return hourFormat === "12" ? "hh:mm a" : "HH:mm";
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Gets the offset in minutes of the provided timeZone.
|
|
346
|
+
* @param timeZone The timeZone to get the offset for.
|
|
347
|
+
* @returns The offset in minutes of the provided timeZone.
|
|
348
|
+
*/
|
|
349
|
+
function getTimezoneOffset(timeZone: string) {
|
|
350
|
+
const now = new Date();
|
|
351
|
+
const localizedTime = new Date(now.toLocaleString("en-US", { timeZone }));
|
|
352
|
+
const utcTime = new Date(now.toLocaleString("en-US", { timeZone: "UTC" }));
|
|
353
|
+
return Math.round((localizedTime.getTime() - utcTime.getTime()) / (60 * 1000));
|
|
354
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { endOfMonth, endOfWeek, startOfMonth, startOfWeek } from "date-fns";
|
|
2
|
+
import useStore from "./useStore";
|
|
3
|
+
|
|
4
|
+
const useArrowDisable = () => {
|
|
5
|
+
const { selectedDate, week, navigationPickerProps, view } = useStore();
|
|
6
|
+
const minDate = navigationPickerProps?.minDate;
|
|
7
|
+
const maxDate = navigationPickerProps?.maxDate;
|
|
8
|
+
const endDate =
|
|
9
|
+
view === "month"
|
|
10
|
+
? endOfMonth(selectedDate)
|
|
11
|
+
: view === "week"
|
|
12
|
+
? endOfWeek(selectedDate, { weekStartsOn: week?.weekStartOn })
|
|
13
|
+
: selectedDate;
|
|
14
|
+
const startDate =
|
|
15
|
+
view === "month"
|
|
16
|
+
? startOfMonth(selectedDate)
|
|
17
|
+
: view === "week"
|
|
18
|
+
? startOfWeek(selectedDate, { weekStartsOn: week?.weekStartOn })
|
|
19
|
+
: selectedDate;
|
|
20
|
+
const prevDisabled = minDate ? startDate <= minDate : false;
|
|
21
|
+
const nextDisabled = maxDate ? endDate >= maxDate : false;
|
|
22
|
+
|
|
23
|
+
return { prevDisabled, nextDisabled };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default useArrowDisable;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DragEvent } from "react";
|
|
2
|
+
import { alpha, useTheme } from "@mui/material";
|
|
3
|
+
import useStore from "./useStore";
|
|
4
|
+
import { revertTimeZonedDate } from "../helpers/generals";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
start: Date;
|
|
8
|
+
end: Date;
|
|
9
|
+
resourceKey: string;
|
|
10
|
+
resourceVal: string | number;
|
|
11
|
+
}
|
|
12
|
+
export const useCellAttributes = ({ start, end, resourceKey, resourceVal }: Props) => {
|
|
13
|
+
const {
|
|
14
|
+
triggerDialog,
|
|
15
|
+
onCellClick,
|
|
16
|
+
onDrop,
|
|
17
|
+
currentDragged,
|
|
18
|
+
setCurrentDragged,
|
|
19
|
+
editable,
|
|
20
|
+
timeZone,
|
|
21
|
+
} = useStore();
|
|
22
|
+
const theme = useTheme();
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
tabIndex: editable ? 0 : -1,
|
|
26
|
+
disableRipple: !editable,
|
|
27
|
+
onClick: () => {
|
|
28
|
+
if (editable) {
|
|
29
|
+
triggerDialog(true, {
|
|
30
|
+
start,
|
|
31
|
+
end,
|
|
32
|
+
[resourceKey]: resourceVal,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (onCellClick && typeof onCellClick === "function") {
|
|
37
|
+
onCellClick(start, end, resourceKey, resourceVal);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
onDragOver: (e: DragEvent<HTMLButtonElement>) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
if (currentDragged) {
|
|
43
|
+
e.currentTarget.style.backgroundColor = alpha(theme.palette.secondary.main, 0.3);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
onDragEnter: (e: DragEvent<HTMLButtonElement>) => {
|
|
47
|
+
if (currentDragged) {
|
|
48
|
+
e.currentTarget.style.backgroundColor = alpha(theme.palette.secondary.main, 0.3);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
onDragLeave: (e: DragEvent<HTMLButtonElement>) => {
|
|
52
|
+
if (currentDragged) {
|
|
53
|
+
e.currentTarget.style.backgroundColor = "";
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
onDrop: (e: DragEvent<HTMLButtonElement>) => {
|
|
57
|
+
if (currentDragged && currentDragged.event_id) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
e.currentTarget.style.backgroundColor = "";
|
|
60
|
+
const zonedStart = revertTimeZonedDate(start, timeZone);
|
|
61
|
+
onDrop(e, currentDragged.event_id.toString(), zonedStart, resourceKey, resourceVal);
|
|
62
|
+
setCurrentDragged();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[resourceKey]: resourceVal,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DragEvent } from "react";
|
|
2
|
+
import { ProcessedEvent } from "../types";
|
|
3
|
+
import { useTheme } from "@mui/material";
|
|
4
|
+
import useStore from "./useStore";
|
|
5
|
+
|
|
6
|
+
const useDragAttributes = (event: ProcessedEvent) => {
|
|
7
|
+
const { setCurrentDragged } = useStore();
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
return {
|
|
10
|
+
draggable: true,
|
|
11
|
+
onDragStart: (e: DragEvent<HTMLElement>) => {
|
|
12
|
+
e.stopPropagation();
|
|
13
|
+
setCurrentDragged(event);
|
|
14
|
+
e.currentTarget.style.backgroundColor = theme.palette.error.main;
|
|
15
|
+
},
|
|
16
|
+
onDragEnd: (e: DragEvent<HTMLElement>) => {
|
|
17
|
+
setCurrentDragged();
|
|
18
|
+
e.currentTarget.style.backgroundColor = event.color || theme.palette.primary.main;
|
|
19
|
+
},
|
|
20
|
+
onDragOver: (e: DragEvent<HTMLElement>) => {
|
|
21
|
+
e.stopPropagation();
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
},
|
|
24
|
+
onDragEnter: (e: DragEvent<HTMLElement>) => {
|
|
25
|
+
e.stopPropagation();
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default useDragAttributes;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { ProcessedEvent } from "../types";
|
|
3
|
+
import useStore from "./useStore";
|
|
4
|
+
|
|
5
|
+
const useEventPermissions = (event: ProcessedEvent) => {
|
|
6
|
+
const { editable, deletable, draggable } = useStore();
|
|
7
|
+
|
|
8
|
+
const canEdit = useMemo(() => {
|
|
9
|
+
// Priority control to event specific editable value
|
|
10
|
+
if (typeof event.editable !== "undefined") {
|
|
11
|
+
return event.editable;
|
|
12
|
+
}
|
|
13
|
+
return editable;
|
|
14
|
+
}, [editable, event.editable]);
|
|
15
|
+
|
|
16
|
+
const canDelete = useMemo(() => {
|
|
17
|
+
// Priority control to event specific deletable value
|
|
18
|
+
if (typeof event.deletable !== "undefined") {
|
|
19
|
+
return event.deletable;
|
|
20
|
+
}
|
|
21
|
+
return deletable;
|
|
22
|
+
}, [deletable, event.deletable]);
|
|
23
|
+
|
|
24
|
+
const canDrag = useMemo(() => {
|
|
25
|
+
if (!canEdit) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Priority control to event specific draggable value
|
|
29
|
+
if (typeof event.draggable !== "undefined") {
|
|
30
|
+
return event.draggable;
|
|
31
|
+
}
|
|
32
|
+
return draggable;
|
|
33
|
+
}, [draggable, event.draggable, canEdit]);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
canEdit,
|
|
37
|
+
canDelete,
|
|
38
|
+
canDrag,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default useEventPermissions;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The solution to make headers sticky with overflow
|
|
5
|
+
*/
|
|
6
|
+
const useSyncScroll = () => {
|
|
7
|
+
const headersRef = useRef<HTMLDivElement>(null);
|
|
8
|
+
const bodyRef = useRef<HTMLDivElement>(null);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const header = headersRef.current;
|
|
12
|
+
const body = bodyRef.current;
|
|
13
|
+
const handleScroll = (event: Event) => {
|
|
14
|
+
const el = event.currentTarget as HTMLElement;
|
|
15
|
+
body?.scroll({ left: el.scrollLeft });
|
|
16
|
+
header?.scroll({ left: el.scrollLeft });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
header?.addEventListener("scroll", handleScroll);
|
|
20
|
+
body?.addEventListener("scroll", handleScroll);
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
header?.removeEventListener("scroll", handleScroll);
|
|
24
|
+
body?.removeEventListener("scroll", handleScroll);
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return { headersRef, bodyRef };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default useSyncScroll;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
export function useWindowResize() {
|
|
4
|
+
const [state, setState] = useState({
|
|
5
|
+
width: 0,
|
|
6
|
+
height: 0,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (typeof window === "undefined") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const handler = () => {
|
|
14
|
+
setState((state) => {
|
|
15
|
+
const { innerWidth, innerHeight } = window;
|
|
16
|
+
//Check state for change, return same state if no change happened to prevent rerender
|
|
17
|
+
return state.width !== innerWidth || state.height !== innerHeight
|
|
18
|
+
? {
|
|
19
|
+
width: innerWidth,
|
|
20
|
+
height: innerHeight,
|
|
21
|
+
}
|
|
22
|
+
: state;
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
handler();
|
|
27
|
+
window.addEventListener("resize", handler, {
|
|
28
|
+
capture: false,
|
|
29
|
+
passive: true,
|
|
30
|
+
});
|
|
31
|
+
return () => {
|
|
32
|
+
window.removeEventListener("resize", handler);
|
|
33
|
+
};
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
return state;
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import SchedulerComponent from "./SchedulerComponent";
|
|
3
|
+
import { Scheduler as SchedulerProps, SchedulerRef } from "./types";
|
|
4
|
+
import { StoreProvider } from "./store/provider";
|
|
5
|
+
|
|
6
|
+
const Scheduler = forwardRef<SchedulerRef, SchedulerProps>(function Scheduler(props, ref) {
|
|
7
|
+
return (
|
|
8
|
+
<StoreProvider initial={props}>
|
|
9
|
+
<SchedulerComponent ref={ref} />
|
|
10
|
+
</StoreProvider>
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export { Scheduler };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
export type PositionManagerState = {
|
|
4
|
+
renderedSlots: { [day: string]: { [resourceId: string]: { [eventId: string]: number } } };
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type PositionManagerProps = {
|
|
8
|
+
setRenderedSlot(day: string, eventId: string, position: number, resourceId?: string): void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const PositionContext = createContext<PositionManagerState & PositionManagerProps>({
|
|
12
|
+
renderedSlots: {},
|
|
13
|
+
setRenderedSlot: () => {},
|
|
14
|
+
});
|