@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.cjs CHANGED
@@ -37,8 +37,6 @@ function useCalendarContext() {
37
37
  if (!context) throw new Error("useCalendarContext must be used in a <Calendar>, among its toolbar items.");
38
38
  return context;
39
39
  }
40
-
41
- // src/recurrence-pattern.ts
42
40
  var RecurrenceFrequency = {
43
41
  Daily: 0,
44
42
  Weekly: 1,
@@ -191,17 +189,8 @@ function describeRecurrence(pattern, start, locale, t) {
191
189
  }
192
190
  return repeats;
193
191
  }
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
192
  function weekOrder(locale) {
204
- const first = firstDayOfWeek(locale);
193
+ const first = core.firstDayOfWeek(locale);
205
194
  return Array.from({ length: 7 }, (_, i) => (first + i) % 7);
206
195
  }
207
196
  function dayName(weekday, locale, width = "long") {
@@ -315,6 +304,7 @@ function RecurrenceEdit({ value, onChange, start, allowNone = true, readOnly = f
315
304
  const t = react$1.usePrefixedT("app.components.calendar.recurrence");
316
305
  const id = react.useId();
317
306
  const language = locale ?? navigator.language;
307
+ const rootRef = react.useRef(null);
318
308
  const startDay = react.useRef(start.weekday);
319
309
  react.useEffect(() => {
320
310
  const was = startDay.current;
@@ -333,7 +323,8 @@ function RecurrenceEdit({ value, onChange, start, allowNone = true, readOnly = f
333
323
  const pattern = startingPattern(frequency, start);
334
324
  onChange(value ? { ...pattern, end: value.end, occurrences: value.occurrences, endDate: value.endDate } : pattern);
335
325
  };
336
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.root, children: [
326
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: styles.root, children: [
327
+ /* @__PURE__ */ jsxRuntime.jsx(reactFluent.SharedTooltip, { containerRef: rootRef }),
337
328
  /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Field, { label: t("label"), children: /* @__PURE__ */ jsxRuntime.jsx(
338
329
  reactComponents.Dropdown,
339
330
  {
@@ -406,7 +397,7 @@ function PatternFields({
406
397
  size: "small",
407
398
  checked: pressed,
408
399
  disabled: readOnly,
409
- title: dayName(weekday, locale),
400
+ "data-ppp-tooltip": dayName(weekday, locale),
410
401
  "aria-label": dayName(weekday, locale),
411
402
  onClick: () => {
412
403
  const days = value.days ^ flag;
@@ -506,12 +497,28 @@ function AppointmentDetailsDialog({
506
497
  onClose,
507
498
  onSaved,
508
499
  children,
509
- location = "center"
500
+ location = "center",
501
+ showRecordLinks = true,
502
+ onNavigate
510
503
  }) {
511
504
  const t = react$1.usePrefixedT("app.components.calendar");
512
505
  const tAll = react$1.useT();
513
506
  const open = appointment !== null || newAppointment2 !== null;
514
507
  const isSeries = appointment?.isSeries === true;
508
+ const recordPages = react$1.useRecordPages({ enabled: showRecordLinks });
509
+ const appointmentPage = showRecordLinks && appointment && !isSeries ? core.findRecordPage(recordPages, "appointment") : void 0;
510
+ const pageUrl = appointmentPage && appointment ? core.recordPageUrl(appointmentPage, appointment.id) : void 0;
511
+ const openPage = pageUrl !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
512
+ reactComponents.Button,
513
+ {
514
+ as: "a",
515
+ href: pageUrl,
516
+ className: "ppp-calendar-open-record",
517
+ onClick: (event) => reactFluent.followRecordLink(event, pageUrl, onNavigate),
518
+ children: tAll("app.buttons.open.label")
519
+ }
520
+ );
521
+ const regardingLinks = { showRecordLinks, ...onNavigate && { onNavigate } };
515
522
  const mayCreateSeries = useMayCreateSeries(open && editable && !isSeries && appointment?.recurring !== true);
516
523
  const saved = () => {
517
524
  onClose();
@@ -538,16 +545,21 @@ function AppointmentDetailsDialog({
538
545
  title: appointment ? appointment.subject : t("new-appointment"),
539
546
  actions: editable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
540
547
  /* @__PURE__ */ jsxRuntime.jsx(DeleteAction, { seriesId: appointment?.seriesId ?? null, onDeleted: saved }),
548
+ openPage,
541
549
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.SaveContextButton, { appearance: "primary", onSaved: saved }),
542
550
  /* @__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(
551
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
552
+ openPage,
553
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { appearance: "primary", onClick: onClose, children: t("close") })
554
+ ] }),
555
+ children: isSeries ? /* @__PURE__ */ jsxRuntime.jsx(SeriesRecord, { editable, regardingTables, regardingLinks }) : /* @__PURE__ */ jsxRuntime.jsx(
545
556
  AppointmentRecord,
546
557
  {
547
558
  recurring: appointment?.recurring ?? false,
548
559
  editable,
549
560
  creating: !appointment,
550
561
  regardingTables,
562
+ regardingLinks,
551
563
  mayCreateSeries,
552
564
  children
553
565
  }
@@ -640,6 +652,7 @@ function AppointmentRecord({
640
652
  editable,
641
653
  creating,
642
654
  regardingTables,
655
+ regardingLinks,
643
656
  mayCreateSeries,
644
657
  children
645
658
  }) {
@@ -666,7 +679,7 @@ function AppointmentRecord({
666
679
  !creating && /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "organizer", readOnly: true }),
667
680
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "requiredattendees", readOnly }),
668
681
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "optionalattendees", readOnly }),
669
- /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables } }),
682
+ /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables }, ...regardingLinks }),
670
683
  !creating && /* @__PURE__ */ jsxRuntime.jsx(reactFluent.StatusReasonEdit, { columnName: "statuscode", allowStateChange: true, readOnly }),
671
684
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "description", readOnly })
672
685
  ] });
@@ -755,7 +768,11 @@ function seriesRequests(pattern, appointment, timeZone) {
755
768
  };
756
769
  return [request];
757
770
  }
758
- function SeriesRecord({ editable, regardingTables }) {
771
+ function SeriesRecord({
772
+ editable,
773
+ regardingTables,
774
+ regardingLinks
775
+ }) {
759
776
  const styles = useStyles2();
760
777
  const t = react$1.usePrefixedT("app.components.calendar");
761
778
  const ctx = react$1.useRecordContext();
@@ -813,7 +830,7 @@ function SeriesRecord({ editable, regardingTables }) {
813
830
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "organizer", readOnly: true }),
814
831
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "requiredattendees", readOnly }),
815
832
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "optionalattendees", readOnly }),
816
- /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables } }),
833
+ /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "regardingobjectid", readOnly, ...regardingTables && { targetTables: regardingTables }, ...regardingLinks }),
817
834
  /* @__PURE__ */ jsxRuntime.jsx(reactFluent.ColumnEdit, { columnName: "description", readOnly })
