nodebb-plugin-calendar-onekite 11.2.2 → 11.2.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/client.js +122 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.2.2",
3
+ "version": "11.2.4",
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
@@ -3,6 +3,27 @@
3
3
  define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alerts, bootbox, hooks) {
4
4
  'use strict';
5
5
 
6
+ // Ensure small UI tweaks are applied even when themes override bootstrap defaults.
7
+ (function ensureOneKiteStyles() {
8
+ try {
9
+ if (document.getElementById('onekite-inline-styles')) return;
10
+ const style = document.createElement('style');
11
+ style.id = 'onekite-inline-styles';
12
+ style.textContent = `
13
+ /* Fix clipped time text in selects on desktop */
14
+ .onekite-time-select.form-select {
15
+ min-height: 38px;
16
+ padding-top: .375rem;
17
+ padding-bottom: .375rem;
18
+ line-height: 1.25;
19
+ }
20
+ `;
21
+ document.head.appendChild(style);
22
+ } catch (e) {
23
+ // ignore
24
+ }
25
+ })();
26
+
6
27
  // Prevent the reservation dialog from opening twice due to select/dateClick
7
28
  // interactions or quick re-renders.
8
29
  let isDialogOpen = false;
@@ -16,9 +37,78 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
16
37
  .replace(/'/g, ''');
17
38
  }
18
39
 
