@powerportalspro/react-calendar 12.0.0 → 13.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/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
- import { makeStyles, tokens, useThemeClassName, Toolbar, Dropdown, Option, mergeClasses, ToolbarButton, Text, Menu, MenuTrigger, MenuButton, MenuPopover, MenuList, MenuItemRadio, SearchBox, Tooltip, MessageBar, MessageBarBody, MessageBarActions, Button, Spinner, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, MenuItem, Field, SpinButton, Checkbox, ToggleButton, RadioGroup, Radio, Input } from '@fluentui/react-components';
2
- import { ChevronRightRegular, ChevronLeftRegular, ArrowClockwiseRegular, ArrowRepeatAllRegular, AddRegular, ChevronDownRegular } from '@fluentui/react-icons';
1
+ import { makeStyles, tokens, useThemeClassName, ToolbarButton, Text, mergeClasses, Menu, MenuTrigger, MenuButton, MenuPopover, MenuList, MenuItemRadio, MessageBar, MessageBarBody, Spinner, Tooltip, Button, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, MenuItem, Field, Dropdown, Option, SpinButton, Checkbox, ToggleButton, RadioGroup, Radio, Input } from '@fluentui/react-components';
2
+ import { ChevronRightRegular, ChevronLeftRegular, ArrowRepeatAllRegular, AddRegular, ChevronDownRegular } from '@fluentui/react-icons';
3
3
  import FullCalendar, { useCalendarController } from '@fullcalendar/react';
4
4
  import dayGridPlugin from '@fullcalendar/react/daygrid';
5
5
  import interactionPlugin from '@fullcalendar/react/interaction';
6
6
  import listPlugin from '@fullcalendar/react/list';
7
7
  import timeGridPlugin from '@fullcalendar/react/timegrid';
8
8
  import formaTheme from '@fullcalendar/react/themes/forma';
9
- import { savedViewControl, SavedViewControlKind, defaultViewId, isViewHidden, TableSecurityPermission, MultiTableViewType, shownViewTables, getRecordReferenceId } from '@powerportalspro/core';
10
- import { useDialogService, usePrefixedT, useT, useUserTimeZone, useLocale, usePowerPortalsPro, useLocalization, RecordContext, useRecordContext, useLocalizationLoaded, QueryStatus, useRunWithOverlay, useMainContext } from '@powerportalspro/react';
11
- import { useSavedViews, useMultiTableViews, ViewSort, ViewFilterMenu, GridSaveViewDialog, GridManageViewsPanel, MultiTableFilterDialog, MultiTableRecordPanel, multiTableFilterTabs, applyMultiTableFilter, RecordDialogShell, RecordDialogFrame, SaveContextButton, multiTableFilterQueries, ColumnEdit, StatusReasonEdit, DeleteContextButton } from '@powerportalspro/react-fluent';
9
+ import { savedViewControl, SavedViewControlKind, MultiTableUses, defaultViewId, MultiTableViewType, isViewHidden, TableSecurityPermission, shownViewTables, findRecordPage, recordPageUrl, getRecordReferenceId, firstDayOfWeek } from '@powerportalspro/core';
10
+ import { useDialogService, usePrefixedT, useT, useUserTimeZone, useLocale, usePowerPortalsPro, useLocalization, replaceQueryParam, useRecordPages, RecordContext, useRecordContext, useLocalizationLoaded, QueryStatus, useRunWithOverlay, useMainContext } from '@powerportalspro/react';
11
+ import { useSavedViews, useMultiTableViews, useMultiTableTableSet, EVERY_TABLE_VIEW_ID, useSavedViewSession, everyRow, ViewToolbar, ViewSearchBox, ViewFilterMenu, ViewRefreshButton, ViewPicker, ViewSort, SavedViewDialogs, MultiTableFilterDialog, ViewLoadMessages, MultiTableRecordPanel, viewLabelOf, followRecordLink, RecordDialogShell, RecordDialogFrame, SaveContextButton, ColumnEdit, StatusReasonEdit, DeleteContextButton, SharedTooltip } from '@powerportalspro/react-fluent';
12
12
  import { createContext, useContext, useState, useMemo, useRef, useEffect, useCallback, useId } from 'react';
13
13
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
14
14
 
@@ -26,8 +26,6 @@ function useCalendarContext() {
26
26
  if (!context) throw new Error("useCalendarContext must be used in a <Calendar>, among its toolbar items.");
27
27
  return context;
28
28
  }
29
-
30
- // src/recurrence-pattern.ts
31
29
  var RecurrenceFrequency = {
32
30
  Daily: 0,
33
31
  Weekly: 1,
@@ -180,15 +178,6 @@ function describeRecurrence(pattern, start, locale, t) {
180
178
  }
181
179
  return repeats;
182
180
  }
183
- function firstDayOfWeek(locale) {
184
- try {
185
- const intlLocale = new Intl.Locale(locale);
186
- const firstDay = intlLocale.getWeekInfo?.().firstDay ?? intlLocale.weekInfo?.firstDay;
187
- return firstDay === void 0 ? 0 : firstDay % 7;
188
- } catch {
189
- return 0;
190
- }
191
- }
192
181
  function weekOrder(locale) {
193
182
  const first = firstDayOfWeek(locale);
194
183
  return Array.from({ length: 7 }, (_, i) => (first + i) % 7);
@@ -304,6 +293,7 @@ function RecurrenceEdit({ value, onChange, start, allowNone = true, readOnly = f
304
293
  const t = usePrefixedT("app.components.calendar.recurrence");
305
294
  const id = useId();
306
295
  const language = locale ?? navigator.language;
296
+ const rootRef = useRef(null);
307
297
  const startDay = useRef(start.weekday);
308
298
  useEffect(() => {
309
299
  const was = startDay.current;
@@ -322,7 +312,8 @@ function RecurrenceEdit({ value, onChange, start, allowNone = true, readOnly = f
322
312
  const pattern = startingPattern(frequency, start);
323
313
  onChange(value ? { ...pattern, end: value.end, occurrences: value.occurrences, endDate: value.endDate } : pattern);
324
314
  };
325
- return /* @__PURE__ */ jsxs("div", { className: styles.root, children: [
315
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: styles.root, children: [
316
+ /* @__PURE__ */ jsx(SharedTooltip, { containerRef: rootRef }),
326
317
  /* @__PURE__ */ jsx(Field, { label: t("label"), children: /* @__PURE__ */ jsx(
327
318
  Dropdown,
328
319
  {
@@ -395,7 +386,7 @@ function PatternFields({
395
386
  size: "small",
396
387
  checked: pressed,
397
388
  disabled: readOnly,
398
- title: dayName(weekday, locale),
389
+ "data-ppp-tooltip": dayName(weekday, locale),
399
390
  "aria-label": dayName(weekday, locale),
400
391
  onClick: () => {
401
392
  const days = value.days ^ flag;
@@ -495,12 +486,28 @@ function AppointmentDetailsDialog({
495
486
  onClose,
496
487
  onSaved,
497
488
  children,
498
- location = "center"
489
+ location = "center",
490
+ showRecordLinks = true,
491
+ onNavigate
499
492
  }) {
500
493
  const t = usePrefixedT("app.components.calendar");
501
494
  const tAll = useT();
502
495
  const open = appointment !== null || newAppointment2 !== null;
503
496
  const isSeries = appointment?.isSeries === true;
497
+ const recordPages = useRecordPages({ enabled: showRecordLinks });
498
+ const appointmentPage = showRecordLinks && appointment && !isSeries ? findRecordPage(recordPages, "appointment") : void 0;
499
+ const pageUrl = appointmentPage && appointment ? recordPageUrl(appointmentPage, appointment.id) : void 0;
500
+ const openPage = pageUrl !== void 0 && /* @__PURE__ */ jsx(
501
+ Button,
502
+ {
503
+ as: "a",
504
+ href: pageUrl,
505
+ className: "ppp-calendar-open-record",
506
+ onClick: (event) => followRecordLink(event, pageUrl, onNavigate),
507
+ children: tAll("app.buttons.open.label")
508
+ }
509
+ );
510
+ const regardingLinks = { showRecordLinks, ...onNavigate && { onNavigate } };
504
511
  const mayCreateSeries = useMayCreateSeries(open && editable && !isSeries && appointment?.recurring !== true);
505
512
  const saved = () => {
506
513
  onClose();
@@ -527,16 +534,21 @@ function AppointmentDetailsDialog({
527
534
  title: appointment ? appointment.subject : t("new-appointment"),
528
535
  actions: editable ? /* @__PURE__ */ jsxs(Fragment, { children: [
529
536
  /* @__PURE__ */ jsx(DeleteAction, { seriesId: appointment?.seriesId ?? null, onDeleted: saved }),
537
+ openPage,
530
538
  /* @__PURE__ */ jsx(SaveContextButton, { appearance: "primary", onSaved: saved }),
531
539
  /* @__PURE__ */ jsx(Button, { onClick: onClose, children: tAll("app.buttons.cancel.label") })
532
- ] }) : /* @__PURE__ */ jsx(Button, { appearance: "primary", onClick: onClose, children: t("close") }),
533
- children: isSeries ? /* @__PURE__ */ jsx(SeriesRecord, { editable, regardingTables }) : /* @__PURE__ */ jsx(
540
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
541
+ openPage,
542
+ /* @__PURE__ */ jsx(Button, { appearance: "primary", onClick: onClose, children: t("close") })
543
+ ] }),
544
+ children: isSeries ? /* @__PURE__ */ jsx(SeriesRecord, { editable, regardingTables, regardingLinks }) : /* @__PURE__ */ jsx(
534
545
  AppointmentRecord,
535
546
  {
536
547
  recurring: appointment?.recurring ?? false,
537
548
  editable,
538
549
  creating: !appointment,
539
550
  regardingTables,
551
+ regardingLinks,
540
552
  mayCreateSeries,
541
553
  children
542
554
  }
@@ -629,6 +641,7 @@ function AppointmentRecord({
629
641
  editable,
630
642
  creating,
631
643
  regardingTables,
644
+ regardingLinks,
632
645
  mayCreateSeries,
633
646
  children
634
647
  }) {
@@ -655,7 +668,7 @@ function AppointmentRecord({
655
668
  !creating && /* @__PURE__ */ jsx(ColumnEdit, { columnName: "organizer", readOnly: true }),
656
669
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "requiredattendees", readOnly }),
657
670
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "optionalattendees", readOnly }),
658
- /* @__PURE__ */ jsx(ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables } }),
671
+ /* @__PURE__ */ jsx(ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables }, ...regardingLinks }),
659
672
  !creating && /* @__PURE__ */ jsx(StatusReasonEdit, { columnName: "statuscode", allowStateChange: true, readOnly }),
660
673
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "description", readOnly })
661
674
  ] });
@@ -744,7 +757,11 @@ function seriesRequests(pattern, appointment, timeZone) {
744
757
  };
745
758
  return [request];
746
759
  }
747
- function SeriesRecord({ editable, regardingTables }) {
760
+ function SeriesRecord({
761
+ editable,
762
+ regardingTables,
763
+ regardingLinks
764
+ }) {
748
765
  const styles = useStyles2();
749
766
  const t = usePrefixedT("app.components.calendar");
750
767
  const ctx = useRecordContext();
@@ -802,7 +819,7 @@ function SeriesRecord({ editable, regardingTables }) {
802
819
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "organizer", readOnly: true }),
803
820
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "requiredattendees", readOnly }),
804
821
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "optionalattendees", readOnly }),
805
- /* @__PURE__ */ jsx(ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables } }),
822
+ /* @__PURE__ */ jsx(ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables }, ...regardingLinks }),
806
823
  /* @__PURE__ */ jsx(ColumnEdit, { columnName: "description", readOnly })
807
824
  ] });