818
835
  ] });
819
836
  }
@@ -1228,8 +1245,47 @@ function newAppointment(range, regarding) {
1228
1245
  }
1229
1246
  };
1230
1247
  }
1248
+ function offeredViewIds(defaultViewId2, viewIds, multiTableViewIds, customViews) {
1249
+ const ids = [
1250
+ ...defaultViewId2 ? [defaultViewId2] : [],
1251
+ ...viewIds ?? [],
1252
+ ...multiTableViewIds ?? [],
1253
+ ...(customViews ?? []).map((view) => view.id)
1254
+ ].map((id) => id.toLowerCase());
1255
+ return ids.length === 0 ? [reactFluent.EVERY_TABLE_VIEW_ID] : [...new Set(ids)];
1256
+ }
1257
+ function findCustomView(id, customViews) {
1258
+ if (!id) return void 0;
1259
+ return customViews?.find((view) => view.id.toLowerCase() === id.toLowerCase());
1260
+ }
1261
+ function selectedViewId(offered, picked) {
1262
+ return picked && offered.includes(picked.toLowerCase()) ? picked.toLowerCase() : offered[0];
1263
+ }
1264
+ function offersViews(offered) {
1265
+ return offered.some((id) => id !== reactFluent.EVERY_TABLE_VIEW_ID);
1266
+ }
1267
+ function withSavedViews(pageViews, savedViews, settings, control, shown, offersSavedViews) {
1268
+ if (!offersSavedViews) return [...pageViews];
1269
+ let all = [...new Set([...pageViews, ...savedViews].map((id) => id.toLowerCase()))];
1270
+ const userDefault = core.defaultViewId(settings, all, control);
1271
+ if (userDefault) all = [userDefault, ...all.filter((id) => id !== userDefault)];
1272
+ const offered = all.filter((id) => id === shown?.toLowerCase() || !core.isViewHidden(settings, id, control));
1273
+ return offered.length > 0 ? offered : all;
1274
+ }
1275
+ var LAYOUTS = ["day", "workWeek", "week", "month", "agenda"];
1276
+ function readSavedLayout(json) {
1277
+ if (!json?.trim()) return void 0;
1278
+ try {
1279
+ const layout = JSON.parse(json).layout;
1280
+ return LAYOUTS.find((x) => x === layout);
1281
+ } catch {
1282
+ return void 0;
1283
+ }
1284
+ }
1285
+ function writeSavedLayout(layout) {
1286
+ return JSON.stringify({ version: 1, layout });
1287
+ }
1231
1288
  var APPOINTMENT_TABLE = "appointment";
1232
- var EVERY_TABLE_VIEW_ID = "00000000-0000-0000-0000-000000000000";
1233
1289
  var SERIES_TABLE2 = "recurringappointmentmaster";
1234
1290
  var DEFAULT_CALENDAR_TABLES = [APPOINTMENT_TABLE, "task"];
