nodebb-plugin-calendar-onekite 12.0.2 → 12.0.3

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/lib/admin.js CHANGED
@@ -202,13 +202,40 @@ admin.purgeByYear = async function (req, res) {
202
202
  const startTs = new Date(Date.UTC(y, 0, 1)).getTime();
203
203
  const endTs = new Date(Date.UTC(y + 1, 0, 1)).getTime() - 1;
204
204
 
205
- const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 100000);
206
- let count = 0;
207
- for (const rid of ids) {
208
- await dbLayer.removeReservation(rid);
209
- count++;
205
+ const ids = await dbLayer.listReservationIdsByStartRange(startTs, endTs, 200000);
206
+ const ts = Date.now();
207
+ let removed = 0;
208
+ let keptForAccounting = 0;
209
+
210
+ // Bulk fetch to avoid one-by-one DB reads.
211
+ const chunkSize = 500;
212
+ for (let i = 0; i < ids.length; i += chunkSize) {
213
+ const chunk = ids.slice(i, i + chunkSize);
214
+ const objs = await dbLayer.getReservations(chunk);
215
+ for (let j = 0; j < chunk.length; j++) {
216
+ const rid = chunk[j];
217
+ const r = objs[j];
218
+ if (!r) {
219
+ // Ensure index doesn't keep dangling ids
220
+ try { await dbLayer.removeReservation(rid); } catch (e) {}
221
+ removed++;
222
+ continue;
223
+ }
224
+
225
+ // IMPORTANT: Do not purge accounting. If the reservation was paid,
226
+ // keep the object for exports and mark it as hidden from the calendar.
227
+ if (String(r.status) === 'paid' || r.paidAt) {
228
+ r.calendarPurgedAt = ts;
229
+ await dbLayer.saveReservation(r);
230
+ keptForAccounting++;
231
+ } else {
232
+ await dbLayer.removeReservation(rid);
233
+ removed++;
234
+ }
235
+ }
210
236
  }
211
- res.json({ ok: true, removed: count });
237
+
238
+ res.json({ ok: true, removed, keptForAccounting });
212
239
  };
213
240
 
