nodebb-plugin-calendar-onekite 11.2.6 → 11.2.7

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
@@ -228,6 +228,8 @@ admin.refuseReservation = async function (req, res) {
228
228
  if (!r) return res.status(404).json({ error: 'not-found' });
229
229
 
230
230
  r.status = 'refused';
231
+ r.refusedAt = Date.now();
232
+ r.refusedReason = String((req.body && (req.body.reason || req.body.refusedReason || req.body.refuseReason)) || '').trim();
231
233
  await dbLayer.saveReservation(r);
232
234
 
233
235
  try {
@@ -236,9 +238,12 @@ admin.refuseReservation = async function (req, res) {
236
238
  await sendEmail('calendar-onekite_refused', requester.email, 'Location matériel - Réservation refusée', {
237
239
  uid: parseInt(r.uid, 10),
238
240
  username: requester.username,
239
- itemName: r.itemName,
241
+ itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
242
+ itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
243
+ dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
240
244
  start: formatFR(r.start),
241
245
  end: formatFR(r.end),
246
+ refusedReason: r.refusedReason || '',
242
247
  });
243
248
  }
244
249
  } catch (e) {}
package/lib/api.js CHANGED
@@ -643,6 +643,7 @@ api.refuseReservation = async function (req, res) {
643
643
 
644
644
  r.status = 'refused';
645
645
  r.refusedAt = Date.now();
646
+ r.refusedReason = String((req.body && (req.body.reason || req.body.refusedReason || req.body.refuseReason)) || '').trim();
646
647
  await dbLayer.saveReservation(r);
647
648
 
648
649
  const requester = await user.getUserFields(r.uid, ['username', 'email']);
@@ -654,6 +655,7 @@ api.refuseReservation = async function (req, res) {
654
655
  dateRange: `Du ${formatFR(r.start)} au ${formatFR(r.end)}`,
655
656
  start: formatFR(r.start),
656
657
  end: formatFR(r.end),
658
+ refusedReason: r.refusedReason || '',
657
659
  });
658
660
  }
659
661
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.2.6",
3
+ "version": "11.2.7",
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/plugin.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "acpScripts": [
32
32
  "public/admin.js"
33
33
  ],
34
- "version": "1.0.48"
34
+ "version": "1.0.49"
35
35
  }
package/public/admin.js CHANGED
@@ -408,8 +408,11 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
408
408
  });
409
409
  }
410
410
 
411
- async function refuse(rid) {
412
- return await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${rid}/refuse`, { method: 'PUT' });
411
+ async function refuse(rid, payload) {
412
+ return await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${rid}/refuse`, {
413
+ method: 'PUT',
414
+ body: JSON.stringify(payload || {}),
415
+ });
413
416
  }
414
417
 
415
418
  async function purge(year) {
@@ -453,6 +456,23 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
453
456
  const form = document.getElementById('onekite-settings-form');
454
457
  if (!form) return;
455
458
 
459
+ // Make the HelloAsso debug output readable in both light and dark ACP themes.
460
+ // NodeBB 4.x uses Bootstrap variables, so we can rely on CSS variables here.
461
+ (function injectAdminCss() {
462
+ const id = 'onekite-admin-css';
463
+ if (document.getElementById(id)) return;
464
+ const style = document.createElement('style');
465
+ style.id = id;
466
+ style.textContent = `
467
+ #onekite-debug-output.onekite-debug-output {
468
+ background: var(--bs-body-bg) !important;
469
+ color: var(--bs-body-color) !important;
470
+ border: 1px solid var(--bs-border-color) !important;
471
+ }
472
+ `;
473
+ document.head.appendChild(style);
474
+ })();
475
+
456
476
  // Load settings
457
477
  try {
458
478
  const s = await loadSettings();
@@ -528,6 +548,9 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
528
548
  const rid = btn.getAttribute('data-rid');
529
549
  if (!rid) return;
530
550
 
551
+ // Remove the row immediately on success for a snappier UX
552
+ const rowEl = btn.closest('tr') || btn.closest('.onekite-pending-row');
553
+
531
554
  try {
532
555
  if (action === 'approve') {
533
556
  const opts = timeOptions(5).map(t => `<option value="${t}">${t}</option>`).join('');
@@ -555,6 +578,7 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
555
578
  const adminNote = (document.getElementById('onekite-admin-note')?.value || '').trim();
556
579
  const pickupTime = (document.getElementById('onekite-pickup-time')?.value || '').trim();
557
580
  await approve(rid, { adminNote, pickupTime });
581
+ if (rowEl && rowEl.parentNode) rowEl.parentNode.removeChild(rowEl);
558
582
  bootbox.alert('Réservation validée.');
559
583
  resolve(true);
560
584
  } catch (e) {
@@ -568,8 +592,38 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
568
592
  });
569
593
  });