1235
1291
  var CALENDAR_COLUMNS = {
@@ -1295,10 +1351,10 @@ function drawnTimes(item, dateOnly, timeZone) {
1295
1351
  return { start: zonedMidnight(first, timeZone), end: zonedMidnight(after, timeZone) };
1296
1352
  }
1297
1353
  function fromMultiTableItem(item, info, drawn) {
1298
- const definition = info?.definition;
1354
+ const dates = info?.definition?.calendar;
1299
1355
  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());
1356
+ const movable = (Number(item.permissions ?? 0) & core.TableSecurityPermission.Write) !== 0 && !isClosed(item) && !!dates?.start && changeable.has(dates.start.toLowerCase());
1357
+ const resizable = movable && !!dates?.end && changeable.has(dates.end.toLowerCase());
1302
1358
  const isAppointment = item.table === APPOINTMENT_TABLE;
1303
1359
  const state = stateOf(item);
1304
1360
  const location = valueOf2(item.record, "location");
@@ -1316,7 +1372,7 @@ function fromMultiTableItem(item, info, drawn) {
1316
1372
  durationEditable: resizable ? null : false,
1317
1373
  showAs: isAppointment ? showAsOf(typeof status === "number" ? status : null, state) : AppointmentShowAs.Busy,
1318
1374
  canceled: state === 2,
1319
- color: definition?.color ?? null,
1375
+ color: info?.definition?.color ?? null,
1320
1376
  record: item.record ?? null
1321
1377
  };
1322
1378
  }
@@ -1324,86 +1380,23 @@ function stored(moment, dateOnly, timeZone) {
1324
1380
  return dateOnly ? `${localDate(moment, timeZone)}T00:00:00.000Z` : moment.toISOString();
1325
1381
  }
1326
1382
  function moveRecord(change, item, info, dateOnly, timeZone) {
1327
- const definition = info?.definition;
1328
- if (!definition?.start) return null;
1383
+ const dates = info?.definition?.calendar;
1384
+ if (!dates?.start) return null;
1329
1385
  const changeable = new Set((info?.changeableColumns ?? []).map((x) => x.toLowerCase()));
1330
- const allDayColumn = definition.allDay && changeable.has(definition.allDay.toLowerCase()) ? definition.allDay : null;
1386
+ const allDayColumn = dates.allDay && changeable.has(dates.allDay.toLowerCase()) ? dates.allDay : null;
1331
1387
  if (change.allDay !== (item.isAllDay ?? false) && !allDayColumn) return null;
1332
1388
  if (dateOnly && !change.allDay) return null;
1333
1389
  const end = change.end ?? new Date(change.start.getTime() + (change.allDay ? DAY_MS2 : 30 * MINUTE_MS2));
1334
1390
  const drawn = drawnTimes(item, dateOnly, timeZone);
1335
1391
  const lengthChanged = end.getTime() - change.start.getTime() !== drawn.end.getTime() - drawn.start.getTime();
1336
1392
  const properties = {
1337
- [definition.start]: { $type: 2, value: stored(change.start, dateOnly, timeZone) }
1393
+ [dates.start]: { $type: 2, value: stored(change.start, dateOnly, timeZone) }
1338
1394
  };
1339
- if (definition.end && (!item.hasNoEnd || lengthChanged))
1340
- properties[definition.end] = { $type: 2, value: stored(end, dateOnly, timeZone) };
1395
+ if (dates.end && (!item.hasNoEnd || lengthChanged))
1396
+ properties[dates.end] = { $type: 2, value: stored(end, dateOnly, timeZone) };
1341
1397
  if (allDayColumn) properties[allDayColumn] = { $type: 0, value: change.allDay };
1342
1398
  return { tableName: item.table, id: item.id, properties };
1343
1399
  }
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
1400
 
1408
1401
  // ../../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
1402
  var skeleton_default = `
@@ -1930,8 +1923,6 @@ function ensureFullCalendarStyles(document2) {
1930
1923
  ensureStyleSheet(document2, STYLE_ID2, `${skeleton_default}