808
825
  }
@@ -1217,8 +1234,47 @@ function newAppointment(range, regarding) {
1217
1234
  }
1218
1235
  };
1219
1236
  }
1237
+ function offeredViewIds(defaultViewId2, viewIds, multiTableViewIds, customViews) {
1238
+ const ids = [
1239
+ ...defaultViewId2 ? [defaultViewId2] : [],
1240
+ ...viewIds ?? [],
1241
+ ...multiTableViewIds ?? [],
1242
+ ...(customViews ?? []).map((view) => view.id)
1243
+ ].map((id) => id.toLowerCase());
1244
+ return ids.length === 0 ? [EVERY_TABLE_VIEW_ID] : [...new Set(ids)];
1245
+ }
1246
+ function findCustomView(id, customViews) {
1247
+ if (!id) return void 0;
1248
+ return customViews?.find((view) => view.id.toLowerCase() === id.toLowerCase());
1249
+ }
1250
+ function selectedViewId(offered, picked) {
1251
+ return picked && offered.includes(picked.toLowerCase()) ? picked.toLowerCase() : offered[0];
1252
+ }
1253
+ function offersViews(offered) {
1254
+ return offered.some((id) => id !== EVERY_TABLE_VIEW_ID);
1255
+ }
1256
+ function withSavedViews(pageViews, savedViews, settings, control, shown, offersSavedViews) {
1257
+ if (!offersSavedViews) return [...pageViews];
1258
+ let all = [...new Set([...pageViews, ...savedViews].map((id) => id.toLowerCase()))];
1259
+ const userDefault = defaultViewId(settings, all, control);
1260
+ if (userDefault) all = [userDefault, ...all.filter((id) => id !== userDefault)];
1261
+ const offered = all.filter((id) => id === shown?.toLowerCase() || !isViewHidden(settings, id, control));
1262
+ return offered.length > 0 ? offered : all;
1263
+ }
1264
+ var LAYOUTS = ["day", "workWeek", "week", "month", "agenda"];
1265
+ function readSavedLayout(json) {
1266
+ if (!json?.trim()) return void 0;
1267
+ try {
1268
+ const layout = JSON.parse(json).layout;
1269
+ return LAYOUTS.find((x) => x === layout);
1270
+ } catch {
1271
+ return void 0;
1272
+ }
1273
+ }
1274
+ function writeSavedLayout(layout) {
1275
+ return JSON.stringify({ version: 1, layout });
1276
+ }
1220
1277
  var APPOINTMENT_TABLE = "appointment";
1221
- var EVERY_TABLE_VIEW_ID = "00000000-0000-0000-0000-000000000000";
1222
1278
  var SERIES_TABLE2 = "recurringappointmentmaster";
1223
1279
  var DEFAULT_CALENDAR_TABLES = [APPOINTMENT_TABLE, "task"];