570
594
  } else if (action === 'refuse') {
571
- await refuse(rid);
572
- bootbox.alert('Réservation refusée.');
595
+ const html = `
596
+ <div class="mb-3">
597
+ <label class="form-label">Raison du refus (incluse dans l'email)</label>
598
+ <textarea class="form-control" id="onekite-refuse-reason" rows="3" placeholder="Ex: matériel indisponible, dates impossibles, dossier incomplet..."></textarea>
599
+ </div>
600
+ `;
601
+ await new Promise((resolve) => {
602
+ bootbox.dialog({
603
+ title: 'Refuser la réservation',
604
+ message: html,
605
+ buttons: {
606
+ cancel: { label: 'Annuler', className: 'btn-secondary', callback: () => resolve(false) },
607
+ ok: {
608
+ label: 'Refuser',
609
+ className: 'btn-danger',
610
+ callback: async () => {
611
+ try {
612
+ const reason = (document.getElementById('onekite-refuse-reason')?.value || '').trim();
613
+ await refuse(rid, { reason });
614
+ if (rowEl && rowEl.parentNode) rowEl.parentNode.removeChild(rowEl);
615
+ bootbox.alert('Réservation refusée.');
616
+ resolve(true);
617
+ } catch (e) {
618
+ showAlert('error', 'Refus impossible.');
619
+ resolve(false);
620
+ }
621
+ return false;
622
+ },
623
+ },
624
+ },
625
+ });
626
+ });
573
627
  }
574
628
  await refreshPending();
575
629
  } catch (e) {
package/public/client.js CHANGED
@@ -19,8 +19,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
19
19
  width: 100%;
20
20
  max-width: 100%;
21
21
  box-sizing: border-box;
22
- padding-right: 3rem; /* leave room for caret */
23
- min-width: 7rem;
22
+ padding-right: 2.25rem; /* leave room for caret */
23
+ min-width: 0;
24
24
  text-overflow: clip;
25
25
  white-space: nowrap;
26
26
  }
@@ -602,9 +602,10 @@ function attachAddressAutocomplete(inputEl, onPick) {
602
602
  });
603
603
  }
604
604
 