1931
1924
  ${theme_default}`);
1932
1925
  }
1933
-
1934
- // src/persisted-calendar-state.ts
1935
1926
  var DATE2 = /^(\d{4})-(\d{2})-(\d{2})$/;
1936
1927
  var GUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1937
1928
  function isDate(value) {
@@ -1969,21 +1960,10 @@ function encodePersistedCalendarState(state) {
1969
1960
  return Object.keys(compact).length > 0 ? JSON.stringify(compact) : null;
1970
1961
  }
1971
1962
  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
- );
1963
+ if (!queryParameterName) return;
1964
+ react$1.replaceQueryParam(queryParameterName, encodePersistedCalendarState(state) ?? void 0);
1984
1965
  }
1985
1966
  var plugins = [formaTheme__default.default, dayGridPlugin__default.default, timeGridPlugin__default.default, listPlugin__default.default, interactionPlugin__default.default];
1986
- var SEARCH_DEBOUNCE_MS = 300;
1987
1967
  var layoutKeys = {
1988
1968
  day: "layouts.day",
1989
1969
  workWeek: "layouts.work-week",
@@ -1998,32 +1978,6 @@ var useStyles3 = reactComponents.makeStyles({
1998
1978
  gap: reactComponents.tokens.spacingVerticalS,
1999
1979
  minHeight: 0
2000
1980
  },
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
1981
  // The period and the moves through it, kept together.
2028
1982
  navigation: {
2029
1983
  display: "flex",
@@ -2045,10 +1999,6 @@ var useStyles3 = reactComponents.makeStyles({
2045
1999
  overflow: "hidden",
2046
2000
  textOverflow: "ellipsis"
2047
2001
  },
2048
- // As wide as a grid's view picker.
2049
- viewPicker: {
2050
- minWidth: "220px"
2051
- },
2052
2002
  search: {
2053
2003
  width: "220px",
2054
2004
  maxWidth: "100%"
@@ -2118,6 +2068,8 @@ function Calendar({
2118
2068
  defaultLayout = "week",
2119
2069
  layouts,
2120
2070
  editable = false,
2071
+ showRecordLinks = true,
2072
+ onNavigate,
2121
2073
  regardingTables,
2122
2074
  height = "600px",
2123
2075
  onAppointmentClick,
@@ -2186,6 +2138,7 @@ function Calendar({
2186
2138
  );
2187
2139
  const viewId = selectedViewId(offered, shownViewId);
2188
2140
  if (viewSettled) openedViewRef.current ??= viewId;
2141
+ const tableSet = reactFluent.useMultiTableTableSet({ use: core.MultiTableUses.Calendar, viewId, tables, defaultTables });
2189
2142
  const openingLayoutRef = react.useRef(void 0);
2190
2143
  if (openingLayoutRef.current === void 0 && viewSettled) {
2191
2144
  const savedLayout = persisted.l ? void 0 : readSavedLayout(savedCalendarLayout(viewId));
@@ -2194,13 +2147,12 @@ function Calendar({
2194
2147
  const initialLayout = openingLayoutRef.current ?? openingLayout(layoutsOffered, persisted.l, defaultLayout);
2195
2148
  const viewOpeningLayout = (id) => openingLayout(layoutsOffered, readSavedLayout(savedCalendarLayout(id)), defaultLayout);
2196
2149
  const showsSearch = !appointments && allowSearch;
2197
- const [searchText, setSearchText] = react.useState(() => includeSearchInPersistedState ? persisted.q ?? "" : "");
2198
- const [search, setSearch] = react.useState(searchText);
2150
+ const [search, setSearch] = react.useState(() => includeSearchInPersistedState ? persisted.q ?? "" : "");
2199
2151
  const picksViews = !appointments && offered.length > 1;
2200
2152
  const namesViews = picksViews || !appointments && saved.available;
2201
2153
  const namedViewIds = saved.available ? [.../* @__PURE__ */ new Set([...offered, ...pageViews])] : offered;
2202
2154
  const dataverseViewIds = namedViewIds.filter(
2203
- (id) => id !== EVERY_TABLE_VIEW_ID && !isMultiTableView(id) && !findCustomView(id, customViewDefinitions) && !findSaved(id) && !findSavedMulti(id)
2155
+ (id) => id !== reactFluent.EVERY_TABLE_VIEW_ID && !isMultiTableView(id) && !findCustomView(id, customViewDefinitions) && !findSaved(id) && !findSavedMulti(id)
2204
2156
  );
2205
2157
  react$1.useLocalization(namesViews ? dataverseViewIds.map((id) => `views.${id}`) : []);
2206
2158
  const [viewNames, setViewNames] = react.useState({ dataverse: /* @__PURE__ */ new Map(), multiTable: /* @__PURE__ */ new Map() });
@@ -2216,10 +2168,10 @@ function Calendar({
2216
2168
  const multiTable = new Map(viewNames.multiTable);
2217
2169
  const missing = multiTableIds.filter((x) => !multiTable.has(x));
2218
2170
  if (missing.length > 0) {
2219
- const registeredViews = (await registeredAsync().catch(() => null))?.views ?? [];
2171
+ const registeredViews = (await tableSet.registeredAsync().catch(() => null))?.views ?? [];
2220
2172
  for (const id of missing) {
2221
2173
  const known2 = registeredViews.find((view2) => view2.id?.toLowerCase() === id);
2222
- multiTable.set(id, known2 ? known2.name ?? null : await storedViewName(id));
2174
+ multiTable.set(id, known2 ? known2.name ?? null : (await tableSet.storedViewName(id)).name);
2223
2175
  }
2224
2176
  }
2225
2177
  if (current) setViewNames({ dataverse, multiTable });
@@ -2229,7 +2181,7 @@ function Calendar({
2229
2181
  };
2230
2182
  }, [namesViews, namesKey, ppp]);
2231
2183
  const labelOf = (id) => {
2232
- if (id === EVERY_TABLE_VIEW_ID) return t("every-table");
2184
+ if (id === reactFluent.EVERY_TABLE_VIEW_ID) return t("every-table");
2233
2185
  const custom = findCustomView(id, customViewDefinitions);
2234
2186
  if (custom) return custom.displayName?.trim() ? custom.displayName : id;
2235
2187
  const ownMulti = findSavedMulti(id);
@@ -2246,12 +2198,7 @@ function Calendar({
2246
2198
  }
2247
2199
  if (!viewNames.dataverse.has(id)) return null;
2248
2200
  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;
2201
+ return metadata ? reactFluent.viewLabelOf({ ...metadata, id }, tAll) : id;
2255
2202
  };
2256
2203
  const showsSavedViewsMenu = allowSavedViews && !appointments && (saved.available || saved.needsSignIn);
2257
2204
  const viewOptions = react.useMemo(() => {
@@ -2261,11 +2208,8 @@ function Calendar({
2261
2208
  return label === null ? null : { id, label };
2262
2209
  });
2263
2210
  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]);
2211
+ return labeled;
2212
+ }, [offered, customViewsKey, saved.views, multiSaved.views, viewNames, tAll, showsSavedViewsMenu]);
2269
2213
  const showsViewPicker = !appointments && viewOptions.length > (showsSavedViewsMenu ? 0 : 1);
2270
2214
  react.useEffect(() => {
2271
2215
  ensureFullCalendarStyles(document);
@@ -2300,7 +2244,7 @@ function Calendar({
2300
2244
  react.useEffect(() => () => void (latestStart.current = -1), []);
2301
2245
  const user = authState.status === "authenticated" ? authState.user?.userId : null;
2302
2246
  const tablesKey = (tables ?? []).map((table) => table.trim().toLowerCase()).join(",");
2303
- const allowedTables = tablesKey ? new Set(tablesKey.split(",")) : null;
2247
+ const allowedTables = tableSet.allowed;
2304
2248
  const mayCreateAppointments = () => {
2305
2249
  let asked = mayCreate.current;
2306
2250
  if (!asked || asked.user !== user) {
@@ -2360,36 +2304,12 @@ function Calendar({
2360
2304
  const [refreshes, setRefreshes] = react.useState(0);
2361
2305
  const latestLoad = react.useRef(null);
2362
2306
  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());
2307
+ const registeredAsync = tableSet.registeredAsync;
2308
+ const infoOf = tableSet.infoOf;
2309
+ const tableLabel = tableSet.tableLabel;
2390
2310
  const isStoredView = (id, response) => isMultiTableView(id) && !(response.views ?? []).some((view2) => view2.id?.toLowerCase() === id);
2391
2311
  const viewTablesAsync = async (id) => {
2392
- if (id === EVERY_TABLE_VIEW_ID) return null;
2312
+ if (id === reactFluent.EVERY_TABLE_VIEW_ID) return null;
2393
2313
  if (isMultiTableView(id))
2394
2314
  return (await registeredAsync()).views?.find((view3) => view3.id?.toLowerCase() === id)?.tables ?? null;
2395
2315
  const custom = findCustomView(id, customViewDefinitions);
@@ -2403,35 +2323,15 @@ function Calendar({
2403
2323
  console.warn(`[Calendar] The view ${id} doesn't exist or is on a table the user may not read, so it shows nothing.`);