1224
1280
  var CALENDAR_COLUMNS = {
@@ -1284,10 +1340,10 @@ function drawnTimes(item, dateOnly, timeZone) {
1284
1340
  return { start: zonedMidnight(first, timeZone), end: zonedMidnight(after, timeZone) };
1285
1341
  }
1286
1342
  function fromMultiTableItem(item, info, drawn) {
1287
- const definition = info?.definition;
1343
+ const dates = info?.definition?.calendar;
1288
1344
  const changeable = new Set((info?.changeableColumns ?? []).map((x) => x.toLowerCase()));
1289
- const movable = (Number(item.permissions ?? 0) & TableSecurityPermission.Write) !== 0 && !isClosed(item) && !!definition?.start && changeable.has(definition.start.toLowerCase());
1290
- const resizable = movable && !!definition?.end && changeable.has(definition.end.toLowerCase());
1345
+ const movable = (Number(item.permissions ?? 0) & TableSecurityPermission.Write) !== 0 && !isClosed(item) && !!dates?.start && changeable.has(dates.start.toLowerCase());
1346
+ const resizable = movable && !!dates?.end && changeable.has(dates.end.toLowerCase());
1291
1347
  const isAppointment = item.table === APPOINTMENT_TABLE;
1292
1348
  const state = stateOf(item);
1293
1349
  const location = valueOf2(item.record, "location");
@@ -1305,7 +1361,7 @@ function fromMultiTableItem(item, info, drawn) {
1305
1361
  durationEditable: resizable ? null : false,
1306
1362
  showAs: isAppointment ? showAsOf(typeof status === "number" ? status : null, state) : AppointmentShowAs.Busy,
1307
1363
  canceled: state === 2,
1308
- color: definition?.color ?? null,
1364
+ color: info?.definition?.color ?? null,
1309
1365
  record: item.record ?? null
1310
1366
  };
1311
1367
  }
@@ -1313,86 +1369,23 @@ function stored(moment, dateOnly, timeZone) {
1313
1369
  return dateOnly ? `${localDate(moment, timeZone)}T00:00:00.000Z` : moment.toISOString();
1314
1370
  }
1315
1371
  function moveRecord(change, item, info, dateOnly, timeZone) {
1316
- const definition = info?.definition;
1317
- if (!definition?.start) return null;
1372
+ const dates = info?.definition?.calendar;
1373
+ if (!dates?.start) return null;
1318
1374
  const changeable = new Set((info?.changeableColumns ?? []).map((x) => x.toLowerCase()));
1319
- const allDayColumn = definition.allDay && changeable.has(definition.allDay.toLowerCase()) ? definition.allDay : null;
1375
+ const allDayColumn = dates.allDay && changeable.has(dates.allDay.toLowerCase()) ? dates.allDay : null;
1320
1376
  if (change.allDay !== (item.isAllDay ?? false) && !allDayColumn) return null;
1321
1377
  if (dateOnly && !change.allDay) return null;
1322
1378
  const end = change.end ?? new Date(change.start.getTime() + (change.allDay ? DAY_MS2 : 30 * MINUTE_MS2));
1323
1379
  const drawn = drawnTimes(item, dateOnly, timeZone);
1324
1380
  const lengthChanged = end.getTime() - change.start.getTime() !== drawn.end.getTime() - drawn.start.getTime();
1325
1381
  const properties = {
1326
- [definition.start]: { $type: 2, value: stored(change.start, dateOnly, timeZone) }
1382
+ [dates.start]: { $type: 2, value: stored(change.start, dateOnly, timeZone) }
1327
1383
  };
1328
- if (definition.end && (!item.hasNoEnd || lengthChanged))
1329
- properties[definition.end] = { $type: 2, value: stored(end, dateOnly, timeZone) };
1384
+ if (dates.end && (!item.hasNoEnd || lengthChanged))
1385
+ properties[dates.end] = { $type: 2, value: stored(end, dateOnly, timeZone) };
1330
1386
  if (allDayColumn) properties[allDayColumn] = { $type: 0, value: change.allDay };
1331
1387
  return { tableName: item.table, id: item.id, properties };
1332
1388
  }
1333
- function tablesOf(tables, registered, allowed, stored2, isStoredView) {
1334
- let named;
1335
- if (tables) named = tables.map((x) => x.table ?? "");
1336
- else if (isStoredView) {
1337
- if (!stored2) return null;
1338
- named = [...stored2];
1339
- } else named = registered.map((x) => x.table ?? "");
1340
- const expanded = named.flatMap(
1341
- (table) => table.toLowerCase() === "activitypointer" ? registered.filter((x) => x.isActivity).map((x) => x.table ?? "") : [table.toLowerCase()]
1342
- );
1343
- return [...new Set(expanded)].filter(
1344
- (table) => registered.some((x) => x.table === table) && (!allowed || allowed.has(table))
1345
- );
1346
- }
1347
- function hiddenByDefault(viewId, shown, defaults) {
1348
- if (viewId !== EVERY_TABLE_VIEW_ID || !shown.some((table) => defaults.has(table))) return [];
1349
- return shown.filter((table) => !defaults.has(table));
1350
- }
1351
-
1352
- // src/calendar-query.ts
1353
- function offeredViewIds(defaultViewId2, viewIds, multiTableViewIds, customViews) {
1354
- const ids = [
1355
- ...defaultViewId2 ? [defaultViewId2] : [],
1356
- ...viewIds ?? [],
1357
- ...multiTableViewIds ?? [],
1358
- ...(customViews ?? []).map((view) => view.id)
1359
- ].map((id) => id.toLowerCase());
1360
- return ids.length === 0 ? [EVERY_TABLE_VIEW_ID] : [...new Set(ids)];
1361
- }
1362
- function findCustomView(id, customViews) {
1363
- if (!id) return void 0;
1364
- return customViews?.find((view) => view.id.toLowerCase() === id.toLowerCase());
1365
- }
1366
- function selectedViewId(offered, picked) {
1367
- return picked && offered.includes(picked.toLowerCase()) ? picked.toLowerCase() : offered[0];
1368
- }
1369
- function offersViews(offered) {
1370
- return offered.some((id) => id !== EVERY_TABLE_VIEW_ID);
1371
- }
1372
- function everyRow(table) {
1373
- return `<fetch><entity name="${table}" /></fetch>`;
1374
- }
1375
- function withSavedViews(pageViews, savedViews, settings, control, shown, offersSavedViews) {
1376
- if (!offersSavedViews) return [...pageViews];
1377
- let all = [...new Set([...pageViews, ...savedViews].map((id) => id.toLowerCase()))];
1378
- const userDefault = defaultViewId(settings, all, control);
1379
- if (userDefault) all = [userDefault, ...all.filter((id) => id !== userDefault)];
1380
- const offered = all.filter((id) => id === shown?.toLowerCase() || !isViewHidden(settings, id, control));
1381
- return offered.length > 0 ? offered : all;
1382
- }
1383
- var LAYOUTS = ["day", "workWeek", "week", "month", "agenda"];
1384
- function readSavedLayout(json) {
1385
- if (!json?.trim()) return void 0;
1386
- try {
1387
- const layout = JSON.parse(json).layout;
1388
- return LAYOUTS.find((x) => x === layout);
1389
- } catch {
1390
- return void 0;
1391
- }
1392
- }
1393
- function writeSavedLayout(layout) {
1394
- return JSON.stringify({ version: 1, layout });
1395
- }
1396
1389
 
1397
1390
  // ../../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
1398
1391
  var skeleton_default = `
@@ -1919,8 +1912,6 @@ function ensureFullCalendarStyles(document2) {
1919
1912
  ensureStyleSheet(document2, STYLE_ID2, `${skeleton_default}
1920
1913
  ${theme_default}`);
1921
1914
  }
1922
-
1923
- // src/persisted-calendar-state.ts
1924
1915
  var DATE2 = /^(\d{4})-(\d{2})-(\d{2})$/;
1925
1916
  var GUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1926
1917
  function isDate(value) {
@@ -1958,21 +1949,10 @@ function encodePersistedCalendarState(state) {
1958
1949
  return Object.keys(compact).length > 0 ? JSON.stringify(compact) : null;
1959
1950
  }
1960
1951
  function writePersistedCalendarState(queryParameterName, state) {
1961
- if (!queryParameterName || typeof window === "undefined") return;
1962
- const encoded = encodePersistedCalendarState(state);
1963
- const params = new URLSearchParams(window.location.search);
1964
- if (params.get(queryParameterName) === encoded) return;
1965
- if (encoded === null) params.delete(queryParameterName);
1966
- else params.set(queryParameterName, encoded);
1967
- const query = params.toString();
1968
- window.history.replaceState(
1969
- window.history.state,
1970
- "",
1971
- `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`
1972
- );
1952
+ if (!queryParameterName) return;
1953
+ replaceQueryParam(queryParameterName, encodePersistedCalendarState(state) ?? void 0);
1973
1954
  }
1974
1955
  var plugins = [formaTheme, dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin];
1975
- var SEARCH_DEBOUNCE_MS = 300;
1976
1956
  var layoutKeys = {
1977
1957
  day: "layouts.day",
1978
1958
  workWeek: "layouts.work-week",
@@ -1987,32 +1967,6 @@ var useStyles3 = makeStyles({
1987
1967
  gap: tokens.spacingVerticalS,
1988
1968
  minHeight: 0
1989
1969
  },
1990
- // Two groups: the views, navigation and title, then search and the rest at the end. On a narrow
1991
- // screen the end group wraps below the first as a whole rather than squeezing the title.
1992
- toolbar: {
1993
- alignItems: "center",
1994
- flexWrap: "wrap",
1995
- justifyContent: "space-between",
1996
- rowGap: tokens.spacingVerticalXS,
1997
- columnGap: tokens.spacingHorizontalS,
1998
- paddingInline: 0
1999
- },
2000
- toolbarStart: {
2001
- display: "flex",
2002
- alignItems: "center",
2003
- gap: tokens.spacingHorizontalXS,
2004
- flex: "1 1 auto",
2005
- minWidth: 0
2006
- },
2007
- toolbarEnd: {
2008
- display: "flex",
2009
- flexWrap: "wrap",
2010
- alignItems: "center",
2011
- justifyContent: "flex-end",
2012
- gap: tokens.spacingHorizontalXS,
2013
- marginInlineStart: "auto",
2014
- minWidth: 0
2015
- },
2016
1970
  // The period and the moves through it, kept together.
2017
1971
  navigation: {
2018
1972
  display: "flex",
@@ -2034,10 +1988,6 @@ var useStyles3 = makeStyles({
2034
1988
  overflow: "hidden",
2035
1989
  textOverflow: "ellipsis"
2036
1990
  },
2037
- // As wide as a grid's view picker.
2038
- viewPicker: {
2039
- minWidth: "220px"
2040
- },
2041
1991
  search: {
2042
1992
  width: "220px",
2043
1993
  maxWidth: "100%"
@@ -2107,6 +2057,8 @@ function Calendar({
2107
2057
  defaultLayout = "week",
2108
2058
  layouts,
2109
2059
  editable = false,
2060
+ showRecordLinks = true,
2061
+ onNavigate,
2110
2062
  regardingTables,
2111
2063
  height = "600px",
2112
2064
  onAppointmentClick,
@@ -2175,6 +2127,7 @@ function Calendar({
2175
2127
  );
2176
2128
  const viewId = selectedViewId(offered, shownViewId);
2177
2129
  if (viewSettled) openedViewRef.current ??= viewId;
2130
+ const tableSet = useMultiTableTableSet({ use: MultiTableUses.Calendar, viewId, tables, defaultTables });
2178
2131
  const openingLayoutRef = useRef(void 0);
2179
2132
  if (openingLayoutRef.current === void 0 && viewSettled) {
2180
2133
  const savedLayout = persisted.l ? void 0 : readSavedLayout(savedCalendarLayout(viewId));
@@ -2183,8 +2136,7 @@ function Calendar({
2183
2136
  const initialLayout = openingLayoutRef.current ?? openingLayout(layoutsOffered, persisted.l, defaultLayout);
2184
2137
  const viewOpeningLayout = (id) => openingLayout(layoutsOffered, readSavedLayout(savedCalendarLayout(id)), defaultLayout);
2185
2138
  const showsSearch = !appointments && allowSearch;
2186
- const [searchText, setSearchText] = useState(() => includeSearchInPersistedState ? persisted.q ?? "" : "");
2187
- const [search, setSearch] = useState(searchText);
2139
+ const [search, setSearch] = useState(() => includeSearchInPersistedState ? persisted.q ?? "" : "");
2188
2140
  const picksViews = !appointments && offered.length > 1;
2189
2141
  const namesViews = picksViews || !appointments && saved.available;
2190
2142
  const namedViewIds = saved.available ? [.../* @__PURE__ */ new Set([...offered, ...pageViews])] : offered;
@@ -2205,10 +2157,10 @@ function Calendar({
2205
2157
  const multiTable = new Map(viewNames.multiTable);
2206
2158
  const missing = multiTableIds.filter((x) => !multiTable.has(x));
2207
2159
  if (missing.length > 0) {
2208
- const registeredViews = (await registeredAsync().catch(() => null))?.views ?? [];
2160
+ const registeredViews = (await tableSet.registeredAsync().catch(() => null))?.views ?? [];
2209
2161
  for (const id of missing) {
2210
2162
  const known2 = registeredViews.find((view2) => view2.id?.toLowerCase() === id);
2211
- multiTable.set(id, known2 ? known2.name ?? null : await storedViewName(id));
2163
+ multiTable.set(id, known2 ? known2.name ?? null : (await tableSet.storedViewName(id)).name);
2212
2164
  }
2213
2165
  }
2214
2166
  if (current) setViewNames({ dataverse, multiTable });
@@ -2235,12 +2187,7 @@ function Calendar({
2235
2187
  }
2236
2188
  if (!viewNames.dataverse.has(id)) return null;
2237
2189
  const metadata = viewNames.dataverse.get(id);
2238
- if (metadata) {
2239
- const key = `tables.${metadata.tableName}.views.${id}.label`;
2240
- const localized = tAll(key);
2241
- if (localized !== key) return localized;
2242
- }
2243
- return metadata?.displayName ?? metadata?.name ?? id;
2190
+ return metadata ? viewLabelOf({ ...metadata, id }, tAll) : id;
2244
2191
  };
2245
2192
  const showsSavedViewsMenu = allowSavedViews && !appointments && (saved.available || saved.needsSignIn);
2246
2193
  const viewOptions = useMemo(() => {
@@ -2250,11 +2197,8 @@ function Calendar({
2250
2197
  return label === null ? null : { id, label };
2251
2198
  });
2252
2199
  if (labeled.some((option) => option === null)) return [];
2253
- const options2 = labeled;
2254
- if (viewSort === ViewSort.NameAscending) options2.sort((a, b) => a.label.localeCompare(b.label, locale));
2255
- if (viewSort === ViewSort.NameDescending) options2.sort((a, b) => b.label.localeCompare(a.label, locale));
2256
- return options2;
2257
- }, [offered, customViewsKey, saved.views, multiSaved.views, viewNames, viewSort, tAll, locale, showsSavedViewsMenu]);
2200
+ return labeled;
2201
+ }, [offered, customViewsKey, saved.views, multiSaved.views, viewNames, tAll, showsSavedViewsMenu]);
2258
2202
  const showsViewPicker = !appointments && viewOptions.length > (showsSavedViewsMenu ? 0 : 1);
2259
2203
  useEffect(() => {
2260
2204
  ensureFullCalendarStyles(document);
@@ -2289,7 +2233,7 @@ function Calendar({
2289
2233
  useEffect(() => () => void (latestStart.current = -1), []);
2290
2234
  const user = authState.status === "authenticated" ? authState.user?.userId : null;
2291
2235
  const tablesKey = (tables ?? []).map((table) => table.trim().toLowerCase()).join(",");
2292
- const allowedTables = tablesKey ? new Set(tablesKey.split(",")) : null;
2236
+ const allowedTables = tableSet.allowed;
2293
2237
  const mayCreateAppointments = () => {
2294
2238
  let asked = mayCreate.current;
2295
2239
  if (!asked || asked.user !== user) {
@@ -2349,33 +2293,9 @@ function Calendar({
2349
2293
  const [refreshes, setRefreshes] = useState(0);
2350
2294
  const latestLoad = useRef(null);
2351
2295
  useEffect(() => () => latestLoad.current?.abort(), []);
2352
- const registeredRef = useRef(null);
2353
- const [registered, setRegistered] = useState(null);
2354
- const registeredAsync = () => {
2355
- let asked = registeredRef.current;
2356
- if (!asked || asked.user !== user) {
2357
- const answer = ppp.getMultiTableTablesAsync();
2358
- asked = registeredRef.current = { user, answer };
2359
- answer.then(
2360
- (response) => registeredRef.current?.answer === answer && setRegistered(response),
2361
- () => {
2362
- if (registeredRef.current?.answer === answer) registeredRef.current = null;
2363
- }
2364
- );
2365
- }
2366
- return asked.answer;
2367
- };
2368
- const infoOf = (table) => registered?.tables?.find((x) => x.table === table);
2369
- const storedViewName = async (id) => {
2370
- try {
2371
- const stored2 = await ppp.retrieveRecordAsync("ppp_multitableview", id, ["ppp_name"]);
2372
- const name = stored2.properties?.["ppp_name"]?.value;
2373
- return typeof name === "string" ? name : null;
2374
- } catch {
2375
- return null;
2376
- }
2377
- };
2378
- const storedViewTables = useRef(/* @__PURE__ */ new Map());
2296
+ const registeredAsync = tableSet.registeredAsync;
2297
+ const infoOf = tableSet.infoOf;
2298
+ const tableLabel = tableSet.tableLabel;
2379
2299
  const isStoredView = (id, response) => isMultiTableView(id) && !(response.views ?? []).some((view2) => view2.id?.toLowerCase() === id);
2380
2300
  const viewTablesAsync = async (id) => {
2381
2301
  if (id === EVERY_TABLE_VIEW_ID) return null;
@@ -2392,35 +2312,15 @@ function Calendar({
2392
2312
  console.warn(`[Calendar] The view ${id} doesn't exist or is on a table the user may not read, so it shows nothing.`);
2393
2313
  return [];
2394
2314
  };