605
- async function refuseReservation(rid) {
605
+ async function refuseReservation(rid, payload) {
606
606
  return await fetchJson(`/api/v3/plugins/calendar-onekite/reservations/${rid}/refuse`, {
607
607
  method: 'PUT',
608
+ body: JSON.stringify(payload || {}),
608
609
  });
609
610
  }
610
611
 
@@ -1123,12 +1124,18 @@ function toDatetimeLocalValue(date) {
1123
1124
  const canModerate = !!p.canModerate;
1124
1125
  const isPending = status === 'pending';
1125
1126
 
1127
+ const refusedReason = String(p.refusedReason || p.refuseReason || '').trim();
1128
+ const refusedReasonHtml = (status === 'refused' && refusedReason)
1129
+ ? `<div class="mb-2"><strong>Raison du refus</strong><br>${escapeHtml(refusedReason)}</div>`
1130
+ : '';
1131
+
1126
1132
  const baseHtml = `
1127
1133
  ${userLine}
1128
1134
  <div class="mb-2"><strong>Matériel</strong><br>${itemsHtml}</div>
1129
1135
  <div class="mb-2"><strong>Période</strong><br>${period}</div>
1130
1136
  ${validatedByHtml}
1131
1137
  ${pickupHtml}
1138
+ ${refusedReasonHtml}
1132
1139
  <div class="text-muted" style="font-size: 12px;">Statut: ${statusLabel(status)}</div>
1133
1140
  `;
1134
1141
 
@@ -1171,16 +1178,41 @@ function toDatetimeLocalValue(date) {
1171
1178
  }
1172
1179
  if (showModeration) {
1173
1180
  buttons.refuse = {
1174
- label: 'Supprimer',
1181
+ label: 'Refuser',
1175
1182
  className: 'btn-outline-danger',
1176
1183
  callback: async () => {
1177
- try {
1178
- await refuseReservation(rid);
1179
- showAlert('success', 'Demande supprimée.');
1180
- calendar.refetchEvents();
1181
- } catch (e) {
1182
- showAlert('error', 'Suppression impossible.');
1183
- }
1184
+ const html = `
1185
+ <div class="mb-3">
1186
+ <label class="form-label">Raison du refus</label>
1187
+ <textarea class="form-control" id="onekite-refuse-reason" rows="3" placeholder="Ex: matériel indisponible, dates impossibles, dossier incomplet..."></textarea>
1188
+ </div>
1189
+ `;
1190
+ return await new Promise((resolve) => {
1191
+ bootbox.dialog({
1192
+ title: 'Refuser la réservation',
1193
+ message: html,
1194
+ buttons: {
1195
+ cancel: { label: 'Annuler', className: 'btn-secondary', callback: () => resolve(false) },
1196
+ ok: {
1197
+ label: 'Refuser',
1198
+ className: 'btn-danger',
1199
+ callback: async () => {
1200
+ try {
1201
+ const reason = (document.getElementById('onekite-refuse-reason')?.value || '').trim();
1202
+ await refuseReservation(rid, { reason });
1203
+ showAlert('success', 'Demande refusée.');
1204
+ calendar.refetchEvents();
1205
+ resolve(true);
1206
+ } catch (e) {
1207
+ showAlert('error', 'Refus impossible.');
1208
+ resolve(false);
1209
+ }
1210
+ return false;
1211
+ },
1212
+ },
1213
+ },
1214
+ });
1215
+ });
1184
1216
  },
1185
1217
  };
1186
1218
  buttons.approve = {
@@ -138,7 +138,7 @@
138
138
  <div class="tab-pane fade" id="onekite-tab-debug" role="tabpanel">
139
139
  <p class="text-muted">Teste la récupération du token et la liste du matériel (catalogue).</p>
140
140
  <button type="button" class="btn btn-secondary me-2" id="onekite-debug-run">Tester le chargement du matériel</button>
141
- <pre id="onekite-debug-output" class="mt-3 p-3 bg-light" style="max-height: 360px; overflow: auto;"></pre>
141
+ <pre id="onekite-debug-output" class="mt-3 p-3 border rounded onekite-debug-output" style="max-height: 360px; overflow: auto;"></pre>
142
142
  </div>
143
143
 
144
144
  <div class="tab-pane fade" id="onekite-tab-accounting" role="tabpanel">
@@ -1,7 +1,7 @@
1
1
  <p>Bonjour {username},</p>
2
2
  <p>Votre demande de location de matériel a été refusée.</p>
3
3
 
4
- <p><strong>Matériel</strong></p>
4
+ <p><strong>Matériel réservé</strong></p>
5
5
  <ul>
6
6
  <!-- BEGIN itemNames -->
7
7
  <li>{itemNames}</li>
@@ -9,3 +9,7 @@
9
9
  </ul>
10
10
 
11
11
  <p>{dateRange}</p>
12
+
13
+ <!-- IF refusedReason -->
14
+ <p><strong>Raison du refus</strong><br>{refusedReason}</p>
15
+ <!-- ENDIF refusedReason -->