nodebb-plugin-equipment-calendar 8.9.7 → 9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "8.9.7",
3
+ "version": "9.0.0",
4
4
  "description": "Equipment reservation calendar for NodeBB (FullCalendar, approvals, HelloAsso payments)",
5
5
  "main": "library.js",
6
6
  "scripts": {
package/plugin.json CHANGED
@@ -25,6 +25,6 @@
25
25
  "scripts": [
26
26
  "public/js/client.js"
27
27
  ],
28
- "version": "3.0.0-stable4p-css-datefix-acp",
28
+ "version": "3.0.0-stable4p-admin-save-alert",
29
29
  "minver": "4.7.1"
30
30
  }
@@ -1,4 +1,4 @@
1
- require(['jquery', 'bootstrap'], function ($, bootstrap) {
1
+ require(['jquery', 'bootstrap', 'bootbox'], function ($, bootstrap, bootbox) {
2
2
  'use strict';
3
3
  /* global window, document, FullCalendar */
4
4
 
@@ -55,25 +55,30 @@ require(['jquery', 'bootstrap'], function ($, bootstrap) {
55
55
  }
56
56
 
57
57
 
58
- function ecAction(rid, action, eventObj) {
58
+ function escapeHtml(str) {
59
+ return String(str || '').replace(/[&<>"']/g, function (c) {
60
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c];
61
+ });
62
+ }
63
+
64
+ function ecDoAction(rid, action, eventObj) {
59
65
  try {
60
- var csrf = window.EC_CSRF || (window.config && window.config.csrf_token) || '';
66
+ const csrf = window.EC_CSRF || (window.config && window.config.csrf_token) || '';
61
67
  return fetch((config.relative_path || '') + '/api/equipment/reservations/' + encodeURIComponent(rid) + '/action', {
62
68
  method: 'POST',
63
69
  headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf },
64
70
  body: JSON.stringify({ action: action })
65
- }).then(function(r){ return r.json().catch(function(){ return {}; }).then(function(j){ return { ok: r.ok, json: j };});})
66
- .then(function(res){
71
+ }).then(function (r) {
72
+ return r.json().catch(function () { return {}; }).then(function (j) { return { ok: r.ok, json: j }; });
73
+ }).then(function (res) {
67
74
  if (!res.ok || (res.json && res.json.error)) {
68
- if (window.app && app.alertError) app.alertError(res.json && res.json.error ? res.json.error : 'Erreur');
75
+ if (window.app && app.alertError) app.alertError((res.json && res.json.error) ? res.json.error : 'Erreur');
69
76
  return false;
70
77
  }
71
78
  if (action === 'delete') {
72
- try { eventObj.remove(); } catch(e){}
73
- } else if (action === 'approve') {
74
- try { eventObj.setExtendedProp('status','approved'); } catch(e){}
75
- } else if (action === 'reject') {
76
- try { eventObj.setExtendedProp('status','rejected'); } catch(e){}
79
+ try { eventObj.remove(); } catch (e) {}
80
+ } else {
81
+ try { eventObj.setExtendedProp('status', action === 'approve' ? 'approved' : 'rejected'); } catch (e) {}
77
82
  }
78
83
  if (window.app && app.alertSuccess) app.alertSuccess('OK');
79
84
  return true;
@@ -166,7 +171,7 @@ function updateTotalPrice() {
166
171
  selectMirror: true,
167
172
  dayMaxEventRows: true,
168
173
  displayEventTime: false,
169
- timeZone: 'local',
174
+ timeZone: 'UTC',
170
175
  events: events,
171
176
  select: function (info) {
172
177
  if (!window.EC_CAN_CREATE) return;
@@ -177,15 +182,34 @@ function updateTotalPrice() {
177
182
  },
178
183
  dateClick: function (info) {
179
184
  if (!window.EC_CAN_CREATE) return;
180
- openCreateModal(info.dateStr, info.dateStr);
185
+ openCreateModal(String(info.dateStr||'').slice(0,10), String(info.dateStr||'').slice(0,10));
181
186
  },
182
187
  eventClick: function (info) {
183
- // simple details popup
184
- const r = info.event.extendedProps || {};
185
- const title = info.event.title || 'Réservation';
186
- const start = info.event.startStr || '';
187
- const end = info.event.endStr ? addDaysIsoLocal(info.event.endStr, -1) : '';
188
- bootbox.alert(`<strong>${title}</strong><br>Du ${start} au ${end}<br>Statut: ${r.status || ''}`);
188
+ try {
189
+ info.jsEvent && info.jsEvent.preventDefault && info.jsEvent.preventDefault();
190
+ const ep = info.event.extendedProps || {};
191
+ const rid = ep.rid || info.event.id;
192
+ const status = ep.status || 'pending';
193
+ const title = info.event.title || 'Reservation';
194
+ const start = String(info.event.startStr || '').slice(0,10);
195
+ const end = String(info.event.endStr || '').slice(0,10);
196
+ const total = ep.total || 0;
197
+
198
+ let msg = '<div><strong>' + escapeHtml(title) + '</strong></div>';
199
+ msg += '<div>Periode: ' + escapeHtml(start) + ' -> ' + escapeHtml(end) + '</div>';
200
+ msg += '<div>Statut: ' + escapeHtml(status) + '</div>';
201
+ if (total) msg += '<div>Total: ' + escapeHtml(String(total)) + ' EUR</div>';
202
+
203
+ const buttons = { close: { label: 'Fermer', className: 'btn-secondary' } };
204
+
205
+ if (window.EC_IS_APPROVER) {
206
+ buttons.approve = { label: 'Valider', className: 'btn-success', callback: function () { return ecDoAction(rid, 'approve', info.event); } };
207
+ buttons.reject = { label: 'Rejeter', className: 'btn-warning', callback: function () { return ecDoAction(rid, 'reject', info.event); } };
208
+ buttons.delete = { label: 'Supprimer', className: 'btn-danger', callback: function () { return ecDoAction(rid, 'delete', info.event); } };
209
+ }
210
+
211
+ bootbox.dialog({ title: 'Reservation', message: msg, buttons: buttons });
212
+ } catch (e) {}
189
213
  },
190
214
  });
191
215
 
@@ -53,23 +53,24 @@
53
53
  <tbody>
54
54
  {{{ each rows }}}
55
55
  <tr>
56
- <td><code></code></td>
56
+ <td><code>{rows.rid}</code></td>
57
57
  <td>{rows.itemName}</td>
58
+ <td>{rows.uid}</td>
58
59
  <td><small>{rows.start}</small></td>
59
60
  <td><small>{rows.end}</small></td>
60
61
  <td><code>{rows.status}</code></td>
61
62
  <td><small>{rows.createdAt}</small></td>
62
63
  <td><small>{rows.notesUser}</small></td>
63
64
  <td class="text-nowrap">
64
- <form method="post" action="/admin/plugins/equipment-calendar/reservations//approve" class="d-inline">
65
+ <form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/approve" class="d-inline">
65
66
  <input type="hidden" name="_csrf" value="{config.csrf_token}">
66
67
  <button class="btn btn-sm btn-success" type="submit">Approve</button>
67
68
  </form>
68
- <form method="post" action="/admin/plugins/equipment-calendar/reservations//reject" class="d-inline ms-1">
69
+ <form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/reject" class="d-inline ms-1">
69
70
  <input type="hidden" name="_csrf" value="{config.csrf_token}">
70
71
  <button class="btn btn-sm btn-warning" type="submit">Reject</button>
71
72
  </form>
72
- <form method="post" action="/admin/plugins/equipment-calendar/reservations//delete" class="d-inline ms-1" onsubmit="return confirm('Supprimer définitivement ?');">
73
+ <form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/delete" class="d-inline ms-1" onsubmit="return confirm('Supprimer définitivement ?');">
73
74
  <input type="hidden" name="_csrf" value="{config.csrf_token}">
74
75
  <button class="btn btn-sm btn-danger" type="submit">Delete</button>
75
76
  </form>
@@ -8,22 +8,20 @@
8
8
  </div>
9
9
  </div>
10
10
 
11
- <form id="ec-admin-form" method="post" action="/admin/plugins/equipment-calendar/save" class="mb-3 mt-3">
12
- <input type="hidden" name="_csrf" value="{config.csrf_token}">
13
-
11
+ <div class="equipment-calendar-settings mt-3">
14
12
  <div class="card card-body mb-3">
15
13
  <h5>Permissions</h5>
16
14
  <div class="mb-3">
17
15
  <label class="form-label">Groupes autorisés à créer une demande (creatorGroups)</label>
18
- <input class="form-control" type="text" name="creatorGroups" value="{settings.creatorGroups}">
16
+ <input class="form-control" type="text" name="creatorGroups" data-field="creatorGroups" value="{settings.creatorGroups}">
19
17
  </div>
20
18
  <div class="mb-3">
21
19
  <label class="form-label">Groupe valideur (approverGroup)</label>
22
- <input class="form-control" type="text" name="approverGroup" value="{settings.approverGroup}">
20
+ <input class="form-control" type="text" name="approverGroup" data-field="approverGroup" value="{settings.approverGroup}">
23
21
  </div>
24
22
  <div class="mb-0">
25
23
  <label class="form-label">Groupe notifié (notifyGroup)</label>
26
- <input class="form-control" type="text" name="notifyGroup" value="{settings.notifyGroup}">
24
+ <input class="form-control" type="text" name="notifyGroup" data-field="notifyGroup" value="{settings.notifyGroup}">
27
25
  </div>
28
26
  </div>
29
27
 
@@ -31,7 +29,7 @@
31
29
  <h5>Paiement</h5>
32
30
  <div class="mb-0">
33
31
  <label class="form-label">Timeout paiement (minutes)</label>
34
- <input class="form-control" type="number" min="1" name="paymentTimeoutMinutes" value="{settings.paymentTimeoutMinutes}">
32
+ <input class="form-control" type="number" min="1" name="paymentTimeoutMinutes" data-field="paymentTimeoutMinutes" value="{settings.paymentTimeoutMinutes}">
35
33
  </div>
36
34
  </div>
37
35
 
@@ -39,45 +37,39 @@
39
37
  <h5>HelloAsso</h5>
40
38
  <div class="mb-3">
41
39
  <label class="form-label">API Base URL (prod/sandbox)</label>
42
- <input class="form-control" type="text" name="ha_apiBaseUrl" value="{settings.ha_apiBaseUrl}">
40
+ <input class="form-control" type="text" name="ha_apiBaseUrl" data-field="ha_apiBaseUrl" value="{settings.ha_apiBaseUrl}">
43
41
  <div class="form-text">Prod: <code>https://api.helloasso.com</code> — Sandbox: <code>https://api.helloasso-sandbox.com</code></div>
44
42
  </div>
45
43
  <div class="mb-3">
46
44
  <label class="form-label">Organization slug</label>
47
- <input class="form-control" type="text" name="ha_organizationSlug" value="{settings.ha_organizationSlug}">
45
+ <input class="form-control" type="text" name="ha_organizationSlug" data-field="ha_organizationSlug" value="{settings.ha_organizationSlug}">
48
46
  </div>
49
47
  <div class="mb-3">
50
48
  <label class="form-label">Client ID</label>
51
- <input class="form-control" type="text" name="ha_clientId" value="{settings.ha_clientId}">
49
+ <input class="form-control" type="text" name="ha_clientId" data-field="ha_clientId" value="{settings.ha_clientId}">
52
50
  </div>
53
51
  <div class="mb-3">
54
52
  <label class="form-label">Client Secret</label>
55
- <input class="form-control" type="password" name="ha_clientSecret" value="{settings.ha_clientSecret}">
53
+ <input class="form-control" type="password" name="ha_clientSecret" data-field="ha_clientSecret" value="{settings.ha_clientSecret}">
56
54
  </div>
57
55
  <div class="row g-3">
58
56
  <div class="col-md-6">
59
57
  <label class="form-label">Form Type</label>
60
- <input class="form-control" type="text" name="ha_itemsFormType" value="{settings.ha_itemsFormType}" placeholder="shop">
58
+ <input class="form-control" type="text" name="ha_itemsFormType" data-field="ha_itemsFormType" value="{settings.ha_itemsFormType}" placeholder="shop">
61
59
  </div>
62
60
  <div class="col-md-6">
63
61
  <label class="form-label">Form Slug</label>
64
- <input class="form-control" type="text" name="ha_itemsFormSlug" value="{settings.ha_itemsFormSlug}" placeholder="locations-materiel-2026">
62
+ <input class="form-control" type="text" name="ha_itemsFormSlug" data-field="ha_itemsFormSlug" value="{settings.ha_itemsFormSlug}" placeholder="locations-materiel-2026">
65
63
  </div>
66
64
  </div>
67
65
  <div class="mb-0 mt-3">
68
66
  <label class="form-label">Préfixe itemName (checkout)</label>
69
- <input class="form-control" type="text" name="ha_calendarItemNamePrefix" value="{settings.ha_calendarItemNamePrefix}">
67
+ <input class="form-control" type="text" name="ha_calendarItemNamePrefix" data-field="ha_calendarItemNamePrefix" value="{settings.ha_calendarItemNamePrefix}">
70
68
  </div>
71
69
  </div>
72
70
 
73
- <button class="btn btn-primary" type="submit">Enregistrer</button>
74
- </form>
71
+ <button id="save" class="btn btn-primary" type="button">Enregistrer</button>
72
+ </div>
75
73
 
76
- {{{ if saved }}}
77
- <script>
78
- if (window.app && typeof window.app.alertSuccess === 'function') {
79
- window.app.alertSuccess('Paramètres enregistrés');
80
- }
81
- </script>
82
- {{{ end }}}
74
+
83
75
  </div>