214
241
  admin.purgeSpecialEventsByYear = async function (req, res) {
package/lib/api.js CHANGED
@@ -153,9 +153,17 @@ api.getEvents = async function (req, res) {
153
153
  const wideStart = Math.max(0, startTs - 366 * 24 * 3600 * 1000);
154
154
  const ids = await dbLayer.listReservationIdsByStartRange(wideStart, endTs, 5000);
155
155
  const out = [];
156
- for (const rid of ids) {
157
- const r = await dbLayer.getReservation(rid);
158
- if (!r) continue;
156
+
157
+ // Bulk fetch to avoid one-by-one DB reads.
158
+ const chunkSize = 500;
159
+ for (let i = 0; i < ids.length; i += chunkSize) {
160
+ const chunk = ids.slice(i, i + chunkSize);
161
+ const objs = await dbLayer.getReservations(chunk);
162
+ for (let j = 0; j < chunk.length; j++) {
163
+ const r = objs[j];
164
+ if (!r) continue;
165
+ // Hidden by year purge (kept for accounting, not shown on calendar)
166
+ if (r.calendarPurgedAt) continue;
159
167
  // Only show active statuses
160
168
  if (!['pending', 'awaiting_payment', 'paid'].includes(r.status)) continue;
161
169
  const rStart = parseInt(r.start, 10);
@@ -192,6 +200,7 @@ api.getEvents = async function (req, res) {
192
200
  }
193
201
  }
194
202
  out.push(minimal);
203
+ }
195
204
  }
196
205
  }
197
206
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "12.0.2",
3
+ "version": "12.0.3",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/client.js CHANGED
@@ -36,6 +36,10 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
36
36
  // interactions or quick re-renders.
37
37
  let isDialogOpen = false;
38
38
 
39
+ // Used to avoid a visible "flash" of events when a live update triggers
40
+ // a refetch during the very first load.
41
+ let hasLoadedEventsOnce = false;
42
+
39
43
  function escapeHtml(str) {
40
44
  return String(str)
41
45
  .replace(/&/g, '&amp;')
@@ -887,34 +891,48 @@ function toDatetimeLocalValue(date) {
887
891
 
888
892
  async function init(selector) {
889
893
 
890
- const container = typeof selector === 'string' ? document.querySelector(selector) : selector;
891
- if (!container) return;
892
-
893
- // Prevent double-init (ajaxify + initial load, or return from payment)
894
- if (container.dataset && container.dataset.onekiteCalendarInit === '1') {
895
- // Still refresh the custom button label in case FC rerendered
896
- try { refreshDesktopModeButton(); } catch (e) {}
897
- return;
898
- }
899
- if (container.dataset) container.dataset.onekiteCalendarInit = '1';
894
+ const container = typeof selector === 'string' ? document.querySelector(selector) : selector;
895
+ if (!container) return;
900
896
 
901
- // If a previous instance exists (shouldn't, but happens in some navigation flows), destroy it.
902
- try {
903
- if (window.oneKiteCalendar && typeof window.oneKiteCalendar.destroy === 'function') {
904
- window.oneKiteCalendar.destroy();
905
- }
906
- window.oneKiteCalendar = null;
907
- } catch (e) {}
908
- const el = document.querySelector(selector);
909
- if (!el) {
897
+ // FullCalendar is loaded via <script> tags in the template. On ajaxify navigation,
898
+ // action:ajaxify.end can fire before those scripts finish downloading.
899
+ // IMPORTANT: do NOT mark the container as initialised until FullCalendar exists,
900
+ // otherwise we permanently block init and the calendar never loads.
901
+ if (typeof FullCalendar === 'undefined') {
902
+ // Retry for a short time.
903
+ const startedAt = Date.now();
904
+ const retry = () => {
905
+ if (typeof FullCalendar !== 'undefined') {
906
+ init(selector);
907
+ return;
908
+ }
909
+ if (Date.now() - startedAt > 6000) {
910
+ showAlert('error', 'FullCalendar non chargé');
911
+ return;
912
+ }
913
+ setTimeout(retry, 100);
914
+ };
915
+ setTimeout(retry, 0);
910
916
  return;
911
917
  }
912
918
 
913
- if (typeof FullCalendar === 'undefined') {
914
- showAlert('error', 'FullCalendar non chargé');
919
+ // Prevent double-init (ajaxify + initial load, or return from payment)
920
+ if (container.dataset && container.dataset.onekiteCalendarInit === '1') {
921
+ try { refreshDesktopModeButton(); } catch (e) {}
915
922
  return;
916
923
  }
917
924
 
925
+ // If a previous instance exists (shouldn't, but happens in some navigation flows), destroy it.
926
+ try {
927
+ if (window.oneKiteCalendar && typeof window.oneKiteCalendar.destroy === 'function') {
928
+ window.oneKiteCalendar.destroy();
929
+ }
930
+ window.oneKiteCalendar = null;
931
+ } catch (e) {}
932
+
933
+ const el = typeof selector === 'string' ? document.querySelector(selector) : container;
934
+ if (!el) return;
935
+
918
936
  const items = await loadItems();
919
937
  const caps = await loadCapabilities().catch(() => ({}));
920
938
  const canCreateSpecial = !!caps.canCreateSpecial;
@@ -1159,6 +1177,9 @@ try {
1159
1177
  });
1160
1178
 
1161
1179
  successCallback(mapped);
1180
+ // Mark first successful events load. We use this to prevent a
1181
+ // refetch-on-socket-update from flashing the UI during initial load.
1182
+ hasLoadedEventsOnce = true;
1162
1183
  } catch (e) {
1163
1184
  failureCallback(e);
1164
1185
  }
@@ -1689,6 +1710,9 @@ try {
1689
1710
  // Expose for live updates
1690
1711
  try { window.oneKiteCalendar = calendar; } catch (e) {}
1691
1712
 
1713
+ // Mark initialised only after we have a valid FullCalendar instance.
1714
+ try { if (container.dataset) container.dataset.onekiteCalendarInit = '1'; } catch (e) {}
1715
+
1692
1716
  calendar.render();
1693
1717
 
1694
1718
  // Keep the custom button label stable even if FullCalendar rerenders the toolbar
@@ -1824,6 +1848,9 @@ try {
1824
1848
  socket.on('event:calendar-onekite.reservationUpdated', function () {
1825
1849
  try {
1826
1850
  const cal = window.oneKiteCalendar;
1851
+ // Avoid a visible "flash" on first load (e.g. returning from payment
1852
+ // while a live update is emitted right away).
1853
+ if (!hasLoadedEventsOnce) return;
1827
1854
  scheduleRefetch(cal);
1828
1855
  } catch (e) {}
1829
1856
  });