2404
2324
  return [];
2405
2325
  };
2326
+ const knownTablesOf = (id, viewTables, response) => tableSet.tablesOf(viewTables, isStoredView(id, response) ? id : null);
2406
2327
  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
- };
2328
+ const known = tableSet.known;
2329
+ const shownTables = tableSet.shown;
2430
2330
  const rows = react.useRef(/* @__PURE__ */ new Map());
2431
2331
  const dateOnly = react.useRef(/* @__PURE__ */ new Map());
2432
2332
  const learnDateOnly = async (tables2, response) => {
2433
2333
  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;
2334
+ const start = response.tables?.find((x) => x.table === table)?.definition?.calendar?.start;
2435
2335
  try {
2436
2336
  const metadata = await tableMetadataCache.getAsync(table);
2437
2337
  const column = metadata?.columns?.find((x) => x.columnName === start);
@@ -2442,22 +2342,12 @@ function Calendar({
2442
2342
  }
2443
2343
  };
2444
2344
  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);
2345
+ const showsFilterButton = !appointments && allowFiltering && !(tableSet.registered && isStoredView(viewId, tableSet.registered));
2346
+ const filtered = tableSet.filter;
2453
2347
  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));
2348
+ await registeredAsync();
2349
+ const tablesShown = await viewTablesAsync(id) ?? (id === reactFluent.EVERY_TABLE_VIEW_ID ? (tableSet.tablesOf(null) ?? []).map((table) => ({ table })) : []);
2350
+ return tableSet.filterQueries(tablesShown);
2461
2351
  };
2462
2352
  const openFilter = async () => {
2463
2353
  const id = viewId;
@@ -2465,36 +2355,14 @@ function Calendar({
2465
2355
  try {
2466
2356
  const response = await registeredAsync();
2467
2357
  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 });
2358
+ const knownNow = knownTablesOf(id, await viewTablesAsync(id) ?? null, response) ?? [];
2359
+ tableSet.openFilter(id, tablesShown, knownNow, tableLabel);
2486
2360
  } catch (error) {
2487
2361
  console.warn("[Calendar] The filters of the view shown could not be read.", error);
2488
2362
  }
2489
2363
  };
2490
2364
  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);
2365
+ if (!tableSet.applyFilter(criteria, restoreView) || !restoreView) return;
2498
2366
  const layout = viewOpeningLayout(viewId);
2499
2367
  if (layout !== currentLayout && layoutsOffered.includes(layout)) controller.changeView(toFullCalendarView(layout));
2500
2368
  };
