nodebb-plugin-equipment-calendar 0.3.1 → 0.4.1

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": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Equipment reservation calendar for NodeBB (FullCalendar, approvals, HelloAsso payments)",
5
5
  "main": "library.js",
6
6
  "scripts": {
package/plugin.json CHANGED
@@ -26,6 +26,6 @@
26
26
  "scripts": [
27
27
  "public/js/client.js"
28
28
  ],
29
- "version": "0.2.4",
29
+ "version": "0.2.5",
30
30
  "minver": "4.7.1"
31
31
  }
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- /* global window, document, FullCalendar, bootstrap */
2
+ /* global window, document, FullCalendar, bootbox */
3
3
 
4
4
  (function () {
5
5
  function overlaps(aStartMs, aEndMs, bStartMs, bEndMs) {
@@ -14,28 +14,7 @@
14
14
  }
15
15
  }
16
16
 
17
- function openModal(startISO, endISO) {
18
- const form = document.getElementById('ec-create-form');
19
- if (!form) return;
20
-
21
- const startDate = new Date(startISO);
22
- const endDate = new Date(endISO);
23
- const startMs = startDate.getTime();
24
- const endMs = endDate.getTime();
25
-
26
- // write hidden ISO values
27
- const startInput = form.querySelector('input[name="start"]');
28
- const endInput = form.querySelector('input[name="end"]');
29
- if (startInput) startInput.value = startDate.toISOString();
30
- if (endInput) endInput.value = endDate.toISOString();
31
-
32
- // displays
33
- const sDisp = document.getElementById('ecStartDisplay');
34
- const eDisp = document.getElementById('ecEndDisplay');
35
- if (sDisp) sDisp.value = fmt(startDate);
36
- if (eDisp) eDisp.value = fmt(endDate);
37
-
38
- // compute availability
17
+ function buildAvailability(startMs, endMs) {
39
18
  const blocks = Array.isArray(window.EC_BLOCKS) ? window.EC_BLOCKS : [];
40
19
  const items = Array.isArray(window.EC_ITEMS) ? window.EC_ITEMS : [];
41
20
 
@@ -51,41 +30,99 @@
51
30
  const hasOverlap = bks.some(b => overlaps(startMs, endMs, Number(b.startMs), Number(b.endMs)));
52
31
  if (!hasOverlap) available.push(it);
53
32
  }
33
+ return available;
34
+ }
35
+
36
+ function submitReservation(startISO, endISO, itemId, notes) {
37
+ const form = document.getElementById('ec-create-form');
38
+ if (!form) return;
39
+
40
+ const startInput = form.querySelector('input[name="start"]');
41
+ const endInput = form.querySelector('input[name="end"]');
42
+ const itemInput = form.querySelector('input[name="itemId"]');
43
+ const notesInput = form.querySelector('input[name="notesUser"]');
54
44
 
55
- const select = document.getElementById('ecItemSelect');
56
- const hint = document.getElementById('ecItemHint');
57
- const noAvail = document.getElementById('ecNoAvailability');
58
- const submitBtn = document.getElementById('ecSubmitBtn');
59
-
60
- if (select) {
61
- select.innerHTML = '';
62
- for (const it of available) {
63
- const opt = document.createElement('option');
64
- opt.value = it.id;
65
- opt.textContent = it.location ? `${it.name} — ${it.location}` : it.name;
66
- select.appendChild(opt);
45
+ if (startInput) startInput.value = startISO;
46
+ if (endInput) endInput.value = endISO;
47
+ if (itemInput) itemInput.value = itemId;
48
+ if (notesInput) notesInput.value = notes || '';
49
+
50
+ form.submit();
51
+ }
52
+
53
+ function openNodeBBModal(startISO, endISO) {
54
+ const startDate = new Date(startISO);
55
+ const endDate = new Date(endISO);
56
+ const startMs = startDate.getTime();
57
+ const endMs = endDate.getTime();
58
+
59
+ const available = buildAvailability(startMs, endMs);
60
+
61
+ if (!available.length) {
62
+ if (typeof bootbox !== 'undefined') {
63
+ bootbox.alert('Aucun matériel n’est disponible sur cette plage.');
64
+ } else {
65
+ alert('Aucun matériel n’est disponible sur cette plage.');
67
66
  }
67
+ return;
68
68
  }
69
69
 
70
- const hasAny = available.length > 0;
71
- if (hint) hint.textContent = hasAny ? '' : '';
72
- if (noAvail) noAvail.classList.toggle('d-none', hasAny);
73
- if (submitBtn) submitBtn.disabled = !hasAny;
74
-
75
- // show modal
76
- const modalEl = document.getElementById('ecReserveModal');
77
- if (!modalEl) return;
78
- if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
79
- const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
80
- modal.show();
81
- } else {
82
- // Fallback: no bootstrap JS available, just submit directly if there is an available item
83
- if (hasAny) {
84
- if (confirm('Envoyer la demande de réservation pour cette plage ?')) {
85
- form.submit();
70
+ // Build modal HTML
71
+ let optionsHtml = '';
72
+ for (const it of available) {
73
+ const label = it.location ? `${it.name} — ${it.location}` : it.name;
74
+ optionsHtml += `<option value="${it.id}">${label}</option>`;
75
+ }
76
+
77
+ const body = `
78
+ <div class="mb-3">
79
+ <label class="form-label">Début</label>
80
+ <input class="form-control" type="text" value="${fmt(startDate)}" readonly>
81
+ </div>
82
+ <div class="mb-3">
83
+ <label class="form-label">Fin</label>
84
+ <input class="form-control" type="text" value="${fmt(endDate)}" readonly>
85
+ </div>
86
+ <div class="mb-3">
87
+ <label class="form-label">Matériel</label>
88
+ <select class="form-select" id="ec-modal-item">
89
+ ${optionsHtml}
90
+ </select>
91
+ </div>
92
+ <div class="mb-3">
93
+ <label class="form-label">Note (optionnel)</label>
94
+ <input class="form-control" type="text" id="ec-modal-notes" maxlength="2000" placeholder="Ex: besoin de trépied, etc.">
95
+ </div>
96
+ `;
97
+
98
+ if (typeof bootbox === 'undefined') {
99
+ // Fallback without bootbox (should be rare on NodeBB pages)
100
+ const itemId = available[0].id;
101
+ submitReservation(startDate.toISOString(), endDate.toISOString(), itemId, '');
102
+ return;
103
+ }
104
+
105
+ bootbox.dialog({
106
+ title: 'Demande de réservation',
107
+ message: body,
108
+ buttons: {
109
+ cancel: {
110
+ label: 'Annuler',
111
+ className: 'btn-outline-secondary',
112
+ },
113
+ ok: {
114
+ label: 'Envoyer la demande',
115
+ className: 'btn-primary',
116
+ callback: function () {
117
+ const itemEl = document.getElementById('ec-modal-item');
118
+ const notesEl = document.getElementById('ec-modal-notes');
119
+ const itemId = itemEl ? itemEl.value : available[0].id;
120
+ const notes = notesEl ? notesEl.value : '';
121
+ submitReservation(startDate.toISOString(), endDate.toISOString(), itemId, notes);
122
+ }
86
123
  }
87
124
  }
88
- }
125
+ });
89
126
  }
90
127
 
91
128
  function initCalendar() {
@@ -103,15 +140,20 @@
103
140
  selectable: window.EC_CAN_CREATE === true,
104
141
  selectMirror: true,
105
142
  events: events,
143
+
144
+ // Range selection (drag)
106
145
  select: function (info) {
107
- openModal(info.startStr, info.endStr);
146
+ openNodeBBModal(info.startStr, info.endStr);
108
147
  },
148
+
149
+ // Single date click
109
150
  dateClick: function (info) {
110
- // default 1 hour
151
+ // Default: 1h slot
111
152
  const start = info.date;
112
153
  const end = new Date(start.getTime() + 60 * 60 * 1000);
113
- openModal(start.toISOString(), end.toISOString());
154
+ openNodeBBModal(start.toISOString(), end.toISOString());
114
155
  },
156
+
115
157
  headerToolbar: {
116
158
  left: 'prev,next today',
117
159
  center: 'title',
@@ -12,53 +12,15 @@
12
12
  <div class="card card-body">
13
13
  <div id="equipment-calendar"></div>
14
14
  </div>
15
- </div>
16
-
17
- <!-- Modal de réservation -->
18
- <div class="modal fade" id="ecReserveModal" tabindex="-1" aria-hidden="true">
19
- <div class="modal-dialog">
20
- <div class="modal-content">
21
- <form id="ec-create-form" method="post" action="/equipment/reservations/create">
22
- <div class="modal-header">
23
- <h5 class="modal-title">Demande de réservation</h5>
24
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
25
- </div>
26
- <div class="modal-body">
27
- <input type="hidden" name="_csrf" value="{config.csrf_token}">
28
- <input type="hidden" name="start" value="">
29
- <input type="hidden" name="end" value="">
30
-
31
- <div class="mb-3">
32
- <label class="form-label">Début</label>
33
- <input type="text" class="form-control" id="ecStartDisplay" readonly>
34
- </div>
35
- <div class="mb-3">
36
- <label class="form-label">Fin</label>
37
- <input type="text" class="form-control" id="ecEndDisplay" readonly>
38
- </div>
39
15
 
40
- <div class="mb-3">
41
- <label class="form-label">Matériel disponible</label>
42
- <select class="form-select" name="itemId" id="ecItemSelect" required></select>
43
- <div class="form-text" id="ecItemHint"></div>
44
- </div>
45
-
46
- <div class="mb-3">
47
- <label class="form-label">Note (optionnel)</label>
48
- <input class="form-control" type="text" name="notesUser" maxlength="2000" placeholder="Ex: besoin de trépied, etc.">
49
- </div>
50
-
51
- <div class="alert alert-warning d-none" id="ecNoAvailability">
52
- Aucun matériel n’est disponible sur cette plage.
53
- </div>
54
- </div>
55
- <div class="modal-footer">
56
- <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Annuler</button>
57
- <button type="submit" class="btn btn-primary" id="ecSubmitBtn">Envoyer la demande</button>
58
- </div>
59
- </form>
60
- </div>
61
- </div>
16
+ <!-- Formulaire de soumission (utilisé par la modale JS) -->
17
+ <form id="ec-create-form" method="post" action="/equipment/reservations/create" class="d-none">
18
+ <input type="hidden" name="_csrf" value="{config.csrf_token}">
19
+ <input type="hidden" name="start" value="">
20
+ <input type="hidden" name="end" value="">
21
+ <input type="hidden" name="itemId" value="">
22
+ <input type="hidden" name="notesUser" value="">
23
+ </form>
62
24
  </div>
63
25
 
64
26
  <script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js"></script>