2315
+ const knownTablesOf = (id, viewTables, response) => tableSet.tablesOf(viewTables, isStoredView(id, response) ? id : null);
2395
2316
  const defaultsKey = defaultTables.map((table) => table.trim().toLowerCase()).join(",");
2396
- const [knownTables, setKnownTables] = useState({ viewId: void 0, tables: [] });
2397
- const known = knownTables.viewId === viewId ? knownTables.tables : [];
2398
- const [hiddenChoice, setHiddenChoice] = useState(null);
2399
- const hiddenIn = (id, of) => hiddenChoice && hiddenChoice.viewId === id ? hiddenChoice.tables : hiddenByDefault(id, of, new Set(defaultsKey.split(",").filter(Boolean)));
2400
- const hidden = hiddenIn(viewId, known);
2401
- const shownIn = (id, of, byView = false) => {
2402
- const off = byView ? hiddenByDefault(id, of, new Set(defaultsKey.split(",").filter(Boolean))) : hiddenIn(id, of);
2403
- return of.length < 2 ? [...of] : of.filter((table) => !off.includes(table));
2404
- };
2405
- const shownTables = shownIn(viewId, known);
2406
- const kinds = known.length > 1 ? known : [];
2407
- const hiddenAtOpen = hiddenByDefault(viewId, known, new Set(defaultsKey.split(",").filter(Boolean)));
2408
- const tablesChanged = kinds.some((table) => hidden.includes(table) !== hiddenAtOpen.includes(table));
2409
- const showTable = (table, shown) => {
2410
- const next = shown ? hidden.filter((x) => x !== table) : [.../* @__PURE__ */ new Set([...hidden, table])];
2411
- setHiddenChoice({ viewId, tables: next });
2412
- };
2413
- useLocalization(known.map((table) => `tables.${table}`));
2414
- const tableLabel = (table) => {
2415
- const key = `tables.${table}.label`;
2416
- const label = tAll(key);
2417
- return label !== key ? label : table;
2418
- };
2317
+ const known = tableSet.known;
2318
+ const shownTables = tableSet.shown;
2419
2319
  const rows = useRef(/* @__PURE__ */ new Map());
2420
2320
  const dateOnly = useRef(/* @__PURE__ */ new Map());