@@ -2507,7 +2375,7 @@ function Calendar({
2507
2375
  customViewsKey,
2508
2376
  filtered?.tables ?? null,
2509
2377
  searched,
2510
- hiddenChoice,
2378
+ tableSet.hiddenChoice,
2511
2379
  tablesKey,
2512
2380
  defaultsKey,
2513
2381
  matchParties ?? null,
@@ -2537,14 +2405,10 @@ function Calendar({
2537
2405
  openingViewRef.current = offered[0];
2538
2406
  if (viewSettled && previous !== void 0 && previous !== offered[0]) persist();
2539
2407
  }, [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]);
2408
+ const searchFor = (text) => {
2409
+ setSearch(text);
2410
+ persist({ search: text });
2411
+ };
2548
2412
  const events = react.useMemo(() => {
2549
2413
  if (appointments) return appointments.map(toEventInput);
2550
2414
  return async (info) => {
@@ -2557,8 +2421,8 @@ function Calendar({
2557
2421
  const response = await registeredAsync();
2558
2422
  const viewTables = await viewTablesAsync(id);
2559
2423
  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 });
2424
+ let knownNow = knownTablesOf(id, viewTables, response);
2425
+ if (latestLoad.current === load && knownNow) tableSet.setKnown(id, knownNow);
2562
2426
  if (knownNow?.length === 0) {
2563
2427
  if (latestLoad.current === load) {
2564
2428
  setFailed(false);
@@ -2574,7 +2438,7 @@ function Calendar({
2574
2438
  viewId: filtered || !isMultiTableView(id) ? null : id,
2575
2439
  tables: filtered ? [...filtered.tables] : viewTables && viewTables.length > 0 ? [...viewTables] : null,
2576
2440
  // A view whose tables aren't known yet is read as the calendar allows.
2577
- includeTables: knownNow ? shownIn(id, knownNow) : allowedTables ? [...allowedTables] : null,
2441
+ includeTables: knownNow ? tableSet.shownIn(id, knownNow) : allowedTables ? [...allowedTables] : null,
2578
2442
  regarding: regarding ? { tableName: regarding.tableName, id: regarding.id } : null,
2579
2443
  matchParties: matchParties ?? null,
2580
2444
  series: series ?? null,
@@ -2583,9 +2447,8 @@ function Calendar({
2583
2447
  };
2584
2448
  const range = await ppp.loadMultiTableRangeAsync(request, load.signal);
2585
2449
  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 });
2450
+ knownNow = tableSet.learnTables(id, range.tables ?? []);
2451
+ if (latestLoad.current === load) tableSet.setKnown(id, knownNow);
2589
2452
  }
2590
2453
  const items = range.items ?? [];
2591
2454
  await learnDateOnly(items.map((item) => item.table ?? ""), response);
@@ -2702,13 +2565,10 @@ function Calendar({
2702
2565
  const shownSaved = findSaved(viewId);
2703
2566
  const shownSavedMulti = findSavedMulti(viewId);
2704
2567
  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);
2568
+ const canSaveView = shownTables.length > 0 && !(tableSet.registered && isStoredView(viewId, tableSet.registered));
2569
+ const viewChanged = !!filtered || tableSet.tablesChanged || currentLayout !== viewOpeningLayout(viewId);
2707
2570
  const savedViewChanged = (!!shownSaved || !!shownSavedMulti) && canSaveView && viewChanged;
2708
2571
  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
2572
  const currentQuery = async () => {
2713
2573
  if (!showsAppointmentsAlone || !viewId) return void 0;
2714
2574
  const own = filtered?.tables.find((x) => x.table === APPOINTMENT_TABLE);
@@ -2721,261 +2581,195 @@ function Calendar({
2721
2581
  const tablesNow = filtered ? [...filtered.tables] : (await filterTablesAsync(viewId)).map((x) => x.table);
2722
2582
  return core.shownViewTables(tablesNow, known, shownTables, (table) => !!response.tables?.find((info) => info.table === table)?.isActivity);
2723
2583
  };
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
2584
  const showSavedView = (id) => {
2751
- setUserFilter(null);
2752
- setHiddenChoice(null);
2585
+ tableSet.reset();
2753
2586
  setPickedViewId(id);
2754
2587
  persist({ viewId: id });
2755
2588
  };
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
2589
  function viewLabel(id) {
2821
2590
  return labelOf(id.toLowerCase()) ?? findSaved(id)?.name ?? findSavedMulti(id)?.name ?? id;
2822
2591
  }
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))
2592
+ const session = reactFluent.useSavedViewSession({
2593
+ saved,
2594
+ signInUrl,
2595
+ operations: {
2596
+ shownViewId: viewId,
2597
+ savedViewName: (id) => findSaved(id)?.name ?? findSavedMulti(id)?.name,
2598
+ managedViewName: viewLabel,
2599
+ // Every view for the Manage views panel, the hidden ones among them.
2600
+ managedViews: () => {
2601
+ const all = [.../* @__PURE__ */ new Set([...pageViews, ...savedIds.map((x) => x.toLowerCase())])];
2602
+ const userDefault = core.defaultViewId(saved.settings, all, savedViewsControl);
2603
+ return all.map((id) => ({
2604
+ id,
2605
+ name: viewLabel(id),
2606
+ isSaved: !!findSaved(id) || !!findSavedMulti(id),
2607
+ isDefault: id === userDefault,
2608
+ isHidden: core.isViewHidden(saved.settings, id, savedViewsControl),
2609
+ isShown: id === viewId
2610
+ }));
2611
+ },
2612
+ save: async () => {
2613
+ if (shownSavedMulti) {
2614
+ const tables2 = await currentTables();
2615
+ if (tables2.length === 0) return;
2616
+ await multiSaved.update(shownSavedMulti.id, { tables: tables2, calendarLayout: writeSavedLayout(currentLayout) });
2617
+ } else {
2618
+ if (!shownSaved) return;
2619
+ const fetchXml = await currentQuery();
2620
+ if (!fetchXml) return;
2621
+ await saved.update(shownSaved.id, { fetchXml, calendarLayout: writeSavedLayout(currentLayout) });
2855
2622
  }
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,
2623
+ tableSet.reset();
2624
+ },
2625
+ saveAs: async (name) => {
2626
+ const fetchXml = showsAppointmentsAlone ? await currentQuery() : void 0;
2627
+ if (fetchXml) {
2628
+ const view3 = await saved.create(name, fetchXml, { calendarLayout: writeSavedLayout(currentLayout) });
2629
+ showSavedView(view3.id.toLowerCase());
2630
+ return;
2631
+ }
2632
+ const tables2 = await currentTables();
2633
+ if (tables2.length === 0) return;
2634
+ const view2 = await multiSaved.create(name, tables2, { calendarLayout: writeSavedLayout(currentLayout) });
2635
+ showSavedView(view2.id.toLowerCase());
2636
+ },
2637
+ clone: async (id, name) => {
2638
+ const own = findSaved(id);
2639
+ const custom = findCustomView(id, customViewDefinitions)?.tables;
2640
+ const only = custom?.length === 1 && custom[0].table === APPOINTMENT_TABLE && (custom[0].viewType ?? core.MultiTableViewType.FetchXml) === core.MultiTableViewType.FetchXml ? custom[0].fetchXml ?? reactFluent.everyRow(APPOINTMENT_TABLE) : void 0;
2641
+ const ownMulti = findSavedMulti(id);
2642
+ const dataverse = own || only || ownMulti || custom ? void 0 : await viewMetadataCache.getAsync(id);
2643
+ const fetchXml = own?.definition.fetchXml ?? only ?? (dataverse?.tableName.toLowerCase() === APPOINTMENT_TABLE ? dataverse.fetchXml : void 0);
2644
+ if (fetchXml) {
2645
+ await saved.create(name, fetchXml, { gridLayout: own?.gridLayout, calendarLayout: own?.calendarLayout });
2646
+ return;
2647
+ }
2648
+ const tables2 = ownMulti?.tables ?? await viewTablesAsync(id) ?? (id === reactFluent.EVERY_TABLE_VIEW_ID ? known.map((table) => ({ table })) : null);
2649
+ if (!tables2 || tables2.length === 0) return;
2650
+ await multiSaved.create(name, tables2, { calendarLayout: ownMulti?.calendarLayout });
2651
+ },
2652
+ rename: async (id, name) => void (findSavedMulti(id) ? await multiSaved.update(id, { name }) : await saved.update(id, { name })),
2653
+ remove: async (id) => {
2654
+ if (findSavedMulti(id)) {
2655
+ await multiSaved.remove(id);
2656
+ await saved.reloadSettings();
2657
+ } else {
2658
+ await saved.remove(id);
2659
+ }
2660
+ if (id.toLowerCase() === viewId) tableSet.clearFilter();
2661
+ },
2662
+ openView: (id) => pickView(id)
2663
+ }
2664
+ });
2665
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.root, style: { height }, children: [
2666
+ /* @__PURE__ */ jsxRuntime.jsxs(
2667
+ reactFluent.ViewToolbar,
2668
+ {
2669
+ className: "ppp-calendar-toolbar",
2670
+ ariaLabel: t("toolbar"),
2671
+ start: showsViewPicker && /* @__PURE__ */ jsxRuntime.jsx(
2672
+ reactFluent.ViewPicker,
2673
+ {
2674
+ className: "ppp-calendar-view-picker",
2675
+ options: viewOptions,
2676
+ sort: viewSort,
2677
+ value: viewId,
2678
+ onChange: pickView,
2679
+ isUserView: (id) => saved.available && (!!findSaved(id) || !!findSavedMulti(id))
2680
+ }
2681
+ ),
2682
+ children: [
2683
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: reactComponents.mergeClasses("ppp-calendar-navigation", styles.navigation), children: [
2684
+ /* @__PURE__ */ jsxRuntime.jsx(
2685
+ reactComponents.ToolbarButton,
2686
+ {
2687
+ appearance: "subtle",
2688
+ disabled: buttons?.today?.isDisabled ?? false,
2689
+ onClick: () => controller.today(),
2690
+ children: t("today")
2691
+ }
2692
+ ),
2693
+ /* @__PURE__ */ jsxRuntime.jsx(
2694
+ reactComponents.ToolbarButton,
2695
+ {
2696
+ appearance: "subtle",
2697
+ icon: direction === "rtl" ? /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronRightRegular, {}) : /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronLeftRegular, {}),
2698
+ "aria-label": t("previous"),
2699
+ onClick: () => controller.prev()
2700
+ }
2701
+ ),
2702
+ /* @__PURE__ */ jsxRuntime.jsx(
2703
+ reactComponents.ToolbarButton,
2704
+ {
2705
+ appearance: "subtle",
2706
+ icon: direction === "rtl" ? /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronLeftRegular, {}) : /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ChevronRightRegular, {}),
2707
+ "aria-label": t("next"),
2708
+ onClick: () => controller.next()
2709
+ }
2710
+ ),
2711
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { className: styles.title, as: "h2", "aria-live": "polite", children: view?.title })
2712
+ ] }),
2713
+ children !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(CalendarContext.Provider, { value: calendarContext, children }),
2714
+ layoutsOffered.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.Menu, { checkedValues: { layout: [currentLayout] }, onCheckedValueChange: onLayoutChecked, children: [
2715
+ /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuTrigger, { disableButtonEnhancement: true, children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MenuButton, { className: "ppp-calendar-layout", appearance: "subtle", children: t(layoutKeys[currentLayout]) }) }),
2716
+ /* @__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)) }) })
2717
+ ] }),
2718
+ showsSearch && /* @__PURE__ */ jsxRuntime.jsx(
2719
+ reactFluent.ViewSearchBox,
2861
2720
  {
2862
- appearance: "subtle",
2863
- disabled: buttons?.today?.isDisabled ?? false,
2864
- onClick: () => controller.today(),
2865
- children: t("today")
2721
+ className: reactComponents.mergeClasses("ppp-calendar-search", styles.search),
2722
+ placeholder: t("search-placeholder"),
2723
+ value: search,
2724
+ onChange: searchFor
2866
2725
  }
2867
2726
  ),
2868
2727
  /* @__PURE__ */ jsxRuntime.jsx(
2869
- reactComponents.ToolbarButton,
2728
+ reactFluent.ViewFilterMenu,
2870
2729
  {
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()
2730
+ changed: viewChanged,
2731
+ onEditFilters: showsFilterButton ? () => void openFilter() : void 0,
2732
+ tables: appointments ? void 0 : tableSet.menuTables(tableLabel),
2733
+ onTableShownChange: tableSet.showTable,
2734
+ savedViews: session.menu({
2735
+ canSave: savedViewChanged,
2736
+ canSaveAs: canSaveView && viewChanged,
2737
+ canSetDefault: !!viewId && !isUserDefault
2738
+ })
2875
2739
  }
2876
2740
  ),
2877
- /* @__PURE__ */ jsxRuntime.jsx(
2878
- reactComponents.ToolbarButton,
2741
+ !appointments && /* @__PURE__ */ jsxRuntime.jsx(
2742
+ reactFluent.ViewRefreshButton,
2879
2743
  {
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()
2744
+ className: "ppp-calendar-refresh",
2745
+ disabled: loading,
2746
+ onClick: () => setRefreshes((count) => count + 1)
2884
2747
  }
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)
2748
+ )
2749
+ ]
2944
2750
  }
2945
2751
  ),
2946
- saved.available && /* @__PURE__ */ jsxRuntime.jsx(
2947
- reactFluent.GridManageViewsPanel,
2752
+ /* @__PURE__ */ jsxRuntime.jsx(reactFluent.SavedViewDialogs, { session }),
2753
+ /* @__PURE__ */ jsxRuntime.jsx(
2754
+ reactFluent.MultiTableFilterDialog,
2948
2755
  {
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)
2756
+ open: tableSet.filterDialog !== null,
2757
+ tabs: tableSet.filterDialog?.tabs ?? [],
2758
+ viewTabs: tableSet.filterDialog?.viewTabs,
2759
+ onApply: applyFilter,
2760
+ onCancel: tableSet.closeFilter
2962
2761
  }
2963
2762
  ),
2964
2763
  /* @__PURE__ */ jsxRuntime.jsx(
2965
- reactFluent.MultiTableFilterDialog,
2764
+ reactFluent.ViewLoadMessages,
2966
2765
  {
2967
- open: filterDialog !== null,
2968
- tabs: filterDialog?.tabs ?? [],
2969
- viewTabs: filterDialog?.viewTabs,
2970
- onApply: applyFilter,
2971
- onCancel: () => setFilterDialog(null)
2766
+ failed,
2767
+ errorText: t("error"),
2768
+ failedTables: failures.length > 0 ? t("failed-tables", [failures.map((x) => tableLabel(x.table ?? "")).join(", ")]) : null,
2769
+ retryText: t("retry"),
2770
+ onRetry: () => setRefreshes((count) => count + 1)
2972
2771
  }
2973
2772
  ),
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
2773
  truncated && /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBar, { intent: "warning", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("truncated") }) }),
2980
2774
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.body, children: [
2981
2775
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.surface, children: viewSettled && /* @__PURE__ */ jsxRuntime.jsx(
@@ -3037,6 +2831,8 @@ function Calendar({
3037
2831
  editable,
3038
2832
  regardingTables,
3039
2833
  location: dialogLocation,
2834
+ showRecordLinks,
2835
+ onNavigate,
3040
2836
  onClose: () => {
3041
2837
  setOpenAppointment(null);
3042
2838
  setCreating(null);