nodebb-plugin-calendar-onekite 11.1.3 → 11.1.4

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/library.js CHANGED
@@ -27,31 +27,31 @@ Plugin.init = async function (params) {
27
27
  routeHelpers.setupPageRoute(router, '/calendar', mw([buildHeader]), controllers.renderCalendar);
28
28
 
29
29
  // Public API (read-only)
30
- // NodeBB v4 uses the v3 API namespace. We register under /api/v3.
31
- routeHelpers.setupApiRoute(router, 'get', '/api/v3/calendar-onekite/events', [], api.getEvents);
32
- routeHelpers.setupApiRoute(router, 'get', '/api/v3/calendar-onekite/items', [], api.getItems);
30
+ // Use a plugin namespace under /api/v3/plugins/... (client-side api module expects this)
31
+ routeHelpers.setupApiRoute(router, 'get', '/plugins/calendar-onekite/events', [], api.getEvents);
32
+ routeHelpers.setupApiRoute(router, 'get', '/plugins/calendar-onekite/items', [], api.getItems);
33
33
 
34
34
  // Authenticated API
35
35
  // req.uid is provided by exposeUid; ensureLoggedIn blocks unauthenticated users.
36
- routeHelpers.setupApiRoute(router, 'post', '/api/v3/calendar-onekite/reservations', mw([exposeUid, ensureLoggedIn]), api.createReservation);
36
+ routeHelpers.setupApiRoute(router, 'post', '/plugins/calendar-onekite/reservations', mw([exposeUid, ensureLoggedIn]), api.createReservation);
37
37
 
38
38
  // Admin ACP page
39
39
  routeHelpers.setupAdminPageRoute(router, '/admin/plugins/calendar-onekite', [], admin.renderAdmin);
40
40
 
41
- // Admin API (under /api/v3/admin)
41
+ // Admin API (under /api)
42
42
  const adminMiddlewares = mw([
43
43
  exposeUid,
44
44
  ensureLoggedIn,
45
45
  middleware.admin && middleware.admin.checkPrivileges,
46
46
  ]);
47
47
 
48
- // Note: setupApiRoute in NodeBB expects the method first (get/post/put/del), then the full path.
49
- routeHelpers.setupApiRoute(router, 'get', '/api/v3/admin/plugins/calendar-onekite', adminMiddlewares, admin.getSettings);
50
- routeHelpers.setupApiRoute(router, 'post', '/api/v3/admin/plugins/calendar-onekite', adminMiddlewares, admin.saveSettings);
51
- routeHelpers.setupApiRoute(router, 'get', '/api/v3/admin/plugins/calendar-onekite/pending', adminMiddlewares, admin.listPending);
52
- routeHelpers.setupApiRoute(router, 'put', '/api/v3/admin/plugins/calendar-onekite/reservations/:rid/approve', adminMiddlewares, admin.approveReservation);
53
- routeHelpers.setupApiRoute(router, 'put', '/api/v3/admin/plugins/calendar-onekite/reservations/:rid/refuse', adminMiddlewares, admin.refuseReservation);
54
- routeHelpers.setupApiRoute(router, 'post', '/api/v3/admin/plugins/calendar-onekite/purge', adminMiddlewares, admin.purgeByYear);
48
+ // Admin API (client uses /api/v3/admin/... via api module)
49
+ routeHelpers.setupApiRoute(router, 'get', '/admin/plugins/calendar-onekite', adminMiddlewares, admin.getSettings);
50
+ routeHelpers.setupApiRoute(router, 'post', '/admin/plugins/calendar-onekite', adminMiddlewares, admin.saveSettings);
51
+ routeHelpers.setupApiRoute(router, 'get', '/admin/plugins/calendar-onekite/pending', adminMiddlewares, admin.listPending);
52
+ routeHelpers.setupApiRoute(router, 'put', '/admin/plugins/calendar-onekite/reservations/:rid/approve', adminMiddlewares, admin.approveReservation);
53
+ routeHelpers.setupApiRoute(router, 'put', '/admin/plugins/calendar-onekite/reservations/:rid/refuse', adminMiddlewares, admin.refuseReservation);
54
+ routeHelpers.setupApiRoute(router, 'post', '/admin/plugins/calendar-onekite/purge', adminMiddlewares, admin.purgeByYear);
55
55
 
56
56
  // Background cleanup for expired "pending" reservations
57
57
  scheduler.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.1.3",
3
+ "version": "11.1.4",
4
4
  "description": "FullCalendar-based equipment reservation workflow with HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "scripts": {
package/public/admin.js CHANGED
@@ -1,9 +1,19 @@
1
- /* global define, app, $ */
2
1
  'use strict';
3
2
 
4
- define('calendar-onekite-admin', function () {
3
+ /* globals define */
4
+
5
+ define('admin/plugins/calendar-onekite', ['api', 'alerts', 'jquery'], function (api, alerts, $) {
6
+ function alertSuccess(msg) {
7
+ if (alerts && typeof alerts.success === 'function') return alerts.success(msg);
8
+ if (alerts && typeof alerts.alert === 'function') return alerts.alert({ type: 'success', message: msg });
9
+ }
10
+ function alertError(msg) {
11
+ if (alerts && typeof alerts.error === 'function') return alerts.error(msg);
12
+ if (alerts && typeof alerts.alert === 'function') return alerts.alert({ type: 'danger', message: msg });
13
+ }
14
+
5
15
  function fillForm($form, settings) {
6
- Object.keys(settings).forEach((k) => {
16
+ Object.keys(settings || {}).forEach((k) => {
7
17
  const $el = $form.find(`[name="${k}"]`);
8
18
  if (!$el.length) return;
9
19
 
@@ -20,7 +30,7 @@ define('calendar-onekite-admin', function () {
20
30
  return '<div class="text-muted">Aucune demande en attente.</div>';
21
31
  }
22
32
 
23
- return list.map(r => {
33
+ return list.map((r) => {
24
34
  const start = new Date(Number(r.start)).toLocaleString();
25
35
  const end = new Date(Number(r.end)).toLocaleString();
26
36
  const expires = new Date(Number(r.expiresAt)).toLocaleString();
@@ -50,7 +60,7 @@ define('calendar-onekite-admin', function () {
50
60
  }
51
61
 
52
62
  async function loadPending() {
53
- const resp = await $.get('/api/v3/admin/plugins/calendar-onekite/pending');
63
+ const resp = await api.get('/admin/plugins/calendar-onekite/pending', {});
54
64
  return resp.pending || [];
55
65
  }
56
66
 
@@ -61,14 +71,11 @@ define('calendar-onekite-admin', function () {
61
71
  $container.on('click', '.js-approve', async function () {
62
72
  const rid = $(this).closest('[data-rid]').data('rid');
63
73
  try {
64
- await $.ajax({
65
- url: `/api/v3/admin/plugins/calendar-onekite/reservations/${rid}/approve`,
66
- method: 'PUT',
67
- });
68
- app.alertSuccess('Réservation validée, email de paiement envoyé.');
74
+ await api.put(`/admin/plugins/calendar-onekite/reservations/${rid}/approve`, {});
75
+ alertSuccess('Réservation validée, email de paiement envoyé.');
69
76
  await refreshPending();
70
77
  } catch (e) {
71
- app.alertError((e.responseJSON && e.responseJSON.error) || 'Erreur');
78
+ alertError((e && e.message) || 'Erreur');
72
79
  }
73
80
  });
74
81
 
@@ -76,15 +83,11 @@ define('calendar-onekite-admin', function () {
76
83
  const rid = $(this).closest('[data-rid]').data('rid');
77
84
  const note = prompt('Motif (optionnel) :') || '';
78
85
  try {
79
- await $.ajax({
80
- url: `/api/v3/admin/plugins/calendar-onekite/reservations/${rid}/refuse`,
81
- method: 'PUT',
82
- data: { note },
83
- });
84
- app.alertSuccess('Réservation refusée, email envoyé.');
86
+ await api.put(`/admin/plugins/calendar-onekite/reservations/${rid}/refuse`, { note });
87
+ alertSuccess('Réservation refusée, email envoyé.');
85
88
  await refreshPending();
86
89
  } catch (e) {
87
- app.alertError((e.responseJSON && e.responseJSON.error) || 'Erreur');
90
+ alertError((e && e.message) || 'Erreur');
88
91
  }
89
92
  });
90
93
  }
@@ -100,7 +103,7 @@ define('calendar-onekite-admin', function () {
100
103
  const $form = $('#calendar-onekite-settings');
101
104
  const $saved = $('#calendar-onekite-saved');
102
105
 
103
- const resp = await $.get('/api/v3/admin/plugins/calendar-onekite');
106
+ const resp = await api.get('/admin/plugins/calendar-onekite', {});
104
107
  fillForm($form, resp.settings || {});
105
108
 
106
109
  $form.on('submit', async function (e) {
@@ -110,12 +113,12 @@ define('calendar-onekite-admin', function () {
110
113
  data.showUsernamesOnCalendar = $('#showUsernamesOnCalendar').prop('checked');
111
114
 
112
115
  try {
113
- await $.post('/api/v3/admin/plugins/calendar-onekite', data);
116
+ await api.post('/admin/plugins/calendar-onekite', data);
114
117
  $saved.removeClass('d-none');
115
118
  setTimeout(() => $saved.addClass('d-none'), 2000);
116
- app.alertSuccess('Paramètres enregistrés.');
119
+ alertSuccess('Paramètres enregistrés.');
117
120
  } catch (err) {
118
- app.alertError((err.responseJSON && err.responseJSON.error) || 'Erreur lors de la sauvegarde');
121
+ alertError((err && err.message) || 'Erreur lors de la sauvegarde');
119
122
  }
120
123
  });
121
124
 
@@ -123,10 +126,10 @@ define('calendar-onekite-admin', function () {
123
126
  e.preventDefault();
124
127
  const year = $(this).find('[name="year"]').val();
125
128
  try {
126
- const resp2 = await $.post('/api/v3/admin/plugins/calendar-onekite/purge', { year });
129
+ const resp2 = await api.post('/admin/plugins/calendar-onekite/purge', { year });
127
130
  $('#calendar-onekite-purge-result').html(`<div class="alert alert-success">Année ${resp2.result.year}: ${resp2.result.purged} réservation(s) supprimée(s).</div>`);
128
131
  } catch (err) {
129
- $('#calendar-onekite-purge-result').html(`<div class="alert alert-danger">${(err.responseJSON && err.responseJSON.error) || 'Erreur'}</div>`);
132
+ $('#calendar-onekite-purge-result').html(`<div class="alert alert-danger">${(err && err.message) || 'Erreur'}</div>`);
130
133
  }
131
134
  });
132
135
 
package/public/client.js CHANGED
@@ -1,145 +1,168 @@
1
- /* global define, app, bootbox, ajaxify, FullCalendar, $ */
2
1
  'use strict';
3
2
 
4
- define('calendar-onekite', function () {
5
- function fmt(dt) {
3
+ /* globals define, ajaxify, FullCalendar */
4
+
5
+ define('calendar-onekite', ['api', 'alerts', 'bootbox'], function (api, alerts, bootbox) {
6
+ function toLocale(dt) {
6
7
  try { return new Date(dt).toLocaleString(); } catch (e) { return String(dt); }
7
8
  }
8
9
 
10
+ function alertSuccess(msg) {
11
+ if (alerts && typeof alerts.success === 'function') {
12
+ return alerts.success(msg);
13
+ }
14
+ if (alerts && typeof alerts.alert === 'function') {
15
+ return alerts.alert({ type: 'success', message: msg });
16
+ }
17
+ console.log(msg);
18
+ }
19
+
20
+ function alertError(msg) {
21
+ if (alerts && typeof alerts.error === 'function') {
22
+ return alerts.error(msg);
23
+ }
24
+ if (alerts && typeof alerts.alert === 'function') {
25
+ return alerts.alert({ type: 'danger', message: msg });
26
+ }
27
+ console.error(msg);
28
+ }
29
+
9
30
  async function fetchItems() {
10
- return await $.get('/api/v3/calendar-onekite/items');
31
+ // NodeBB client api module automatically prefixes /api/v3
32
+ return await api.get('/plugins/calendar-onekite/items', {});
33
+ }
34
+
35
+ function waitForFullCalendar(cb) {
36
+ if (typeof FullCalendar !== 'undefined' && FullCalendar && FullCalendar.Calendar) {
37
+ return cb();
38
+ }
39
+ setTimeout(function () { waitForFullCalendar(cb); }, 50);
11
40
  }
12
41
 
13
42
  function init(opts) {
14
- const el = document.querySelector(opts.el);
43
+ const el = document.querySelector((opts && opts.el) || '#onekite-calendar');
15
44
  if (!el) return;
16
45
 
17
- const calendar = new FullCalendar.Calendar(el, {
18
- initialView: 'dayGridMonth',
19
- selectable: true,
20
- locale: opts.locale || 'fr',
21
- headerToolbar: {
22
- left: 'prev,next today',
23
- center: 'title',
24
- right: 'dayGridMonth,timeGridWeek,timeGridDay',
25
- },
26
- events: function (info, success, failure) {
27
- $.get('/api/v3/calendar-onekite/events', { start: info.startStr, end: info.endStr })
28
- .done(success)
29
- .fail(function (xhr) {
30
- failure(xhr);
31
- });
32
- },
33
- select: function (selectionInfo) {
34
- if (!ajaxify.data.loggedIn) {
35
- return bootbox.alert('Vous devez être connecté pour faire une demande.');
36
- }
37
- openReservationDialog(selectionInfo.startStr, selectionInfo.endStr);
38
- },
39
- dateClick: function (info) {
40
- if (!ajaxify.data.loggedIn) {
41
- return bootbox.alert('Vous devez être connecté pour faire une demande.');
42
- }
43
- // One day by default (for month view)
44
- const start = info.dateStr;
45
- const end = new Date(info.date.getTime() + 60 * 60 * 1000).toISOString();
46
- openReservationDialog(start, end);
47
- },
48
- eventClick: function (clickInfo) {
49
- const ev = clickInfo.event;
50
- const st = ev.extendedProps && ev.extendedProps.status;
51
- const pay = ev.extendedProps && ev.extendedProps.paymentUrl;
52
- let msg = `<p><strong>${ev.title}</strong></p>
53
- <p>Du ${fmt(ev.start)}<br/>Au ${fmt(ev.end)}</p>
54
- <p>Statut: <strong>${st}</strong></p>`;
55
- if (pay) {
56
- msg += `<p><a class="btn btn-primary" href="${pay}" target="_blank" rel="noopener">Payer sur HelloAsso</a></p>`;
46
+ waitForFullCalendar(function () {
47
+ const calendar = new FullCalendar.Calendar(el, {
48
+ initialView: 'dayGridMonth',
49
+ selectable: true,
50
+ locale: (opts && opts.locale) || 'fr',
51
+ headerToolbar: {
52
+ left: 'prev,next today',
53
+ center: 'title',
54
+ right: 'dayGridMonth,timeGridWeek,timeGridDay',
55
+ },
56
+ events: async function (info, success, failure) {
57
+ try {
58
+ const resp = await api.get('/plugins/calendar-onekite/events', { start: info.startStr, end: info.endStr });
59
+ success(resp.events || resp);
60
+ } catch (err) {
61
+ failure(err);
62
+ alertError((err && err.message) || 'Erreur lors du chargement du calendrier');
63
+ }
64
+ },
65
+ select: function (selectionInfo) {
66
+ if (!ajaxify || !ajaxify.data || !ajaxify.data.loggedIn) {
67
+ return bootbox.alert('Vous devez être connecté pour faire une demande.');
68
+ }
69
+ openReservationDialog(selectionInfo.startStr, selectionInfo.endStr);
70
+ },
71
+ dateClick: function (info) {
72
+ if (!ajaxify || !ajaxify.data || !ajaxify.data.loggedIn) {
73
+ return bootbox.alert('Vous devez être connecté pour faire une demande.');
74
+ }
75
+ const start = info.dateStr;
76
+ const end = new Date(info.date.getTime() + 60 * 60 * 1000).toISOString();
77
+ openReservationDialog(start, end);
78
+ },
79
+ eventClick: function (clickInfo) {
80
+ const ev = clickInfo.event;
81
+ const st = ev.extendedProps && ev.extendedProps.status;
82
+ const pay = ev.extendedProps && ev.extendedProps.paymentUrl;
83
+ let msg = `<p><strong>${ev.title}</strong></p>
84
+ <p>Du ${toLocale(ev.start)}<br/>Au ${toLocale(ev.end)}</p>
85
+ <p>Statut: <strong>${st}</strong></p>`;
86
+ if (pay) {
87
+ msg += `<p><a class="btn btn-primary" href="${pay}" target="_blank" rel="noopener">Payer sur HelloAsso</a></p>`;
88
+ }
89
+ bootbox.alert({ title: 'Réservation', message: msg });
90
+ },
91
+ });
92
+
93
+ calendar.render();
94
+
95
+ async function openReservationDialog(startStr, endStr) {
96
+ let itemsResp;
97
+ try {
98
+ itemsResp = await fetchItems();
99
+ } catch (e) {
100
+ return bootbox.alert('Impossible de récupérer la liste du matériel (HelloAsso). Vérifiez les paramètres dans l’ACP.');
57
101
  }
58
- bootbox.alert({ title: 'Réservation', message: msg });
59
- },
60
- });
102
+ const items = (itemsResp && itemsResp.items) || [];
103
+ if (!items.length) return bootbox.alert('Aucun matériel disponible (liste HelloAsso vide).');
61
104
 
62
- calendar.render();
105
+ const optionsHtml = items.map(function (it) {
106
+ const price = (it.price !== '' && it.price !== null && it.price !== undefined) ? ` — ${it.price}` : '';
107
+ const safeName = String(it.name || '').replace(/"/g, '&quot;');
108
+ return `<option value="${String(it.id)}" data-name="${safeName}" data-price="${String(it.price ?? '')}">${safeName}${price}</option>`;
109
+ }).join('');
63
110
 
64
- async function openReservationDialog(startStr, endStr) {
65
- let itemsResp;
66
- try {
67
- itemsResp = await fetchItems();
68
- } catch (e) {
69
- return bootbox.alert('Impossible de récupérer la liste du matériel (HelloAsso). Vérifiez les paramètres dans l’ACP.');
70
- }
71
- const items = (itemsResp && itemsResp.items) || [];
72
- if (!items.length) return bootbox.alert('Aucun matériel disponible (liste HelloAsso vide).');
73
-
74
- const optionsHtml = items.map(it => {
75
- const price = (it.price !== '' && it.price !== null && it.price !== undefined) ? ` — ${it.price}` : '';
76
- return `<option value="${String(it.id)}" data-name="${String(it.name).replace(/"/g, '&quot;')}" data-price="${String(it.price)}">${it.name}${price}</option>`;
77
- }).join('');
78
-
79
- const formHtml = `
80
- <form id="onekite-reserve-form">
81
- <div class="mb-2">
82
- <label class="form-label">Matériel</label>
83
- <select class="form-select" name="itemId">${optionsHtml}</select>
84
- </div>
85
- <div class="mb-2">
86
- <label class="form-label">Début</label>
87
- <input class="form-control" name="start" value="${startStr}" />
88
- </div>
89
- <div class="mb-2">
90
- <label class="form-label">Fin</label>
91
- <input class="form-control" name="end" value="${endStr}" />
92
- </div>
93
- <div class="alert alert-warning mt-3">
94
- Votre demande sera <strong>en attente</strong> jusqu’à validation, et le matériel sera bloqué pendant la durée définie.
95
- </div>
96
- </form>
97
- `;
98
-
99
- const dlg = bootbox.dialog({
100
- title: 'Nouvelle demande de réservation',
101
- message: formHtml,
102
- buttons: {
103
- cancel: { label: 'Annuler', className: 'btn-secondary' },
104
- ok: {
105
- label: 'Envoyer la demande',
106
- className: 'btn-primary',
107
- callback: function () {
108
- const $f = $('#onekite-reserve-form');
109
- const itemId = $f.find('[name="itemId"]').val();
110
- const $opt = $f.find('[name="itemId"] option:selected');
111
- const itemName = $opt.data('name');
112
- const itemPrice = $opt.data('price');
113
-
114
- const payload = {
115
- itemId,
116
- itemName,
117
- itemPrice,
118
- start: $f.find('[name="start"]').val(),
119
- end: $f.find('[name="end"]').val(),
120
- };
121
-
122
- return $.ajax({
123
- url: '/api/v3/calendar-onekite/reservations',
124
- method: 'POST',
125
- data: payload,
126
- }).done(function () {
127
- app.alertSuccess('Demande envoyée (en attente de validation).');
128
- calendar.refetchEvents();
129
- }).fail(function (xhr) {
130
- const err = (xhr.responseJSON && xhr.responseJSON.error) || 'Erreur';
131
- app.alertError(err);
132
- });
111
+ const formHtml = `
112
+ <form id="onekite-reserve-form">
113
+ <div class="mb-2">
114
+ <label class="form-label">Matériel</label>
115
+ <select class="form-select" name="itemId">${optionsHtml}</select>
116
+ </div>
117
+ <div class="mb-2">
118
+ <label class="form-label">Début</label>
119
+ <input class="form-control" name="start" value="${startStr}" />
120
+ </div>
121
+ <div class="mb-2">
122
+ <label class="form-label">Fin</label>
123
+ <input class="form-control" name="end" value="${endStr}" />
124
+ </div>
125
+ <div class="alert alert-warning mt-3">
126
+ Votre demande sera <strong>en attente</strong> jusqu’à validation, et le matériel sera bloqué pendant la durée définie.
127
+ </div>
128
+ </form>
129
+ `;
130
+
131
+ bootbox.dialog({
132
+ title: 'Nouvelle demande de réservation',
133
+ message: formHtml,
134
+ buttons: {
135
+ cancel: { label: 'Annuler', className: 'btn-secondary' },
136
+ ok: {
137
+ label: 'Envoyer la demande',
138
+ className: 'btn-primary',
139
+ callback: async function () {
140
+ const form = document.getElementById('onekite-reserve-form');
141
+ const itemSelect = form.querySelector('[name="itemId"]');
142
+ const selected = itemSelect.options[itemSelect.selectedIndex];
143
+
144
+ const payload = {
145
+ itemId: itemSelect.value,
146
+ itemName: selected.getAttribute('data-name'),
147
+ itemPrice: selected.getAttribute('data-price'),
148
+ start: form.querySelector('[name="start"]').value,
149
+ end: form.querySelector('[name="end"]').value,
150
+ };
151
+
152
+ try {
153
+ await api.post('/plugins/calendar-onekite/reservations', payload);
154
+ alertSuccess('Demande envoyée (en attente de validation).');
155
+ calendar.refetchEvents();
156
+ } catch (err) {
157
+ alertError((err && err.message) || 'Erreur');
158
+ }
159
+ return false;
160
+ },
133
161
  },
134
162
  },
135
- },
136
- });
137
-
138
- dlg.init(function () {
139
- // focus select
140
- $('#onekite-reserve-form select').focus();
141
- });
142
- }
163
+ });
164
+ }
165
+ });
143
166
  }
144
167
 
145
168
  return { init };
@@ -106,7 +106,7 @@
106
106
  (function () {
107
107
  function boot() {
108
108
  if (!window.require) return setTimeout(boot, 50);
109
- window.require(['calendar-onekite-admin'], function (Admin) {
109
+ window.require(['admin/plugins/calendar-onekite'], function (Admin) {
110
110
  Admin.init();
111
111
  });
112
112
  }
@@ -11,7 +11,7 @@
11
11
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.css" />
12
12
 
13
13
  <script defer src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/index.global.min.js"></script>
14
- <script defer src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.11/locales-all.global.min.js"></script>
14
+ <script defer src="https://cdn.jsdelivr.net/npm/@fullcalendar/core@6.1.11/locales-all.global.min.js"></script>
15
15
 
16
16
  <script>
17
17
  (function () {