2421
2321
  const learnDateOnly = async (tables2, response) => {
2422
2322
  for (const table of [...new Set(tables2)].filter((x) => !dateOnly.current.has(x))) {
2423
- const start = response.tables?.find((x) => x.table === table)?.definition?.start;
2323
+ const start = response.tables?.find((x) => x.table === table)?.definition?.calendar?.start;
2424
2324
  try {
2425
2325
  const metadata = await tableMetadataCache.getAsync(table);
2426
2326
  const column = metadata?.columns?.find((x) => x.columnName === start);
@@ -2431,22 +2331,12 @@ function Calendar({
2431
2331
  }
2432
2332
  };
2433
2333
  const [failures, setFailures] = useState([]);
2434
- const showsFilterButton = !appointments && allowFiltering && !(registered && isStoredView(viewId, registered));
2435
- const [userFilter, setUserFilter] = useState(null);
2436
- useEffect(() => {
2437
- setUserFilter((current) => current && current.viewId !== viewId ? null : current);
2438
- setHiddenChoice((current) => current && current.viewId !== viewId ? null : current);
2439
- }, [viewId]);
2440
- const filtered = userFilter && userFilter.viewId === viewId ? userFilter : null;
2441
- const [filterDialog, setFilterDialog] = useState(null);
2334
+ const showsFilterButton = !appointments && allowFiltering && !(tableSet.registered && isStoredView(viewId, tableSet.registered));
2335
+ const filtered = tableSet.filter;
2442
2336
  const filterTablesAsync = async (id) => {
2443
- const response = await registeredAsync();
2444
- const tablesShown = await viewTablesAsync(id) ?? (id === EVERY_TABLE_VIEW_ID ? (tablesOf(null, response.tables ?? [], allowedTables, null, false) ?? []).map((table) => ({ table })) : []);
2445
- return multiTableFilterQueries(tablesShown, async (viewOf) => (await viewMetadataCache.getAsync(viewOf))?.fetchXml);
2446
- };
2447
- const isShownTable = (table, named, shownNow, response) => {
2448
- const lower = (x) => x.toLowerCase();
2449
- 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));
2337
+ await registeredAsync();
2338
+ const tablesShown = await viewTablesAsync(id) ?? (id === EVERY_TABLE_VIEW_ID ? (tableSet.tablesOf(null) ?? []).map((table) => ({ table })) : []);
2339
+ return tableSet.filterQueries(tablesShown);
2450
2340
  };
2451
2341
  const openFilter = async () => {
2452
2342
  const id = viewId;
@@ -2454,36 +2344,14 @@ function Calendar({
2454
2344
  try {
2455
2345
  const response = await registeredAsync();
2456
2346
  const tablesShown = await filterTablesAsync(id);
2457
- const knownNow = tablesOf(
2458
- await viewTablesAsync(id) ?? null,
2459
- response.tables ?? [],
2460
- allowedTables,
2461
- storedViewTables.current.get(id),
2462
- isStoredView(id, response)
2463
- ) ?? [];
2464
- const shownNow = shownIn(id, knownNow);
2465
- const named = tablesShown.map((x) => x.table.table ?? "");
2466
- const tabs = multiTableFilterTabs(
2467
- tablesShown,
2468
- (table) => isShownTable(table, named, shownNow, response),
2469
- tableLabel,
2470
- filtered?.criteria
2471
- );
2472
- const shownByView = shownIn(id, knownNow, true);
2473
- const viewTabs = multiTableFilterTabs(tablesShown, (table) => isShownTable(table, named, shownByView, response), tableLabel);
2474
- if (tabs.length > 0 && id === viewId) setFilterDialog({ viewId: id, tables: tablesShown, tabs, viewTabs });
2347
+ const knownNow = knownTablesOf(id, await viewTablesAsync(id) ?? null, response) ?? [];
2348
+ tableSet.openFilter(id, tablesShown, knownNow, tableLabel);
2475
2349
  } catch (error) {
2476
2350
  console.warn("[Calendar] The filters of the view shown could not be read.", error);
2477
2351
  }
2478
2352
  };
2479
2353
  const applyFilter = (criteria, restoreView) => {
2480
- const dialog2 = filterDialog;
2481
- setFilterDialog(null);
2482
- if (!dialog2 || dialog2.viewId !== viewId) return;
2483
- const filter = applyMultiTableFilter(dialog2.tables, restoreView ? dialog2.viewTabs : dialog2.tabs, criteria);
2484
- setUserFilter(filter ? { viewId, tables: filter.tables, criteria: filter.criteria } : null);
2485
- if (!restoreView) return;
2486
- setHiddenChoice(null);
2354
+ if (!tableSet.applyFilter(criteria, restoreView) || !restoreView) return;
2487
2355
  const layout = viewOpeningLayout(viewId);
2488
2356
  if (layout !== currentLayout && layoutsOffered.includes(layout)) controller.changeView(toFullCalendarView(layout));
2489
2357
  };
@@ -2496,7 +2364,7 @@ function Calendar({
2496
2364
  customViewsKey,
2497
2365
  filtered?.tables ?? null,
2498
2366
  searched,
2499
- hiddenChoice,
2367
+ tableSet.hiddenChoice,
2500
2368
  tablesKey,
2501
2369
  defaultsKey,
2502
2370
  matchParties ?? null,
@@ -2526,14 +2394,10 @@ function Calendar({
2526
2394
  openingViewRef.current = offered[0];
2527
2395
  if (viewSettled && previous !== void 0 && previous !== offered[0]) persist();
2528
2396
  }, [offered[0]]);
2529
- useEffect(() => {
2530
- if (searchText === search) return;
2531
- const timer = setTimeout(() => {
2532
- setSearch(searchText);
2533
- persist({ search: searchText });
2534
- }, SEARCH_DEBOUNCE_MS);
2535
- return () => clearTimeout(timer);
2536
- }, [searchText]);
2397
+ const searchFor = (text) => {
2398
+ setSearch(text);
2399
+ persist({ search: text });
2400
+ };
2537
2401
  const events = useMemo(() => {
2538
2402
  if (appointments) return appointments.map(toEventInput);
2539
2403
  return async (info) => {
@@ -2546,8 +2410,8 @@ function Calendar({
2546
2410
  const response = await registeredAsync();
2547
2411
  const viewTables = await viewTablesAsync(id);
2548
2412
  const stored2 = isStoredView(id, response);
2549
- let knownNow = tablesOf(viewTables, response.tables ?? [], allowedTables, storedViewTables.current.get(id), stored2);
2550
- if (latestLoad.current === load && knownNow) setKnownTables({ viewId: id, tables: knownNow });
2413
+ let knownNow = knownTablesOf(id, viewTables, response);
2414
+ if (latestLoad.current === load && knownNow) tableSet.setKnown(id, knownNow);
2551
2415
  if (knownNow?.length === 0) {
2552
2416
  if (latestLoad.current === load) {
2553
2417
  setFailed(false);
@@ -2563,7 +2427,7 @@ function Calendar({
2563
2427
  viewId: filtered || !isMultiTableView(id) ? null : id,
2564
2428
  tables: filtered ? [...filtered.tables] : viewTables && viewTables.length > 0 ? [...viewTables] : null,
2565
2429
  // A view whose tables aren't known yet is read as the calendar allows.
2566
- includeTables: knownNow ? shownIn(id, knownNow) : allowedTables ? [...allowedTables] : null,
2430
+ includeTables: knownNow ? tableSet.shownIn(id, knownNow) : allowedTables ? [...allowedTables] : null,
2567
2431
  regarding: regarding ? { tableName: regarding.tableName, id: regarding.id } : null,
2568
2432
  matchParties: matchParties ?? null,
2569
2433
  series: series ?? null,
@@ -2572,9 +2436,8 @@ function Calendar({
2572
2436
  };
2573
2437
  const range = await ppp.loadMultiTableRangeAsync(request, load.signal);
2574
2438
  if (!knownNow && stored2) {
2575
- storedViewTables.current.set(id, range.tables ?? []);
2576
- knownNow = tablesOf(null, response.tables ?? [], allowedTables, range.tables ?? [], true) ?? [];
2577
- if (latestLoad.current === load) setKnownTables({ viewId: id, tables: knownNow });
2439
+ knownNow = tableSet.learnTables(id, range.tables ?? []);
2440
+ if (latestLoad.current === load) tableSet.setKnown(id, knownNow);
2578
2441
  }
2579
2442
  const items = range.items ?? [];
2580
2443
  await learnDateOnly(items.map((item) => item.table ?? ""), response);
@@ -2691,13 +2554,10 @@ function Calendar({
2691
2554
  const shownSaved = findSaved(viewId);
2692
2555
  const shownSavedMulti = findSavedMulti(viewId);
2693
2556
  const showsAppointmentsAlone = shownTables.length === 1 && shownTables[0] === APPOINTMENT_TABLE;
2694
- const canSaveView = shownTables.length > 0 && !(registered && isStoredView(viewId, registered));
2695
- const viewChanged = !!filtered || tablesChanged || currentLayout !== viewOpeningLayout(viewId);
2557
+ const canSaveView = shownTables.length > 0 && !(tableSet.registered && isStoredView(viewId, tableSet.registered));
2558
+ const viewChanged = !!filtered || tableSet.tablesChanged || currentLayout !== viewOpeningLayout(viewId);
2696
2559
  const savedViewChanged = (!!shownSaved || !!shownSavedMulti) && canSaveView && viewChanged;
2697
2560
  const isUserDefault = !!viewId && defaultViewId(saved.settings, offered, savedViewsControl) === viewId;
2698
- const [savingView, setSavingView] = useState(false);
2699
- const [nameDialog, setNameDialog] = useState(null);
2700
- const [manageViewsOpen, setManageViewsOpen] = useState(false);
2701
2561
  const currentQuery = async () => {
2702
2562
  if (!showsAppointmentsAlone || !viewId) return void 0;
2703
2563
  const own = filtered?.tables.find((x) => x.table === APPOINTMENT_TABLE);
@@ -2710,261 +2570,195 @@ function Calendar({
2710
2570
  const tablesNow = filtered ? [...filtered.tables] : (await filterTablesAsync(viewId)).map((x) => x.table);
2711
2571
  return shownViewTables(tablesNow, known, shownTables, (table) => !!response.tables?.find((info) => info.table === table)?.isActivity);
2712
2572
  };
2713
- const runSavedViewChange = async (change) => {
2714
- if (savingView) return;
2715
- setSavingView(true);
2716
- try {
2717
- await change();
2718
- } catch (error) {
2719
- console.warn("[Calendar] A change to the user\u2019s saved views failed.", error);
2720
- await dialog.showErrorAsync(tAll("app.components.grid.saved-views-error"));
2721
- } finally {
2722
- setSavingView(false);
2723
- }
2724
- };
2725
- const signedIn = async (action) => {
2726
- if (!saved.needsSignIn) {
2727
- await action();
2728
- return;
2729
- }
2730
- const result = await dialog.showConfirmationAsync(tAll("app.components.grid.saved-views-sign-in-message"), {
2731
- title: tAll("app.components.grid.saved-views-sign-in-title"),
2732
- primaryText: tAll("app.buttons.login.label"),
2733
- secondaryText: tAll("app.buttons.cancel.label")
2734
- });
2735
- if (result.canceled) return;
2736
- const returnUrl = window.location.pathname + window.location.search + window.location.hash;
2737
- window.location.assign(`${signInUrl}${signInUrl.includes("?") ? "&" : "?"}ReturnUrl=${encodeURIComponent(returnUrl)}`);
2738
- };
2739
2573
  const showSavedView = (id) => {
2740
- setUserFilter(null);
2741
- setHiddenChoice(null);
2574
+ tableSet.reset();
2742
2575
  setPickedViewId(id);
2743
2576
  persist({ viewId: id });
2744
2577
  };
2745
- const saveView = () => runSavedViewChange(async () => {
2746
- if (shownSavedMulti) {
2747
- const tables2 = await currentTables();
2748
- if (tables2.length === 0) return;
2749
- await multiSaved.update(shownSavedMulti.id, { tables: tables2, calendarLayout: writeSavedLayout(currentLayout) });
2750
- } else {
2751
- if (!shownSaved) return;
2752
- const fetchXml = await currentQuery();
2753
- if (!fetchXml) return;
2754
- await saved.update(shownSaved.id, { fetchXml, calendarLayout: writeSavedLayout(currentLayout) });
2755
- }
2756
- setUserFilter(null);
2757
- setHiddenChoice(null);
2758
- });
2759
- const saveViewAs = (name) => runSavedViewChange(async () => {
2760
- const fetchXml = showsAppointmentsAlone ? await currentQuery() : void 0;
2761
- if (fetchXml) {
2762
- const view3 = await saved.create(name, fetchXml, { calendarLayout: writeSavedLayout(currentLayout) });
2763
- showSavedView(view3.id.toLowerCase());
2764
- return;
2765
- }
2766
- const tables2 = await currentTables();
2767
- if (tables2.length === 0) return;
2768
- const view2 = await multiSaved.create(name, tables2, { calendarLayout: writeSavedLayout(currentLayout) });
2769
- showSavedView(view2.id.toLowerCase());
2770
- });
2771
- const cloneView = (id, name) => runSavedViewChange(async () => {
2772
- const own = findSaved(id);
2773
- const custom = findCustomView(id, customViewDefinitions)?.tables;
2774
- const only = custom?.length === 1 && custom[0].table === APPOINTMENT_TABLE && (custom[0].viewType ?? MultiTableViewType.FetchXml) === MultiTableViewType.FetchXml ? custom[0].fetchXml ?? everyRow(APPOINTMENT_TABLE) : void 0;
2775
- const ownMulti = findSavedMulti(id);
2776
- const dataverse = own || only || ownMulti || custom ? void 0 : await viewMetadataCache.getAsync(id);
2777
- const fetchXml = own?.definition.fetchXml ?? only ?? (dataverse?.tableName.toLowerCase() === APPOINTMENT_TABLE ? dataverse.fetchXml : void 0);
2778
- if (fetchXml) {
2779
- await saved.create(name, fetchXml, { gridLayout: own?.gridLayout, calendarLayout: own?.calendarLayout });
2780
- return;
2781
- }
2782
- const tables2 = ownMulti?.tables ?? await viewTablesAsync(id) ?? (id === EVERY_TABLE_VIEW_ID ? known.map((table) => ({ table })) : null);
2783
- if (!tables2 || tables2.length === 0) return;
2784
- await multiSaved.create(name, tables2, { calendarLayout: ownMulti?.calendarLayout });
2785
- });
2786
- const renameView = (id, name) => runSavedViewChange(async () => void (findSavedMulti(id) ? await multiSaved.update(id, { name }) : await saved.update(id, { name })));
2787
- const deleteView = async (id) => {
2788
- const result = await dialog.showConfirmationAsync(
2789
- tAll("app.components.grid.saved-views-delete-message", [findSaved(id)?.name ?? findSavedMulti(id)?.name ?? ""]),
2790
- {
2791
- title: tAll("app.components.grid.saved-views-delete-title"),
2792
- primaryText: tAll("app.components.grid.saved-views-delete"),
2793
- secondaryText: tAll("app.buttons.cancel.label")
2794
- }
2795
- );
2796
- if (result.canceled) return;
2797
- await runSavedViewChange(async () => {
2798
- if (findSavedMulti(id)) {
2799
- await multiSaved.remove(id);
2800
- await saved.reloadSettings();
2801
- } else {
2802
- await saved.remove(id);
2803
- }
2804
- if (id.toLowerCase() === viewId) setUserFilter(null);
2805
- });
2806
- };
2807
- const setDefault = (id, isDefault) => runSavedViewChange(() => saved.setDefault(id, isDefault, viewLabel(id)));
2808
- const setHidden = (id, isHidden) => runSavedViewChange(() => saved.setHidden(id, isHidden, viewLabel(id)));
2809
2578
  function viewLabel(id) {
2810
2579
  return labelOf(id.toLowerCase()) ?? findSaved(id)?.name ?? findSavedMulti(id)?.name ?? id;
2811
2580
  }
2812
- const managedViews = useMemo(() => {
2813
- if (!manageViewsOpen) return [];
2814
- const all = [.../* @__PURE__ */ new Set([...pageViews, ...savedIds.map((x) => x.toLowerCase())])];
2815
- const userDefault = defaultViewId(saved.settings, all, savedViewsControl);
2816
- return all.map((id) => ({
2817
- id,
2818
- name: viewLabel(id),
2819
- isSaved: !!findSaved(id) || !!findSavedMulti(id),
2820
- isDefault: id === userDefault,
2821
- isHidden: isViewHidden(saved.settings, id, savedViewsControl),
2822
- isShown: id === viewId
2823
- }));
2824
- }, [manageViewsOpen, pageViews, savedIds, saved.settings, savedViewsControl, viewId, viewOptions, viewNames]);
2825
- const onNameDialogSave = (name) => {
2826
- const asked = nameDialog;
2827
- setNameDialog(null);
2828
- if (!asked) return;
2829
- if (asked.kind === "rename") void renameView(asked.viewId, name);
2830
- else if (asked.kind === "clone") void cloneView(asked.viewId, name);
2831
- else void saveViewAs(name);
2832
- };
2833
- return /* @__PURE__ */ jsxs("div", { className: styles.root, style: { height }, children: [
2834
- /* @__PURE__ */ jsxs(Toolbar, { className: styles.toolbar, "aria-label": t("toolbar"), children: [
2835
- /* @__PURE__ */ jsx("div", { className: styles.toolbarStart, children: showsViewPicker && /* @__PURE__ */ jsx(
2836
- Dropdown,
2837
- {
2838
- className: mergeClasses("ppp-calendar-view-picker", styles.viewPicker),
2839
- "aria-label": tAll("app.components.view-selector.view-name"),
2840
- value: viewOptions.find((option) => option.id === viewId)?.label ?? "",
2841
- selectedOptions: viewId ? [viewId] : [],
2842
- onOptionSelect: (_, data) => pickView(data.optionValue),
2843
- children: viewOptions.map((option) => /* @__PURE__ */ jsx(Option, { value: option.id, text: option.label, children: option.label }, option.id))
2581
+ const session = useSavedViewSession({
2582
+ saved,
2583
+ signInUrl,
2584
+ operations: {
2585
+ shownViewId: viewId,
2586
+ savedViewName: (id) => findSaved(id)?.name ?? findSavedMulti(id)?.name,
2587
+ managedViewName: viewLabel,
2588
+ // Every view for the Manage views panel, the hidden ones among them.
2589
+ managedViews: () => {
2590
+ const all = [.../* @__PURE__ */ new Set([...pageViews, ...savedIds.map((x) => x.toLowerCase())])];
2591
+ const userDefault = defaultViewId(saved.settings, all, savedViewsControl);
2592
+ return all.map((id) => ({
2593
+ id,
2594
+ name: viewLabel(id),
2595
+ isSaved: !!findSaved(id) || !!findSavedMulti(id),
2596
+ isDefault: id === userDefault,
2597
+ isHidden: isViewHidden(saved.settings, id, savedViewsControl),
2598
+ isShown: id === viewId
2599
+ }));
2600
+ },
2601
+ save: async () => {
2602
+ if (shownSavedMulti) {
2603
+ const tables2 = await currentTables();
2604
+ if (tables2.length === 0) return;
2605
+ await multiSaved.update(shownSavedMulti.id, { tables: tables2, calendarLayout: writeSavedLayout(currentLayout) });
2606
+ } else {
2607
+ if (!shownSaved) return;
2608
+ const fetchXml = await currentQuery();
2609
+ if (!fetchXml) return;
2610
+ await saved.update(shownSaved.id, { fetchXml, calendarLayout: writeSavedLayout(currentLayout) });
2844
2611
  }
2845
- ) }),
2846
- /* @__PURE__ */ jsxs("div", { className: styles.toolbarEnd, children: [
2847
- /* @__PURE__ */ jsxs("div", { className: mergeClasses("ppp-calendar-navigation", styles.navigation), children: [
2848
- /* @__PURE__ */ jsx(
2849
- ToolbarButton,
2612
+ tableSet.reset();
2613
+ },
2614
+ saveAs: async (name) => {
2615
+ const fetchXml = showsAppointmentsAlone ? await currentQuery() : void 0;
2616
+ if (fetchXml) {
2617
+ const view3 = await saved.create(name, fetchXml, { calendarLayout: writeSavedLayout(currentLayout) });
2618
+ showSavedView(view3.id.toLowerCase());
2619
+ return;
2620
+ }
2621
+ const tables2 = await currentTables();
2622
+ if (tables2.length === 0) return;
2623
+ const view2 = await multiSaved.create(name, tables2, { calendarLayout: writeSavedLayout(currentLayout) });
2624
+ showSavedView(view2.id.toLowerCase());
2625
+ },
2626
+ clone: async (id, name) => {
2627
+ const own = findSaved(id);
2628
+ const custom = findCustomView(id, customViewDefinitions)?.tables;
2629
+ const only = custom?.length === 1 && custom[0].table === APPOINTMENT_TABLE && (custom[0].viewType ?? MultiTableViewType.FetchXml) === MultiTableViewType.FetchXml ? custom[0].fetchXml ?? everyRow(APPOINTMENT_TABLE) : void 0;
2630
+ const ownMulti = findSavedMulti(id);
2631
+ const dataverse = own || only || ownMulti || custom ? void 0 : await viewMetadataCache.getAsync(id);
2632
+ const fetchXml = own?.definition.fetchXml ?? only ?? (dataverse?.tableName.toLowerCase() === APPOINTMENT_TABLE ? dataverse.fetchXml : void 0);
2633
+ if (fetchXml) {
2634
+ await saved.create(name, fetchXml, { gridLayout: own?.gridLayout, calendarLayout: own?.calendarLayout });
2635
+ return;
2636
+ }
2637
+ const tables2 = ownMulti?.tables ?? await viewTablesAsync(id) ?? (id === EVERY_TABLE_VIEW_ID ? known.map((table) => ({ table })) : null);
2638
+ if (!tables2 || tables2.length === 0) return;
2639
+ await multiSaved.create(name, tables2, { calendarLayout: ownMulti?.calendarLayout });
2640
+ },
2641
+ rename: async (id, name) => void (findSavedMulti(id) ? await multiSaved.update(id, { name }) : await saved.update(id, { name })),
2642
+ remove: async (id) => {
2643
+ if (findSavedMulti(id)) {
2644
+ await multiSaved.remove(id);
2645
+ await saved.reloadSettings();
2646
+ } else {
2647
+ await saved.remove(id);
2648
+ }
2649
+ if (id.toLowerCase() === viewId) tableSet.clearFilter();
2650
+ },
2651
+ openView: (id) => pickView(id)
2652
+ }
2653
+ });
2654
+ return /* @__PURE__ */ jsxs("div", { className: styles.root, style: { height }, children: [
2655
+ /* @__PURE__ */ jsxs(
2656
+ ViewToolbar,
2657
+ {
2658
+ className: "ppp-calendar-toolbar",
2659
+ ariaLabel: t("toolbar"),
2660
+ start: showsViewPicker && /* @__PURE__ */ jsx(
2661
+ ViewPicker,
2662
+ {
2663
+ className: "ppp-calendar-view-picker",
2664
+ options: viewOptions,
2665
+ sort: viewSort,
2666
+ value: viewId,
2667
+ onChange: pickView,
2668
+ isUserView: (id) => saved.available && (!!findSaved(id) || !!findSavedMulti(id))
2669
+ }
2670
+ ),
2671
+ children: [
2672
+ /* @__PURE__ */ jsxs("div", { className: mergeClasses("ppp-calendar-navigation", styles.navigation), children: [
2673
+ /* @__PURE__ */ jsx(
2674
+ ToolbarButton,
2675
+ {
2676
+ appearance: "subtle",
2677
+ disabled: buttons?.today?.isDisabled ?? false,
2678
+ onClick: () => controller.today(),
2679
+ children: t("today")
2680
+ }
2681
+ ),
2682
+ /* @__PURE__ */ jsx(
2683
+ ToolbarButton,
2684
+ {
2685
+ appearance: "subtle",
2686
+ icon: direction === "rtl" ? /* @__PURE__ */ jsx(ChevronRightRegular, {}) : /* @__PURE__ */ jsx(ChevronLeftRegular, {}),
2687
+ "aria-label": t("previous"),
2688
+ onClick: () => controller.prev()
2689
+ }
2690
+ ),
2691
+ /* @__PURE__ */ jsx(
2692
+ ToolbarButton,
2693
+ {
2694
+ appearance: "subtle",
2695
+ icon: direction === "rtl" ? /* @__PURE__ */ jsx(ChevronLeftRegular, {}) : /* @__PURE__ */ jsx(ChevronRightRegular, {}),
2696
+ "aria-label": t("next"),
2697
+ onClick: () => controller.next()
2698
+ }
2699
+ ),
2700
+ /* @__PURE__ */ jsx(Text, { className: styles.title, as: "h2", "aria-live": "polite", children: view?.title })
2701
+ ] }),
2702
+ children !== void 0 && /* @__PURE__ */ jsx(CalendarContext.Provider, { value: calendarContext, children }),
2703
+ layoutsOffered.length > 1 && /* @__PURE__ */ jsxs(Menu, { checkedValues: { layout: [currentLayout] }, onCheckedValueChange: onLayoutChecked, children: [
2704
+ /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(MenuButton, { className: "ppp-calendar-layout", appearance: "subtle", children: t(layoutKeys[currentLayout]) }) }),
2705
+ /* @__PURE__ */ jsx(MenuPopover, { children: /* @__PURE__ */ jsx(MenuList, { children: layoutsOffered.map((layout) => /* @__PURE__ */ jsx(MenuItemRadio, { name: "layout", value: layout, children: t(layoutKeys[layout]) }, layout)) }) })
2706
+ ] }),
2707
+ showsSearch && /* @__PURE__ */ jsx(
2708
+ ViewSearchBox,
2850
2709
  {
2851
- appearance: "subtle",
2852
- disabled: buttons?.today?.isDisabled ?? false,
2853
- onClick: () => controller.today(),
2854
- children: t("today")
2710
+ className: mergeClasses("ppp-calendar-search", styles.search),
2711
+ placeholder: t("search-placeholder"),
2712
+ value: search,
2713
+ onChange: searchFor
2855
2714
  }
2856
2715
  ),
2857
2716
  /* @__PURE__ */ jsx(
2858
- ToolbarButton,
2717
+ ViewFilterMenu,
2859
2718
  {
2860
- appearance: "subtle",
2861
- icon: direction === "rtl" ? /* @__PURE__ */ jsx(ChevronRightRegular, {}) : /* @__PURE__ */ jsx(ChevronLeftRegular, {}),
2862
- "aria-label": t("previous"),
2863
- onClick: () => controller.prev()
2719
+ changed: viewChanged,
2720
+ onEditFilters: showsFilterButton ? () => void openFilter() : void 0,
2721
+ tables: appointments ? void 0 : tableSet.menuTables(tableLabel),
2722
+ onTableShownChange: tableSet.showTable,
2723
+ savedViews: session.menu({
2724
+ canSave: savedViewChanged,
2725
+ canSaveAs: canSaveView && viewChanged,
2726
+ canSetDefault: !!viewId && !isUserDefault
2727
+ })
2864
2728
  }
2865
2729
  ),
2866
- /* @__PURE__ */ jsx(
2867
- ToolbarButton,
2730
+ !appointments && /* @__PURE__ */ jsx(
2731
+ ViewRefreshButton,
2868
2732
  {
2869
- appearance: "subtle",
2870
- icon: direction === "rtl" ? /* @__PURE__ */ jsx(ChevronLeftRegular, {}) : /* @__PURE__ */ jsx(ChevronRightRegular, {}),
2871
- "aria-label": t("next"),
2872
- onClick: () => controller.next()
2733
+ className: "ppp-calendar-refresh",
2734
+ disabled: loading,
2735
+ onClick: () => setRefreshes((count) => count + 1)
2873
2736
  }
2874
- ),
2875
- /* @__PURE__ */ jsx(Text, { className: styles.title, as: "h2", "aria-live": "polite", children: view?.title })
2876
- ] }),
2877
- children !== void 0 && /* @__PURE__ */ jsx(CalendarContext.Provider, { value: calendarContext, children }),
2878
- layoutsOffered.length > 1 && /* @__PURE__ */ jsxs(Menu, { checkedValues: { layout: [currentLayout] }, onCheckedValueChange: onLayoutChecked, children: [
2879
- /* @__PURE__ */ jsx(MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsx(MenuButton, { className: "ppp-calendar-layout", appearance: "subtle", children: t(layoutKeys[currentLayout]) }) }),
2880
- /* @__PURE__ */ jsx(MenuPopover, { children: /* @__PURE__ */ jsx(MenuList, { children: layoutsOffered.map((layout) => /* @__PURE__ */ jsx(MenuItemRadio, { name: "layout", value: layout, children: t(layoutKeys[layout]) }, layout)) }) })
2881
- ] }),
2882
- showsSearch && /* @__PURE__ */ jsx(
2883
- SearchBox,
2884
- {
2885
- className: mergeClasses("ppp-calendar-search", styles.search),
2886
- placeholder: t("search-placeholder"),
2887
- "aria-label": t("search-placeholder"),
2888
- value: searchText,
2889
- onChange: (_, data) => setSearchText(data.value)
2890
- }
2891
- ),
2892
- /* @__PURE__ */ jsx(
2893
- ViewFilterMenu,
2894
- {
2895
- changed: viewChanged,
2896
- onEditFilters: showsFilterButton ? () => void openFilter() : void 0,
2897
- tables: appointments ? void 0 : kinds.map((table) => ({ table, label: tableLabel(table), shown: !hidden.includes(table) })),
2898
- onTableShownChange: showTable,
2899
- savedViews: showsSavedViewsMenu ? {
2900
- canSave: savedViewChanged,
2901
- canSaveAs: canSaveView && viewChanged,
2902
- canSetDefault: !!viewId && !isUserDefault,
2903
- busy: savingView,
2904
- onSave: () => void signedIn(saveView),
2905
- onSaveAs: () => void signedIn(() => setNameDialog({ kind: "saveAs", viewId: viewId ?? "", name: "" })),
2906
- onSetDefault: () => void signedIn(() => viewId ? setDefault(viewId, true) : void 0),
2907
- onManage: () => void signedIn(() => setManageViewsOpen(true))
2908
- } : void 0
2909
- }
2910
- ),
2911
- !appointments && /* @__PURE__ */ jsx(Tooltip, { content: tAll("app.buttons.refresh.label"), relationship: "label", withArrow: true, children: /* @__PURE__ */ jsx(
2912
- ToolbarButton,
2913
- {
2914
- className: "ppp-calendar-refresh",
2915
- appearance: "subtle",
2916
- icon: /* @__PURE__ */ jsx(ArrowClockwiseRegular, {}),
2917
- disabled: loading,
2918
- onClick: () => setRefreshes((count) => count + 1)
2919
- }
2920
- ) })
2921
- ] })
2922
- ] }),
2923
- /* @__PURE__ */ jsx(
2924
- GridSaveViewDialog,
2925
- {
2926
- open: nameDialog !== null,
2927
- title: tAll(
2928
- 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"
2929
- ),
2930
- name: nameDialog?.name ?? "",
2931
- onSave: onNameDialogSave,
2932
- onCancel: () => setNameDialog(null)
2737
+ )
2738
+ ]
2933
2739
  }
2934
2740
  ),
2935
- saved.available && /* @__PURE__ */ jsx(
2936
- GridManageViewsPanel,
2741
+ /* @__PURE__ */ jsx(SavedViewDialogs, { session }),
2742
+ /* @__PURE__ */ jsx(
2743
+ MultiTableFilterDialog,
2937
2744
  {
2938
- open: manageViewsOpen,
2939
- onOpenChange: setManageViewsOpen,
2940
- views: managedViews,
2941
- busy: savingView,
2942
- onOpenView: (id) => {
2943
- setManageViewsOpen(false);
2944
- pickView(id);
2945
- },
2946
- onSetDefault: (id, isDefault) => void setDefault(id, isDefault),
2947
- onSetHidden: (id, isHidden) => void setHidden(id, isHidden),
2948
- onRename: (id) => setNameDialog({ kind: "rename", viewId: id, name: findSaved(id)?.name ?? findSavedMulti(id)?.name ?? "" }),
2949
- onClone: (id) => setNameDialog({ kind: "clone", viewId: id, name: tAll("app.components.grid.saved-views-copy-of", [viewLabel(id)]) }),
2950
- onDelete: (id) => void deleteView(id)
2745
+ open: tableSet.filterDialog !== null,
2746
+ tabs: tableSet.filterDialog?.tabs ?? [],
2747
+ viewTabs: tableSet.filterDialog?.viewTabs,
2748
+ onApply: applyFilter,
2749
+ onCancel: tableSet.closeFilter
2951
2750
  }
2952
2751
  ),
2953
2752
  /* @__PURE__ */ jsx(
2954
- MultiTableFilterDialog,
2753
+ ViewLoadMessages,
2955
2754
  {
2956
- open: filterDialog !== null,
2957
- tabs: filterDialog?.tabs ?? [],
2958
- viewTabs: filterDialog?.viewTabs,
2959
- onApply: applyFilter,
2960
- onCancel: () => setFilterDialog(null)
2755
+ failed,
2756
+ errorText: t("error"),
2757
+ failedTables: failures.length > 0 ? t("failed-tables", [failures.map((x) => tableLabel(x.table ?? "")).join(", ")]) : null,
2758
+ retryText: t("retry"),
2759
+ onRetry: () => setRefreshes((count) => count + 1)
2961
2760
  }
2962
2761
  ),
2963
- failed ? /* @__PURE__ */ jsx(MessageBar, { intent: "error", children: /* @__PURE__ */ jsx(MessageBarBody, { children: t("error") }) }) : failures.length > 0 && // A table that couldn't be read leaves the rest of the range as it is.
2964
- /* @__PURE__ */ jsxs(MessageBar, { intent: "warning", children: [
2965
- /* @__PURE__ */ jsx(MessageBarBody, { children: t("failed-tables", [failures.map((x) => tableLabel(x.table ?? "")).join(", ")]) }),
2966
- /* @__PURE__ */ jsx(MessageBarActions, { children: /* @__PURE__ */ jsx(Button, { size: "small", onClick: () => setRefreshes((count) => count + 1), children: t("retry") }) })
2967
- ] }),
2968
2762
  truncated && /* @__PURE__ */ jsx(MessageBar, { intent: "warning", children: /* @__PURE__ */ jsx(MessageBarBody, { children: t("truncated") }) }),
2969
2763
  /* @__PURE__ */ jsxs("div", { className: styles.body, children: [
2970
2764
  /* @__PURE__ */ jsx("div", { className: styles.surface, children: viewSettled && /* @__PURE__ */ jsx(
@@ -3026,6 +2820,8 @@ function Calendar({
3026
2820
  editable,
3027
2821
  regardingTables,
3028
2822
  location: dialogLocation,
2823
+ showRecordLinks,
2824
+ onNavigate,
3029
2825
  onClose: () => {
3030
2826
  setOpenAppointment(null);
3031
2827
  setCreating(null);