40
+ function pad2(n) { return String(n).padStart(2, '0'); }
41
+
42
+ function toDateInputValue(d) {
43
+ const dt = (d instanceof Date) ? d : new Date(d);
44
+ return `${dt.getFullYear()}-${pad2(dt.getMonth() + 1)}-${pad2(dt.getDate())}`;
45
+ }
46
+
47
+ function roundTo5Minutes(dt) {
48
+ const d = new Date(dt);
49
+ const m = d.getMinutes();
50
+ const rounded = Math.round(m / 5) * 5;
51
+ d.setMinutes(rounded, 0, 0);
52
+ return d;
53
+ }
54
+
55
+ function timeString(dt) {
56
+ const d = (dt instanceof Date) ? dt : new Date(dt);
57
+ return `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
58
+ }
59
+
60
+ function buildLocalDatetime(dateStr, timeStr) {
61
+ return `${dateStr}T${timeStr}`;
62
+ }
63
+
64
+ function seTimeOptions(selected, include2359) {
65
+ const opts = [];
66
+ for (let h = 7; h < 24; h++) {
67
+ for (let m = 0; m < 60; m += 5) {
68
+ const t = `${pad2(h)}:${pad2(m)}`;
69
+ opts.push(`<option value="${t}" ${t === selected ? 'selected' : ''}>${t}</option>`);
70
+ }
71
+ }
72
+ if (include2359) {
73
+ const t = '23:59';
74
+ opts.push(`<option value="${t}" ${t === selected ? 'selected' : ''}>${t}</option>`);
75
+ }
76
+ return opts.join('');
77
+ }
78
+
19
79
  async function openSpecialEventDialog(selectionInfo) {
20
80
  const start = selectionInfo.start;
21
81
  const end = selectionInfo.end;
82
+
83
+ // Prefer event times starting at 07:00 for day-clicks (all-day selections).
84
+ let seStart = new Date(start);
85
+ let seEnd = new Date(end);
86
+
87
+ const isAllDayClick = selectionInfo && (selectionInfo.allDay || (
88
+ seStart.getHours() === 0 && seStart.getMinutes() === 0 &&
89
+ seEnd.getHours() === 0 && seEnd.getMinutes() === 0 &&
90
+ Math.abs(seEnd.getTime() - seStart.getTime() - 24 * 60 * 60 * 1000) < 1000
91
+ ));
92
+
93
+ if (isAllDayClick) {
94
+ // Keep same day; default start at 07:00 and end at 23:59
95
+ seStart.setHours(7, 0, 0, 0);
96
+ seEnd = new Date(seStart);
97
+ seEnd.setHours(23, 59, 0, 0);
98
+ } else {
99
+ seStart = roundTo5Minutes(seStart);
100
+ seEnd = roundTo5Minutes(seEnd);
101
+ if (seStart.getHours() < 7) {
102
+ seStart.setHours(7, 0, 0, 0);
103
+ if (seEnd <= seStart) {
104
+ seEnd = new Date(seStart);
105
+ seEnd.setHours(8, 0, 0, 0);
106
+ }
107
+ }
108
+ }
109
+
110
+ const seStartTime = timeString(seStart);
111
+ const seEndTime = timeString(seEnd);
22
112
  const html = `
23
113
  <div class="mb-3">
24
114
  <label class="form-label">Titre</label>
@@ -27,11 +117,25 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
27
117
  <div class="row g-2">
28
118
  <div class="col-12 col-md-6">
29
119
  <label class="form-label">Début</label>
30
- <input type="datetime-local" class="form-control" id="onekite-se-start" value="${escapeHtml(toDatetimeLocalValue(start))}" />
120
+ <div class="row g-2">
121
+ <div class="col-7">
122
+ <input type="date" class="form-control" id="onekite-se-start-date" value="${escapeHtml(toDateInputValue(seStart))}" />
123
+ </div>
124
+ <div class="col-5">
125
+ <select class="form-select onekite-time-select" id="onekite-se-start-time">${seTimeOptions(seStartTime, false)}</select>
126
+ </div>
127
+ </div>
31
128
  </div>
32
129
  <div class="col-12 col-md-6">
33
130
  <label class="form-label">Fin</label>
34
- <input type="datetime-local" class="form-control" id="onekite-se-end" value="${escapeHtml(toDatetimeLocalValue(end))}" />
131
+ <div class="row g-2">
132
+ <div class="col-7">
133
+ <input type="date" class="form-control" id="onekite-se-end-date" value="${escapeHtml(toDateInputValue(seEnd))}" />
134
+ </div>
135
+ <div class="col-5">
136
+ <select class="form-select onekite-time-select" id="onekite-se-end-time">${seTimeOptions(seEndTime, true)}</select>
137
+ </div>
138
+ </div>
35
139
  </div>
36
140
  </div>
37
141
  <div class="mt-3">
@@ -65,8 +169,12 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
65
169
  className: 'btn-primary',
66
170
  callback: () => {
67
171
  const title = (document.getElementById('onekite-se-title')?.value || '').trim();
68
- const startVal = (document.getElementById('onekite-se-start')?.value || '').trim();
69
- const endVal = (document.getElementById('onekite-se-end')?.value || '').trim();
172
+ const sd = (document.getElementById('onekite-se-start-date')?.value || '').trim();
173
+ const st = (document.getElementById('onekite-se-start-time')?.value || '').trim();
174
+ const ed = (document.getElementById('onekite-se-end-date')?.value || '').trim();
175
+ const et = (document.getElementById('onekite-se-end-time')?.value || '').trim();
176
+ const startVal = (sd && st) ? buildLocalDatetime(sd, st) : '';
177
+ const endVal = (ed && et) ? buildLocalDatetime(ed, et) : '';
70
178
  const address = (document.getElementById('onekite-se-address')?.value || '').trim();
71
179
  const notes = (document.getElementById('onekite-se-notes')?.value || '').trim();
72
180
  const lat = (document.getElementById('onekite-se-lat')?.value || '').trim();
@@ -848,7 +956,16 @@ function toDatetimeLocalValue(date) {
848
956
  } else if (code === '409') {
849
957
  showAlert('error', 'Impossible : au moins un matériel est déjà réservé ou en attente sur cette période.');
850
958
  } else {
851
- const msg = payload && (payload.message || payload.error || payload.msg) ? String(payload.message || payload.error || payload.msg) : '';
959
+ const msgRaw = payload && (payload.message || payload.error || payload.msg)
960
+ ? String(payload.message || payload.error || payload.msg)
961
+ : '';
962
+
963
+ // NodeBB can return a plain "not-logged-in" string when the user is not authenticated.
964
+ // We want a user-friendly message consistent with the membership requirement.
965
+ const msg = (/\bnot-logged-in\b/i.test(msgRaw) || /\[\[error:not-logged-in\]\]/i.test(msgRaw))
966
+ ? 'Vous devez être adhérent Onekite'
967
+ : msgRaw;
968
+
852
969
  showAlert('error', msg || ((e && (e.status === 401 || e.status === 403)) ? 'Vous devez être adhérent Onekite' : 'Erreur lors de la création de la demande.'));
853
970
  }
854
971
  calendar.unselect();