@powerportalspro/react-calendar 0.0.0-stage → 12.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/EULA.txt +263 -0
- package/dist/index.cjs +3170 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +368 -0
- package/dist/index.d.ts +368 -0
- package/dist/index.js +3154 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -3
- package/README.md +0 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var reactComponents = require('@fluentui/react-components');
|
|
4
|
+
var reactIcons = require('@fluentui/react-icons');
|
|
5
|
+
var FullCalendar = require('@fullcalendar/react');
|
|
6
|
+
var dayGridPlugin = require('@fullcalendar/react/daygrid');
|
|
7
|
+
var interactionPlugin = require('@fullcalendar/react/interaction');
|
|
8
|
+
var listPlugin = require('@fullcalendar/react/list');
|
|
9
|
+
var timeGridPlugin = require('@fullcalendar/react/timegrid');
|
|
10
|
+
var formaTheme = require('@fullcalendar/react/themes/forma');
|
|
11
|
+
var core = require('@powerportalspro/core');
|
|
12
|
+
var react$1 = require('@powerportalspro/react');
|
|
13
|
+
var reactFluent = require('@powerportalspro/react-fluent');
|
|
14
|
+
var react = require('react');
|
|
15
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
16
|
+
|
|
17
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
+
|
|
19
|
+
var FullCalendar__default = /*#__PURE__*/_interopDefault(FullCalendar);
|
|
20
|
+
var dayGridPlugin__default = /*#__PURE__*/_interopDefault(dayGridPlugin);
|
|
21
|
+
var interactionPlugin__default = /*#__PURE__*/_interopDefault(interactionPlugin);
|
|
22
|
+
var listPlugin__default = /*#__PURE__*/_interopDefault(listPlugin);
|
|
23
|
+
var timeGridPlugin__default = /*#__PURE__*/_interopDefault(timeGridPlugin);
|
|
24
|
+
var formaTheme__default = /*#__PURE__*/_interopDefault(formaTheme);
|
|
25
|
+
|
|
26
|
+
// src/calendar.tsx
|
|
27
|
+
|
|
28
|
+
// src/appointment-click.ts
|
|
29
|
+
var GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
30
|
+
function appointmentToOpen(event, hostAppointments) {
|
|
31
|
+
if (hostAppointments || !GUID.test(event.id)) return null;
|
|
32
|
+
return { id: event.id, subject: event.title, recurring: Boolean(event.extendedProps["recurring"]) };
|
|
33
|
+
}
|
|
34
|
+
var CalendarContext = react.createContext(null);
|
|
35
|
+
function useCalendarContext() {
|
|
36
|
+
const context = react.useContext(CalendarContext);
|
|
37
|
+
if (!context) throw new Error("useCalendarContext must be used in a <Calendar>, among its toolbar items.");
|
|
38
|
+
return context;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/recurrence-pattern.ts
|
|
42
|
+
var RecurrenceFrequency = {
|
|
43
|
+
Daily: 0,
|
|
44
|
+
Weekly: 1,
|
|
45
|
+
Monthly: 2,
|
|
46
|
+
Yearly: 3
|
|
47
|
+
};
|
|
48
|
+
var RecurrenceEnd = {
|
|
49
|
+
/** Never: Dataverse adds occurrences up to a year ahead, and more as time passes. */
|
|
50
|
+
Never: 1,
|
|
51
|
+
AfterOccurrences: 2,
|
|
52
|
+
/** On a date, its last occurrence on or before it. */
|
|
53
|
+
OnDate: 3
|
|
54
|
+
};
|
|
55
|
+
function boolColumn(value) {
|
|
56
|
+
return { $type: BOOL, value };
|
|
57
|
+
}
|
|
58
|
+
var SERIES_TABLE = "recurringappointmentmaster";
|
|
59
|
+
var SHAPE_COLUMNS = ["daysofweekmask", "dayofmonth", "instance", "monthofyear", "occurrences", "patternenddate"];
|
|
60
|
+
var WEEKDAYS = 62;
|
|
61
|
+
var LAST_INSTANCE = 5;
|
|
62
|
+
var BOOL = 0;
|
|
63
|
+
var DATE = 2;
|
|
64
|
+
var INT = 5;
|
|
65
|
+
var CHOICE = 11;
|
|
66
|
+
function localDay(instant, timeZone) {
|
|
67
|
+
const { year, month, day } = zonedParts(instant, timeZone);
|
|
68
|
+
return { year, month, day, weekday: new Date(Date.UTC(year, month - 1, day)).getUTCDay() };
|
|
69
|
+
}
|
|
70
|
+
function dayOf(weekday) {
|
|
71
|
+
return 1 << weekday;
|
|
72
|
+
}
|
|
73
|
+
function startingPattern(frequency, start) {
|
|
74
|
+
return {
|
|
75
|
+
frequency,
|
|
76
|
+
interval: 1,
|
|
77
|
+
weekdaysOnly: false,
|
|
78
|
+
days: dayOf(start.weekday),
|
|
79
|
+
byWeekday: false,
|
|
80
|
+
end: RecurrenceEnd.Never,
|
|
81
|
+
occurrences: 10,
|
|
82
|
+
endDate: null
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function instanceOf(day) {
|
|
86
|
+
const daysInMonth = new Date(Date.UTC(day.year, day.month, 0)).getUTCDate();
|
|
87
|
+
return day.day + 7 > daysInMonth ? LAST_INSTANCE : Math.floor((day.day - 1) / 7) + 1;
|
|
88
|
+
}
|
|
89
|
+
function seriesColumns(pattern, start, end, timeZone, existing) {
|
|
90
|
+
const local = localDay(start, timeZone);
|
|
91
|
+
const day = dayOf(local.weekday);
|
|
92
|
+
const weekdaysOnly = pattern.frequency === RecurrenceFrequency.Daily && pattern.weekdaysOnly;
|
|
93
|
+
const yearlyOrMonthly = pattern.frequency === RecurrenceFrequency.Monthly || pattern.frequency === RecurrenceFrequency.Yearly;
|
|
94
|
+
const byWeekday = yearlyOrMonthly && pattern.byWeekday;
|
|
95
|
+
const columns = {
|
|
96
|
+
recurrencepatterntype: { $type: CHOICE, value: weekdaysOnly ? RecurrenceFrequency.Weekly : pattern.frequency },
|
|
97
|
+
interval: { $type: INT, value: weekdaysOnly ? 1 : Math.max(1, pattern.interval) },
|
|
98
|
+
starttime: { $type: DATE, value: start.toISOString() },
|
|
99
|
+
endtime: { $type: DATE, value: end.toISOString() },
|
|
100
|
+
patternstartdate: { $type: DATE, value: midnight(local, timeZone).toISOString() },
|
|
101
|
+
isweekdaypattern: { $type: BOOL, value: weekdaysOnly },
|
|
102
|
+
isnthmonthly: { $type: BOOL, value: byWeekday && pattern.frequency === RecurrenceFrequency.Monthly },
|
|
103
|
+
isnthyearly: { $type: BOOL, value: byWeekday && pattern.frequency === RecurrenceFrequency.Yearly }
|
|
104
|
+
};
|
|
105
|
+
if (weekdaysOnly) columns["daysofweekmask"] = { $type: INT, value: WEEKDAYS };
|
|
106
|
+
else if (pattern.frequency === RecurrenceFrequency.Weekly)
|
|
107
|
+
columns["daysofweekmask"] = { $type: INT, value: pattern.days === 0 ? day : pattern.days };
|
|
108
|
+
if (yearlyOrMonthly) {
|
|
109
|
+
if (byWeekday) {
|
|
110
|
+
columns["instance"] = { $type: CHOICE, value: instanceOf(local) };
|
|
111
|
+
columns["daysofweekmask"] = { $type: INT, value: day };
|
|
112
|
+
} else {
|
|
113
|
+
columns["dayofmonth"] = { $type: INT, value: local.day };
|
|
114
|
+
}
|
|
115
|
+
if (pattern.frequency === RecurrenceFrequency.Yearly) columns["monthofyear"] = { $type: CHOICE, value: local.month };
|
|
116
|
+
}
|
|
117
|
+
columns["patternendtype"] = { $type: CHOICE, value: pattern.end };
|
|
118
|
+
if (pattern.end === RecurrenceEnd.AfterOccurrences)
|
|
119
|
+
columns["occurrences"] = { $type: INT, value: Math.max(1, pattern.occurrences) };
|
|
120
|
+
else if (pattern.end === RecurrenceEnd.OnDate && pattern.endDate) {
|
|
121
|
+
const [year, month, date] = pattern.endDate.split("-").map(Number);
|
|
122
|
+
columns["patternenddate"] = {
|
|
123
|
+
$type: DATE,
|
|
124
|
+
value: midnight({ year, month, day: date }, timeZone).toISOString()
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const held = existing?.properties ?? {};
|
|
128
|
+
const cleared = SHAPE_COLUMNS.filter((column) => !(column in columns) && held[column] != null);
|
|
129
|
+
return { ...columns, ...Object.fromEntries(cleared.map((column) => [column, null])) };
|
|
130
|
+
}
|
|
131
|
+
function toSeries(pattern, start, end, timeZone) {
|
|
132
|
+
return { tableName: SERIES_TABLE, properties: seriesColumns(pattern, start, end, timeZone) };
|
|
133
|
+
}
|
|
134
|
+
function fromSeries(series, timeZone) {
|
|
135
|
+
const properties = series.properties;
|
|
136
|
+
const value = (column) => properties[column]?.value;
|
|
137
|
+
const type = value("recurrencepatterntype");
|
|
138
|
+
if (typeof type !== "number" || !Object.values(RecurrenceFrequency).includes(type)) return null;
|
|
139
|
+
const endType = value("patternendtype");
|
|
140
|
+
const end = Object.values(RecurrenceEnd).includes(endType) ? endType : RecurrenceEnd.Never;
|
|
141
|
+
const endMoment = value("patternenddate");
|
|
142
|
+
const endDay = typeof endMoment === "string" ? localDay(new Date(endMoment), timeZone) : null;
|
|
143
|
+
const weekdaysOnly = value("isweekdaypattern") === true;
|
|
144
|
+
return {
|
|
145
|
+
frequency: weekdaysOnly ? RecurrenceFrequency.Daily : type,
|
|
146
|
+
interval: weekdaysOnly ? 1 : Math.max(1, Number(value("interval") ?? 1)),
|
|
147
|
+
weekdaysOnly,
|
|
148
|
+
days: weekdaysOnly ? 0 : Number(value("daysofweekmask") ?? 0),
|
|
149
|
+
byWeekday: value("isnthmonthly") === true || value("isnthyearly") === true,
|
|
150
|
+
end,
|
|
151
|
+
occurrences: Number(value("occurrences") ?? 10),
|
|
152
|
+
endDate: endDay ? isoDate(endDay) : null
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function monthlyOn(pattern, start, locale, t, phrases = "summary") {
|
|
156
|
+
return pattern.byWeekday ? t(`${phrases}.on-the-weekday`, [t(`instances.${instanceOf(start)}`), dayName(start.weekday, locale)]) : t(`${phrases}.on-day`, [start.day]);
|
|
157
|
+
}
|
|
158
|
+
function yearlyOn(pattern, start, locale, t, phrases = "summary") {
|
|
159
|
+
return pattern.byWeekday ? t(`${phrases}.on-the-weekday-of`, [
|
|
160
|
+
t(`instances.${instanceOf(start)}`),
|
|
161
|
+
dayName(start.weekday, locale),
|
|
162
|
+
monthName(start.month, locale)
|
|
163
|
+
]) : t(`${phrases}.on-date`, [
|
|
164
|
+
new Intl.DateTimeFormat(locale, { month: "long", day: "numeric", timeZone: "UTC" }).format(
|
|
165
|
+
Date.UTC(2e3, start.month - 1, start.day)
|
|
166
|
+
)
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
function describeRecurrence(pattern, start, locale, t) {
|
|
170
|
+
const interval = Math.max(1, pattern.interval);
|
|
171
|
+
const many = interval > 1;
|
|
172
|
+
const days = () => dayNames(pattern.days === 0 ? dayOf(start.weekday) : pattern.days, locale);
|
|
173
|
+
let repeats;
|
|
174
|
+
switch (pattern.frequency) {
|
|
175
|
+
case RecurrenceFrequency.Daily:
|
|
176
|
+
repeats = pattern.weekdaysOnly ? t("summary.every-weekday") : many ? t("summary.every-n-days", [interval]) : t("summary.every-day");
|
|
177
|
+
break;
|
|
178
|
+
case RecurrenceFrequency.Weekly:
|
|
179
|
+
repeats = many ? t("summary.every-n-weeks", [interval, days()]) : t("summary.every-week", [days()]);
|
|
180
|
+
break;
|
|
181
|
+
case RecurrenceFrequency.Monthly:
|
|
182
|
+
repeats = many ? t("summary.every-n-months", [interval, monthlyOn(pattern, start, locale, t)]) : t("summary.every-month", [monthlyOn(pattern, start, locale, t)]);
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
repeats = many ? t("summary.every-n-years", [interval, yearlyOn(pattern, start, locale, t)]) : t("summary.every-year", [yearlyOn(pattern, start, locale, t)]);
|
|
186
|
+
}
|
|
187
|
+
if (pattern.end === RecurrenceEnd.AfterOccurrences) return t("summary.times", [repeats, Math.max(1, pattern.occurrences)]);
|
|
188
|
+
if (pattern.end === RecurrenceEnd.OnDate && pattern.endDate) {
|
|
189
|
+
const [year, month, day] = pattern.endDate.split("-").map(Number);
|
|
190
|
+
return t("summary.until", [repeats, new Intl.DateTimeFormat(locale, { timeZone: "UTC" }).format(Date.UTC(year, month - 1, day))]);
|
|
191
|
+
}
|
|
192
|
+
return repeats;
|
|
193
|
+
}
|
|
194
|
+
function firstDayOfWeek(locale) {
|
|
195
|
+
try {
|
|
196
|
+
const intlLocale = new Intl.Locale(locale);
|
|
197
|
+
const firstDay = intlLocale.getWeekInfo?.().firstDay ?? intlLocale.weekInfo?.firstDay;
|
|
198
|
+
return firstDay === void 0 ? 0 : firstDay % 7;
|
|
199
|
+
} catch {
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function weekOrder(locale) {
|
|
204
|
+
const first = firstDayOfWeek(locale);
|
|
205
|
+
return Array.from({ length: 7 }, (_, i) => (first + i) % 7);
|
|
206
|
+
}
|
|
207
|
+
function dayName(weekday, locale, width = "long") {
|
|
208
|
+
return new Intl.DateTimeFormat(locale, { weekday: width, timeZone: "UTC" }).format(Date.UTC(2026, 10, 1 + weekday));
|
|
209
|
+
}
|
|
210
|
+
function monthName(month, locale) {
|
|
211
|
+
return new Intl.DateTimeFormat(locale, { month: "long", timeZone: "UTC" }).format(Date.UTC(2e3, month - 1, 1));
|
|
212
|
+
}
|
|
213
|
+
function dayNames(days, locale) {
|
|
214
|
+
return weekOrder(locale).filter((weekday) => (days & dayOf(weekday)) !== 0).map((weekday) => dayName(weekday, locale)).join(", ");
|
|
215
|
+
}
|
|
216
|
+
function isoDate(day) {
|
|
217
|
+
return `${String(day.year).padStart(4, "0")}-${String(day.month).padStart(2, "0")}-${String(day.day).padStart(2, "0")}`;
|
|
218
|
+
}
|
|
219
|
+
function zonedParts(instant, timeZone) {
|
|
220
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
221
|
+
...timeZone ? { timeZone } : {},
|
|
222
|
+
year: "numeric",
|
|
223
|
+
month: "numeric",
|
|
224
|
+
day: "numeric",
|
|
225
|
+
hour: "numeric",
|
|
226
|
+
minute: "numeric",
|
|
227
|
+
hourCycle: "h23"
|
|
228
|
+
}).formatToParts(instant);
|
|
229
|
+
const part = (type) => Number(parts.find((x) => x.type === type)?.value ?? 0);
|
|
230
|
+
return { year: part("year"), month: part("month"), day: part("day"), hour: part("hour"), minute: part("minute") };
|
|
231
|
+
}
|
|
232
|
+
function allDayTimes(start, end, allDay, timeZone) {
|
|
233
|
+
const first = localDay(start, timeZone);
|
|
234
|
+
if (!allDay) {
|
|
235
|
+
const at = hourOn(first, 9, timeZone);
|
|
236
|
+
return { start: at, end: new Date(at.getTime() + 30 * 60 * 1e3) };
|
|
237
|
+
}
|
|
238
|
+
const last = zonedParts(end, timeZone);
|
|
239
|
+
const endsAtMidnight = last.hour === 0 && last.minute === 0;
|
|
240
|
+
const after = addDays(last, endsAtMidnight ? 0 : 1);
|
|
241
|
+
const dayAfter = isoDate(after) > isoDate(first) ? after : addDays(first, 1);
|
|
242
|
+
return { start: midnight(first, timeZone), end: midnight(dayAfter, timeZone) };
|
|
243
|
+
}
|
|
244
|
+
function addDays(day, days) {
|
|
245
|
+
const moved = new Date(Date.UTC(day.year, day.month - 1, day.day + days));
|
|
246
|
+
return { year: moved.getUTCFullYear(), month: moved.getUTCMonth() + 1, day: moved.getUTCDate() };
|
|
247
|
+
}
|
|
248
|
+
function midnight(day, timeZone) {
|
|
249
|
+
return hourOn(day, 0, timeZone);
|
|
250
|
+
}
|
|
251
|
+
function hourOn(day, hour, timeZone) {
|
|
252
|
+
const target = Date.UTC(day.year, day.month - 1, day.day, hour);
|
|
253
|
+
let guess = target;
|
|
254
|
+
for (let i = 0; i < 3; i++) {
|
|
255
|
+
const p = zonedParts(new Date(guess), timeZone);
|
|
256
|
+
const shown = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute);
|
|
257
|
+
if (shown === target) return new Date(guess);
|
|
258
|
+
guess += target - shown;
|
|
259
|
+
}
|
|
260
|
+
while (Date.UTC(...partsOf(zonedParts(new Date(guess), timeZone))) < target) guess += 30 * 60 * 1e3;
|
|
261
|
+
return new Date(guess);
|
|
262
|
+
}
|
|
263
|
+
function partsOf(p) {
|
|
264
|
+
return [p.year, p.month - 1, p.day, p.hour, p.minute];
|
|
265
|
+
}
|
|
266
|
+
var MAX_INTERVAL = 99;
|
|
267
|
+
var MAX_OCCURRENCES = 999;
|
|
268
|
+
var NO_REPEAT = "none";
|
|
269
|
+
var FREQUENCY_NAMES = {
|
|
270
|
+
[RecurrenceFrequency.Daily]: "daily",
|
|
271
|
+
[RecurrenceFrequency.Weekly]: "weekly",
|
|
272
|
+
[RecurrenceFrequency.Monthly]: "monthly",
|
|
273
|
+
[RecurrenceFrequency.Yearly]: "yearly"
|
|
274
|
+
};
|
|
275
|
+
var useStyles = reactComponents.makeStyles({
|
|
276
|
+
root: {
|
|
277
|
+
display: "flex",
|
|
278
|
+
flexDirection: "column",
|
|
279
|
+
gap: reactComponents.tokens.spacingVerticalM
|
|
280
|
+
},
|
|
281
|
+
// A radio with a field beside it: apart from the radios above and below, which its field would
|
|
282
|
+
// otherwise touch.
|
|
283
|
+
row: {
|
|
284
|
+
display: "flex",
|
|
285
|
+
alignItems: "center",
|
|
286
|
+
flexWrap: "wrap",
|
|
287
|
+
gap: reactComponents.tokens.spacingHorizontalS,
|
|
288
|
+
marginTop: reactComponents.tokens.spacingVerticalS,
|
|
289
|
+
marginBottom: reactComponents.tokens.spacingVerticalS
|
|
290
|
+
},
|
|
291
|
+
every: {
|
|
292
|
+
display: "flex",
|
|
293
|
+
alignItems: "flex-end",
|
|
294
|
+
flexWrap: "wrap",
|
|
295
|
+
gap: reactComponents.tokens.spacingHorizontalS
|
|
296
|
+
},
|
|
297
|
+
number: {
|
|
298
|
+
width: "96px"
|
|
299
|
+
},
|
|
300
|
+
unit: {
|
|
301
|
+
color: reactComponents.tokens.colorNeutralForeground2
|
|
302
|
+
},
|
|
303
|
+
days: {
|
|
304
|
+
display: "flex",
|
|
305
|
+
alignItems: "center",
|
|
306
|
+
flexWrap: "wrap",
|
|
307
|
+
gap: reactComponents.tokens.spacingHorizontalXS
|
|
308
|
+
},
|
|
309
|
+
note: {
|
|
310
|
+
color: reactComponents.tokens.colorNeutralForeground3
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
function RecurrenceEdit({ value, onChange, start, allowNone = true, readOnly = false, locale }) {
|
|
314
|
+
const styles = useStyles();
|
|
315
|
+
const t = react$1.usePrefixedT("app.components.calendar.recurrence");
|
|
316
|
+
const id = react.useId();
|
|
317
|
+
const language = locale ?? navigator.language;
|
|
318
|
+
const startDay = react.useRef(start.weekday);
|
|
319
|
+
react.useEffect(() => {
|
|
320
|
+
const was = startDay.current;
|
|
321
|
+
startDay.current = start.weekday;
|
|
322
|
+
if (was !== start.weekday && value?.frequency === RecurrenceFrequency.Weekly && value.days === dayOf(was)) {
|
|
323
|
+
onChange({ ...value, days: dayOf(start.weekday) });
|
|
324
|
+
}
|
|
325
|
+
}, [start.weekday]);
|
|
326
|
+
const frequencies = Object.values(RecurrenceFrequency);
|
|
327
|
+
const chosen = value ? String(value.frequency) : NO_REPEAT;
|
|
328
|
+
const frequencyLabel = (option) => option === NO_REPEAT ? t("never") : t(`frequencies.${FREQUENCY_NAMES[Number(option)]}`);
|
|
329
|
+
const changeFrequency = (option) => {
|
|
330
|
+
if (option === NO_REPEAT) return onChange(null);
|
|
331
|
+
const frequency = Number(option);
|
|
332
|
+
if (value?.frequency === frequency) return;
|
|
333
|
+
const pattern = startingPattern(frequency, start);
|
|
334
|
+
onChange(value ? { ...pattern, end: value.end, occurrences: value.occurrences, endDate: value.endDate } : pattern);
|
|
335
|
+
};
|
|
336
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.root, children: [
|
|
337
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: t("label"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
338
|
+
reactComponents.Dropdown,
|
|
339
|
+
{
|
|
340
|
+
value: frequencyLabel(chosen),
|
|
341
|
+
selectedOptions: [chosen],
|
|
342
|
+
disabled: readOnly,
|
|
343
|
+
onOptionSelect: (_, data) => data.optionValue !== void 0 && changeFrequency(data.optionValue),
|
|
344
|
+
children: [...allowNone ? [NO_REPEAT] : [], ...frequencies.map(String)].map((option) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { value: option, children: frequencyLabel(option) }, option))
|
|
345
|
+
}
|
|
346
|
+
) }),
|
|
347
|
+
value && /* @__PURE__ */ jsxRuntime.jsx(PatternFields, { value, onChange, start, readOnly, locale: language, id })
|
|
348
|
+
] });
|
|
349
|
+
}
|
|
350
|
+
function monthOn(day) {
|
|
351
|
+
const year = day.month === 12 ? day.year + 1 : day.year;
|
|
352
|
+
const month = day.month % 12 + 1;
|
|
353
|
+
const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
354
|
+
return isoDate({ year, month, day: Math.min(day.day, daysInMonth) });
|
|
355
|
+
}
|
|
356
|
+
function PatternFields({
|
|
357
|
+
value,
|
|
358
|
+
onChange,
|
|
359
|
+
start,
|
|
360
|
+
readOnly,
|
|
361
|
+
locale,
|
|
362
|
+
id
|
|
363
|
+
}) {
|
|
364
|
+
const styles = useStyles();
|
|
365
|
+
const t = react$1.usePrefixedT("app.components.calendar.recurrence");
|
|
366
|
+
const clamp = (n, max) => Math.min(max, Math.max(1, Math.round(n ?? 1)));
|
|
367
|
+
const number = (displayValue, picked) => picked ?? (displayValue === void 0 ? void 0 : Number(displayValue));
|
|
368
|
+
const changeEnd = (end) => onChange({
|
|
369
|
+
...value,
|
|
370
|
+
end,
|
|
371
|
+
endDate: end === RecurrenceEnd.OnDate ? value.endDate ?? monthOn(start) : value.endDate
|
|
372
|
+
});
|
|
373
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
374
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.every, children: [
|
|
375
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: t("every"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
376
|
+
reactComponents.SpinButton,
|
|
377
|
+
{
|
|
378
|
+
className: styles.number,
|
|
379
|
+
value: value.interval,
|
|
380
|
+
min: 1,
|
|
381
|
+
max: MAX_INTERVAL,
|
|
382
|
+
disabled: readOnly || value.frequency === RecurrenceFrequency.Daily && value.weekdaysOnly,
|
|
383
|
+
onChange: (_, data) => onChange({ ...value, interval: clamp(number(data.displayValue, data.value), MAX_INTERVAL) })
|
|
384
|
+
}
|
|
385
|
+
) }),
|
|
386
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { className: styles.unit, children: t(`units.${FREQUENCY_NAMES[value.frequency]}`) })
|
|
387
|
+
] }),
|
|
388
|
+
value.frequency === RecurrenceFrequency.Daily && /* @__PURE__ */ jsxRuntime.jsx(
|
|
389
|
+
reactComponents.Checkbox,
|
|
390
|
+
{
|
|
391
|
+
label: t("weekdays-only"),
|
|
392
|
+
checked: value.weekdaysOnly,
|
|
393
|
+
disabled: readOnly,
|
|
394
|
+
onChange: (_, data) => onChange({ ...value, weekdaysOnly: data.checked === true })
|
|
395
|
+
}
|
|
396
|
+
),
|
|
397
|
+
value.frequency === RecurrenceFrequency.Weekly && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.days, role: "group", "aria-label": t("days"), children: [
|
|
398
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { children: t("days") }),
|
|
399
|
+
weekOrder(locale).map((weekday) => {
|
|
400
|
+
const flag = dayOf(weekday);
|
|
401
|
+
const pressed = (value.days & flag) !== 0;
|
|
402
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
403
|
+
reactComponents.ToggleButton,
|
|
404
|
+
{
|
|
405
|
+
shape: "circular",
|
|
406
|
+
size: "small",
|
|
407
|
+
checked: pressed,
|
|
408
|
+
disabled: readOnly,
|
|
409
|
+
title: dayName(weekday, locale),
|
|
410
|
+
"aria-label": dayName(weekday, locale),
|
|
411
|
+
onClick: () => {
|
|
412
|
+
const days = value.days ^ flag;
|
|
413
|
+
if (days !== 0) onChange({ ...value, days });
|
|
414
|
+
},
|
|
415
|
+
children: dayName(weekday, locale, "narrow")
|
|
416
|
+
},
|
|
417
|
+
weekday
|
|
418
|
+
);
|
|
419
|
+
})
|
|
420
|
+
] }),
|
|
421
|
+
(value.frequency === RecurrenceFrequency.Monthly || value.frequency === RecurrenceFrequency.Yearly) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
422
|
+
reactComponents.RadioGroup,
|
|
423
|
+
{
|
|
424
|
+
value: value.byWeekday ? "weekday" : "date",
|
|
425
|
+
disabled: readOnly,
|
|
426
|
+
onChange: (_, data) => onChange({ ...value, byWeekday: data.value === "weekday" }),
|
|
427
|
+
children: ["date", "weekday"].map((by) => {
|
|
428
|
+
const option = { ...value, byWeekday: by === "weekday" };
|
|
429
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
430
|
+
reactComponents.Radio,
|
|
431
|
+
{
|
|
432
|
+
value: by,
|
|
433
|
+
label: value.frequency === RecurrenceFrequency.Monthly ? monthlyOn(option, start, locale, t, "options") : yearlyOn(option, start, locale, t, "options")
|
|
434
|
+
},
|
|
435
|
+
by
|
|
436
|
+
);
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
),
|
|
440
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: t("ends"), children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.RadioGroup, { value: String(value.end), disabled: readOnly, onChange: (_, data) => changeEnd(Number(data.value)), children: [
|
|
441
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Radio, { value: String(RecurrenceEnd.Never), label: t("end-never") }),
|
|
442
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.row, children: [
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Radio, { value: String(RecurrenceEnd.AfterOccurrences), label: t("end-after") }),
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
445
|
+
reactComponents.SpinButton,
|
|
446
|
+
{
|
|
447
|
+
className: styles.number,
|
|
448
|
+
"aria-label": t("end-after-unit"),
|
|
449
|
+
value: value.occurrences,
|
|
450
|
+
min: 1,
|
|
451
|
+
max: MAX_OCCURRENCES,
|
|
452
|
+
disabled: readOnly,
|
|
453
|
+
onChange: (_, data) => onChange({ ...value, end: RecurrenceEnd.AfterOccurrences, occurrences: clamp(number(data.displayValue, data.value), MAX_OCCURRENCES) })
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { className: styles.unit, children: t("end-after-unit") })
|
|
457
|
+
] }),
|
|
458
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.row, children: [
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Radio, { value: String(RecurrenceEnd.OnDate), label: t("end-on") }),
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
461
|
+
reactComponents.Input,
|
|
462
|
+
{
|
|
463
|
+
type: "date",
|
|
464
|
+
id: `${id}-end`,
|
|
465
|
+
"aria-label": t("end-on"),
|
|
466
|
+
value: value.endDate ?? "",
|
|
467
|
+
min: isoDate(start),
|
|
468
|
+
disabled: readOnly,
|
|
469
|
+
onChange: (_, data) => data.value && onChange({ ...value, end: RecurrenceEnd.OnDate, endDate: data.value })
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
] })
|
|
473
|
+
] }) }),
|
|
474
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { "aria-live": "polite", children: describeRecurrence(value, start, locale, t) }),
|
|
475
|
+
value.end === RecurrenceEnd.Never && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { size: 200, className: styles.note, children: t("never-ends") })
|
|
476
|
+
] });
|
|
477
|
+
}
|
|
478
|
+
var APPOINTMENT_STRINGS = ["tables.appointment"];
|
|
479
|
+
var SERIES_STRINGS = [`tables.${SERIES_TABLE}`];
|
|
480
|
+
var OPEN_STATES = /* @__PURE__ */ new Set([0, 3]);
|
|
481
|
+
var useStyles2 = reactComponents.makeStyles({
|
|
482
|
+
fields: {
|
|
483
|
+
display: "flex",
|
|
484
|
+
flexDirection: "column",
|
|
485
|
+
gap: reactComponents.tokens.spacingVerticalM
|
|
486
|
+
},
|
|
487
|
+
// The form stays out until the appointment is read, rather than showing empty fields.
|
|
488
|
+
loading: {
|
|
489
|
+
display: "flex",
|
|
490
|
+
alignItems: "center",
|
|
491
|
+
justifyContent: "center",
|
|
492
|
+
minHeight: "160px"
|
|
493
|
+
},
|
|
494
|
+
recurring: {
|
|
495
|
+
display: "flex",
|
|
496
|
+
alignItems: "center",
|
|
497
|
+
gap: reactComponents.tokens.spacingHorizontalXS,
|
|
498
|
+
color: reactComponents.tokens.colorNeutralForeground2
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
function AppointmentDetailsDialog({
|
|
502
|
+
appointment,
|
|
503
|
+
newAppointment: newAppointment2 = null,
|
|
504
|
+
editable = false,
|
|
505
|
+
regardingTables,
|
|
506
|
+
onClose,
|
|
507
|
+
onSaved,
|
|
508
|
+
children,
|
|
509
|
+
location = "center"
|
|
510
|
+
}) {
|
|
511
|
+
const t = react$1.usePrefixedT("app.components.calendar");
|
|
512
|
+
const tAll = react$1.useT();
|
|
513
|
+
const open = appointment !== null || newAppointment2 !== null;
|
|
514
|
+
const isSeries = appointment?.isSeries === true;
|
|
515
|
+
const mayCreateSeries = useMayCreateSeries(open && editable && !isSeries && appointment?.recurring !== true);
|
|
516
|
+
const saved = () => {
|
|
517
|
+
onClose();
|
|
518
|
+
onSaved?.();
|
|
519
|
+
};
|
|
520
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
521
|
+
reactFluent.RecordDialogShell,
|
|
522
|
+
{
|
|
523
|
+
open,
|
|
524
|
+
onOpenChange: (next) => !next && onClose(),
|
|
525
|
+
location,
|
|
526
|
+
tableName: isSeries ? SERIES_TABLE : "appointment",
|
|
527
|
+
enablePanelResizing: true,
|
|
528
|
+
children: open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
529
|
+
react$1.RecordContext,
|
|
530
|
+
{
|
|
531
|
+
table: isSeries ? SERIES_TABLE : "appointment",
|
|
532
|
+
...appointment ? { id: appointment.id } : { initialRecord: newAppointment2 },
|
|
533
|
+
isolated: true,
|
|
534
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
535
|
+
reactFluent.RecordDialogFrame,
|
|
536
|
+
{
|
|
537
|
+
location,
|
|
538
|
+
title: appointment ? appointment.subject : t("new-appointment"),
|
|
539
|
+
actions: editable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
540
|
+
/* @__PURE__ */ jsxRuntime.jsx(DeleteAction, { seriesId: appointment?.seriesId ?? null, onDeleted: saved }),
|
|
541
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.SaveContextButton, { appearance: "primary", onSaved: saved }),
|
|
542
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { onClick: onClose, children: tAll("app.buttons.cancel.label") })
|
|
543
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { appearance: "primary", onClick: onClose, children: t("close") }),
|
|
544
|
+
children: isSeries ? /* @__PURE__ */ jsxRuntime.jsx(SeriesRecord, { editable, regardingTables }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
545
|
+
AppointmentRecord,
|
|
546
|
+
{
|
|
547
|
+
recurring: appointment?.recurring ?? false,
|
|
548
|
+
editable,
|
|
549
|
+
creating: !appointment,
|
|
550
|
+
regardingTables,
|
|
551
|
+
mayCreateSeries,
|
|
552
|
+
children
|
|
553
|
+
}
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
function RecurringChoiceDialog({ appointment, onChoose, onClose }) {
|
|
563
|
+
const t = react$1.usePrefixedT("app.components.calendar.open-recurring");
|
|
564
|
+
const tAll = react$1.useT();
|
|
565
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Dialog, { open: appointment !== null, onOpenChange: (_, data) => !data.open && onClose(), modalType: "modal", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: t("title") }),
|
|
567
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: t("message") }),
|
|
568
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { fluid: true, children: [
|
|
569
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { appearance: "primary", onClick: () => onChoose(false), children: t("occurrence") }),
|
|
570
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { onClick: () => onChoose(true), children: t("series") }),
|
|
571
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { appearance: "subtle", onClick: onClose, children: tAll("app.buttons.cancel.label") })
|
|
572
|
+
] })
|
|
573
|
+
] }) }) });
|
|
574
|
+
}
|
|
575
|
+
function useMayCreateSeries(asked) {
|
|
576
|
+
const { ppp, authState } = react$1.usePowerPortalsPro();
|
|
577
|
+
const user = authState.status === "authenticated" ? authState.user?.userId : null;
|
|
578
|
+
const [answer, setAnswer] = react.useState(null);
|
|
579
|
+
const asking = react.useRef(void 0);
|
|
580
|
+
const known = answer?.user === user;
|
|
581
|
+
react.useEffect(() => {
|
|
582
|
+
if (!asked || known || asking.current === user) return;
|
|
583
|
+
asking.current = user;
|
|
584
|
+
ppp.retrieveTablePermissionsAsync(SERIES_TABLE).then((permissions) => setAnswer({ user, mayCreate: (Number(permissions) & core.TableSecurityPermission.Create) !== 0 })).catch(() => asking.current = void 0);
|
|
585
|
+
}, [asked, known, user, ppp]);
|
|
586
|
+
return answer !== null && known && answer.mayCreate;
|
|
587
|
+
}
|
|
588
|
+
function grants(record, permissions) {
|
|
589
|
+
return record?.permissions !== void 0 && (Number(record.permissions) & permissions) === permissions;
|
|
590
|
+
}
|
|
591
|
+
function useMayDeleteSeries(seriesId) {
|
|
592
|
+
const { ppp } = react$1.usePowerPortalsPro();
|
|
593
|
+
const [mayDelete, setMayDelete] = react.useState(false);
|
|
594
|
+
react.useEffect(() => {
|
|
595
|
+
setMayDelete(false);
|
|
596
|
+
if (!seriesId) return;
|
|
597
|
+
let current = true;
|
|
598
|
+
ppp.retrieveRecordAsync(SERIES_TABLE, seriesId, ["activityid"]).then((series) => current && setMayDelete(grants(series, core.TableSecurityPermission.Delete))).catch(() => current && setMayDelete(false));
|
|
599
|
+
return () => {
|
|
600
|
+
current = false;
|
|
601
|
+
};
|
|
602
|
+
}, [ppp, seriesId]);
|
|
603
|
+
return mayDelete;
|
|
604
|
+
}
|
|
605
|
+
function DeleteAction({ seriesId, onDeleted }) {
|
|
606
|
+
const { record } = react$1.useRecordContext();
|
|
607
|
+
const t = react$1.usePrefixedT("app.components.calendar");
|
|
608
|
+
const tAll = react$1.useT();
|
|
609
|
+
const { ppp } = react$1.usePowerPortalsPro();
|
|
610
|
+
const dialog = react$1.useDialogService();
|
|
611
|
+
const runWithOverlay = react$1.useRunWithOverlay();
|
|
612
|
+
const mayDeleteSeries = useMayDeleteSeries(seriesId);
|
|
613
|
+
const mayDelete = !!record?.id && grants(record, core.TableSecurityPermission.Delete);
|
|
614
|
+
if (!seriesId) return mayDelete ? /* @__PURE__ */ jsxRuntime.jsx(reactFluent.DeleteContextButton, { appearance: "secondary", onDeleted }) : null;
|
|
615
|
+
if (!record?.id || !mayDelete && !mayDeleteSeries) return null;
|
|
616
|
+
const remove = async (table, id, message) => {
|
|
617
|
+
const confirmation = await dialog.showConfirmationAsync(message, {
|
|
618
|
+
title: tAll("app.messages.delete-title"),
|
|
619
|
+
primaryText: tAll("app.buttons.delete.label"),
|
|
620
|
+
secondaryText: tAll("app.buttons.cancel.label")
|
|
621
|
+
});
|
|
622
|
+
if (confirmation.canceled) return;
|
|
623
|
+
try {
|
|
624
|
+
await runWithOverlay({ description: tAll("app.messages.deleting-record") }, () => ppp.deleteRecordAsync(table, id));
|
|
625
|
+
onDeleted();
|
|
626
|
+
} catch (error) {
|
|
627
|
+
await dialog.showErrorAsync(error instanceof Error ? error.message : String(error), { title: tAll("app.errors.record-delete") });
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { children: [
|
|
631
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronDownRegular, {}), iconPosition: "after", children: tAll("app.buttons.delete.label") }) }),
|
|
632
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MenuList, { children: [
|
|
633
|
+
mayDelete && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { onClick: () => void remove("appointment", record.id, tAll("app.messages.delete-record-confirmation")), children: t("delete-occurrence") }),
|
|
634
|
+
mayDeleteSeries && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItem, { onClick: () => void remove(SERIES_TABLE, seriesId, t("delete-series-confirmation")), children: t("delete-series") })
|
|
635
|
+
] }) })
|
|
636
|
+
] });
|
|
637
|
+
}
|
|
638
|
+
function AppointmentRecord({
|
|
639
|
+
recurring,
|
|
640
|
+
editable,
|
|
641
|
+
creating,
|
|
642
|
+
regardingTables,
|
|
643
|
+
mayCreateSeries,
|
|
644
|
+
children
|
|
645
|
+
}) {
|
|
646
|
+
const styles = useStyles2();
|
|
647
|
+
const t = react$1.usePrefixedT("app.components.calendar");
|
|
648
|
+
const { status } = react$1.useRecordContext();
|
|
649
|
+
useAllDayTimes("scheduledstart", "scheduledend");
|
|
650
|
+
react$1.useLocalization(APPOINTMENT_STRINGS);
|
|
651
|
+
const labeled = react$1.useLocalizationLoaded(APPOINTMENT_STRINGS);
|
|
652
|
+
if (status !== react$1.QueryStatus.Success || !labeled) return /* @__PURE__ */ jsxRuntime.jsx(Loading, { status });
|
|
653
|
+
if (children) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
654
|
+
const readOnly = !editable;
|
|
655
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.fields, children: [
|
|
656
|
+
recurring && /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Text, { className: styles.recurring, children: [
|
|
657
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowRepeatAllRegular, {}),
|
|
658
|
+
t("recurring")
|
|
659
|
+
] }),
|
|
660
|
+
editable && /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "subject" }),
|
|
661
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "scheduledstart", readOnly }),
|
|
662
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "scheduledend", readOnly }),
|
|
663
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "isalldayevent", readOnly }),
|
|
664
|
+
editable && !recurring && mayCreateSeries && /* @__PURE__ */ jsxRuntime.jsx(AppointmentRepeat, { creating }),
|
|
665
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "location", readOnly }),
|
|
666
|
+
!creating && /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "organizer", readOnly: true }),
|
|
667
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "requiredattendees", readOnly }),
|
|
668
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "optionalattendees", readOnly }),
|
|
669
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables } }),
|
|
670
|
+
!creating && /* @__PURE__ */ jsxRuntime.jsx(reactFluent.StatusReasonEdit, { columnName: "statuscode", allowStateChange: true, readOnly }),
|
|
671
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "description", readOnly })
|
|
672
|
+
] });
|
|
673
|
+
}
|
|
674
|
+
function Loading({ status }) {
|
|
675
|
+
const styles = useStyles2();
|
|
676
|
+
const t = react$1.usePrefixedT("app.components.calendar");
|
|
677
|
+
if (status === react$1.QueryStatus.Error) {
|
|
678
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBar, { intent: "error", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("details-error") }) });
|
|
679
|
+
}
|
|
680
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.loading, role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Spinner, { label: t("details-loading") }) });
|
|
681
|
+
}
|
|
682
|
+
function useAllDayTimes(startColumn, endColumn) {
|
|
683
|
+
const ctx = react$1.useRecordContext();
|
|
684
|
+
const timeZone = react$1.useUserTimeZone() ?? void 0;
|
|
685
|
+
const read = valueOf(ctx.record, "isalldayevent");
|
|
686
|
+
const allDay = typeof read === "boolean" ? read : void 0;
|
|
687
|
+
const previous = react.useRef(void 0);
|
|
688
|
+
react.useEffect(() => {
|
|
689
|
+
const was = previous.current;
|
|
690
|
+
previous.current = allDay;
|
|
691
|
+
if (was === void 0 || allDay === void 0 || was === allDay) return;
|
|
692
|
+
const start = valueOf(ctx.record, startColumn);
|
|
693
|
+
const end = valueOf(ctx.record, endColumn);
|
|
694
|
+
if (typeof start !== "string") return;
|
|
695
|
+
const times = allDayTimes(new Date(start), new Date(typeof end === "string" ? end : start), allDay, timeZone);
|
|
696
|
+
ctx.setColumnValue(startColumn, { $type: 2, value: times.start.toISOString() });
|
|
697
|
+
ctx.setColumnValue(endColumn, { $type: 2, value: times.end.toISOString() });
|
|
698
|
+
}, [allDay]);
|
|
699
|
+
}
|
|
700
|
+
function valueOf(record, column) {
|
|
701
|
+
return record?.properties?.[column]?.value;
|
|
702
|
+
}
|
|
703
|
+
function AppointmentRepeat({ creating }) {
|
|
704
|
+
const ctx = react$1.useRecordContext();
|
|
705
|
+
const main = react$1.useMainContext();
|
|
706
|
+
const timeZone = react$1.useUserTimeZone() ?? void 0;
|
|
707
|
+
const [pattern, setPattern] = react.useState(null);
|
|
708
|
+
const patternRef = react.useRef(pattern);
|
|
709
|
+
patternRef.current = pattern;
|
|
710
|
+
const recordRef = react.useRef(ctx.record);
|
|
711
|
+
recordRef.current = ctx.record;
|
|
712
|
+
const timeZoneRef = react.useRef(timeZone);
|
|
713
|
+
timeZoneRef.current = timeZone;
|
|
714
|
+
const register = main?.registerDescendant;
|
|
715
|
+
react.useEffect(() => {
|
|
716
|
+
if (!register) return;
|
|
717
|
+
return register({
|
|
718
|
+
getRequests: () => seriesRequests(patternRef.current, recordRef.current, timeZoneRef.current),
|
|
719
|
+
validate: () => true,
|
|
720
|
+
refresh: async () => {
|
|
721
|
+
},
|
|
722
|
+
resetState: () => setPattern(null),
|
|
723
|
+
isDirty: () => patternRef.current !== null
|
|
724
|
+
});
|
|
725
|
+
}, [register]);
|
|
726
|
+
const notify = main?.notifyDescendantDirtyChanged;
|
|
727
|
+
react.useEffect(() => notify?.(), [notify, pattern]);
|
|
728
|
+
const record = ctx.record;
|
|
729
|
+
const state = valueOf(record, "statecode");
|
|
730
|
+
const offered = creating || grants(record, core.TableSecurityPermission.Write | core.TableSecurityPermission.Delete) && (state === void 0 || state === null || OPEN_STATES.has(Number(state)));
|
|
731
|
+
if (!offered) return null;
|
|
732
|
+
const start = valueOf(record, "scheduledstart");
|
|
733
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
734
|
+
RecurrenceEdit,
|
|
735
|
+
{
|
|
736
|
+
value: pattern,
|
|
737
|
+
onChange: setPattern,
|
|
738
|
+
start: localDay(typeof start === "string" ? new Date(start) : /* @__PURE__ */ new Date(), timeZone)
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
function seriesRequests(pattern, appointment, timeZone) {
|
|
743
|
+
const start = valueOf(appointment, "scheduledstart");
|
|
744
|
+
const end = valueOf(appointment, "scheduledend");
|
|
745
|
+
const id = core.getRecordReferenceId(appointment);
|
|
746
|
+
if (!pattern || !appointment || typeof start !== "string" || typeof end !== "string" || !id) return [];
|
|
747
|
+
const series = toSeries(pattern, new Date(start), new Date(end), timeZone);
|
|
748
|
+
if (valueOf(appointment, "isalldayevent") === true) {
|
|
749
|
+
series.properties["isalldayevent"] = boolColumn(true);
|
|
750
|
+
}
|
|
751
|
+
const request = {
|
|
752
|
+
$type: "AddRecurrenceRequest",
|
|
753
|
+
appointment: { tableName: "appointment", id },
|
|
754
|
+
series
|
|
755
|
+
};
|
|
756
|
+
return [request];
|
|
757
|
+
}
|
|
758
|
+
function SeriesRecord({ editable, regardingTables }) {
|
|
759
|
+
const styles = useStyles2();
|
|
760
|
+
const t = react$1.usePrefixedT("app.components.calendar");
|
|
761
|
+
const ctx = react$1.useRecordContext();
|
|
762
|
+
const timeZone = react$1.useUserTimeZone() ?? void 0;
|
|
763
|
+
react$1.useLocalization(SERIES_STRINGS);
|
|
764
|
+
const labeled = react$1.useLocalizationLoaded(SERIES_STRINGS);
|
|
765
|
+
const [pattern, setPattern] = react.useState(null);
|
|
766
|
+
const [edited, setEdited] = react.useState(false);
|
|
767
|
+
useAllDayTimes("starttime", "endtime");
|
|
768
|
+
const loaded = ctx.status === react$1.QueryStatus.Success ? ctx.record : void 0;
|
|
769
|
+
const loadedId = loaded?.id;
|
|
770
|
+
react.useEffect(() => {
|
|
771
|
+
if (loaded && !edited) setPattern(fromSeries(loaded, timeZone));
|
|
772
|
+
}, [loadedId, timeZone, edited]);
|
|
773
|
+
const start = valueOf(ctx.record, "starttime");
|
|
774
|
+
const end = valueOf(ctx.record, "endtime");
|
|
775
|
+
const write = (next) => {
|
|
776
|
+
if (typeof start !== "string" || typeof end !== "string") return;
|
|
777
|
+
for (const [column, value] of Object.entries(seriesColumns(next, new Date(start), new Date(end), timeZone, ctx.record))) {
|
|
778
|
+
ctx.setColumnValue(column, value);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
const timesChanged = ctx.dirtyColumns.has("starttime") || ctx.dirtyColumns.has("endtime");
|
|
782
|
+
react.useEffect(() => {
|
|
783
|
+
if (timesChanged && pattern) write(pattern);
|
|
784
|
+
}, [timesChanged, start, end]);
|
|
785
|
+
if (ctx.status !== react$1.QueryStatus.Success || !labeled) return /* @__PURE__ */ jsxRuntime.jsx(Loading, { status: ctx.status });
|
|
786
|
+
const change = (next) => {
|
|
787
|
+
if (!next) return;
|
|
788
|
+
setEdited(true);
|
|
789
|
+
setPattern(next);
|
|
790
|
+
write(next);
|
|
791
|
+
};
|
|
792
|
+
const readOnly = !editable;
|
|
793
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.fields, children: [
|
|
794
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Text, { className: styles.recurring, children: [
|
|
795
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowRepeatAllRegular, {}),
|
|
796
|
+
t("open-recurring.series")
|
|
797
|
+
] }),
|
|
798
|
+
editable && /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "subject" }),
|
|
799
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "starttime", readOnly }),
|
|
800
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "endtime", readOnly }),
|
|
801
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "isalldayevent", readOnly }),
|
|
802
|
+
pattern && /* @__PURE__ */ jsxRuntime.jsx(
|
|
803
|
+
RecurrenceEdit,
|
|
804
|
+
{
|
|
805
|
+
value: pattern,
|
|
806
|
+
onChange: change,
|
|
807
|
+
start: localDay(typeof start === "string" ? new Date(start) : /* @__PURE__ */ new Date(), timeZone),
|
|
808
|
+
allowNone: false,
|
|
809
|
+
readOnly
|
|
810
|
+
}
|
|
811
|
+
),
|
|
812
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "location", readOnly }),
|
|
813
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "organizer", readOnly: true }),
|
|
814
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "requiredattendees", readOnly }),
|
|
815
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "optionalattendees", readOnly }),
|
|
816
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables } }),
|
|
817
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "description", readOnly })
|
|
818
|
+
] });
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/core/styles.ts
|
|
822
|
+
var STYLE_ID = "ppp-calendar-styles";
|
|
823
|
+
var calendarStyles = `
|
|
824
|
+
.ppp-calendar,
|
|
825
|
+
.ppp-calendar-popover {
|
|
826
|
+
--fc-forma-primary: var(--colorBrandBackground);
|
|
827
|
+
--fc-forma-primary-over: var(--colorBrandBackgroundHover);
|
|
828
|
+
--fc-forma-primary-down: var(--colorBrandBackgroundPressed);
|
|
829
|
+
--fc-forma-primary-foreground: var(--colorNeutralForegroundOnBrand);
|
|
830
|
+
|
|
831
|
+
--fc-forma-event: var(--colorBrandBackground);
|
|
832
|
+
--fc-forma-event-contrast: var(--colorNeutralForegroundOnBrand);
|
|
833
|
+
--fc-forma-background-event: var(--colorBrandBackground2);
|
|
834
|
+
--fc-forma-highlight: color-mix(in srgb, var(--colorBrandBackground) 25%, transparent);
|
|
835
|
+
|
|
836
|
+
--fc-forma-background: var(--colorNeutralBackground1);
|
|
837
|
+
--fc-forma-faint: var(--colorNeutralBackground2);
|
|
838
|
+
--fc-forma-muted: var(--colorNeutralBackground1Hover);
|
|
839
|
+
--fc-forma-strong: var(--colorNeutralBackground1Selected);
|
|
840
|
+
--fc-forma-stronger: var(--colorNeutralBackground1Pressed);
|
|
841
|
+
--fc-forma-strongest: var(--colorNeutralBackground1Pressed);
|
|
842
|
+
|
|
843
|
+
--fc-forma-foreground: var(--colorNeutralForeground1);
|
|
844
|
+
--fc-forma-muted-foreground: var(--colorNeutralForeground3);
|
|
845
|
+
--fc-forma-faint-foreground: var(--colorNeutralForeground4);
|
|
846
|
+
|
|
847
|
+
--fc-forma-border: var(--colorNeutralStroke2);
|
|
848
|
+
--fc-forma-muted-border: var(--colorNeutralStroke3);
|
|
849
|
+
--fc-forma-strong-border: var(--colorNeutralStroke1);
|
|
850
|
+
--fc-forma-ring-color: var(--colorStrokeFocus2);
|
|
851
|
+
|
|
852
|
+
color: var(--colorNeutralForeground1);
|
|
853
|
+
background-color: var(--colorNeutralBackground1);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/* Forma sets 16px type on its root; Fluent's body text is 14px in its own family. Doubling the
|
|
857
|
+
class outranks Forma's (hashed, single-class) rule without naming it. */
|
|
858
|
+
.ppp-calendar.ppp-calendar,
|
|
859
|
+
.ppp-calendar-popover.ppp-calendar-popover {
|
|
860
|
+
font-family: var(--fontFamilyBase);
|
|
861
|
+
font-size: var(--fontSizeBase300);
|
|
862
|
+
line-height: var(--lineHeightBase300);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/* No taller than the appointment's box, which FullCalendar doesn't clip: a short appointment shows
|
|
866
|
+
what fits of its subject and place, as Outlook's do. */
|
|
867
|
+
.ppp-calendar-event {
|
|
868
|
+
display: flex;
|
|
869
|
+
flex-direction: column;
|
|
870
|
+
min-width: 0;
|
|
871
|
+
min-height: 0;
|
|
872
|
+
max-height: 100%;
|
|
873
|
+
overflow: hidden;
|
|
874
|
+
padding-inline: var(--spacingHorizontalXS);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.ppp-calendar-event-title {
|
|
878
|
+
display: flex;
|
|
879
|
+
align-items: center;
|
|
880
|
+
gap: var(--spacingHorizontalXXS);
|
|
881
|
+
font-weight: var(--fontWeightSemibold);
|
|
882
|
+
white-space: nowrap;
|
|
883
|
+
overflow: hidden;
|
|
884
|
+
text-overflow: ellipsis;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.ppp-calendar-event-detail {
|
|
888
|
+
white-space: nowrap;
|
|
889
|
+
overflow: hidden;
|
|
890
|
+
text-overflow: ellipsis;
|
|
891
|
+
font-size: var(--fontSizeBase200);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.ppp-calendar-event-compact {
|
|
895
|
+
flex-direction: row;
|
|
896
|
+
align-items: center;
|
|
897
|
+
gap: var(--spacingHorizontalXS);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.ppp-calendar-event-compact .ppp-calendar-event-title {
|
|
901
|
+
font-weight: var(--fontWeightRegular);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.ppp-calendar-event-compact .ppp-calendar-event-detail {
|
|
905
|
+
flex: none;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.ppp-calendar-event-icon,
|
|
909
|
+
.ppp-calendar-event-icon > svg {
|
|
910
|
+
flex: none;
|
|
911
|
+
width: 12px;
|
|
912
|
+
height: 12px;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/* How an appointment shows its time, as Outlook draws it. Busy is FullCalendar's own drawing: a tint
|
|
916
|
+
of the appointment's color with a bar in it. Out of office is that in purple; free is the bar
|
|
917
|
+
outlined on no tint, staying free under the pointer, and tentative the tint striped. Only a block
|
|
918
|
+
has a fill to draw free and tentative in: a month's dots and the agenda's rows show the color. */
|
|
919
|
+
.ppp-calendar .ppp-calendar-appointment-out-of-office,
|
|
920
|
+
.ppp-calendar-popover .ppp-calendar-appointment-out-of-office {
|
|
921
|
+
/* Over the color FullCalendar sets on the appointment itself. */
|
|
922
|
+
--fc-event-color: var(--colorPalettePurpleBorderActive, #8764b8) !important;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.ppp-calendar .ppp-calendar-appointment-free.ppp-calendar-appointment-block,
|
|
926
|
+
.ppp-calendar-popover .ppp-calendar-appointment-free.ppp-calendar-appointment-block {
|
|
927
|
+
background-color: var(--fc-forma-background);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
.ppp-calendar .ppp-calendar-appointment-free.ppp-calendar-appointment-block:hover,
|
|
931
|
+
.ppp-calendar-popover .ppp-calendar-appointment-free.ppp-calendar-appointment-block:hover {
|
|
932
|
+
background-color: color-mix(in srgb, var(--fc-event-color) 8%, var(--fc-forma-background));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/* A focused one keeps FullCalendar's focus outline. */
|
|
936
|
+
.ppp-calendar .ppp-calendar-appointment-free.ppp-calendar-appointment-block:not(:focus-visible),
|
|
937
|
+
.ppp-calendar-popover .ppp-calendar-appointment-free.ppp-calendar-appointment-block:not(:focus-visible) {
|
|
938
|
+
outline: var(--strokeWidthThin) solid color-mix(in srgb, var(--fc-event-color) 50%, transparent);
|
|
939
|
+
outline-offset: calc(var(--strokeWidthThin) * -1);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.ppp-calendar .ppp-calendar-appointment-tentative.ppp-calendar-appointment-block,
|
|
943
|
+
.ppp-calendar-popover .ppp-calendar-appointment-tentative.ppp-calendar-appointment-block {
|
|
944
|
+
background-image: repeating-linear-gradient(
|
|
945
|
+
-45deg,
|
|
946
|
+
transparent 0 4px,
|
|
947
|
+
color-mix(in srgb, var(--fc-event-color) 30%, transparent) 4px 6px
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.ppp-calendar-appointment-canceled .ppp-calendar-event-title {
|
|
952
|
+
text-decoration: line-through;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* An appointment's tooltip: its subject, then when, where and whether it repeats. */
|
|
956
|
+
.ppp-calendar-tooltip {
|
|
957
|
+
display: flex;
|
|
958
|
+
flex-direction: column;
|
|
959
|
+
gap: var(--spacingVerticalXXS);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.ppp-calendar-tooltip-subject {
|
|
963
|
+
font-weight: var(--fontWeightSemibold);
|
|
964
|
+
}
|
|
965
|
+
`;
|
|
966
|
+
function ensureCalendarStyles(document2) {
|
|
967
|
+
ensureStyleSheet(document2, STYLE_ID, calendarStyles);
|
|
968
|
+
}
|
|
969
|
+
function ensureStyleSheet(document2, id, css) {
|
|
970
|
+
if (document2.getElementById(id)) return;
|
|
971
|
+
const style = document2.createElement("style");
|
|
972
|
+
style.id = id;
|
|
973
|
+
style.textContent = css;
|
|
974
|
+
document2.head.appendChild(style);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/core/events.ts
|
|
978
|
+
var AppointmentShowAs = {
|
|
979
|
+
Busy: 0,
|
|
980
|
+
Free: 1,
|
|
981
|
+
Tentative: 2,
|
|
982
|
+
OutOfOffice: 3
|
|
983
|
+
};
|
|
984
|
+
var SHOW_AS_CLASS = {
|
|
985
|
+
[AppointmentShowAs.Busy]: "ppp-calendar-appointment-busy",
|
|
986
|
+
[AppointmentShowAs.Free]: "ppp-calendar-appointment-free",
|
|
987
|
+
[AppointmentShowAs.Tentative]: "ppp-calendar-appointment-tentative",
|
|
988
|
+
[AppointmentShowAs.OutOfOffice]: "ppp-calendar-appointment-out-of-office"
|
|
989
|
+
};
|
|
990
|
+
function toEventInput(appointment) {
|
|
991
|
+
return {
|
|
992
|
+
id: appointment.id,
|
|
993
|
+
title: appointment.subject,
|
|
994
|
+
start: appointment.start,
|
|
995
|
+
end: appointment.end,
|
|
996
|
+
allDay: appointment.allDay ?? false,
|
|
997
|
+
...appointment.editable == null ? {} : { editable: appointment.editable },
|
|
998
|
+
...appointment.durationEditable == null ? {} : { durationEditable: appointment.durationEditable },
|
|
999
|
+
...appointment.color ? { backgroundColor: appointment.color, borderColor: appointment.color } : {},
|
|
1000
|
+
extendedProps: {
|
|
1001
|
+
table: appointment.table ?? null,
|
|
1002
|
+
location: appointment.location ?? null,
|
|
1003
|
+
recurring: appointment.recurring ?? false,
|
|
1004
|
+
// Read by the calendar's eventClass, which draws it: busy when it says nothing.
|
|
1005
|
+
showAs: appointment.showAs ?? null,
|
|
1006
|
+
canceled: appointment.canceled ?? false,
|
|
1007
|
+
record: appointment.record ?? null
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function appointmentFromEvent(event) {
|
|
1012
|
+
return {
|
|
1013
|
+
id: event.id,
|
|
1014
|
+
subject: event.title,
|
|
1015
|
+
start: event.start ?? new Date(Number.NaN),
|
|
1016
|
+
end: event.end ?? event.start ?? new Date(Number.NaN),
|
|
1017
|
+
allDay: event.allDay,
|
|
1018
|
+
location: event.extendedProps["location"] ?? null,
|
|
1019
|
+
recurring: event.extendedProps["recurring"] === true,
|
|
1020
|
+
showAs: event.extendedProps["showAs"] ?? null,
|
|
1021
|
+
canceled: event.extendedProps["canceled"] === true,
|
|
1022
|
+
record: event.extendedProps["record"] ?? null,
|
|
1023
|
+
table: event.extendedProps["table"] ?? null
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function appointmentClassName(appointment) {
|
|
1027
|
+
const showAs = SHOW_AS_CLASS[appointment.showAs ?? AppointmentShowAs.Busy] ?? SHOW_AS_CLASS[AppointmentShowAs.Busy];
|
|
1028
|
+
return appointment.canceled === true ? `${showAs} ppp-calendar-appointment-canceled` : showAs;
|
|
1029
|
+
}
|
|
1030
|
+
function toAppointmentChange(event) {
|
|
1031
|
+
return {
|
|
1032
|
+
id: event.id,
|
|
1033
|
+
start: event.start ?? new Date(Number.NaN),
|
|
1034
|
+
end: event.end,
|
|
1035
|
+
allDay: event.allDay,
|
|
1036
|
+
table: event.extendedProps?.["table"] ?? null
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/core/options.ts
|
|
1041
|
+
var calendarLayouts = ["day", "workWeek", "week", "month", "agenda"];
|
|
1042
|
+
function offeredLayouts(layouts) {
|
|
1043
|
+
const named = [...new Set((layouts ?? []).filter((layout) => calendarLayouts.includes(layout)))];
|
|
1044
|
+
return named.length > 0 ? named : calendarLayouts;
|
|
1045
|
+
}
|
|
1046
|
+
function openingLayout(offered, ...preferred) {
|
|
1047
|
+
return preferred.find((layout) => layout !== void 0 && offered.includes(layout)) ?? offered[0];
|
|
1048
|
+
}
|
|
1049
|
+
var fullCalendarViewNames = {
|
|
1050
|
+
day: "timeGridDay",
|
|
1051
|
+
workWeek: "pppWorkWeek",
|
|
1052
|
+
week: "timeGridWeek",
|
|
1053
|
+
month: "dayGridMonth",
|
|
1054
|
+
agenda: "listWeek"
|
|
1055
|
+
};
|
|
1056
|
+
function toFullCalendarView(layout) {
|
|
1057
|
+
return fullCalendarViewNames[layout];
|
|
1058
|
+
}
|
|
1059
|
+
function fromFullCalendarView(name) {
|
|
1060
|
+
return calendarLayouts.find((layout) => fullCalendarViewNames[layout] === name);
|
|
1061
|
+
}
|
|
1062
|
+
function isCompactView(fullCalendarViewType) {
|
|
1063
|
+
return fullCalendarViewType.startsWith("dayGrid");
|
|
1064
|
+
}
|
|
1065
|
+
function calendarOptions(input) {
|
|
1066
|
+
return {
|
|
1067
|
+
initialView: toFullCalendarView(input.layout),
|
|
1068
|
+
timeZone: input.timeZone ?? "local",
|
|
1069
|
+
...input.date ? { initialDate: input.date } : {},
|
|
1070
|
+
views: {
|
|
1071
|
+
pppWorkWeek: { type: "timeGridWeek", weekends: false }
|
|
1072
|
+
},
|
|
1073
|
+
class: "ppp-calendar",
|
|
1074
|
+
// FullCalendar draws the "+ more" popover at the end of the page, outside the calendar, so it
|
|
1075
|
+
// carries the calendar's styles by a class of its own.
|
|
1076
|
+
popoverClass: "ppp-calendar-popover",
|
|
1077
|
+
// The toolbar is each stack's own Fluent toolbar, driven through FullCalendar's controller.
|
|
1078
|
+
headerToolbar: false,
|
|
1079
|
+
height: "100%",
|
|
1080
|
+
locale: input.locale,
|
|
1081
|
+
direction: input.direction,
|
|
1082
|
+
editable: input.editable,
|
|
1083
|
+
selectable: input.editable,
|
|
1084
|
+
selectMirror: true,
|
|
1085
|
+
nowIndicator: true,
|
|
1086
|
+
dayMaxEvents: true,
|
|
1087
|
+
// Appointments at the same time sit side by side, as Outlook draws them, not cascaded.
|
|
1088
|
+
slotEventOverlap: false,
|
|
1089
|
+
navLinks: input.layouts.includes("day"),
|
|
1090
|
+
scrollTime: "08:00:00",
|
|
1091
|
+
slotDuration: "00:30:00",
|
|
1092
|
+
snapDuration: "00:15:00",
|
|
1093
|
+
// Each appointment's class says how it shows its time; a block (not a month's dot nor the
|
|
1094
|
+
// agenda's row) is marked too, for only a block has the fill free and tentative are drawn in.
|
|
1095
|
+
// FullCalendar joins these with its theme's own classes.
|
|
1096
|
+
eventClass: (info) => `ppp-calendar-event-host ${appointmentClassName(info.event.extendedProps)}`,
|
|
1097
|
+
blockEventClass: "ppp-calendar-appointment-block",
|
|
1098
|
+
allDayText: input.text.allDay,
|
|
1099
|
+
noEventsText: input.text.noEvents,
|
|
1100
|
+
closeHint: input.text.close,
|
|
1101
|
+
moreLinkText: (count) => input.text.more(count)
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/core/state.ts
|
|
1106
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1107
|
+
function periodDate(currentStart, currentEnd, now = /* @__PURE__ */ new Date(), timeZone) {
|
|
1108
|
+
if (now >= currentStart && now < currentEnd) return void 0;
|
|
1109
|
+
const days = Math.round((currentEnd.getTime() - currentStart.getTime()) / DAY_MS);
|
|
1110
|
+
const middle = new Date(currentStart.getTime() + Math.floor(days / 2) * DAY_MS + DAY_MS / 2);
|
|
1111
|
+
return localDate(middle, timeZone);
|
|
1112
|
+
}
|
|
1113
|
+
function localDate(instant, timeZone) {
|
|
1114
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
1115
|
+
...timeZone ? { timeZone } : {},
|
|
1116
|
+
year: "numeric",
|
|
1117
|
+
month: "2-digit",
|
|
1118
|
+
day: "2-digit"
|
|
1119
|
+
}).formatToParts(instant);
|
|
1120
|
+
const part = (type) => parts.find((x) => x.type === type)?.value ?? "";
|
|
1121
|
+
return `${part("year").padStart(4, "0")}-${part("month")}-${part("day")}`;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/core/new-appointment.ts
|
|
1125
|
+
var MINUTE_MS = 60 * 1e3;
|
|
1126
|
+
var LENGTH_MINUTES = 30;
|
|
1127
|
+
var DAY_START_MINUTES = 8 * 60;
|
|
1128
|
+
function newAppointmentRange(currentStart, currentEnd, now = /* @__PURE__ */ new Date(), timeZone) {
|
|
1129
|
+
let start;
|
|
1130
|
+
if (now >= currentStart && now < currentEnd) {
|
|
1131
|
+
const past = minutesOfDay(now, timeZone) % LENGTH_MINUTES * MINUTE_MS + now.getTime() % MINUTE_MS;
|
|
1132
|
+
start = new Date(now.getTime() + (past === 0 ? 0 : LENGTH_MINUTES * MINUTE_MS - past));
|
|
1133
|
+
} else {
|
|
1134
|
+
const guess = new Date(currentStart.getTime() + DAY_START_MINUTES * MINUTE_MS);
|
|
1135
|
+
start = new Date(guess.getTime() - (minutesOfDay(guess, timeZone) - DAY_START_MINUTES) * MINUTE_MS);
|
|
1136
|
+
}
|
|
1137
|
+
return { start, end: new Date(start.getTime() + LENGTH_MINUTES * MINUTE_MS), allDay: false };
|
|
1138
|
+
}
|
|
1139
|
+
function minutesOfDay(instant, timeZone) {
|
|
1140
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
1141
|
+
...timeZone ? { timeZone } : {},
|
|
1142
|
+
hourCycle: "h23",
|
|
1143
|
+
hour: "2-digit",
|
|
1144
|
+
minute: "2-digit"
|
|
1145
|
+
}).formatToParts(instant);
|
|
1146
|
+
const part = (type) => Number(parts.find((x) => x.type === type)?.value ?? 0);
|
|
1147
|
+
return part("hour") * 60 + part("minute");
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// src/core/summary.ts
|
|
1151
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
1152
|
+
function formatter(locale, timeZone, allDay) {
|
|
1153
|
+
const key = `${locale}|${timeZone ?? ""}|${allDay}`;
|
|
1154
|
+
let format = formatters.get(key);
|
|
1155
|
+
if (!format) {
|
|
1156
|
+
format = new Intl.DateTimeFormat(locale, {
|
|
1157
|
+
...timeZone ? { timeZone } : {},
|
|
1158
|
+
...allDay ? { weekday: "short", month: "short", day: "numeric", year: "numeric" } : { weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }
|
|
1159
|
+
});
|
|
1160
|
+
formatters.set(key, format);
|
|
1161
|
+
}
|
|
1162
|
+
return format;
|
|
1163
|
+
}
|
|
1164
|
+
function formatAppointmentWhen(start, end, allDay, locale, timeZone) {
|
|
1165
|
+
if (allDay) {
|
|
1166
|
+
const last = end && end > start ? new Date(end.getTime() - 1) : start;
|
|
1167
|
+
return formatter(locale, timeZone, true).formatRange(start, last);
|
|
1168
|
+
}
|
|
1169
|
+
const times = formatter(locale, timeZone, false);
|
|
1170
|
+
return end && end > start ? times.formatRange(start, end) : times.format(start);
|
|
1171
|
+
}
|
|
1172
|
+
function summarizeAppointment(event, locale, recurringText, timeZone) {
|
|
1173
|
+
return {
|
|
1174
|
+
subject: event.title,
|
|
1175
|
+
when: event.start ? formatAppointmentWhen(event.start, event.end, event.allDay, locale, timeZone) : "",
|
|
1176
|
+
location: event.extendedProps["location"] ?? null,
|
|
1177
|
+
recurring: event.extendedProps["recurring"] ? recurringText : null
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
var APPOINTMENT_TOOLTIP_DELAY_MS = 300;
|
|
1181
|
+
function AppointmentTooltip({ content, children }) {
|
|
1182
|
+
const [visible, setVisible] = react.useState(false);
|
|
1183
|
+
const wait = react.useRef(void 0);
|
|
1184
|
+
react.useEffect(() => () => clearTimeout(wait.current), []);
|
|
1185
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1186
|
+
reactComponents.Tooltip,
|
|
1187
|
+
{
|
|
1188
|
+
relationship: "description",
|
|
1189
|
+
withArrow: true,
|
|
1190
|
+
visible,
|
|
1191
|
+
showDelay: 0,
|
|
1192
|
+
hideDelay: 0,
|
|
1193
|
+
onVisibleChange: (_, data) => {
|
|
1194
|
+
clearTimeout(wait.current);
|
|
1195
|
+
if (data.visible) wait.current = setTimeout(() => setVisible(true), APPOINTMENT_TOOLTIP_DELAY_MS);
|
|
1196
|
+
else setVisible(false);
|
|
1197
|
+
},
|
|
1198
|
+
content: typeof content !== "function" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ppp-calendar-tooltip", children: content }) : visible ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ppp-calendar-tooltip", children: content() }) : "",
|
|
1199
|
+
children
|
|
1200
|
+
}
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
function AppointmentSummaryContent({ summary }) {
|
|
1204
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1205
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "ppp-calendar-tooltip-subject", children: summary.subject }),
|
|
1206
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: summary.when }),
|
|
1207
|
+
summary.location && /* @__PURE__ */ jsxRuntime.jsx("span", { children: summary.location }),
|
|
1208
|
+
summary.recurring && /* @__PURE__ */ jsxRuntime.jsx("span", { children: summary.recurring })
|
|
1209
|
+
] });
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// src/appointment-record.ts
|
|
1213
|
+
function newAppointment(range, regarding) {
|
|
1214
|
+
return {
|
|
1215
|
+
tableName: "appointment",
|
|
1216
|
+
properties: {
|
|
1217
|
+
scheduledstart: { $type: 2, value: range.start.toISOString() },
|
|
1218
|
+
scheduledend: { $type: 2, value: range.end.toISOString() },
|
|
1219
|
+
isalldayevent: { $type: 0, value: range.allDay },
|
|
1220
|
+
...regarding?.id ? {
|
|
1221
|
+
regardingobjectid: {
|
|
1222
|
+
$type: 6,
|
|
1223
|
+
value: regarding.id,
|
|
1224
|
+
tableName: regarding.tableName,
|
|
1225
|
+
...regarding.name ? { name: regarding.name } : {}
|
|
1226
|
+
}
|
|
1227
|
+
} : {}
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
var APPOINTMENT_TABLE = "appointment";
|
|
1232
|
+
var EVERY_TABLE_VIEW_ID = "00000000-0000-0000-0000-000000000000";
|
|
1233
|
+
var SERIES_TABLE2 = "recurringappointmentmaster";
|
|
1234
|
+
var DEFAULT_CALENDAR_TABLES = [APPOINTMENT_TABLE, "task"];
|
|
1235
|
+
var CALENDAR_COLUMNS = {
|
|
1236
|
+
[APPOINTMENT_TABLE]: ["location", "seriesid", "instancetypecode", "statecode", "statuscode"],
|
|
1237
|
+
"*": ["statecode"]
|
|
1238
|
+
};
|
|
1239
|
+
var MINUTE_MS2 = 60 * 1e3;
|
|
1240
|
+
var DAY_MS2 = 24 * 60 * MINUTE_MS2;
|
|
1241
|
+
function valueOf2(record, column) {
|
|
1242
|
+
return record?.properties?.[column]?.value;
|
|
1243
|
+
}
|
|
1244
|
+
function stateOf(item) {
|
|
1245
|
+
const state = valueOf2(item.record, "statecode");
|
|
1246
|
+
return typeof state === "number" ? state : null;
|
|
1247
|
+
}
|
|
1248
|
+
function isClosed(item) {
|
|
1249
|
+
const state = stateOf(item);
|
|
1250
|
+
return state === 1 || state === 2;
|
|
1251
|
+
}
|
|
1252
|
+
function showAsOf(statusCode, stateCode) {
|
|
1253
|
+
switch (statusCode) {
|
|
1254
|
+
case 1:
|
|
1255
|
+
case 4:
|
|
1256
|
+
return AppointmentShowAs.Free;
|
|
1257
|
+
case 2:
|
|
1258
|
+
return AppointmentShowAs.Tentative;
|
|
1259
|
+
case 3:
|
|
1260
|
+
case 5:
|
|
1261
|
+
return AppointmentShowAs.Busy;
|
|
1262
|
+
case 6:
|
|
1263
|
+
return AppointmentShowAs.OutOfOffice;
|
|
1264
|
+
default:
|
|
1265
|
+
return stateCode === 0 || stateCode === 2 ? AppointmentShowAs.Free : AppointmentShowAs.Busy;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function zonedMidnight(date, timeZone) {
|
|
1269
|
+
const [year, month, day] = date.split("-").map(Number);
|
|
1270
|
+
const wanted = Date.UTC(year, month - 1, day);
|
|
1271
|
+
let guess = wanted;
|
|
1272
|
+
for (let i = 0; i < 2; i++) {
|
|
1273
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
1274
|
+
...timeZone ? { timeZone } : {},
|
|
1275
|
+
hourCycle: "h23",
|
|
1276
|
+
year: "numeric",
|
|
1277
|
+
month: "2-digit",
|
|
1278
|
+
day: "2-digit",
|
|
1279
|
+
hour: "2-digit",
|
|
1280
|
+
minute: "2-digit"
|
|
1281
|
+
}).formatToParts(new Date(guess));
|
|
1282
|
+
const part = (type) => Number(parts.find((x) => x.type === type)?.value ?? 0);
|
|
1283
|
+
const shown = Date.UTC(part("year"), part("month") - 1, part("day"), part("hour"), part("minute"));
|
|
1284
|
+
guess = wanted - (shown - guess);
|
|
1285
|
+
}
|
|
1286
|
+
return new Date(guess);
|
|
1287
|
+
}
|
|
1288
|
+
function drawnTimes(item, dateOnly, timeZone) {
|
|
1289
|
+
const start = new Date(item.start);
|
|
1290
|
+
const end = new Date(item.end);
|
|
1291
|
+
if (!dateOnly) return { start, end };
|
|
1292
|
+
const first = start.toISOString().slice(0, 10);
|
|
1293
|
+
let after = end.toISOString().slice(0, 10);
|
|
1294
|
+
if (after <= first) after = new Date(start.getTime() + DAY_MS2).toISOString().slice(0, 10);
|
|
1295
|
+
return { start: zonedMidnight(first, timeZone), end: zonedMidnight(after, timeZone) };
|
|
1296
|
+
}
|
|
1297
|
+
function fromMultiTableItem(item, info, drawn) {
|
|
1298
|
+
const definition = info?.definition;
|
|
1299
|
+
const changeable = new Set((info?.changeableColumns ?? []).map((x) => x.toLowerCase()));
|
|
1300
|
+
const movable = (Number(item.permissions ?? 0) & core.TableSecurityPermission.Write) !== 0 && !isClosed(item) && !!definition?.start && changeable.has(definition.start.toLowerCase());
|
|
1301
|
+
const resizable = movable && !!definition?.end && changeable.has(definition.end.toLowerCase());
|
|
1302
|
+
const isAppointment = item.table === APPOINTMENT_TABLE;
|
|
1303
|
+
const state = stateOf(item);
|
|
1304
|
+
const location = valueOf2(item.record, "location");
|
|
1305
|
+
const status = valueOf2(item.record, "statuscode");
|
|
1306
|
+
return {
|
|
1307
|
+
id: item.id,
|
|
1308
|
+
table: item.table,
|
|
1309
|
+
subject: item.title ?? "",
|
|
1310
|
+
start: drawn.start,
|
|
1311
|
+
end: drawn.end,
|
|
1312
|
+
allDay: item.isAllDay ?? false,
|
|
1313
|
+
location: isAppointment && typeof location === "string" ? location : null,
|
|
1314
|
+
recurring: item.table === SERIES_TABLE2 || isAppointment && !!valueOf2(item.record, "seriesid"),
|
|
1315
|
+
editable: movable ? null : false,
|
|
1316
|
+
durationEditable: resizable ? null : false,
|
|
1317
|
+
showAs: isAppointment ? showAsOf(typeof status === "number" ? status : null, state) : AppointmentShowAs.Busy,
|
|
1318
|
+
canceled: state === 2,
|
|
1319
|
+
color: definition?.color ?? null,
|
|
1320
|
+
record: item.record ?? null
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function stored(moment, dateOnly, timeZone) {
|
|
1324
|
+
return dateOnly ? `${localDate(moment, timeZone)}T00:00:00.000Z` : moment.toISOString();
|
|
1325
|
+
}
|
|
1326
|
+
function moveRecord(change, item, info, dateOnly, timeZone) {
|
|
1327
|
+
const definition = info?.definition;
|
|
1328
|
+
if (!definition?.start) return null;
|
|
1329
|
+
const changeable = new Set((info?.changeableColumns ?? []).map((x) => x.toLowerCase()));
|
|
1330
|
+
const allDayColumn = definition.allDay && changeable.has(definition.allDay.toLowerCase()) ? definition.allDay : null;
|
|
1331
|
+
if (change.allDay !== (item.isAllDay ?? false) && !allDayColumn) return null;
|
|
1332
|
+
if (dateOnly && !change.allDay) return null;
|
|
1333
|
+
const end = change.end ?? new Date(change.start.getTime() + (change.allDay ? DAY_MS2 : 30 * MINUTE_MS2));
|
|
1334
|
+
const drawn = drawnTimes(item, dateOnly, timeZone);
|
|
1335
|
+
const lengthChanged = end.getTime() - change.start.getTime() !== drawn.end.getTime() - drawn.start.getTime();
|
|
1336
|
+
const properties = {
|
|
1337
|
+
[definition.start]: { $type: 2, value: stored(change.start, dateOnly, timeZone) }
|
|
1338
|
+
};
|
|
1339
|
+
if (definition.end && (!item.hasNoEnd || lengthChanged))
|
|
1340
|
+
properties[definition.end] = { $type: 2, value: stored(end, dateOnly, timeZone) };
|
|
1341
|
+
if (allDayColumn) properties[allDayColumn] = { $type: 0, value: change.allDay };
|
|
1342
|
+
return { tableName: item.table, id: item.id, properties };
|
|
1343
|
+
}
|
|
1344
|
+
function tablesOf(tables, registered, allowed, stored2, isStoredView) {
|
|
1345
|
+
let named;
|
|
1346
|
+
if (tables) named = tables.map((x) => x.table ?? "");
|
|
1347
|
+
else if (isStoredView) {
|
|
1348
|
+
if (!stored2) return null;
|
|
1349
|
+
named = [...stored2];
|
|
1350
|
+
} else named = registered.map((x) => x.table ?? "");
|
|
1351
|
+
const expanded = named.flatMap(
|
|
1352
|
+
(table) => table.toLowerCase() === "activitypointer" ? registered.filter((x) => x.isActivity).map((x) => x.table ?? "") : [table.toLowerCase()]
|
|
1353
|
+
);
|
|
1354
|
+
return [...new Set(expanded)].filter(
|
|
1355
|
+
(table) => registered.some((x) => x.table === table) && (!allowed || allowed.has(table))
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
function hiddenByDefault(viewId, shown, defaults) {
|
|
1359
|
+
if (viewId !== EVERY_TABLE_VIEW_ID || !shown.some((table) => defaults.has(table))) return [];
|
|
1360
|
+
return shown.filter((table) => !defaults.has(table));
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/calendar-query.ts
|
|
1364
|
+
function offeredViewIds(defaultViewId2, viewIds, multiTableViewIds, customViews) {
|
|
1365
|
+
const ids = [
|
|
1366
|
+
...defaultViewId2 ? [defaultViewId2] : [],
|
|
1367
|
+
...viewIds ?? [],
|
|
1368
|
+
...multiTableViewIds ?? [],
|
|
1369
|
+
...(customViews ?? []).map((view) => view.id)
|
|
1370
|
+
].map((id) => id.toLowerCase());
|
|
1371
|
+
return ids.length === 0 ? [EVERY_TABLE_VIEW_ID] : [...new Set(ids)];
|
|
1372
|
+
}
|
|
1373
|
+
function findCustomView(id, customViews) {
|
|
1374
|
+
if (!id) return void 0;
|
|
1375
|
+
return customViews?.find((view) => view.id.toLowerCase() === id.toLowerCase());
|
|
1376
|
+
}
|
|
1377
|
+
function selectedViewId(offered, picked) {
|
|
1378
|
+
return picked && offered.includes(picked.toLowerCase()) ? picked.toLowerCase() : offered[0];
|
|
1379
|
+
}
|
|
1380
|
+
function offersViews(offered) {
|
|
1381
|
+
return offered.some((id) => id !== EVERY_TABLE_VIEW_ID);
|
|
1382
|
+
}
|
|
1383
|
+
function everyRow(table) {
|
|
1384
|
+
return `<fetch><entity name="${table}" /></fetch>`;
|
|
1385
|
+
}
|
|
1386
|
+
function withSavedViews(pageViews, savedViews, settings, control, shown, offersSavedViews) {
|
|
1387
|
+
if (!offersSavedViews) return [...pageViews];
|
|
1388
|
+
let all = [...new Set([...pageViews, ...savedViews].map((id) => id.toLowerCase()))];
|
|
1389
|
+
const userDefault = core.defaultViewId(settings, all, control);
|
|
1390
|
+
if (userDefault) all = [userDefault, ...all.filter((id) => id !== userDefault)];
|
|
1391
|
+
const offered = all.filter((id) => id === shown?.toLowerCase() || !core.isViewHidden(settings, id, control));
|
|
1392
|
+
return offered.length > 0 ? offered : all;
|
|
1393
|
+
}
|
|
1394
|
+
var LAYOUTS = ["day", "workWeek", "week", "month", "agenda"];
|
|
1395
|
+
function readSavedLayout(json) {
|
|
1396
|
+
if (!json?.trim()) return void 0;
|
|
1397
|
+
try {
|
|
1398
|
+
const layout = JSON.parse(json).layout;
|
|
1399
|
+
return LAYOUTS.find((x) => x === layout);
|
|
1400
|
+
} catch {
|
|
1401
|
+
return void 0;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function writeSavedLayout(layout) {
|
|
1405
|
+
return JSON.stringify({ version: 1, layout });
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// ../../node_modules/.pnpm/@fullcalendar+react@7.1.3_react-dom@19.2.5_react@19.2.5__react@19.2.5_temporal-polyfill@1.0.6/node_modules/@fullcalendar/react/skeleton.css
|
|
1409
|
+
var skeleton_default = `
|
|
1410
|
+
:root {
|
|
1411
|
+
--fc-sticky-header-footer-z: 3;
|
|
1412
|
+
--fc-popover-z: 4;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
.fc-rk {
|
|
1416
|
+
z-index: var(--fc-popover-z) !important;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
.fc-gE {
|
|
1420
|
+
isolation: isolate;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/*
|
|
1424
|
+
This will prevent all ancestors from customizing their box size unless they use .contentBox
|
|
1425
|
+
*/
|
|
1426
|
+
.fc-wY,
|
|
1427
|
+
.fc-wY *,
|
|
1428
|
+
.fc-wY *:before,
|
|
1429
|
+
.fc-wY *:after {
|
|
1430
|
+
box-sizing: border-box !important;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/* classes attached to <body> */
|
|
1434
|
+
.fc-8m,
|
|
1435
|
+
.fc-8m * {
|
|
1436
|
+
cursor: not-allowed !important;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
.fc-Is {
|
|
1440
|
+
-ms-overflow-style: none !important; /* IE and Edge */
|
|
1441
|
+
scrollbar-width: none !important; /* Firefox */
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/* Hide scrollbar for Chrome, Safari and Opera */
|
|
1445
|
+
.fc-Is::-webkit-scrollbar {
|
|
1446
|
+
display: none !important;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
.fc-Ps {
|
|
1450
|
+
flex-shrink: 0 !important;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
.fc-qC .fc-VR {
|
|
1454
|
+
display: flex !important;
|
|
1455
|
+
flex-direction: row !important;
|
|
1456
|
+
flex-wrap: wrap !important;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/* HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
|
|
1460
|
+
https://stackoverflow.com/a/60256345 */
|
|
1461
|
+
.fc-hF .fc-VR > * {
|
|
1462
|
+
float: left !important;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
[dir=rtl] .fc-hF .fc-VR > *,
|
|
1466
|
+
.fc-hF[dir=rtl] .fc-VR > * {
|
|
1467
|
+
float: right !important;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.fc-hF .fc-VR::after {
|
|
1471
|
+
content: "" !important;
|
|
1472
|
+
display: block !important;
|
|
1473
|
+
clear: both !important;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
.fc-Zu {
|
|
1477
|
+
cursor: pointer !important;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.fc-Hs {
|
|
1481
|
+
cursor: n-resize !important;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
.fc-J0 {
|
|
1485
|
+
cursor: s-resize !important;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.fc-jI {
|
|
1489
|
+
cursor: w-resize !important;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
[dir=rtl] .fc-jI {
|
|
1493
|
+
cursor: e-resize !important;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.fc-Nk {
|
|
1497
|
+
cursor: e-resize !important;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
[dir=rtl] .fc-Nk {
|
|
1501
|
+
cursor: w-resize !important;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
.fc-Mg {
|
|
1505
|
+
cursor: col-resize !important;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.fc-0r,
|
|
1509
|
+
.fc-Zc,
|
|
1510
|
+
.fc-yR {
|
|
1511
|
+
position: absolute !important;
|
|
1512
|
+
box-sizing: content-box !important;
|
|
1513
|
+
width: 100% !important;
|
|
1514
|
+
height: 100% !important;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.fc-0r {
|
|
1518
|
+
padding: 10px !important;
|
|
1519
|
+
margin: -10px !important;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
.fc-Zc {
|
|
1523
|
+
padding-left: 10px !important;
|
|
1524
|
+
padding-right: 10px !important;
|
|
1525
|
+
margin-left: -10px !important;
|
|
1526
|
+
margin-right: -10px !important;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
.fc-yR {
|
|
1530
|
+
padding-top: 10px !important;
|
|
1531
|
+
padding-bottom: 10px !important;
|
|
1532
|
+
margin-top: -10px !important;
|
|
1533
|
+
margin-bottom: -10px !important;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/* QUESTION: why not use -left -right instead of negative margins/padding for ALL? */
|
|
1537
|
+
.fc-JB {
|
|
1538
|
+
position: absolute;
|
|
1539
|
+
top: 0;
|
|
1540
|
+
bottom: 0;
|
|
1541
|
+
left: -5px;
|
|
1542
|
+
right: -5px;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.fc-uv {
|
|
1546
|
+
-webkit-user-select: none;
|
|
1547
|
+
-moz-user-select: none;
|
|
1548
|
+
user-select: none;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
.fc-B9 {
|
|
1552
|
+
visibility: hidden;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/* Border Utils */
|
|
1556
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1557
|
+
|
|
1558
|
+
.fc-13 {
|
|
1559
|
+
border: 0 !important;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
.fc-MT {
|
|
1563
|
+
border-left: 0 !important;
|
|
1564
|
+
border-right: 0 !important;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
.fc-60 {
|
|
1568
|
+
border-top: 0 !important;
|
|
1569
|
+
border-bottom: 0 !important;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
.fc-qy {
|
|
1573
|
+
border-top: 0 !important;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
.fc-lE {
|
|
1577
|
+
border-bottom: 0 !important;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
.fc-MR {
|
|
1581
|
+
border-inline-start: 0 !important;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.fc-li {
|
|
1585
|
+
border-inline-end: 0 !important;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/* Flexbox Utils */
|
|
1589
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1590
|
+
|
|
1591
|
+
.fc-mN {
|
|
1592
|
+
display: flex !important;
|
|
1593
|
+
flex-direction: row !important;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.fc-mU {
|
|
1597
|
+
display: flex !important;
|
|
1598
|
+
flex-direction: column !important;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.fc-l7 {
|
|
1602
|
+
flex-grow: 1 !important;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
.fc-OV {
|
|
1606
|
+
flex-grow: 1 !important;
|
|
1607
|
+
flex-basis: 0 !important;
|
|
1608
|
+
min-width: 0 !important;
|
|
1609
|
+
min-height: 0 !important;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.fc-bW {
|
|
1613
|
+
min-height: 0 !important;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/*
|
|
1617
|
+
TODO: use liquidX/Y elsewhere because frees up min other dimension; less collisions
|
|
1618
|
+
*/
|
|
1619
|
+
.fc-cq {
|
|
1620
|
+
flex-grow: 1 !important;
|
|
1621
|
+
flex-basis: 0 !important;
|
|
1622
|
+
min-width: 0 !important;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/* Semantic Print Table */
|
|
1626
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1627
|
+
|
|
1628
|
+
.fc-A8 {
|
|
1629
|
+
width: 100% !important;
|
|
1630
|
+
table-layout: fixed !important;
|
|
1631
|
+
border: 0 !important;
|
|
1632
|
+
border-collapse: separate !important;
|
|
1633
|
+
border-spacing: 0 !important;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
.fc-A8 > thead {
|
|
1637
|
+
/* Both properties are needed for cross-browser repeated headers across printed pages. */
|
|
1638
|
+
-moz-column-break-inside: avoid !important;
|
|
1639
|
+
break-inside: avoid !important;
|
|
1640
|
+
page-break-inside: avoid !important;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
.fc-A8 > tbody {
|
|
1644
|
+
/* Timeline print-layout hack (resource and non-resource): lets shared absolute backgrounds show through. */
|
|
1645
|
+
background: transparent !important;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
.fc-A8 > thead > tr > th,
|
|
1649
|
+
.fc-A8 > tbody > tr > th,
|
|
1650
|
+
.fc-A8 > tbody > tr > td {
|
|
1651
|
+
vertical-align: top !important;
|
|
1652
|
+
font-weight: inherit;
|
|
1653
|
+
text-align: inherit;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/* Table Utils */
|
|
1657
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1658
|
+
|
|
1659
|
+
.fc-Ju {
|
|
1660
|
+
padding: 0 !important;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
.fc-Eh {
|
|
1664
|
+
padding-top: 0 !important;
|
|
1665
|
+
padding-bottom: 0 !important;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.fc-yU {
|
|
1669
|
+
margin: 0 !important;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
.fc-6S {
|
|
1673
|
+
margin-top: 0 !important;
|
|
1674
|
+
margin-bottom: 0 !important;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.fc-ve {
|
|
1678
|
+
margin-left: 0 !important;
|
|
1679
|
+
margin-right: 0 !important;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
.fc-aG {
|
|
1683
|
+
white-space: nowrap !important;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
.fc-v9 {
|
|
1687
|
+
white-space: pre !important;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/* Misc Utils */
|
|
1691
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1692
|
+
|
|
1693
|
+
.fc-C4 {
|
|
1694
|
+
overflow-anchor: none;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
.fc-rX {
|
|
1698
|
+
pointer-events: none !important;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
.fc-mR {
|
|
1702
|
+
overflow: hidden !important;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
/*
|
|
1706
|
+
TODO: eventually use this on daygrid/timegrid events' inner, time, and title, FOR PRINT
|
|
1707
|
+
Needed to prevent wrapping to multiple lines, increasing height, throwing off print-positioning,
|
|
1708
|
+
which can't rely on dynamic height detection after print-flag activated.
|
|
1709
|
+
*/
|
|
1710
|
+
.fc-Kv {
|
|
1711
|
+
white-space: nowrap !important;
|
|
1712
|
+
overflow: hidden !important;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
.fc-Ru {
|
|
1716
|
+
position: relative !important;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.fc-r6 {
|
|
1720
|
+
position: absolute !important;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.fc-2W {
|
|
1724
|
+
inset-inline-start: 0 !important;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
.fc-0D {
|
|
1728
|
+
inset-inline-end: 0 !important;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
.fc-9b {
|
|
1732
|
+
position: absolute !important;
|
|
1733
|
+
top: 0 !important;
|
|
1734
|
+
left: 0 !important;
|
|
1735
|
+
right: 0 !important;
|
|
1736
|
+
bottom: 0 !important;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
.fc-6Q,
|
|
1740
|
+
.fc-zm {
|
|
1741
|
+
position: absolute !important;
|
|
1742
|
+
left: 0 !important;
|
|
1743
|
+
right: 0 !important;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
.fc-9F,
|
|
1747
|
+
.fc-Ma {
|
|
1748
|
+
position: absolute !important;
|
|
1749
|
+
top: 0 !important;
|
|
1750
|
+
bottom: 0 !important;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
.fc-6Q {
|
|
1754
|
+
top: 0 !important;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.fc-Ma {
|
|
1758
|
+
left: 0 !important;
|
|
1759
|
+
right: 0 !important;
|
|
1760
|
+
width: 0 !important;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
@media not print {
|
|
1764
|
+
.fc-Yy {
|
|
1765
|
+
position: sticky !important;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
.fc-DR {
|
|
1769
|
+
position: sticky !important;
|
|
1770
|
+
top: 0 !important;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/* Sticks to either left or right (the flex "start") depending on ltr/rtl */
|
|
1774
|
+
.fc-QU {
|
|
1775
|
+
position: sticky !important;
|
|
1776
|
+
inset-inline-start: 0 !important;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
.fc-5M {
|
|
1780
|
+
position: sticky !important;
|
|
1781
|
+
top: 0 !important;
|
|
1782
|
+
z-index: var(--fc-sticky-header-footer-z) !important;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
/* Only needed when padding/border must be separate from width/height */
|
|
1787
|
+
.fc-qN {
|
|
1788
|
+
box-sizing: content-box !important;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
/* Out of view but keeps its real size, for measurement probes. For screen-reader text, use .srOnly.
|
|
1792
|
+
Hidden also means not focusable, clickable, or read by screen readers */
|
|
1793
|
+
.fc-rU {
|
|
1794
|
+
position: absolute !important;
|
|
1795
|
+
left: -10000px !important;
|
|
1796
|
+
visibility: hidden !important;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/* Visually hidden but read by screen readers. Unlike .offscreen, never causes scroll overflow */
|
|
1800
|
+
.fc-og {
|
|
1801
|
+
position: absolute !important;
|
|
1802
|
+
width: 1px !important;
|
|
1803
|
+
height: 1px !important;
|
|
1804
|
+
margin: 0 !important;
|
|
1805
|
+
padding: 0 !important;
|
|
1806
|
+
border: 0 !important;
|
|
1807
|
+
overflow: hidden !important;
|
|
1808
|
+
clip-path: inset(50%) !important;
|
|
1809
|
+
white-space: nowrap !important;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
.fc-hm {
|
|
1813
|
+
align-items: center !important;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.fc-cI {
|
|
1817
|
+
align-items: flex-start !important;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
.fc-Qq {
|
|
1821
|
+
align-items: flex-end !important;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/* Footer Scrollbar */
|
|
1825
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1826
|
+
|
|
1827
|
+
.fc-se {
|
|
1828
|
+
position: sticky !important;
|
|
1829
|
+
bottom: 0 !important;
|
|
1830
|
+
z-index: var(--fc-sticky-header-footer-z) !important;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
.fc-Vd > * {
|
|
1834
|
+
margin-top: -1px !important;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.fc-Vd > * > * {
|
|
1838
|
+
height: 1px !important;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
/* Print Utils */
|
|
1842
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1843
|
+
|
|
1844
|
+
.fc-or {
|
|
1845
|
+
-moz-column-break-inside: avoid !important;
|
|
1846
|
+
break-inside: avoid !important;
|
|
1847
|
+
page-break-inside: avoid !important;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
.fc-Lx {
|
|
1851
|
+
min-height: 4em !important;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
.fc-vn {
|
|
1855
|
+
display: flow-root !important;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
/* Z-index */
|
|
1859
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1860
|
+
|
|
1861
|
+
.fc-mP {
|
|
1862
|
+
z-index: 0;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
.fc-NW {
|
|
1866
|
+
z-index: 1;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
.fc-Zs {
|
|
1870
|
+
z-index: 2;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
.fc-D7 {
|
|
1874
|
+
z-index: 3;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
.fc-Se {
|
|
1878
|
+
z-index: 4;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
.fc-kE {
|
|
1882
|
+
z-index: 5;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
.fc-Y9 {
|
|
1886
|
+
z-index: 1000;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
.fc-DP {
|
|
1890
|
+
z-index: 9999;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.fc-Xr:focus-visible {
|
|
1894
|
+
z-index: 2;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/* INTERNAL MARKER -- used to mark an element for internal significance */
|
|
1898
|
+
/* ------------------------------------------------------------------------------------------------- */
|
|
1899
|
+
|
|
1900
|
+
.fc-8W {}
|
|
1901
|
+
|
|
1902
|
+
.fc-Ad {}
|
|
1903
|
+
.fc-oG {}
|
|
1904
|
+
.fc-Yb {}
|
|
1905
|
+
.fc-JA {}
|
|
1906
|
+
.fc-4p {}
|
|
1907
|
+
.fc-IR {}
|
|
1908
|
+
.fc-tW {}
|
|
1909
|
+
.fc-8h {}
|
|
1910
|
+
|
|
1911
|
+
.fc-Jv {}
|
|
1912
|
+
|
|
1913
|
+
.fc-XX {}
|
|
1914
|
+
|
|
1915
|
+
.fc-89 {}
|
|
1916
|
+
|
|
1917
|
+
.fc-wF {}
|
|
1918
|
+
|
|
1919
|
+
.fc-tw {}
|
|
1920
|
+
|
|
1921
|
+
.fc-ox {}
|
|
1922
|
+
`;
|
|
1923
|
+
|
|
1924
|
+
// ../../node_modules/.pnpm/@fullcalendar+react@7.1.3_react-dom@19.2.5_react@19.2.5__react@19.2.5_temporal-polyfill@1.0.6/node_modules/@fullcalendar/react/themes/forma/theme.css
|
|
1925
|
+
var theme_default = ".fc-forma-n5m {\n font-size: 16px;\n line-height: 1.5;\n --fc-forma-border-style: solid;\n}\n\n.fc-forma-Z9U {\n appearance: button;\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n opacity: 1;\n background-color: #0000;\n border-radius: 0;\n margin: 0;\n border: 0;\n padding: 0\n}\n\n.fc-forma-JiE {\n color: inherit;\n text-decoration: inherit;\n}\n\n.fc-forma-3Lc {\n pointer-events: none\n}\n\n.fc-forma-1EY {\n position: absolute\n}\n\n.fc-forma-eYX {\n position: relative\n}\n\n.fc-forma-MaV {\n inset-inline: 0\n}\n\n.fc-forma-AWB {\n inset-block: 0\n}\n\n.fc-forma-0fm {\n inset-inline-start: -8px\n}\n\n.fc-forma-Tuc {\n inset-inline-end: -4px\n}\n\n.fc-forma-iD1 {\n inset-inline-end: 0\n}\n\n.fc-forma-SyR {\n inset-inline-end: 4px\n}\n\n.fc-forma-dnf {\n inset-inline-end: -3px\n}\n\n.fc-forma-YDC {\n top: -4px\n}\n\n.fc-forma-n9G {\n top: 0\n}\n\n.fc-forma-2ik {\n top: 2px\n}\n\n.fc-forma-ZnE {\n top: 4px\n}\n\n.fc-forma-ERR {\n top: 50%\n}\n\n.fc-forma-fJL {\n bottom: -4px\n}\n\n.fc-forma-1V6 {\n left: 50%\n}\n\n.fc-forma-aAW {\n margin: -6px\n}\n\n.fc-forma-CH7 {\n margin: -1px\n}\n\n.fc-forma-gMS {\n margin: 4px\n}\n\n.fc-forma-bvX {\n margin: 8px\n}\n\n.fc-forma-jD5 {\n margin: 16px\n}\n\n.fc-forma-cKZ {\n margin-inline: 2px\n}\n\n.fc-forma-148 {\n margin-inline: 1px\n}\n\n.fc-forma-cJ3 {\n margin-block: 2px\n}\n\n.fc-forma-V9v {\n margin-block: 4px\n}\n\n.fc-forma-SEP {\n margin-block: 1px\n}\n\n.fc-forma-Jzj {\n margin-inline-start: 2px\n}\n\n.fc-forma-Wga {\n margin-inline-start: 4px\n}\n\n.fc-forma-qvL {\n margin-inline-start: 1px\n}\n\n.fc-forma-p9t {\n margin-inline-end: -6px\n}\n\n.fc-forma-3e1 {\n margin-inline-end: 2px\n}\n\n.fc-forma-KYn {\n margin-inline-end: 4px\n}\n\n.fc-forma-B3G {\n margin-inline-end: 2.5%\n}\n\n.fc-forma-9hC {\n margin-inline-end: 1px\n}\n\n.fc-forma-Dq8 {\n margin-top: -4px\n}\n\n.fc-forma-Ika {\n margin-bottom: 1px\n}\n\n.fc-forma-F99 {\n margin-left: -4px\n}\n\n.fc-forma-I48 {\n display: block\n}\n\n.fc-forma-dl1 {\n display: flex\n}\n\n.fc-forma-i3N {\n display: grid\n}\n\n.fc-forma-pps {\n display: none\n}\n\n.fc-forma-63n {\n width: 0;\n height: 0\n}\n\n.fc-forma-3wQ {\n width: 8px;\n height: 8px\n}\n\n.fc-forma-vnf {\n width: 16px;\n height: 16px\n}\n\n.fc-forma-XUJ {\n width: 20px;\n height: 20px\n}\n\n.fc-forma-uuA {\n height: 8px\n}\n\n.fc-forma-vYi {\n height: 10px\n}\n\n.fc-forma-oM6 {\n height: 20px\n}\n\n.fc-forma-TFV {\n height: 24px\n}\n\n.fc-forma-84e {\n min-height: 2px\n}\n\n.fc-forma-jgW {\n min-height: 16px\n}\n\n.fc-forma-toR {\n min-height: 1px\n}\n\n.fc-forma-roZ {\n width: 50%\n}\n\n.fc-forma-vVE {\n width: 25%\n}\n\n.fc-forma-hza {\n width: 8px\n}\n\n.fc-forma-79F {\n width: 20px\n}\n\n.fc-forma-ilz {\n width: 24px\n}\n\n.fc-forma-4Tv {\n width: 5px\n}\n\n.fc-forma-aHX {\n max-width: 160px\n}\n\n.fc-forma-2KU {\n min-width: 0\n}\n\n.fc-forma-aNc {\n min-width: 220px\n}\n\n.fc-forma-yi0 {\n flex-shrink: 0\n}\n\n.fc-forma-1Zl {\n flex-shrink: 1\n}\n\n.fc-forma-OLq {\n flex-shrink: 100\n}\n\n.fc-forma-1El {\n flex-grow: 1\n}\n\n.fc-forma-QX7 {\n rotate: -45deg\n}\n\n.fc-forma-KxI {\n rotate: -90deg\n}\n\n.fc-forma-ABq {\n rotate: 45deg\n}\n\n.fc-forma-z44 {\n rotate: 90deg\n}\n\n.fc-forma-jmT {\n rotate: 180deg\n}\n\n.fc-forma-sgX {\n flex-direction: column\n}\n\n.fc-forma-1sP {\n flex-direction: row\n}\n\n.fc-forma-dNl {\n flex-wrap: wrap\n}\n\n.fc-forma-XpK {\n align-items: center\n}\n\n.fc-forma-RNn {\n align-items: flex-end\n}\n\n.fc-forma-EF4 {\n align-items: flex-start\n}\n\n.fc-forma-N2M {\n justify-content: space-between\n}\n\n.fc-forma-E9P {\n justify-content: center\n}\n\n.fc-forma-LMv {\n justify-content: flex-end\n}\n\n.fc-forma-NWN {\n gap: 4px\n}\n\n.fc-forma-tgZ {\n gap: 8px\n}\n\n.fc-forma-wwb {\n gap: 12px\n}\n\n.fc-forma-Pms {\n gap: 16px\n}\n\n.fc-forma-sI7 {\n align-self: flex-start\n}\n\n.fc-forma-pKG {\n overflow: hidden\n}\n\n.fc-forma-AAA {\n border-radius: 3.40282e38px\n}\n\n.fc-forma-Fvv {\n border-radius: 4px\n}\n\n.fc-forma-kmj {\n border-start-start-radius: 4px;\n border-end-start-radius: 4px\n}\n\n.fc-forma-Skl {\n border-start-end-radius: 4px;\n border-end-end-radius: 4px\n}\n\n.fc-forma-Z7Q {\n border-top-left-radius: 4px;\n border-top-right-radius: 4px\n}\n\n.fc-forma-2qh {\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px\n}\n\n.fc-forma-wsy,\n.fc-forma-qQW {\n border-style: var(--fc-forma-border-style);\n border-width: 1px\n}\n\n.fc-forma-5JF {\n border-style: var(--fc-forma-border-style);\n border-width: 4px\n}\n\n.fc-forma-Vpk {\n border-style: var(--fc-forma-border-style);\n border-width: 6px\n}\n\n.fc-forma-1Wx {\n border-inline-style: var(--fc-forma-border-style);\n border-inline-width: 1px\n}\n\n.fc-forma-8UH {\n border-inline-start-style: var(--fc-forma-border-style);\n border-inline-start-width: 1px\n}\n\n.fc-forma-iSi {\n border-inline-start-style: var(--fc-forma-border-style);\n border-inline-start-width: 4px\n}\n\n.fc-forma-riO {\n border-inline-start-style: var(--fc-forma-border-style);\n border-inline-start-width: 6px\n}\n\n.fc-forma-USt,\n.fc-forma-lzW {\n border-inline-end-style: var(--fc-forma-border-style);\n border-inline-end-width: 1px\n}\n\n.fc-forma-ku3,\n.fc-forma-u78 {\n border-top-style: var(--fc-forma-border-style);\n border-top-width: 1px\n}\n\n.fc-forma-tb8 {\n border-top-style: var(--fc-forma-border-style);\n border-top-width: 4px\n}\n\n.fc-forma-zi1 {\n border-bottom-style: var(--fc-forma-border-style);\n border-bottom-width: 1px\n}\n\n.fc-forma-TN2 {\n --fc-forma-border-style: dotted;\n border-style: dotted\n}\n\n.fc-forma-lNM {\n border-color: var(--fc-event-color)\n}\n\n.fc-forma-tfB {\n border-color: var(--fc-forma-border)\n}\n\n.fc-forma-MlZ {\n border-color: var(--fc-forma-muted-foreground)\n}\n\n.fc-forma-Baf {\n border-color: var(--fc-forma-primary)\n}\n\n.fc-forma-BST {\n border-color: var(--fc-forma-strong-border)\n}\n\n.fc-forma-d0j {\n border-color: #0000\n}\n\n.fc-forma-RJG {\n background-color: var(--fc-forma-background)\n}\n\n.fc-forma-zNL {\n background-color: var(--fc-forma-faint)\n}\n\n.fc-forma-rRL {\n background-color: var(--fc-forma-highlight)\n}\n\n.fc-forma-Wv4 {\n background-color: var(--fc-forma-muted)\n}\n\n.fc-forma-7I6 {\n background-color: var(--fc-forma-primary)\n}\n\n@media not print {\n @supports (color:color-mix(in lab, red, red)) {\n .fc-forma-gTC {\n background-color: color-mix(in oklab, var(--fc-event-color)15%, transparent)\n }\n }\n @supports not (color:color-mix(in lab, red, red)) {\n .fc-forma-gTC {\n position: relative;\n isolation: isolate\n }\n .fc-forma-gTC:before {\n content: '';\n position: absolute;\n inset: 0;\n background-color: var(--fc-event-color);\n opacity: 0.15;\n z-index: -1\n }\n }\n}\n\n.fc-forma-lrD {\n background-color: var(--fc-event-color);\n color: var(--fc-event-contrast-color)\n}\n\n@supports (color:color-mix(in lab, red, red)) {\n .fc-forma-lrD {\n background-color: color-mix(in oklab, var(--fc-event-color)20%, var(--fc-forma-background));\n color: inherit\n }\n}\n\n.fc-forma-i6P {\n background-color: var(--fc-event-color);\n color: var(--fc-event-contrast-color)\n}\n\n@supports (color:color-mix(in lab, red, red)) {\n .fc-forma-i6P {\n background-color: color-mix(in oklab, var(--fc-event-color)30%, var(--fc-forma-background));\n color: inherit\n }\n}\n\n.fc-forma-KUX {\n padding: 2px\n}\n\n.fc-forma-iS4 {\n padding: 4px\n}\n\n.fc-forma-3N5 {\n padding: 8px\n}\n\n.fc-forma-IJJ {\n padding: 12px\n}\n\n.fc-forma-lqx {\n padding: 16px\n}\n\n.fc-forma-7A6 {\n padding: 1px\n}\n\n.fc-forma-oQ2 {\n padding-inline: 2px\n}\n\n.fc-forma-aCI {\n padding-inline: 4px\n}\n\n.fc-forma-Nca {\n padding-inline: 8px\n}\n\n.fc-forma-Apf {\n padding-inline: 12px\n}\n\n.fc-forma-2rx {\n padding-block: 2px\n}\n\n.fc-forma-Jhn {\n padding-block: 4px\n}\n\n.fc-forma-End {\n padding-block: 6px\n}\n\n.fc-forma-dl6 {\n padding-block: 8px\n}\n\n.fc-forma-P9h {\n padding-block: 60px\n}\n\n.fc-forma-s0x {\n padding-block: 3px\n}\n\n.fc-forma-z5u {\n padding-block: 1px\n}\n\n.fc-forma-a7i {\n padding-inline-start: 2px\n}\n\n.fc-forma-C2j {\n padding-inline-start: 4px\n}\n\n.fc-forma-166 {\n padding-top: 2px\n}\n\n.fc-forma-4dx {\n padding-top: 4px\n}\n\n.fc-forma-cM0 {\n padding-bottom: 16px\n}\n\n.fc-forma-2HE {\n text-align: end\n}\n\n.fc-forma-vQz {\n font-size: 11px;\n line-height: 1.09091\n}\n\n.fc-forma-9ZS {\n font-size: 18px;\n line-height: calc(1.75/1.125)\n}\n\n.fc-forma-1Po {\n font-size: 16px;\n line-height: calc(1.5/1)\n}\n\n.fc-forma-9yp {\n font-size: 14px;\n line-height: calc(1.25/.875)\n}\n\n.fc-forma-2rA {\n font-size: 20px;\n line-height: calc(1.75/1.25)\n}\n\n.fc-forma-a3B {\n font-size: 12px;\n line-height: calc(1/.75)\n}\n\n.fc-forma-DIS {\n font-weight: 700\n}\n\n.fc-forma-1OT {\n font-weight: 500\n}\n\n.fc-forma-C8a {\n font-weight: 600\n}\n\n.fc-forma-IPx {\n text-overflow: ellipsis\n}\n\n.fc-forma-TZ4 {\n white-space: nowrap\n}\n\n.fc-forma-jm6 {\n white-space: pre\n}\n\n.fc-forma-b7K {\n color: var(--fc-forma-foreground)\n}\n\n.fc-forma-cOV {\n color: var(--fc-forma-faint-foreground)\n}\n\n.fc-forma-V1v {\n color: var(--fc-forma-muted-foreground)\n}\n\n.fc-forma-TSf {\n color: var(--fc-forma-primary-foreground)\n}\n\n.fc-forma-L1Y {\n font-style: italic\n}\n\n.fc-forma-lMo {\n opacity: .5\n}\n\n.fc-forma-iTG {\n opacity: .75\n}\n\n.fc-forma-1kP {\n box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a\n}\n\n.fc-forma-tkw {\n box-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a\n}\n\n.fc-forma-eSM {\n box-shadow: 0 1px 2px 0 #0000000d\n}\n\n.fc-forma-A3h {\n box-shadow: 0 0 0 1px var(--fc-forma-ring-color, currentcolor)\n}\n\n.fc-forma-GBJ {\n box-shadow: 0 0 0 2px var(--fc-forma-ring-color, currentcolor)\n}\n\n.fc-forma-c3P {\n --fc-forma-ring-color: var(--fc-forma-background)\n}\n\n.fc-forma-2R2 {\n outline-style: solid;\n outline-width: 1px\n}\n\n.fc-forma-0Bj {\n outline-style: solid;\n outline-width: 2px\n}\n\n.fc-forma-07j {\n outline-offset: -1px\n}\n\n.fc-forma-fFh {\n outline-offset: -2px\n}\n\n.fc-forma-3Xj {\n outline-offset: 2px\n}\n\n.fc-forma-Fmy {\n outline-color: var(--fc-forma-primary)\n}\n\n.fc-forma-mr2 {\n background: linear-gradient(var(--fc-forma-strong), var(--fc-forma-strong))var(--fc-forma-background)\n}\n\n.fc-forma-iCr:not(:last-child) {\n border-bottom-style: var(--fc-forma-border-style);\n border-bottom-width: 1px\n}\n\n@media not print {\n .fc-forma-530 {\n padding-block: 1px\n }\n\n .fc-forma-ZNR {\n padding-inline-end: 1px\n }\n\n .fc-forma-jVY {\n padding-top: 1px\n }\n\n .fc-forma-K3J {\n padding-bottom: 1px\n }\n}\n\n@media (hover:hover) {\n .fc-forma-vs6:is(:where(.fc-forma-bCs):hover *) {\n display: block\n }\n\n .fc-forma-sS4:is(:where(.fc-forma-bCs):hover *) {\n color: var(--fc-forma-primary)\n }\n\n .fc-forma-Ogp:is(:where(.fc-forma-bCs):hover *) {\n text-decoration-line: underline\n }\n}\n\n.fc-forma-Di9:is(:where(.fc-forma-bCs):focus-visible *) {\n color: var(--fc-forma-primary)\n}\n\n@media (hover:hover) {\n .fc-forma-cBw:hover {\n border-color: var(--fc-forma-strong-border)\n }\n\n .fc-forma-ShG:hover {\n background-color: var(--fc-forma-muted)\n }\n\n .fc-forma-2Qu:hover {\n background-color: var(--fc-forma-primary-over)\n }\n\n .fc-forma-mcn:hover {\n background-color: var(--fc-forma-strong)\n }\n\n .fc-forma-rom:hover {\n background-color: var(--fc-event-color)\n }\n\n @supports (color:color-mix(in lab, red, red)) {\n .fc-forma-rom:hover {\n background-color: color-mix(in oklab, var(--fc-event-color)25%, var(--fc-forma-background))\n }\n }\n\n .fc-forma-SWh:hover {\n background-color: var(--fc-event-color)\n }\n\n @supports (color:color-mix(in lab, red, red)) {\n .fc-forma-SWh:hover {\n background-color: color-mix(in oklab, var(--fc-event-color)35%, var(--fc-forma-background))\n }\n }\n\n .fc-forma-Eu0:hover {\n text-decoration-line: underline\n }\n\n .fc-forma-Vyz:hover {\n background: linear-gradient(var(--fc-forma-stronger), var(--fc-forma-stronger))var(--fc-forma-background)\n }\n}\n\n.fc-forma-nge:focus-visible {\n background-color: var(--fc-forma-muted)\n}\n\n.fc-forma-lgp:focus-visible {\n background-color: var(--fc-forma-primary-over)\n}\n\n.fc-forma-uqo:focus-visible {\n outline-style: solid;\n outline-width: 2px\n}\n\n.fc-forma-PQx:active {\n background-color: var(--fc-forma-primary-down)\n}\n\n.fc-forma-rKU:active {\n background-color: var(--fc-forma-strong)\n}\n\n.fc-forma-0sn:active {\n background-color: var(--fc-forma-stronger)\n}\n\n.fc-forma-DUE:active {\n background-color: var(--fc-event-color)\n}\n\n@supports (color:color-mix(in lab, red, red)) {\n .fc-forma-DUE:active {\n background-color: color-mix(in oklab, var(--fc-event-color)30%, var(--fc-forma-background))\n }\n}\n\n.fc-forma-RHy:active {\n background-color: var(--fc-event-color)\n}\n\n@supports (color:color-mix(in lab, red, red)) {\n .fc-forma-RHy:active {\n background-color: color-mix(in oklab, var(--fc-event-color)40%, var(--fc-forma-background))\n }\n}\n\n.fc-forma-pgt:active {\n background: linear-gradient(var(--fc-forma-strongest), var(--fc-forma-strongest))var(--fc-forma-background)\n}\n\n@media print {\n .fc-forma-2dx {\n border-block-style: var(--fc-forma-border-style);\n border-block-width: 1px\n }\n\n .fc-forma-9wT {\n border-inline-end-style: var(--fc-forma-border-style);\n border-inline-end-width: 1px\n }\n\n .fc-forma-Qex {\n border-top-style: var(--fc-forma-border-style);\n border-top-width: 1px\n }\n\n .fc-forma-wZV {\n border-bottom-style: var(--fc-forma-border-style);\n border-bottom-width: 1px\n }\n\n .fc-forma-jsy {\n border-width: 1px\n }\n\n .fc-forma-DO7 {\n border-color: var(--fc-event-color)\n }\n\n .fc-forma-4MR {\n border-color: #000\n }\n\n .fc-forma-vwH {\n background-color: #fff\n }\n}\n\n[dir=rtl] .fc-forma-qIw {\n rotate: -45deg\n}\n\n[dir=rtl] .fc-forma-keW {\n rotate: -90deg\n}\n\n[dir=rtl] .fc-forma-jY6 {\n rotate: none\n}\n\n[dir=rtl] .fc-forma-vk6 {\n rotate: 45deg\n}\n\n[dir=rtl] .fc-forma-ZW3 {\n rotate: 90deg\n}\n\n[dir=rtl] .fc-forma-asP {\n rotate: 180deg\n}\n";
|
|
1926
|
+
|
|
1927
|
+
// src/fullcalendar-styles.ts
|
|
1928
|
+
var STYLE_ID2 = "ppp-calendar-fullcalendar-styles";
|
|
1929
|
+
function ensureFullCalendarStyles(document2) {
|
|
1930
|
+
ensureStyleSheet(document2, STYLE_ID2, `${skeleton_default}
|
|
1931
|
+
${theme_default}`);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/persisted-calendar-state.ts
|
|
1935
|
+
var DATE2 = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
1936
|
+
var GUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1937
|
+
function isDate(value) {
|
|
1938
|
+
if (typeof value !== "string") return false;
|
|
1939
|
+
const match = DATE2.exec(value);
|
|
1940
|
+
if (!match) return false;
|
|
1941
|
+
const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
1942
|
+
const date = new Date(year, month - 1, day);
|
|
1943
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
|
1944
|
+
}
|
|
1945
|
+
function readPersistedCalendarState(queryParameterName) {
|
|
1946
|
+
if (!queryParameterName || typeof window === "undefined") return {};
|
|
1947
|
+
const raw = new URLSearchParams(window.location.search).get(queryParameterName);
|
|
1948
|
+
if (!raw) return {};
|
|
1949
|
+
try {
|
|
1950
|
+
const parsed = JSON.parse(raw);
|
|
1951
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
1952
|
+
const { l, d, v, q } = parsed;
|
|
1953
|
+
const state = {};
|
|
1954
|
+
if (calendarLayouts.includes(l)) state.l = l;
|
|
1955
|
+
if (isDate(d)) state.d = d;
|
|
1956
|
+
if (typeof v === "string" && GUID2.test(v)) state.v = v.toLowerCase();
|
|
1957
|
+
if (typeof q === "string" && q.trim()) state.q = q;
|
|
1958
|
+
return state;
|
|
1959
|
+
} catch {
|
|
1960
|
+
return {};
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
function encodePersistedCalendarState(state) {
|
|
1964
|
+
const compact = {};
|
|
1965
|
+
if (state.l) compact.l = state.l;
|
|
1966
|
+
if (state.d) compact.d = state.d;
|
|
1967
|
+
if (state.v) compact.v = state.v;
|
|
1968
|
+
if (state.q?.trim()) compact.q = state.q;
|
|
1969
|
+
return Object.keys(compact).length > 0 ? JSON.stringify(compact) : null;
|
|
1970
|
+
}
|
|
1971
|
+
function writePersistedCalendarState(queryParameterName, state) {
|
|
1972
|
+
if (!queryParameterName || typeof window === "undefined") return;
|
|
1973
|
+
const encoded = encodePersistedCalendarState(state);
|
|
1974
|
+
const params = new URLSearchParams(window.location.search);
|
|
1975
|
+
if (params.get(queryParameterName) === encoded) return;
|
|
1976
|
+
if (encoded === null) params.delete(queryParameterName);
|
|
1977
|
+
else params.set(queryParameterName, encoded);
|
|
1978
|
+
const query = params.toString();
|
|
1979
|
+
window.history.replaceState(
|
|
1980
|
+
window.history.state,
|
|
1981
|
+
"",
|
|
1982
|
+
`${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
var plugins = [formaTheme__default.default, dayGridPlugin__default.default, timeGridPlugin__default.default, listPlugin__default.default, interactionPlugin__default.default];
|
|
1986
|
+
var SEARCH_DEBOUNCE_MS = 300;
|
|
1987
|
+
var layoutKeys = {
|
|
1988
|
+
day: "layouts.day",
|
|
1989
|
+
workWeek: "layouts.work-week",
|
|
1990
|
+
week: "layouts.week",
|
|
1991
|
+
month: "layouts.month",
|
|
1992
|
+
agenda: "layouts.agenda"
|
|
1993
|
+
};
|
|
1994
|
+
var useStyles3 = reactComponents.makeStyles({
|
|
1995
|
+
root: {
|
|
1996
|
+
display: "flex",
|
|
1997
|
+
flexDirection: "column",
|
|
1998
|
+
gap: reactComponents.tokens.spacingVerticalS,
|
|
1999
|
+
minHeight: 0
|
|
2000
|
+
},
|
|
2001
|
+
// Two groups: the views, navigation and title, then search and the rest at the end. On a narrow
|
|
2002
|
+
// screen the end group wraps below the first as a whole rather than squeezing the title.
|
|
2003
|
+
toolbar: {
|
|
2004
|
+
alignItems: "center",
|
|
2005
|
+
flexWrap: "wrap",
|
|
2006
|
+
justifyContent: "space-between",
|
|
2007
|
+
rowGap: reactComponents.tokens.spacingVerticalXS,
|
|
2008
|
+
columnGap: reactComponents.tokens.spacingHorizontalS,
|
|
2009
|
+
paddingInline: 0
|
|
2010
|
+
},
|
|
2011
|
+
toolbarStart: {
|
|
2012
|
+
display: "flex",
|
|
2013
|
+
alignItems: "center",
|
|
2014
|
+
gap: reactComponents.tokens.spacingHorizontalXS,
|
|
2015
|
+
flex: "1 1 auto",
|
|
2016
|
+
minWidth: 0
|
|
2017
|
+
},
|
|
2018
|
+
toolbarEnd: {
|
|
2019
|
+
display: "flex",
|
|
2020
|
+
flexWrap: "wrap",
|
|
2021
|
+
alignItems: "center",
|
|
2022
|
+
justifyContent: "flex-end",
|
|
2023
|
+
gap: reactComponents.tokens.spacingHorizontalXS,
|
|
2024
|
+
marginInlineStart: "auto",
|
|
2025
|
+
minWidth: 0
|
|
2026
|
+
},
|
|
2027
|
+
// The period and the moves through it, kept together.
|
|
2028
|
+
navigation: {
|
|
2029
|
+
display: "flex",
|
|
2030
|
+
alignItems: "center",
|
|
2031
|
+
gap: reactComponents.tokens.spacingHorizontalXS,
|
|
2032
|
+
minWidth: 0
|
|
2033
|
+
},
|
|
2034
|
+
title: {
|
|
2035
|
+
flex: "0 1 auto",
|
|
2036
|
+
minWidth: 0,
|
|
2037
|
+
// A heading, which a page's own styles may give margins: none above or below, so it stays
|
|
2038
|
+
// centered on the toolbar's buttons.
|
|
2039
|
+
marginTop: 0,
|
|
2040
|
+
marginBottom: 0,
|
|
2041
|
+
marginInlineStart: reactComponents.tokens.spacingHorizontalS,
|
|
2042
|
+
fontSize: reactComponents.tokens.fontSizeBase400,
|
|
2043
|
+
fontWeight: reactComponents.tokens.fontWeightSemibold,
|
|
2044
|
+
whiteSpace: "nowrap",
|
|
2045
|
+
overflow: "hidden",
|
|
2046
|
+
textOverflow: "ellipsis"
|
|
2047
|
+
},
|
|
2048
|
+
// As wide as a grid's view picker.
|
|
2049
|
+
viewPicker: {
|
|
2050
|
+
minWidth: "220px"
|
|
2051
|
+
},
|
|
2052
|
+
search: {
|
|
2053
|
+
width: "220px",
|
|
2054
|
+
maxWidth: "100%"
|
|
2055
|
+
},
|
|
2056
|
+
// The appointments, and the overlay over them while a range loads.
|
|
2057
|
+
body: {
|
|
2058
|
+
position: "relative",
|
|
2059
|
+
flex: 1,
|
|
2060
|
+
minHeight: 0,
|
|
2061
|
+
display: "flex",
|
|
2062
|
+
flexDirection: "column"
|
|
2063
|
+
},
|
|
2064
|
+
surface: {
|
|
2065
|
+
flex: 1,
|
|
2066
|
+
minHeight: 0
|
|
2067
|
+
},
|
|
2068
|
+
// As a grid's loading overlay: the appointments dimmed under a spinner.
|
|
2069
|
+
loadingOverlay: {
|
|
2070
|
+
position: "absolute",
|
|
2071
|
+
inset: 0,
|
|
2072
|
+
display: "flex",
|
|
2073
|
+
alignItems: "center",
|
|
2074
|
+
justifyContent: "center",
|
|
2075
|
+
zIndex: 4,
|
|
2076
|
+
"::before": {
|
|
2077
|
+
content: '""',
|
|
2078
|
+
position: "absolute",
|
|
2079
|
+
inset: 0,
|
|
2080
|
+
backgroundColor: reactComponents.tokens.colorNeutralBackground2,
|
|
2081
|
+
opacity: 0.7,
|
|
2082
|
+
zIndex: -1
|
|
2083
|
+
}
|
|
2084
|
+
},
|
|
2085
|
+
visuallyHidden: {
|
|
2086
|
+
position: "absolute",
|
|
2087
|
+
width: "1px",
|
|
2088
|
+
height: "1px",
|
|
2089
|
+
margin: "-1px",
|
|
2090
|
+
padding: 0,
|
|
2091
|
+
overflow: "hidden",
|
|
2092
|
+
clip: "rect(0 0 0 0)",
|
|
2093
|
+
whiteSpace: "nowrap",
|
|
2094
|
+
border: 0
|
|
2095
|
+
}
|
|
2096
|
+
});
|
|
2097
|
+
function Calendar({
|
|
2098
|
+
appointments,
|
|
2099
|
+
regarding,
|
|
2100
|
+
tables,
|
|
2101
|
+
defaultTables = DEFAULT_CALENDAR_TABLES,
|
|
2102
|
+
viewIds,
|
|
2103
|
+
multiTableViewIds,
|
|
2104
|
+
defaultViewId: defaultViewId2,
|
|
2105
|
+
customViewDefinitions,
|
|
2106
|
+
matchParties,
|
|
2107
|
+
series,
|
|
2108
|
+
templates,
|
|
2109
|
+
forms,
|
|
2110
|
+
formLocation = "center",
|
|
2111
|
+
viewSort = reactFluent.ViewSort.NameAscending,
|
|
2112
|
+
allowFiltering = false,
|
|
2113
|
+
allowSavedViews = false,
|
|
2114
|
+
savedViewsKey,
|
|
2115
|
+
signInUrl = "/Account/Login",
|
|
2116
|
+
allowSearch = true,
|
|
2117
|
+
includeSearchInPersistedState = false,
|
|
2118
|
+
defaultLayout = "week",
|
|
2119
|
+
layouts,
|
|
2120
|
+
editable = false,
|
|
2121
|
+
regardingTables,
|
|
2122
|
+
height = "600px",
|
|
2123
|
+
onAppointmentClick,
|
|
2124
|
+
appointmentDetails,
|
|
2125
|
+
appointmentTooltip,
|
|
2126
|
+
onRangeSelect,
|
|
2127
|
+
onAppointmentChange,
|
|
2128
|
+
onRangeChange,
|
|
2129
|
+
persistedStateQueryParameter,
|
|
2130
|
+
children
|
|
2131
|
+
}) {
|
|
2132
|
+
const styles = useStyles3();
|
|
2133
|
+
const dialog = react$1.useDialogService();
|
|
2134
|
+
const dialogLocation = formLocation === "left" ? "panel-left" : formLocation === "right" ? "panel" : "center";
|
|
2135
|
+
const t = react$1.usePrefixedT("app.components.calendar");
|
|
2136
|
+
const tAll = react$1.useT();
|
|
2137
|
+
const timeZone = react$1.useUserTimeZone();
|
|
2138
|
+
const { locale } = react$1.useLocale();
|
|
2139
|
+
const { ppp, authState, tableMetadataCache, viewMetadataCache } = react$1.usePowerPortalsPro();
|
|
2140
|
+
const awaitsTimeZone = authState.status === "loading";
|
|
2141
|
+
const controller = FullCalendar.useCalendarController();
|
|
2142
|
+
const themeClassName = reactComponents.useThemeClassName();
|
|
2143
|
+
const [direction, setDirection] = react.useState("ltr");
|
|
2144
|
+
const [loading, setLoading] = react.useState(false);
|
|
2145
|
+
const [failed, setFailed] = react.useState(false);
|
|
2146
|
+
const [truncated, setTruncated] = react.useState(false);
|
|
2147
|
+
const [persisted] = react.useState(() => readPersistedCalendarState(persistedStateQueryParameter));
|
|
2148
|
+
const layoutsKey = (layouts ?? []).join(",");
|
|
2149
|
+
const layoutsOffered = react.useMemo(() => offeredLayouts(layouts), [layoutsKey]);
|
|
2150
|
+
const savedViewsControl = react.useMemo(
|
|
2151
|
+
() => core.savedViewControl(core.SavedViewControlKind.Calendar, allowSavedViews ? savedViewsKey : void 0),
|
|
2152
|
+
[allowSavedViews, savedViewsKey]
|
|
2153
|
+
);
|
|
2154
|
+
const saved = reactFluent.useSavedViews({
|
|
2155
|
+
enabled: allowSavedViews && !appointments,
|
|
2156
|
+
tableName: APPOINTMENT_TABLE,
|
|
2157
|
+
control: savedViewsControl
|
|
2158
|
+
});
|
|
2159
|
+
const multiSaved = reactFluent.useMultiTableViews({ enabled: allowSavedViews && !appointments && saved.available, control: savedViewsControl });
|
|
2160
|
+
const settledRef = react.useRef(false);
|
|
2161
|
+
if (!awaitsTimeZone && !saved.loading && !multiSaved.loading) settledRef.current = true;
|
|
2162
|
+
const viewSettled = settledRef.current;
|
|
2163
|
+
const findSaved = (id) => id ? saved.views.find((x) => x.id.toLowerCase() === id.toLowerCase()) : void 0;
|
|
2164
|
+
const findSavedMulti = (id) => id ? multiSaved.views.find((x) => x.id.toLowerCase() === id.toLowerCase()) : void 0;
|
|
2165
|
+
const savedIds = react.useMemo(
|
|
2166
|
+
() => [...saved.views.map((x) => x.id), ...multiSaved.views.map((x) => x.id)],
|
|
2167
|
+
[saved.views, multiSaved.views]
|
|
2168
|
+
);
|
|
2169
|
+
const savedCalendarLayout = (id) => findSaved(id)?.calendarLayout ?? findSavedMulti(id)?.calendarLayout;
|
|
2170
|
+
const viewIdsKey = (viewIds ?? []).join(",");
|
|
2171
|
+
const multiTableKey = (multiTableViewIds ?? []).map((id) => id.toLowerCase()).join(",");
|
|
2172
|
+
const customViewsKey = JSON.stringify(customViewDefinitions ?? []);
|
|
2173
|
+
const isMultiTableView = (id) => !!id && multiTableKey.split(",").includes(id.toLowerCase());
|
|
2174
|
+
const pageViews = react.useMemo(
|
|
2175
|
+
() => offeredViewIds(defaultViewId2, viewIds, multiTableViewIds, customViewDefinitions),
|
|
2176
|
+
// The keys cover the lists' content, so lists passed inline don't rebuild it each render.
|
|
2177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2178
|
+
[defaultViewId2, viewIdsKey, multiTableKey, customViewsKey]
|
|
2179
|
+
);
|
|
2180
|
+
const [pickedViewId, setPickedViewId] = react.useState(persisted.v);
|
|
2181
|
+
const openedViewRef = react.useRef(void 0);
|
|
2182
|
+
const shownViewId = pickedViewId ?? openedViewRef.current;
|
|
2183
|
+
const offered = react.useMemo(
|
|
2184
|
+
() => withSavedViews(pageViews, savedIds, saved.settings, savedViewsControl, shownViewId, saved.available),
|
|
2185
|
+
[pageViews, savedIds, saved.settings, savedViewsControl, shownViewId, saved.available]
|
|
2186
|
+
);
|
|
2187
|
+
const viewId = selectedViewId(offered, shownViewId);
|
|
2188
|
+
if (viewSettled) openedViewRef.current ??= viewId;
|
|
2189
|
+
const openingLayoutRef = react.useRef(void 0);
|
|
2190
|
+
if (openingLayoutRef.current === void 0 && viewSettled) {
|
|
2191
|
+
const savedLayout = persisted.l ? void 0 : readSavedLayout(savedCalendarLayout(viewId));
|
|
2192
|
+
openingLayoutRef.current = openingLayout(layoutsOffered, persisted.l ?? savedLayout, defaultLayout);
|
|
2193
|
+
}
|
|
2194
|
+
const initialLayout = openingLayoutRef.current ?? openingLayout(layoutsOffered, persisted.l, defaultLayout);
|
|
2195
|
+
const viewOpeningLayout = (id) => openingLayout(layoutsOffered, readSavedLayout(savedCalendarLayout(id)), defaultLayout);
|
|
2196
|
+
const showsSearch = !appointments && allowSearch;
|
|
2197
|
+
const [searchText, setSearchText] = react.useState(() => includeSearchInPersistedState ? persisted.q ?? "" : "");
|
|
2198
|
+
const [search, setSearch] = react.useState(searchText);
|
|
2199
|
+
const picksViews = !appointments && offered.length > 1;
|
|
2200
|
+
const namesViews = picksViews || !appointments && saved.available;
|
|
2201
|
+
const namedViewIds = saved.available ? [.../* @__PURE__ */ new Set([...offered, ...pageViews])] : offered;
|
|
2202
|
+
const dataverseViewIds = namedViewIds.filter(
|
|
2203
|
+
(id) => id !== EVERY_TABLE_VIEW_ID && !isMultiTableView(id) && !findCustomView(id, customViewDefinitions) && !findSaved(id) && !findSavedMulti(id)
|
|
2204
|
+
);
|
|
2205
|
+
react$1.useLocalization(namesViews ? dataverseViewIds.map((id) => `views.${id}`) : []);
|
|
2206
|
+
const [viewNames, setViewNames] = react.useState({ dataverse: /* @__PURE__ */ new Map(), multiTable: /* @__PURE__ */ new Map() });
|
|
2207
|
+
const multiTableIds = namedViewIds.filter(isMultiTableView);
|
|
2208
|
+
const namesKey = [...dataverseViewIds, "|", ...multiTableIds].join(",");
|
|
2209
|
+
react.useEffect(() => {
|
|
2210
|
+
if (!namesViews) return;
|
|
2211
|
+
let current = true;
|
|
2212
|
+
void (async () => {
|
|
2213
|
+
const dataverse = new Map(viewNames.dataverse);
|
|
2214
|
+
for (const id of dataverseViewIds.filter((x) => !dataverse.has(x)))
|
|
2215
|
+
dataverse.set(id, await viewMetadataCache.getAsync(id).catch(() => null));
|
|
2216
|
+
const multiTable = new Map(viewNames.multiTable);
|
|
2217
|
+
const missing = multiTableIds.filter((x) => !multiTable.has(x));
|
|
2218
|
+
if (missing.length > 0) {
|
|
2219
|
+
const registeredViews = (await registeredAsync().catch(() => null))?.views ?? [];
|
|
2220
|
+
for (const id of missing) {
|
|
2221
|
+
const known2 = registeredViews.find((view2) => view2.id?.toLowerCase() === id);
|
|
2222
|
+
multiTable.set(id, known2 ? known2.name ?? null : await storedViewName(id));
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (current) setViewNames({ dataverse, multiTable });
|
|
2226
|
+
})();
|
|
2227
|
+
return () => {
|
|
2228
|
+
current = false;
|
|
2229
|
+
};
|
|
2230
|
+
}, [namesViews, namesKey, ppp]);
|
|
2231
|
+
const labelOf = (id) => {
|
|
2232
|
+
if (id === EVERY_TABLE_VIEW_ID) return t("every-table");
|
|
2233
|
+
const custom = findCustomView(id, customViewDefinitions);
|
|
2234
|
+
if (custom) return custom.displayName?.trim() ? custom.displayName : id;
|
|
2235
|
+
const ownMulti = findSavedMulti(id);
|
|
2236
|
+
if (ownMulti) return ownMulti.name;
|
|
2237
|
+
const own = findSaved(id);
|
|
2238
|
+
if (own) {
|
|
2239
|
+
const key = `tables.${APPOINTMENT_TABLE}.views.${id}.label`;
|
|
2240
|
+
const localized = tAll(key);
|
|
2241
|
+
return localized !== key ? localized : own.name;
|
|
2242
|
+
}
|
|
2243
|
+
if (isMultiTableView(id)) {
|
|
2244
|
+
if (!viewNames.multiTable.has(id)) return null;
|
|
2245
|
+
return viewNames.multiTable.get(id) ?? id;
|
|
2246
|
+
}
|
|
2247
|
+
if (!viewNames.dataverse.has(id)) return null;
|
|
2248
|
+
const metadata = viewNames.dataverse.get(id);
|
|
2249
|
+
if (metadata) {
|
|
2250
|
+
const key = `tables.${metadata.tableName}.views.${id}.label`;
|
|
2251
|
+
const localized = tAll(key);
|
|
2252
|
+
if (localized !== key) return localized;
|
|
2253
|
+
}
|
|
2254
|
+
return metadata?.displayName ?? metadata?.name ?? id;
|
|
2255
|
+
};
|
|
2256
|
+
const showsSavedViewsMenu = allowSavedViews && !appointments && (saved.available || saved.needsSignIn);
|
|
2257
|
+
const viewOptions = react.useMemo(() => {
|
|
2258
|
+
if (offered.length < (showsSavedViewsMenu ? 1 : 2)) return [];
|
|
2259
|
+
const labeled = offered.map((id) => {
|
|
2260
|
+
const label = labelOf(id);
|
|
2261
|
+
return label === null ? null : { id, label };
|
|
2262
|
+
});
|
|
2263
|
+
if (labeled.some((option) => option === null)) return [];
|
|
2264
|
+
const options2 = labeled;
|
|
2265
|
+
if (viewSort === reactFluent.ViewSort.NameAscending) options2.sort((a, b) => a.label.localeCompare(b.label, locale));
|
|
2266
|
+
if (viewSort === reactFluent.ViewSort.NameDescending) options2.sort((a, b) => b.label.localeCompare(a.label, locale));
|
|
2267
|
+
return options2;
|
|
2268
|
+
}, [offered, customViewsKey, saved.views, multiSaved.views, viewNames, viewSort, tAll, locale, showsSavedViewsMenu]);
|
|
2269
|
+
const showsViewPicker = !appointments && viewOptions.length > (showsSavedViewsMenu ? 0 : 1);
|
|
2270
|
+
react.useEffect(() => {
|
|
2271
|
+
ensureFullCalendarStyles(document);
|
|
2272
|
+
ensureCalendarStyles(document);
|
|
2273
|
+
setDirection(document.dir === "rtl" ? "rtl" : "ltr");
|
|
2274
|
+
}, []);
|
|
2275
|
+
const options = react.useMemo(
|
|
2276
|
+
() => calendarOptions({
|
|
2277
|
+
layout: initialLayout,
|
|
2278
|
+
layouts: layoutsOffered,
|
|
2279
|
+
date: persisted.d,
|
|
2280
|
+
timeZone,
|
|
2281
|
+
editable,
|
|
2282
|
+
locale,
|
|
2283
|
+
direction,
|
|
2284
|
+
text: {
|
|
2285
|
+
allDay: t("all-day"),
|
|
2286
|
+
noEvents: t("no-events"),
|
|
2287
|
+
close: t("close"),
|
|
2288
|
+
more: (count) => t("more", [count])
|
|
2289
|
+
}
|
|
2290
|
+
}),
|
|
2291
|
+
[initialLayout, layoutsOffered, persisted.d, timeZone, editable, locale, direction, t]
|
|
2292
|
+
);
|
|
2293
|
+
const [openAppointment, setOpenAppointment] = react.useState(null);
|
|
2294
|
+
const [recurringChoice, setRecurringChoice] = react.useState(null);
|
|
2295
|
+
const seriesOf = react.useRef(/* @__PURE__ */ new Map());
|
|
2296
|
+
const [creating, setCreating] = react.useState(null);
|
|
2297
|
+
const mayCreate = react.useRef(null);
|
|
2298
|
+
const regardingName = react.useRef(null);
|
|
2299
|
+
const latestStart = react.useRef(0);
|
|
2300
|
+
react.useEffect(() => () => void (latestStart.current = -1), []);
|
|
2301
|
+
const user = authState.status === "authenticated" ? authState.user?.userId : null;
|
|
2302
|
+
const tablesKey = (tables ?? []).map((table) => table.trim().toLowerCase()).join(",");
|
|
2303
|
+
const allowedTables = tablesKey ? new Set(tablesKey.split(",")) : null;
|
|
2304
|
+
const mayCreateAppointments = () => {
|
|
2305
|
+
let asked = mayCreate.current;
|
|
2306
|
+
if (!asked || asked.user !== user) {
|
|
2307
|
+
asked = mayCreate.current = {
|
|
2308
|
+
user,
|
|
2309
|
+
// Only where the calendar may show appointments.
|
|
2310
|
+
answer: (allowedTables && !allowedTables.has(APPOINTMENT_TABLE) ? Promise.resolve(core.TableSecurityPermission.None) : ppp.retrieveTablePermissionsAsync(APPOINTMENT_TABLE)).then((permissions) => (Number(permissions) & core.TableSecurityPermission.Create) !== 0).catch(() => {
|
|
2311
|
+
mayCreate.current = null;
|
|
2312
|
+
return false;
|
|
2313
|
+
})
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
return asked.answer;
|
|
2317
|
+
};
|
|
2318
|
+
const startAppointment = async (range) => {
|
|
2319
|
+
const start = ++latestStart.current;
|
|
2320
|
+
if (!await mayCreateAppointments()) return;
|
|
2321
|
+
let name = null;
|
|
2322
|
+
if (regarding?.id) {
|
|
2323
|
+
const key = `${regarding.tableName}:${regarding.id}`;
|
|
2324
|
+
if (regardingName.current?.key !== key) {
|
|
2325
|
+
regardingName.current = { key, name: readName(regarding.tableName, regarding.id) };
|
|
2326
|
+
}
|
|
2327
|
+
name = await regardingName.current.name;
|
|
2328
|
+
}
|
|
2329
|
+
if (start === latestStart.current) setCreating(newAppointment(range, regarding ? { ...regarding, name } : null));
|
|
2330
|
+
};
|
|
2331
|
+
const readName = async (tableName, id) => {
|
|
2332
|
+
try {
|
|
2333
|
+
const table = await tableMetadataCache.getAsync(tableName);
|
|
2334
|
+
if (!table) return null;
|
|
2335
|
+
const record = await ppp.retrieveRecordAsync(tableName, id, [table.primaryNameColumn]);
|
|
2336
|
+
const value = record.properties?.[table.primaryNameColumn]?.value;
|
|
2337
|
+
return typeof value === "string" ? value : null;
|
|
2338
|
+
} catch {
|
|
2339
|
+
return null;
|
|
2340
|
+
}
|
|
2341
|
+
};
|
|
2342
|
+
const recurringText = t("recurring");
|
|
2343
|
+
const customTooltip = appointmentTooltip !== void 0;
|
|
2344
|
+
const appointmentTooltipRef = react.useRef(appointmentTooltip);
|
|
2345
|
+
appointmentTooltipRef.current = appointmentTooltip;
|
|
2346
|
+
const templatesRef = react.useRef(templates);
|
|
2347
|
+
templatesRef.current = templates;
|
|
2348
|
+
const templatedTables = Object.keys(templates ?? {}).map((x) => x.trim().toLowerCase()).sort().join();
|
|
2349
|
+
const renderAppointment = react.useCallback(
|
|
2350
|
+
(info) => {
|
|
2351
|
+
const content = customTooltip ? () => appointmentTooltipRef.current?.(appointmentFromEvent(info.event)) : /* @__PURE__ */ jsxRuntime.jsx(AppointmentSummaryContent, { summary: summarizeAppointment(info.event, locale, recurringText, timeZone) });
|
|
2352
|
+
const table = info.event.extendedProps["table"];
|
|
2353
|
+
const template = table ? templateFor(templatesRef.current, table) : void 0;
|
|
2354
|
+
const row = template ? rows.current.get(info.event.id) : void 0;
|
|
2355
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AppointmentTooltip, { content, children: template && row ? templateContent(info, template(row)) : appointmentContent(info) });
|
|
2356
|
+
},
|
|
2357
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2358
|
+
[locale, recurringText, timeZone, customTooltip, templatedTables]
|
|
2359
|
+
);
|
|
2360
|
+
const [refreshes, setRefreshes] = react.useState(0);
|
|
2361
|
+
const latestLoad = react.useRef(null);
|
|
2362
|
+
react.useEffect(() => () => latestLoad.current?.abort(), []);
|
|
2363
|
+
const registeredRef = react.useRef(null);
|
|
2364
|
+
const [registered, setRegistered] = react.useState(null);
|
|
2365
|
+
const registeredAsync = () => {
|
|
2366
|
+
let asked = registeredRef.current;
|
|
2367
|
+
if (!asked || asked.user !== user) {
|
|
2368
|
+
const answer = ppp.getMultiTableTablesAsync();
|
|
2369
|
+
asked = registeredRef.current = { user, answer };
|
|
2370
|
+
answer.then(
|
|
2371
|
+
(response) => registeredRef.current?.answer === answer && setRegistered(response),
|
|
2372
|
+
() => {
|
|
2373
|
+
if (registeredRef.current?.answer === answer) registeredRef.current = null;
|
|
2374
|
+
}
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return asked.answer;
|
|
2378
|
+
};
|
|
2379
|
+
const infoOf = (table) => registered?.tables?.find((x) => x.table === table);
|
|
2380
|
+
const storedViewName = async (id) => {
|
|
2381
|
+
try {
|
|
2382
|
+
const stored2 = await ppp.retrieveRecordAsync("ppp_multitableview", id, ["ppp_name"]);
|
|
2383
|
+
const name = stored2.properties?.["ppp_name"]?.value;
|
|
2384
|
+
return typeof name === "string" ? name : null;
|
|
2385
|
+
} catch {
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
const storedViewTables = react.useRef(/* @__PURE__ */ new Map());
|
|
2390
|
+
const isStoredView = (id, response) => isMultiTableView(id) && !(response.views ?? []).some((view2) => view2.id?.toLowerCase() === id);
|
|
2391
|
+
const viewTablesAsync = async (id) => {
|
|
2392
|
+
if (id === EVERY_TABLE_VIEW_ID) return null;
|
|
2393
|
+
if (isMultiTableView(id))
|
|
2394
|
+
return (await registeredAsync()).views?.find((view3) => view3.id?.toLowerCase() === id)?.tables ?? null;
|
|
2395
|
+
const custom = findCustomView(id, customViewDefinitions);
|
|
2396
|
+
if (custom) return custom.tables;
|
|
2397
|
+
const ownMulti = findSavedMulti(id);
|
|
2398
|
+
if (ownMulti) return ownMulti.tables;
|
|
2399
|
+
const own = findSaved(id);
|
|
2400
|
+
if (own) return [{ table: APPOINTMENT_TABLE, viewType: core.MultiTableViewType.FetchXml, fetchXml: own.definition.fetchXml }];
|
|
2401
|
+
const view2 = await viewMetadataCache.getAsync(id);
|
|
2402
|
+
if (view2) return [{ table: view2.tableName.toLowerCase(), viewType: core.MultiTableViewType.SystemView, viewId: id }];
|
|
2403
|
+
console.warn(`[Calendar] The view ${id} doesn't exist or is on a table the user may not read, so it shows nothing.`);
|
|
2404
|
+
return [];
|
|
2405
|
+
};
|
|
2406
|
+
const defaultsKey = defaultTables.map((table) => table.trim().toLowerCase()).join(",");
|
|
2407
|
+
const [knownTables, setKnownTables] = react.useState({ viewId: void 0, tables: [] });
|
|
2408
|
+
const known = knownTables.viewId === viewId ? knownTables.tables : [];
|
|
2409
|
+
const [hiddenChoice, setHiddenChoice] = react.useState(null);
|
|
2410
|
+
const hiddenIn = (id, of) => hiddenChoice && hiddenChoice.viewId === id ? hiddenChoice.tables : hiddenByDefault(id, of, new Set(defaultsKey.split(",").filter(Boolean)));
|
|
2411
|
+
const hidden = hiddenIn(viewId, known);
|
|
2412
|
+
const shownIn = (id, of, byView = false) => {
|
|
2413
|
+
const off = byView ? hiddenByDefault(id, of, new Set(defaultsKey.split(",").filter(Boolean))) : hiddenIn(id, of);
|
|
2414
|
+
return of.length < 2 ? [...of] : of.filter((table) => !off.includes(table));
|
|
2415
|
+
};
|
|
2416
|
+
const shownTables = shownIn(viewId, known);
|
|
2417
|
+
const kinds = known.length > 1 ? known : [];
|
|
2418
|
+
const hiddenAtOpen = hiddenByDefault(viewId, known, new Set(defaultsKey.split(",").filter(Boolean)));
|
|
2419
|
+
const tablesChanged = kinds.some((table) => hidden.includes(table) !== hiddenAtOpen.includes(table));
|
|
2420
|
+
const showTable = (table, shown) => {
|
|
2421
|
+
const next = shown ? hidden.filter((x) => x !== table) : [.../* @__PURE__ */ new Set([...hidden, table])];
|
|
2422
|
+
setHiddenChoice({ viewId, tables: next });
|
|
2423
|
+
};
|
|
2424
|
+
react$1.useLocalization(known.map((table) => `tables.${table}`));
|
|
2425
|
+
const tableLabel = (table) => {
|
|
2426
|
+
const key = `tables.${table}.label`;
|
|
2427
|
+
const label = tAll(key);
|
|
2428
|
+
return label !== key ? label : table;
|
|
2429
|
+
};
|
|
2430
|
+
const rows = react.useRef(/* @__PURE__ */ new Map());
|
|
2431
|
+
const dateOnly = react.useRef(/* @__PURE__ */ new Map());
|
|
2432
|
+
const learnDateOnly = async (tables2, response) => {
|
|
2433
|
+
for (const table of [...new Set(tables2)].filter((x) => !dateOnly.current.has(x))) {
|
|
2434
|
+
const start = response.tables?.find((x) => x.table === table)?.definition?.start;
|
|
2435
|
+
try {
|
|
2436
|
+
const metadata = await tableMetadataCache.getAsync(table);
|
|
2437
|
+
const column = metadata?.columns?.find((x) => x.columnName === start);
|
|
2438
|
+
dateOnly.current.set(table, column?.dateTimeBehavior === 1);
|
|
2439
|
+
} catch {
|
|
2440
|
+
dateOnly.current.set(table, false);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
};
|
|
2444
|
+
const [failures, setFailures] = react.useState([]);
|
|
2445
|
+
const showsFilterButton = !appointments && allowFiltering && !(registered && isStoredView(viewId, registered));
|
|
2446
|
+
const [userFilter, setUserFilter] = react.useState(null);
|
|
2447
|
+
react.useEffect(() => {
|
|
2448
|
+
setUserFilter((current) => current && current.viewId !== viewId ? null : current);
|
|
2449
|
+
setHiddenChoice((current) => current && current.viewId !== viewId ? null : current);
|
|
2450
|
+
}, [viewId]);
|
|
2451
|
+
const filtered = userFilter && userFilter.viewId === viewId ? userFilter : null;
|
|
2452
|
+
const [filterDialog, setFilterDialog] = react.useState(null);
|
|
2453
|
+
const filterTablesAsync = async (id) => {
|
|
2454
|
+
const response = await registeredAsync();
|
|
2455
|
+
const tablesShown = await viewTablesAsync(id) ?? (id === EVERY_TABLE_VIEW_ID ? (tablesOf(null, response.tables ?? [], allowedTables, null, false) ?? []).map((table) => ({ table })) : []);
|
|
2456
|
+
return reactFluent.multiTableFilterQueries(tablesShown, async (viewOf) => (await viewMetadataCache.getAsync(viewOf))?.fetchXml);
|
|
2457
|
+
};
|
|
2458
|
+
const isShownTable = (table, named, shownNow, response) => {
|
|
2459
|
+
const lower = (x) => x.toLowerCase();
|
|
2460
|
+
return lower(table) === "activitypointer" ? shownNow.some((x) => response.tables?.find((info) => info.table === x)?.isActivity && !named.map(lower).includes(lower(x))) : shownNow.map(lower).includes(lower(table));
|
|
2461
|
+
};
|
|
2462
|
+
const openFilter = async () => {
|
|
2463
|
+
const id = viewId;
|
|
2464
|
+
if (!id) return;
|
|
2465
|
+
try {
|
|
2466
|
+
const response = await registeredAsync();
|
|
2467
|
+
const tablesShown = await filterTablesAsync(id);
|
|
2468
|
+
const knownNow = tablesOf(
|
|
2469
|
+
await viewTablesAsync(id) ?? null,
|
|
2470
|
+
response.tables ?? [],
|
|
2471
|
+
allowedTables,
|
|
2472
|
+
storedViewTables.current.get(id),
|
|
2473
|
+
isStoredView(id, response)
|
|
2474
|
+
) ?? [];
|
|
2475
|
+
const shownNow = shownIn(id, knownNow);
|
|
2476
|
+
const named = tablesShown.map((x) => x.table.table ?? "");
|
|
2477
|
+
const tabs = reactFluent.multiTableFilterTabs(
|
|
2478
|
+
tablesShown,
|
|
2479
|
+
(table) => isShownTable(table, named, shownNow, response),
|
|
2480
|
+
tableLabel,
|
|
2481
|
+
filtered?.criteria
|
|
2482
|
+
);
|
|
2483
|
+
const shownByView = shownIn(id, knownNow, true);
|
|
2484
|
+
const viewTabs = reactFluent.multiTableFilterTabs(tablesShown, (table) => isShownTable(table, named, shownByView, response), tableLabel);
|
|
2485
|
+
if (tabs.length > 0 && id === viewId) setFilterDialog({ viewId: id, tables: tablesShown, tabs, viewTabs });
|
|
2486
|
+
} catch (error) {
|
|
2487
|
+
console.warn("[Calendar] The filters of the view shown could not be read.", error);
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
const applyFilter = (criteria, restoreView) => {
|
|
2491
|
+
const dialog2 = filterDialog;
|
|
2492
|
+
setFilterDialog(null);
|
|
2493
|
+
if (!dialog2 || dialog2.viewId !== viewId) return;
|
|
2494
|
+
const filter = reactFluent.applyMultiTableFilter(dialog2.tables, restoreView ? dialog2.viewTabs : dialog2.tabs, criteria);
|
|
2495
|
+
setUserFilter(filter ? { viewId, tables: filter.tables, criteria: filter.criteria } : null);
|
|
2496
|
+
if (!restoreView) return;
|
|
2497
|
+
setHiddenChoice(null);
|
|
2498
|
+
const layout = viewOpeningLayout(viewId);
|
|
2499
|
+
if (layout !== currentLayout && layoutsOffered.includes(layout)) controller.changeView(toFullCalendarView(layout));
|
|
2500
|
+
};
|
|
2501
|
+
const searched = showsSearch ? search.trim() : "";
|
|
2502
|
+
const sourceKey = JSON.stringify([
|
|
2503
|
+
regarding ?? null,
|
|
2504
|
+
viewId ?? null,
|
|
2505
|
+
findSaved(viewId)?.definition.fetchXml ?? null,
|
|
2506
|
+
findSavedMulti(viewId)?.tables ?? null,
|
|
2507
|
+
customViewsKey,
|
|
2508
|
+
filtered?.tables ?? null,
|
|
2509
|
+
searched,
|
|
2510
|
+
hiddenChoice,
|
|
2511
|
+
tablesKey,
|
|
2512
|
+
defaultsKey,
|
|
2513
|
+
matchParties ?? null,
|
|
2514
|
+
series ?? null,
|
|
2515
|
+
multiTableKey,
|
|
2516
|
+
user ?? null
|
|
2517
|
+
]);
|
|
2518
|
+
const shownState = react.useRef({});
|
|
2519
|
+
const persistRef = react.useRef(() => {
|
|
2520
|
+
});
|
|
2521
|
+
persistRef.current = (next = {}) => {
|
|
2522
|
+
const v = "viewId" in next ? next.viewId : !offersViews(offered) ? pickedViewId : viewId;
|
|
2523
|
+
const q = next.search ?? search;
|
|
2524
|
+
const shown = shownState.current;
|
|
2525
|
+
const opens = viewOpeningLayout("viewId" in next ? next.viewId : viewId);
|
|
2526
|
+
writePersistedCalendarState(persistedStateQueryParameter, {
|
|
2527
|
+
...shown.layout && shown.layout !== opens ? { l: shown.layout } : {},
|
|
2528
|
+
...shown.d ? { d: shown.d } : {},
|
|
2529
|
+
...v && v !== offered[0] ? { v } : {},
|
|
2530
|
+
...includeSearchInPersistedState && showsSearch && q.trim() ? { q } : {}
|
|
2531
|
+
});
|
|
2532
|
+
};
|
|
2533
|
+
const persist = (next) => persistRef.current(next);
|
|
2534
|
+
const openingViewRef = react.useRef(offered[0]);
|
|
2535
|
+
react.useEffect(() => {
|
|
2536
|
+
const previous = openingViewRef.current;
|
|
2537
|
+
openingViewRef.current = offered[0];
|
|
2538
|
+
if (viewSettled && previous !== void 0 && previous !== offered[0]) persist();
|
|
2539
|
+
}, [offered[0]]);
|
|
2540
|
+
react.useEffect(() => {
|
|
2541
|
+
if (searchText === search) return;
|
|
2542
|
+
const timer = setTimeout(() => {
|
|
2543
|
+
setSearch(searchText);
|
|
2544
|
+
persist({ search: searchText });
|
|
2545
|
+
}, SEARCH_DEBOUNCE_MS);
|
|
2546
|
+
return () => clearTimeout(timer);
|
|
2547
|
+
}, [searchText]);
|
|
2548
|
+
const events = react.useMemo(() => {
|
|
2549
|
+
if (appointments) return appointments.map(toEventInput);
|
|
2550
|
+
return async (info) => {
|
|
2551
|
+
latestLoad.current?.abort();
|
|
2552
|
+
const load = new AbortController();
|
|
2553
|
+
latestLoad.current = load;
|
|
2554
|
+
const id = viewId;
|
|
2555
|
+
try {
|
|
2556
|
+
if (!id) return [];
|
|
2557
|
+
const response = await registeredAsync();
|
|
2558
|
+
const viewTables = await viewTablesAsync(id);
|
|
2559
|
+
const stored2 = isStoredView(id, response);
|
|
2560
|
+
let knownNow = tablesOf(viewTables, response.tables ?? [], allowedTables, storedViewTables.current.get(id), stored2);
|
|
2561
|
+
if (latestLoad.current === load && knownNow) setKnownTables({ viewId: id, tables: knownNow });
|
|
2562
|
+
if (knownNow?.length === 0) {
|
|
2563
|
+
if (latestLoad.current === load) {
|
|
2564
|
+
setFailed(false);
|
|
2565
|
+
setTruncated(false);
|
|
2566
|
+
setFailures([]);
|
|
2567
|
+
}
|
|
2568
|
+
rows.current = /* @__PURE__ */ new Map();
|
|
2569
|
+
return [];
|
|
2570
|
+
}
|
|
2571
|
+
const request = {
|
|
2572
|
+
start: info.start.toISOString(),
|
|
2573
|
+
end: info.end.toISOString(),
|
|
2574
|
+
viewId: filtered || !isMultiTableView(id) ? null : id,
|
|
2575
|
+
tables: filtered ? [...filtered.tables] : viewTables && viewTables.length > 0 ? [...viewTables] : null,
|
|
2576
|
+
// A view whose tables aren't known yet is read as the calendar allows.
|
|
2577
|
+
includeTables: knownNow ? shownIn(id, knownNow) : allowedTables ? [...allowedTables] : null,
|
|
2578
|
+
regarding: regarding ? { tableName: regarding.tableName, id: regarding.id } : null,
|
|
2579
|
+
matchParties: matchParties ?? null,
|
|
2580
|
+
series: series ?? null,
|
|
2581
|
+
search: searched || null,
|
|
2582
|
+
columns: CALENDAR_COLUMNS
|
|
2583
|
+
};
|
|
2584
|
+
const range = await ppp.loadMultiTableRangeAsync(request, load.signal);
|
|
2585
|
+
if (!knownNow && stored2) {
|
|
2586
|
+
storedViewTables.current.set(id, range.tables ?? []);
|
|
2587
|
+
knownNow = tablesOf(null, response.tables ?? [], allowedTables, range.tables ?? [], true) ?? [];
|
|
2588
|
+
if (latestLoad.current === load) setKnownTables({ viewId: id, tables: knownNow });
|
|
2589
|
+
}
|
|
2590
|
+
const items = range.items ?? [];
|
|
2591
|
+
await learnDateOnly(items.map((item) => item.table ?? ""), response);
|
|
2592
|
+
if (latestLoad.current === load) {
|
|
2593
|
+
setFailed(false);
|
|
2594
|
+
setTruncated((range.truncated ?? []).length > 0);
|
|
2595
|
+
setFailures(range.failures ?? []);
|
|
2596
|
+
rows.current = new Map(items.map((item) => [item.id, item]));
|
|
2597
|
+
seriesOf.current = new Map(
|
|
2598
|
+
items.flatMap((item) => {
|
|
2599
|
+
const seriesId = item.record?.properties?.["seriesid"]?.value;
|
|
2600
|
+
return item.table === APPOINTMENT_TABLE && typeof seriesId === "string" ? [[item.id, seriesId]] : [];
|
|
2601
|
+
})
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
return items.map(
|
|
2605
|
+
(item) => toEventInput(
|
|
2606
|
+
fromMultiTableItem(
|
|
2607
|
+
item,
|
|
2608
|
+
response.tables?.find((x) => x.table === item.table),
|
|
2609
|
+
drawnTimes(item, dateOnly.current.get(item.table ?? "") ?? false, timeZone)
|
|
2610
|
+
)
|
|
2611
|
+
)
|
|
2612
|
+
);
|
|
2613
|
+
} catch {
|
|
2614
|
+
if (latestLoad.current === load) {
|
|
2615
|
+
setFailed(true);
|
|
2616
|
+
setTruncated(false);
|
|
2617
|
+
setFailures([]);
|
|
2618
|
+
}
|
|
2619
|
+
return [];
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
}, [appointments, ppp, sourceKey, refreshes, timeZone]);
|
|
2623
|
+
const handleChange = async (info) => {
|
|
2624
|
+
const change = toAppointmentChange(info.event);
|
|
2625
|
+
if (onAppointmentChange) {
|
|
2626
|
+
if (!await onAppointmentChange(change)) info.revert();
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
if (appointments) return;
|
|
2630
|
+
const item = rows.current.get(change.id);
|
|
2631
|
+
const record = item ? moveRecord(change, item, infoOf(item.table ?? ""), dateOnly.current.get(item.table ?? "") ?? false, timeZone) : null;
|
|
2632
|
+
if (!record) {
|
|
2633
|
+
info.revert();
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
try {
|
|
2637
|
+
await ppp.updateRecordAsync(record);
|
|
2638
|
+
setRefreshes((count) => count + 1);
|
|
2639
|
+
} catch (error) {
|
|
2640
|
+
console.error("[Calendar] The move could not be saved.", error);
|
|
2641
|
+
info.revert();
|
|
2642
|
+
await dialog.showErrorAsync(t("move-failed"));
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2645
|
+
const [panelRow, setPanelRow] = react.useState(null);
|
|
2646
|
+
const openRow = async (table, id) => {
|
|
2647
|
+
try {
|
|
2648
|
+
const record = await ppp.retrieveRecordAsync(table, id);
|
|
2649
|
+
const permissions = Number(record.permissions ?? 0);
|
|
2650
|
+
setPanelRow({
|
|
2651
|
+
table,
|
|
2652
|
+
id,
|
|
2653
|
+
record: editable ? record : { ...record, permissions: permissions & ~(core.TableSecurityPermission.Write | core.TableSecurityPermission.Delete) }
|
|
2654
|
+
});
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
console.error(`[Calendar] The ${table} ${id} could not be read for its form.`, error);
|
|
2657
|
+
await dialog.showErrorAsync(t("open-failed"));
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
const view = controller.view;
|
|
2661
|
+
const currentLayout = (view && fromFullCalendarView(view.type)) ?? initialLayout;
|
|
2662
|
+
const buttons = view ? controller.getButtonState() : void 0;
|
|
2663
|
+
const [mayCreateHere, setMayCreateHere] = react.useState(false);
|
|
2664
|
+
const asksPermission = editable && !onRangeSelect && !appointments;
|
|
2665
|
+
react.useEffect(() => {
|
|
2666
|
+
if (!asksPermission) return;
|
|
2667
|
+
let current = true;
|
|
2668
|
+
void mayCreateAppointments().then((answer) => current && setMayCreateHere(answer));
|
|
2669
|
+
return () => {
|
|
2670
|
+
current = false;
|
|
2671
|
+
};
|
|
2672
|
+
}, [asksPermission, user]);
|
|
2673
|
+
const canCreateAppointments = editable && (onRangeSelect !== void 0 || asksPermission && mayCreateHere);
|
|
2674
|
+
const createAppointment = async (range) => {
|
|
2675
|
+
if (!editable) return;
|
|
2676
|
+
const shown = controller.view;
|
|
2677
|
+
const chosen = range ?? (shown && newAppointmentRange(shown.currentStart, shown.currentEnd, /* @__PURE__ */ new Date(), timeZone));
|
|
2678
|
+
if (!chosen) return;
|
|
2679
|
+
if (onRangeSelect) onRangeSelect(chosen);
|
|
2680
|
+
else if (!appointments) await startAppointment(chosen);
|
|
2681
|
+
};
|
|
2682
|
+
const calendarContext = {
|
|
2683
|
+
layout: currentLayout,
|
|
2684
|
+
start: view?.activeStart,
|
|
2685
|
+
end: view?.activeEnd,
|
|
2686
|
+
regarding,
|
|
2687
|
+
canCreateAppointments,
|
|
2688
|
+
createAppointment,
|
|
2689
|
+
refresh: () => setRefreshes((count) => count + 1)
|
|
2690
|
+
};
|
|
2691
|
+
const onLayoutChecked = (_, data) => {
|
|
2692
|
+
const next = data.checkedItems[0];
|
|
2693
|
+
if (next && layoutsOffered.includes(next)) controller.changeView(toFullCalendarView(next));
|
|
2694
|
+
};
|
|
2695
|
+
const pickView = (next) => {
|
|
2696
|
+
if (!next || next === viewId) return;
|
|
2697
|
+
setPickedViewId(next);
|
|
2698
|
+
const layout = readSavedLayout(savedCalendarLayout(next));
|
|
2699
|
+
if (layout && layout !== currentLayout && layoutsOffered.includes(layout)) controller.changeView(toFullCalendarView(layout));
|
|
2700
|
+
persist({ viewId: next });
|
|
2701
|
+
};
|
|
2702
|
+
const shownSaved = findSaved(viewId);
|
|
2703
|
+
const shownSavedMulti = findSavedMulti(viewId);
|
|
2704
|
+
const showsAppointmentsAlone = shownTables.length === 1 && shownTables[0] === APPOINTMENT_TABLE;
|
|
2705
|
+
const canSaveView = shownTables.length > 0 && !(registered && isStoredView(viewId, registered));
|
|
2706
|
+
const viewChanged = !!filtered || tablesChanged || currentLayout !== viewOpeningLayout(viewId);
|
|
2707
|
+
const savedViewChanged = (!!shownSaved || !!shownSavedMulti) && canSaveView && viewChanged;
|
|
2708
|
+
const isUserDefault = !!viewId && core.defaultViewId(saved.settings, offered, savedViewsControl) === viewId;
|
|
2709
|
+
const [savingView, setSavingView] = react.useState(false);
|
|
2710
|
+
const [nameDialog, setNameDialog] = react.useState(null);
|
|
2711
|
+
const [manageViewsOpen, setManageViewsOpen] = react.useState(false);
|
|
2712
|
+
const currentQuery = async () => {
|
|
2713
|
+
if (!showsAppointmentsAlone || !viewId) return void 0;
|
|
2714
|
+
const own = filtered?.tables.find((x) => x.table === APPOINTMENT_TABLE);
|
|
2715
|
+
if (own && (own.viewType ?? core.MultiTableViewType.FetchXml) === core.MultiTableViewType.FetchXml && own.fetchXml) return own.fetchXml;
|
|
2716
|
+
return (await filterTablesAsync(viewId)).find((x) => x.table.table === APPOINTMENT_TABLE)?.fetchXml ?? void 0;
|
|
2717
|
+
};
|
|
2718
|
+
const currentTables = async () => {
|
|
2719
|
+
if (!viewId) return [];
|
|
2720
|
+
const response = await registeredAsync();
|
|
2721
|
+
const tablesNow = filtered ? [...filtered.tables] : (await filterTablesAsync(viewId)).map((x) => x.table);
|
|
2722
|
+
return core.shownViewTables(tablesNow, known, shownTables, (table) => !!response.tables?.find((info) => info.table === table)?.isActivity);
|
|
2723
|
+
};
|
|
2724
|
+
const runSavedViewChange = async (change) => {
|
|
2725
|
+
if (savingView) return;
|
|
2726
|
+
setSavingView(true);
|
|
2727
|
+
try {
|
|
2728
|
+
await change();
|
|
2729
|
+
} catch (error) {
|
|
2730
|
+
console.warn("[Calendar] A change to the user\u2019s saved views failed.", error);
|
|
2731
|
+
await dialog.showErrorAsync(tAll("app.components.grid.saved-views-error"));
|
|
2732
|
+
} finally {
|
|
2733
|
+
setSavingView(false);
|
|
2734
|
+
}
|
|
2735
|
+
};
|
|
2736
|
+
const signedIn = async (action) => {
|
|
2737
|
+
if (!saved.needsSignIn) {
|
|
2738
|
+
await action();
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
const result = await dialog.showConfirmationAsync(tAll("app.components.grid.saved-views-sign-in-message"), {
|
|
2742
|
+
title: tAll("app.components.grid.saved-views-sign-in-title"),
|
|
2743
|
+
primaryText: tAll("app.buttons.login.label"),
|
|
2744
|
+
secondaryText: tAll("app.buttons.cancel.label")
|
|
2745
|
+
});
|
|
2746
|
+
if (result.canceled) return;
|
|
2747
|
+
const returnUrl = window.location.pathname + window.location.search + window.location.hash;
|
|
2748
|
+
window.location.assign(`${signInUrl}${signInUrl.includes("?") ? "&" : "?"}ReturnUrl=${encodeURIComponent(returnUrl)}`);
|
|
2749
|
+
};
|
|
2750
|
+
const showSavedView = (id) => {
|
|
2751
|
+
setUserFilter(null);
|
|
2752
|
+
setHiddenChoice(null);
|
|
2753
|
+
setPickedViewId(id);
|
|
2754
|
+
persist({ viewId: id });
|
|
2755
|
+
};
|
|
2756
|
+
const saveView = () => runSavedViewChange(async () => {
|
|
2757
|
+
if (shownSavedMulti) {
|
|
2758
|
+
const tables2 = await currentTables();
|
|
2759
|
+
if (tables2.length === 0) return;
|
|
2760
|
+
await multiSaved.update(shownSavedMulti.id, { tables: tables2, calendarLayout: writeSavedLayout(currentLayout) });
|
|
2761
|
+
} else {
|
|
2762
|
+
if (!shownSaved) return;
|
|
2763
|
+
const fetchXml = await currentQuery();
|
|
2764
|
+
if (!fetchXml) return;
|
|
2765
|
+
await saved.update(shownSaved.id, { fetchXml, calendarLayout: writeSavedLayout(currentLayout) });
|
|
2766
|
+
}
|
|
2767
|
+
setUserFilter(null);
|
|
2768
|
+
setHiddenChoice(null);
|
|
2769
|
+
});
|
|
2770
|
+
const saveViewAs = (name) => runSavedViewChange(async () => {
|
|
2771
|
+
const fetchXml = showsAppointmentsAlone ? await currentQuery() : void 0;
|
|
2772
|
+
if (fetchXml) {
|
|
2773
|
+
const view3 = await saved.create(name, fetchXml, { calendarLayout: writeSavedLayout(currentLayout) });
|
|
2774
|
+
showSavedView(view3.id.toLowerCase());
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
const tables2 = await currentTables();
|
|
2778
|
+
if (tables2.length === 0) return;
|
|
2779
|
+
const view2 = await multiSaved.create(name, tables2, { calendarLayout: writeSavedLayout(currentLayout) });
|
|
2780
|
+
showSavedView(view2.id.toLowerCase());
|
|
2781
|
+
});
|
|
2782
|
+
const cloneView = (id, name) => runSavedViewChange(async () => {
|
|
2783
|
+
const own = findSaved(id);
|
|
2784
|
+
const custom = findCustomView(id, customViewDefinitions)?.tables;
|
|
2785
|
+
const only = custom?.length === 1 && custom[0].table === APPOINTMENT_TABLE && (custom[0].viewType ?? core.MultiTableViewType.FetchXml) === core.MultiTableViewType.FetchXml ? custom[0].fetchXml ?? everyRow(APPOINTMENT_TABLE) : void 0;
|
|
2786
|
+
const ownMulti = findSavedMulti(id);
|
|
2787
|
+
const dataverse = own || only || ownMulti || custom ? void 0 : await viewMetadataCache.getAsync(id);
|
|
2788
|
+
const fetchXml = own?.definition.fetchXml ?? only ?? (dataverse?.tableName.toLowerCase() === APPOINTMENT_TABLE ? dataverse.fetchXml : void 0);
|
|
2789
|
+
if (fetchXml) {
|
|
2790
|
+
await saved.create(name, fetchXml, { gridLayout: own?.gridLayout, calendarLayout: own?.calendarLayout });
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
const tables2 = ownMulti?.tables ?? await viewTablesAsync(id) ?? (id === EVERY_TABLE_VIEW_ID ? known.map((table) => ({ table })) : null);
|
|
2794
|
+
if (!tables2 || tables2.length === 0) return;
|
|
2795
|
+
await multiSaved.create(name, tables2, { calendarLayout: ownMulti?.calendarLayout });
|
|
2796
|
+
});
|
|
2797
|
+
const renameView = (id, name) => runSavedViewChange(async () => void (findSavedMulti(id) ? await multiSaved.update(id, { name }) : await saved.update(id, { name })));
|
|
2798
|
+
const deleteView = async (id) => {
|
|
2799
|
+
const result = await dialog.showConfirmationAsync(
|
|
2800
|
+
tAll("app.components.grid.saved-views-delete-message", [findSaved(id)?.name ?? findSavedMulti(id)?.name ?? ""]),
|
|
2801
|
+
{
|
|
2802
|
+
title: tAll("app.components.grid.saved-views-delete-title"),
|
|
2803
|
+
primaryText: tAll("app.components.grid.saved-views-delete"),
|
|
2804
|
+
secondaryText: tAll("app.buttons.cancel.label")
|
|
2805
|
+
}
|
|
2806
|
+
);
|
|
2807
|
+
if (result.canceled) return;
|
|
2808
|
+
await runSavedViewChange(async () => {
|
|
2809
|
+
if (findSavedMulti(id)) {
|
|
2810
|
+
await multiSaved.remove(id);
|
|
2811
|
+
await saved.reloadSettings();
|
|
2812
|
+
} else {
|
|
2813
|
+
await saved.remove(id);
|
|
2814
|
+
}
|
|
2815
|
+
if (id.toLowerCase() === viewId) setUserFilter(null);
|
|
2816
|
+
});
|
|
2817
|
+
};
|
|
2818
|
+
const setDefault = (id, isDefault) => runSavedViewChange(() => saved.setDefault(id, isDefault, viewLabel(id)));
|
|
2819
|
+
const setHidden = (id, isHidden) => runSavedViewChange(() => saved.setHidden(id, isHidden, viewLabel(id)));
|
|
2820
|
+
function viewLabel(id) {
|
|
2821
|
+
return labelOf(id.toLowerCase()) ?? findSaved(id)?.name ?? findSavedMulti(id)?.name ?? id;
|
|
2822
|
+
}
|
|
2823
|
+
const managedViews = react.useMemo(() => {
|
|
2824
|
+
if (!manageViewsOpen) return [];
|
|
2825
|
+
const all = [.../* @__PURE__ */ new Set([...pageViews, ...savedIds.map((x) => x.toLowerCase())])];
|
|
2826
|
+
const userDefault = core.defaultViewId(saved.settings, all, savedViewsControl);
|
|
2827
|
+
return all.map((id) => ({
|
|
2828
|
+
id,
|
|
2829
|
+
name: viewLabel(id),
|
|
2830
|
+
isSaved: !!findSaved(id) || !!findSavedMulti(id),
|
|
2831
|
+
isDefault: id === userDefault,
|
|
2832
|
+
isHidden: core.isViewHidden(saved.settings, id, savedViewsControl),
|
|
2833
|
+
isShown: id === viewId
|
|
2834
|
+
}));
|
|
2835
|
+
}, [manageViewsOpen, pageViews, savedIds, saved.settings, savedViewsControl, viewId, viewOptions, viewNames]);
|
|
2836
|
+
const onNameDialogSave = (name) => {
|
|
2837
|
+
const asked = nameDialog;
|
|
2838
|
+
setNameDialog(null);
|
|
2839
|
+
if (!asked) return;
|
|
2840
|
+
if (asked.kind === "rename") void renameView(asked.viewId, name);
|
|
2841
|
+
else if (asked.kind === "clone") void cloneView(asked.viewId, name);
|
|
2842
|
+
else void saveViewAs(name);
|
|
2843
|
+
};
|
|
2844
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.root, style: { height }, children: [
|
|
2845
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Toolbar, { className: styles.toolbar, "aria-label": t("toolbar"), children: [
|
|
2846
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.toolbarStart, children: showsViewPicker && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2847
|
+
reactComponents.Dropdown,
|
|
2848
|
+
{
|
|
2849
|
+
className: reactComponents.mergeClasses("ppp-calendar-view-picker", styles.viewPicker),
|
|
2850
|
+
"aria-label": tAll("app.components.view-selector.view-name"),
|
|
2851
|
+
value: viewOptions.find((option) => option.id === viewId)?.label ?? "",
|
|
2852
|
+
selectedOptions: viewId ? [viewId] : [],
|
|
2853
|
+
onOptionSelect: (_, data) => pickView(data.optionValue),
|
|
2854
|
+
children: viewOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Option, { value: option.id, text: option.label, children: option.label }, option.id))
|
|
2855
|
+
}
|
|
2856
|
+
) }),
|
|
2857
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.toolbarEnd, children: [
|
|
2858
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: reactComponents.mergeClasses("ppp-calendar-navigation", styles.navigation), children: [
|
|
2859
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2860
|
+
reactComponents.ToolbarButton,
|
|
2861
|
+
{
|
|
2862
|
+
appearance: "subtle",
|
|
2863
|
+
disabled: buttons?.today?.isDisabled ?? false,
|
|
2864
|
+
onClick: () => controller.today(),
|
|
2865
|
+
children: t("today")
|
|
2866
|
+
}
|
|
2867
|
+
),
|
|
2868
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2869
|
+
reactComponents.ToolbarButton,
|
|
2870
|
+
{
|
|
2871
|
+
appearance: "subtle",
|
|
2872
|
+
icon: direction === "rtl" ? /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronRightRegular, {}) : /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronLeftRegular, {}),
|
|
2873
|
+
"aria-label": t("previous"),
|
|
2874
|
+
onClick: () => controller.prev()
|
|
2875
|
+
}
|
|
2876
|
+
),
|
|
2877
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2878
|
+
reactComponents.ToolbarButton,
|
|
2879
|
+
{
|
|
2880
|
+
appearance: "subtle",
|
|
2881
|
+
icon: direction === "rtl" ? /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronLeftRegular, {}) : /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronRightRegular, {}),
|
|
2882
|
+
"aria-label": t("next"),
|
|
2883
|
+
onClick: () => controller.next()
|
|
2884
|
+
}
|
|
2885
|
+
),
|
|
2886
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { className: styles.title, as: "h2", "aria-live": "polite", children: view?.title })
|
|
2887
|
+
] }),
|
|
2888
|
+
children !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(CalendarContext.Provider, { value: calendarContext, children }),
|
|
2889
|
+
layoutsOffered.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { checkedValues: { layout: [currentLayout] }, onCheckedValueChange: onLayoutChecked, children: [
|
|
2890
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuButton, { className: "ppp-calendar-layout", appearance: "subtle", children: t(layoutKeys[currentLayout]) }) }),
|
|
2891
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuPopover, { children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuList, { children: layoutsOffered.map((layout) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuItemRadio, { name: "layout", value: layout, children: t(layoutKeys[layout]) }, layout)) }) })
|
|
2892
|
+
] }),
|
|
2893
|
+
showsSearch && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2894
|
+
reactComponents.SearchBox,
|
|
2895
|
+
{
|
|
2896
|
+
className: reactComponents.mergeClasses("ppp-calendar-search", styles.search),
|
|
2897
|
+
placeholder: t("search-placeholder"),
|
|
2898
|
+
"aria-label": t("search-placeholder"),
|
|
2899
|
+
value: searchText,
|
|
2900
|
+
onChange: (_, data) => setSearchText(data.value)
|
|
2901
|
+
}
|
|
2902
|
+
),
|
|
2903
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2904
|
+
reactFluent.ViewFilterMenu,
|
|
2905
|
+
{
|
|
2906
|
+
changed: viewChanged,
|
|
2907
|
+
onEditFilters: showsFilterButton ? () => void openFilter() : void 0,
|
|
2908
|
+
tables: appointments ? void 0 : kinds.map((table) => ({ table, label: tableLabel(table), shown: !hidden.includes(table) })),
|
|
2909
|
+
onTableShownChange: showTable,
|
|
2910
|
+
savedViews: showsSavedViewsMenu ? {
|
|
2911
|
+
canSave: savedViewChanged,
|
|
2912
|
+
canSaveAs: canSaveView && viewChanged,
|
|
2913
|
+
canSetDefault: !!viewId && !isUserDefault,
|
|
2914
|
+
busy: savingView,
|
|
2915
|
+
onSave: () => void signedIn(saveView),
|
|
2916
|
+
onSaveAs: () => void signedIn(() => setNameDialog({ kind: "saveAs", viewId: viewId ?? "", name: "" })),
|
|
2917
|
+
onSetDefault: () => void signedIn(() => viewId ? setDefault(viewId, true) : void 0),
|
|
2918
|
+
onManage: () => void signedIn(() => setManageViewsOpen(true))
|
|
2919
|
+
} : void 0
|
|
2920
|
+
}
|
|
2921
|
+
),
|
|
2922
|
+
!appointments && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Tooltip, { content: tAll("app.buttons.refresh.label"), relationship: "label", withArrow: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2923
|
+
reactComponents.ToolbarButton,
|
|
2924
|
+
{
|
|
2925
|
+
className: "ppp-calendar-refresh",
|
|
2926
|
+
appearance: "subtle",
|
|
2927
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowClockwiseRegular, {}),
|
|
2928
|
+
disabled: loading,
|
|
2929
|
+
onClick: () => setRefreshes((count) => count + 1)
|
|
2930
|
+
}
|
|
2931
|
+
) })
|
|
2932
|
+
] })
|
|
2933
|
+
] }),
|
|
2934
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2935
|
+
reactFluent.GridSaveViewDialog,
|
|
2936
|
+
{
|
|
2937
|
+
open: nameDialog !== null,
|
|
2938
|
+
title: tAll(
|
|
2939
|
+
nameDialog?.kind === "rename" ? "app.components.grid.saved-views-rename-title" : nameDialog?.kind === "clone" ? "app.components.grid.saved-views-clone-title" : "app.components.grid.saved-views-save-as-title"
|
|
2940
|
+
),
|
|
2941
|
+
name: nameDialog?.name ?? "",
|
|
2942
|
+
onSave: onNameDialogSave,
|
|
2943
|
+
onCancel: () => setNameDialog(null)
|
|
2944
|
+
}
|
|
2945
|
+
),
|
|
2946
|
+
saved.available && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2947
|
+
reactFluent.GridManageViewsPanel,
|
|
2948
|
+
{
|
|
2949
|
+
open: manageViewsOpen,
|
|
2950
|
+
onOpenChange: setManageViewsOpen,
|
|
2951
|
+
views: managedViews,
|
|
2952
|
+
busy: savingView,
|
|
2953
|
+
onOpenView: (id) => {
|
|
2954
|
+
setManageViewsOpen(false);
|
|
2955
|
+
pickView(id);
|
|
2956
|
+
},
|
|
2957
|
+
onSetDefault: (id, isDefault) => void setDefault(id, isDefault),
|
|
2958
|
+
onSetHidden: (id, isHidden) => void setHidden(id, isHidden),
|
|
2959
|
+
onRename: (id) => setNameDialog({ kind: "rename", viewId: id, name: findSaved(id)?.name ?? findSavedMulti(id)?.name ?? "" }),
|
|
2960
|
+
onClone: (id) => setNameDialog({ kind: "clone", viewId: id, name: tAll("app.components.grid.saved-views-copy-of", [viewLabel(id)]) }),
|
|
2961
|
+
onDelete: (id) => void deleteView(id)
|
|
2962
|
+
}
|
|
2963
|
+
),
|
|
2964
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2965
|
+
reactFluent.MultiTableFilterDialog,
|
|
2966
|
+
{
|
|
2967
|
+
open: filterDialog !== null,
|
|
2968
|
+
tabs: filterDialog?.tabs ?? [],
|
|
2969
|
+
viewTabs: filterDialog?.viewTabs,
|
|
2970
|
+
onApply: applyFilter,
|
|
2971
|
+
onCancel: () => setFilterDialog(null)
|
|
2972
|
+
}
|
|
2973
|
+
),
|
|
2974
|
+
failed ? /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBar, { intent: "error", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("error") }) }) : failures.length > 0 && // A table that couldn't be read leaves the rest of the range as it is.
|
|
2975
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactComponents.MessageBar, { intent: "warning", children: [
|
|
2976
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("failed-tables", [failures.map((x) => tableLabel(x.table ?? "")).join(", ")]) }),
|
|
2977
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarActions, { children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { size: "small", onClick: () => setRefreshes((count) => count + 1), children: t("retry") }) })
|
|
2978
|
+
] }),
|
|
2979
|
+
truncated && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBar, { intent: "warning", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("truncated") }) }),
|
|
2980
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.body, children: [
|
|
2981
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.surface, children: viewSettled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2982
|
+
FullCalendar__default.default,
|
|
2983
|
+
{
|
|
2984
|
+
...options,
|
|
2985
|
+
popoverClass: `${options.popoverClass} ${themeClassName}`,
|
|
2986
|
+
plugins,
|
|
2987
|
+
controller,
|
|
2988
|
+
events,
|
|
2989
|
+
loading: setLoading,
|
|
2990
|
+
eventContent: renderAppointment,
|
|
2991
|
+
eventClick: (info) => {
|
|
2992
|
+
info.jsEvent.preventDefault();
|
|
2993
|
+
const table = info.event.extendedProps["table"];
|
|
2994
|
+
if (onAppointmentClick) onAppointmentClick(info.event.id);
|
|
2995
|
+
else if (table === SERIES_TABLE2 && !appointments) {
|
|
2996
|
+
latestStart.current++;
|
|
2997
|
+
setOpenAppointment({ id: info.event.id, subject: info.event.title, recurring: false, isSeries: true });
|
|
2998
|
+
} else if (table && table !== APPOINTMENT_TABLE && !appointments) {
|
|
2999
|
+
latestStart.current++;
|
|
3000
|
+
void openRow(table, info.event.id);
|
|
3001
|
+
} else {
|
|
3002
|
+
const opened = appointmentToOpen(info.event, appointments !== void 0);
|
|
3003
|
+
if (opened) {
|
|
3004
|
+
latestStart.current++;
|
|
3005
|
+
const seriesId = opened.recurring ? seriesOf.current.get(opened.id) : void 0;
|
|
3006
|
+
if (seriesId) setRecurringChoice({ ...opened, seriesId });
|
|
3007
|
+
else setOpenAppointment(opened);
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
},
|
|
3011
|
+
select: (info) => {
|
|
3012
|
+
const range = { start: info.start, end: info.end, allDay: info.allDay };
|
|
3013
|
+
if (onRangeSelect) onRangeSelect(range);
|
|
3014
|
+
else if (!appointments) void startAppointment(range);
|
|
3015
|
+
info.view.calendar.unselect();
|
|
3016
|
+
},
|
|
3017
|
+
eventDrop: handleChange,
|
|
3018
|
+
eventResize: handleChange,
|
|
3019
|
+
datesSet: (info) => {
|
|
3020
|
+
const layout = fromFullCalendarView(info.view.type);
|
|
3021
|
+
if (!layout) return;
|
|
3022
|
+
const date = periodDate(info.view.currentStart, info.view.currentEnd, /* @__PURE__ */ new Date(), timeZone);
|
|
3023
|
+
shownState.current = { layout, ...date ? { d: date } : {} };
|
|
3024
|
+
persist();
|
|
3025
|
+
onRangeChange?.({ start: info.start, end: info.end, layout });
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
) }),
|
|
3029
|
+
(loading || awaitsTimeZone || saved.loading || multiSaved.loading) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.loadingOverlay, children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Spinner, { size: "large" }) })
|
|
3030
|
+
] }),
|
|
3031
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.visuallyHidden, role: "status", "aria-live": "polite", children: loading ? t("loading") : "" }),
|
|
3032
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3033
|
+
AppointmentDetailsDialog,
|
|
3034
|
+
{
|
|
3035
|
+
appointment: openAppointment,
|
|
3036
|
+
newAppointment: creating,
|
|
3037
|
+
editable,
|
|
3038
|
+
regardingTables,
|
|
3039
|
+
location: dialogLocation,
|
|
3040
|
+
onClose: () => {
|
|
3041
|
+
setOpenAppointment(null);
|
|
3042
|
+
setCreating(null);
|
|
3043
|
+
},
|
|
3044
|
+
onSaved: () => setRefreshes((count) => count + 1),
|
|
3045
|
+
children: appointmentDetails
|
|
3046
|
+
}
|
|
3047
|
+
),
|
|
3048
|
+
panelRow && infoOf(panelRow.table)?.definition && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3049
|
+
reactFluent.MultiTableRecordPanel,
|
|
3050
|
+
{
|
|
3051
|
+
title: tableLabel(panelRow.table),
|
|
3052
|
+
existing: { table: panelRow.table, id: panelRow.id },
|
|
3053
|
+
loaded: panelRow.record,
|
|
3054
|
+
form: forms?.[panelRow.table]?.edit,
|
|
3055
|
+
location: dialogLocation,
|
|
3056
|
+
definition: infoOf(panelRow.table).definition,
|
|
3057
|
+
onSaved: () => {
|
|
3058
|
+
setPanelRow(null);
|
|
3059
|
+
setRefreshes((count) => count + 1);
|
|
3060
|
+
},
|
|
3061
|
+
onClose: () => setPanelRow(null)
|
|
3062
|
+
},
|
|
3063
|
+
`${panelRow.table}:${panelRow.id}`
|
|
3064
|
+
),
|
|
3065
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3066
|
+
RecurringChoiceDialog,
|
|
3067
|
+
{
|
|
3068
|
+
appointment: recurringChoice,
|
|
3069
|
+
onChoose: (series2) => {
|
|
3070
|
+
const chosen = recurringChoice;
|
|
3071
|
+
setRecurringChoice(null);
|
|
3072
|
+
if (!chosen?.seriesId) return;
|
|
3073
|
+
setOpenAppointment(series2 ? { id: chosen.seriesId, subject: chosen.subject, recurring: false, isSeries: true } : chosen);
|
|
3074
|
+
},
|
|
3075
|
+
onClose: () => setRecurringChoice(null)
|
|
3076
|
+
}
|
|
3077
|
+
)
|
|
3078
|
+
] });
|
|
3079
|
+
}
|
|
3080
|
+
function templateFor(templates, table) {
|
|
3081
|
+
if (!templates) return void 0;
|
|
3082
|
+
const name = table.toLowerCase();
|
|
3083
|
+
return templates[table] ?? Object.entries(templates).find(([key]) => key.trim().toLowerCase() === name)?.[1];
|
|
3084
|
+
}
|
|
3085
|
+
function templateContent(info, content) {
|
|
3086
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: isCompactView(info.view.type) ? "ppp-calendar-event ppp-calendar-event-compact" : "ppp-calendar-event", children: content });
|
|
3087
|
+
}
|
|
3088
|
+
function appointmentContent(info) {
|
|
3089
|
+
const { event } = info;
|
|
3090
|
+
const location = event.extendedProps["location"];
|
|
3091
|
+
const recurring = event.extendedProps["recurring"];
|
|
3092
|
+
const title = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ppp-calendar-event-title", children: [
|
|
3093
|
+
recurring && /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowRepeatAllRegular, { className: "ppp-calendar-event-icon" }),
|
|
3094
|
+
event.title
|
|
3095
|
+
] });
|
|
3096
|
+
if (isCompactView(info.view.type)) {
|
|
3097
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ppp-calendar-event ppp-calendar-event-compact", children: [
|
|
3098
|
+
info.timeText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ppp-calendar-event-detail", children: info.timeText }),
|
|
3099
|
+
title
|
|
3100
|
+
] });
|
|
3101
|
+
}
|
|
3102
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ppp-calendar-event", children: [
|
|
3103
|
+
title,
|
|
3104
|
+
location && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ppp-calendar-event-detail", children: location })
|
|
3105
|
+
] });
|
|
3106
|
+
}
|
|
3107
|
+
function CalendarButton({
|
|
3108
|
+
onClick,
|
|
3109
|
+
label,
|
|
3110
|
+
icon,
|
|
3111
|
+
tooltip,
|
|
3112
|
+
appearance = "subtle",
|
|
3113
|
+
disabled = false,
|
|
3114
|
+
className
|
|
3115
|
+
}) {
|
|
3116
|
+
const calendar = useCalendarContext();
|
|
3117
|
+
const [working, setWorking] = react.useState(false);
|
|
3118
|
+
const click = async () => {
|
|
3119
|
+
if (working || !onClick) return;
|
|
3120
|
+
setWorking(true);
|
|
3121
|
+
try {
|
|
3122
|
+
await onClick(calendar);
|
|
3123
|
+
} finally {
|
|
3124
|
+
setWorking(false);
|
|
3125
|
+
}
|
|
3126
|
+
};
|
|
3127
|
+
const button = /* @__PURE__ */ jsxRuntime.jsx(
|
|
3128
|
+
reactComponents.ToolbarButton,
|
|
3129
|
+
{
|
|
3130
|
+
className: reactComponents.mergeClasses("ppp-calendar-button", className),
|
|
3131
|
+
appearance,
|
|
3132
|
+
disabled: disabled || working,
|
|
3133
|
+
icon: icon ?? null,
|
|
3134
|
+
"aria-label": label === void 0 ? tooltip : void 0,
|
|
3135
|
+
onClick: () => void click(),
|
|
3136
|
+
children: label
|
|
3137
|
+
}
|
|
3138
|
+
);
|
|
3139
|
+
return tooltip ? /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Tooltip, { content: tooltip, relationship: label === void 0 ? "label" : "description", withArrow: true, children: button }) : button;
|
|
3140
|
+
}
|
|
3141
|
+
function NewAppointmentCalendarButton({
|
|
3142
|
+
label,
|
|
3143
|
+
icon,
|
|
3144
|
+
tooltip,
|
|
3145
|
+
appearance = "subtle"
|
|
3146
|
+
}) {
|
|
3147
|
+
const calendar = useCalendarContext();
|
|
3148
|
+
const t = react$1.useT();
|
|
3149
|
+
if (!calendar.canCreateAppointments) return null;
|
|
3150
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3151
|
+
CalendarButton,
|
|
3152
|
+
{
|
|
3153
|
+
className: "ppp-calendar-new",
|
|
3154
|
+
label: label ?? t("app.buttons.new.label"),
|
|
3155
|
+
icon: icon ?? /* @__PURE__ */ jsxRuntime.jsx(reactIcons.AddRegular, {}),
|
|
3156
|
+
appearance,
|
|
3157
|
+
...tooltip !== void 0 && { tooltip },
|
|
3158
|
+
onClick: (context) => context.createAppointment()
|
|
3159
|
+
}
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
exports.AppointmentShowAs = AppointmentShowAs;
|
|
3164
|
+
exports.Calendar = Calendar;
|
|
3165
|
+
exports.CalendarButton = CalendarButton;
|
|
3166
|
+
exports.NewAppointmentCalendarButton = NewAppointmentCalendarButton;
|
|
3167
|
+
exports.calendarLayouts = calendarLayouts;
|
|
3168
|
+
exports.useCalendarContext = useCalendarContext;
|
|
3169
|
+
//# sourceMappingURL=index.cjs.map
|
|
3170
|
+
//# sourceMappingURL=index.cjs.map
|