nodebb-plugin-calendar-onekite 11.1.73 → 11.1.75

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/api.js CHANGED
@@ -15,15 +15,12 @@ const helloasso = require('./helloasso');
15
15
  async function sendEmail(template, toEmail, subject, data) {
16
16
  if (!toEmail) return;
17
17
  const emailer = require.main.require('./src/emailer');
18
+ const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
18
19
  try {
19
- // Newer NodeBB builds expose sendToEmail
20
+ // NodeBB's Emailer API differs across versions; the most reliable approach is to pass `subject` inside `data`.
20
21
  if (typeof emailer.sendToEmail === 'function') {
21
- if (emailer.sendToEmail.length >= 4) {
22
- await emailer.sendToEmail(template, toEmail, subject, data);
23
- return;
24
- }
25
- if (emailer.sendToEmail.length === 3) {
26
- const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
22
+ // Prefer 3-args (template, email, data) when possible
23
+ if (emailer.sendToEmail.length >= 3) {
27
24
  await emailer.sendToEmail(template, toEmail, dataWithSubject);
28
25
  return;
29
26
  }
@@ -32,28 +29,20 @@ async function sendEmail(template, toEmail, subject, data) {
32
29
  return;
33
30
  }
34
31
  if (typeof emailer.send === 'function') {
35
- // Common: (template, email, subject, data)
36
- if (emailer.send.length >= 4) {
37
- await emailer.send(template, toEmail, subject, data);
38
- return;
39
- }
40
- // Some builds: (template, email, data)
41
- // In that case, subject is expected inside `data.subject`.
42
- if (emailer.send.length === 3) {
43
- const dataWithSubject = Object.assign({}, data || {}, subject ? { subject } : {});
32
+ if (emailer.send.length >= 3) {
44
33
  await emailer.send(template, toEmail, dataWithSubject);
45
34
  return;
46
35
  }
47
- // Fallback: try 4-args anyway
48
36
  await emailer.send(template, toEmail, subject, data);
49
37
  return;
50
38
  }
51
39
  } catch (err) {
52
40
  // eslint-disable-next-line no-console
53
- console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err && err.message || err) });
41
+ console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String((err && err.message) || err) });
54
42
  }
55
43
  }
56
44
 
45
+
57
46
  function normalizeBaseUrl(meta) {
58
47
  // Prefer meta.config.url, fallback to nconf.get('url')
59
48
  let base = (meta && meta.config && (meta.config.url || meta.config['url'])) ? (meta.config.url || meta.config['url']) : '';
@@ -239,7 +228,12 @@ function eventsForSpecial(ev) {
239
228
  allDay: false,
240
229
  start: startIso,
241
230
  end: endIso,
242
- color: '#8e44ad',
231
+ // In month (dayGrid) view, timed events default to a "list-item" rendering (dot only).
232
+ // Force a block rendering so the event stays visually distinct (purple background).
233
+ display: 'block',
234
+ backgroundColor: '#8e44ad',
235
+ borderColor: '#8e44ad',
236
+ textColor: '#ffffff',
243
237
  extendedProps: {
244
238
  type: 'special',
245
239
  eid: ev.eid,
@@ -507,7 +501,7 @@ api.createReservation = async function (req, res) {
507
501
  await sendEmail(
508
502
  'calendar-onekite_pending',
509
503
  md.email,
510
- 'Location matériel - Demande de réservation',
504
+ 'Location - Demande de réservation',
511
505
  {
512
506
  username: md.username,
513
507
  requester: requester.username,
@@ -605,7 +599,7 @@ api.approveReservation = async function (req, res) {
605
599
  const mapUrl = (Number.isFinite(latNum) && Number.isFinite(lonNum))
606
600
  ? `https://www.openstreetmap.org/?mlat=${encodeURIComponent(String(latNum))}&mlon=${encodeURIComponent(String(lonNum))}#map=18/${encodeURIComponent(String(latNum))}/${encodeURIComponent(String(lonNum))}`
607
601
  : '';
608
- await sendEmail('calendar-onekite_approved', requester.email, 'Location matériel - Réservation validée', {
602
+ await sendEmail('calendar-onekite_approved', requester.email, 'Location - Réservation validée', {
609
603
  username: requester.username,
610
604
  itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
611
605
  itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
@@ -641,7 +635,7 @@ api.refuseReservation = async function (req, res) {
641
635
 
642
636
  const requester = await user.getUserFields(r.uid, ['username', 'email']);
643
637
  if (requester && requester.email) {
644
- await sendEmail('calendar-onekite_refused', requester.email, 'Location matériel - Demande de réservation', {
638
+ await sendEmail('calendar-onekite_refused', requester.email, 'Location - Demande de réservation', {
645
639
  username: requester.username,
646
640
  itemName: (Array.isArray(r.itemNames) ? r.itemNames.join(', ') : (r.itemName || '')),
647
641
  itemNames: (Array.isArray(r.itemNames) ? r.itemNames : (r.itemName ? [r.itemName] : [])),
package/library.js CHANGED
@@ -77,12 +77,12 @@ Plugin.renderMiniWidget = async function (hookData, callback) {
77
77
  // Note: We render client-side to avoid extra server-side queries. The widget fetches
78
78
  // events via the existing public API endpoint.
79
79
  widget.html = `
80
- <div class="calendar-onekite-widget" id="${widgetId}">
80
+ <div class="calendar-onekite-widget" id="${widgetId}" data-show-special="${showSpecial ? '1' : '0'}">
81
81
  <div class="calendar-onekite-widget__header">
82
82
  <strong>${escapeHtml(title)}</strong>
83
83
  <a class="calendar-onekite-widget__link" href="/calendar">Ouvrir</a>
84
84
  </div>
85
- <div class="calendar-onekite-widget__month" data-show-special="${showSpecial ? '1' : '0'}"></div>
85
+ <div class="calendar-onekite-widget__month"></div>
86
86
  <div class="calendar-onekite-widget__legend">
87
87
  <span class="calendar-onekite-dot calendar-onekite-dot--pending"></span> en attente
88
88
  <span class="calendar-onekite-dot calendar-onekite-dot--paid"></span> payée
@@ -108,87 +108,6 @@ Plugin.renderMiniWidget = async function (hookData, callback) {
108
108
  .calendar-onekite-badge--special{background:#fd7e14}
109
109
  .calendar-onekite-widget__legend{margin-top:8px;font-size:12px;opacity:.8}
110
110
  </style>
111
- <script>
112
- (function(){
113
- const widgetEl = document.getElementById(${JSON.stringify(widgetId)});
114
- if (!widgetEl) return;
115
-
116
- const monthEl = widgetEl.querySelector('.calendar-onekite-widget__month');
117
- const showSpecial = monthEl.getAttribute('data-show-special') === '1';
118
-
119
- const now = new Date();
120
- const year = now.getFullYear();
121
- const month = now.getMonth();
122
- const first = new Date(year, month, 1);
123
- const last = new Date(year, month + 1, 0);
124
- const start = new Date(year, month, 1);
125
- start.setDate(start.getDate() - ((start.getDay() + 6) % 7)); // Monday-start
126
- const end = new Date(start);
127
- end.setDate(end.getDate() + 41);
128
-
129
- function iso(d){ return d.toISOString(); }
130
- function ymd(d){ return d.toISOString().slice(0,10); }
131
-
132
- const apiUrl = window.location.origin + '/api/v3/plugins/calendar-onekite/events?from=' + encodeURIComponent(iso(start)) + '&to=' + encodeURIComponent(iso(end));
133
-
134
- fetch(apiUrl, { credentials: 'same-origin' })
135
- .then(r => r.ok ? r.json() : Promise.reject(r))
136
- .then(payload => {
137
- const events = Array.isArray(payload) ? payload : (payload.events || payload.data || []);
138
- const dayMap = new Map();
139
- for (const ev of events) {
140
- if (!ev || !ev.start) continue;
141
- const d = new Date(ev.start);
142
- const key = ymd(d);
143
- const props = ev.extendedProps || {};
144
- const isSpecial = props.type === 'special' || ev.type === 'special' || (ev.classNames || []).includes('onekite-special');
145
- if (isSpecial && !showSpecial) continue;
146
- const status = props.status || ev.status || '';
147
- const bucket = dayMap.get(key) || { pending: 0, paid: 0, special: 0 };
148
- if (isSpecial) bucket.special++;
149
- else if (status === 'paid') bucket.paid++;
150
- else bucket.pending++;
151
- dayMap.set(key, bucket);
152
- }
153
- render(dayMap);
154
- })
155
- .catch(() => render(new Map()));
156
-
157
- function render(dayMap){
158
- const grid = document.createElement('div');
159
- grid.className = 'calendar-onekite-widget__grid';
160
- const labels = ['L','M','M','J','V','S','D'];
161
- for (const l of labels){
162
- const el = document.createElement('div');
163
- el.className = 'calendar-onekite-widget__cell calendar-onekite-widget__cell--muted';
164
- el.textContent = l;
165
- grid.appendChild(el);
166
- }
167
- for (let i=0;i<42;i++){
168
- const d = new Date(start);
169
- d.setDate(start.getDate()+i);
170
- const cell = document.createElement('a');
171
- cell.href = '/calendar';
172
- cell.className = 'calendar-onekite-widget__cell';
173
- if (d.getMonth() !== month) cell.classList.add('calendar-onekite-widget__cell--muted');
174
- if (ymd(d) === ymd(now)) cell.classList.add('calendar-onekite-widget__cell--today');
175
- cell.textContent = d.getDate();
176
- const b = dayMap.get(ymd(d));
177
- if (b && (b.pending || b.paid || b.special)){
178
- const badges = document.createElement('div');
179
- badges.className = 'calendar-onekite-badges';
180
- if (b.pending) { const s=document.createElement('span'); s.className='calendar-onekite-badge calendar-onekite-badge--pending'; badges.appendChild(s); }
181
- if (b.paid) { const s=document.createElement('span'); s.className='calendar-onekite-badge calendar-onekite-badge--paid'; badges.appendChild(s); }
182
- if (showSpecial && b.special) { const s=document.createElement('span'); s.className='calendar-onekite-badge calendar-onekite-badge--special'; badges.appendChild(s); }
183
- cell.appendChild(badges);
184
- }
185
- grid.appendChild(cell);
186
- }
187
- monthEl.innerHTML = '';
188
- monthEl.appendChild(grid);
189
- }
190
- })();
191
- </script>
192
111
  `;
193
112
 
194
113
  hookData.widget = widget;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.1.73",
3
+ "version": "11.1.75",
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
@@ -37,5 +37,5 @@
37
37
  "acpScripts": [
38
38
  "public/admin.js"
39
39
  ],
40
- "version": "1.0.48"
40
+ "version": "1.0.49"
41
41
  }
package/public/client.js CHANGED
@@ -18,7 +18,15 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
18
18
 
19
19
  async function openSpecialEventDialog(selectionInfo) {
20
20
  const start = selectionInfo.start;
21
- const end = selectionInfo.end;
21
+ let end = selectionInfo.end;
22
+ // FullCalendar all-day selection uses an exclusive end (next day 00:00). For a single-day click,
23
+ // this looks like a 2-day range. Default to a 1-hour duration in that case.
24
+ try {
25
+ if (selectionInfo && selectionInfo.allDay && start && end && (end.getTime() - start.getTime()) <= 86400000 + 1000) {
26
+ end = new Date(start.getTime() + 60 * 60 * 1000);
27
+ }
28
+ } catch (e) {}
29
+
22
30
  const html = `
23
31
  <div class="mb-3">
24
32
  <label class="form-label">Titre</label>
@@ -51,7 +59,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
51
59
  `;
52
60
 
53
61
  return await new Promise((resolve) => {
54
- bootbox.dialog({
62
+ const dlg = bootbox.dialog({
55
63
  title: 'Créer un évènement',
56
64
  message: html,
57
65
  buttons: {
@@ -78,14 +86,18 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
78
86
  },
79
87
  });
80
88
 
81
- // init leaflet
82
- setTimeout(async () => {
89
+ // init leaflet once the modal is visible (Leaflet needs a laid-out container)
90
+ dlg.on('shown.bs.modal', async () => {
83
91
  try {
84
92
  const mapEl = document.getElementById('onekite-se-map');
85
93
  if (!mapEl) return;
86
94
  const L = await loadLeaflet();
87
95
  const map = L.map(mapEl).setView([46.5, 2.5], 5);
88
96
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '&copy; OpenStreetMap' }).addTo(map);
97
+
98
+ setTimeout(() => {
99
+ try { map.invalidateSize(); } catch (e) {}
100
+ }, 50);
89
101
  let marker = null;
90
102
  function setMarker(lat, lon) {
91
103
  if (marker) map.removeLayer(marker);
@@ -114,7 +126,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
114
126
  } catch (e) {
115
127
  // ignore leaflet errors
116
128
  }
117
- }, 0);
129
+ });
118
130
  });
119
131
  }
120
132
 
@@ -125,12 +137,12 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
125
137
  ${safeAddr ? `<div class="mb-2">${escapeHtml(safeAddr)}</div>` : ''}
126
138
  <div id="${mapId}" style="height:260px; border:1px solid #ddd; border-radius:6px;"></div>
127
139
  `;
128
- bootbox.dialog({
140
+ const dlg = bootbox.dialog({
129
141
  title: title || 'Carte',
130
142
  message: html,
131
143
  buttons: { close: { label: 'Fermer', className: 'btn-secondary' } },
132
144
  });
133
- setTimeout(async () => {
145
+ dlg.on('shown.bs.modal', async () => {
134
146
  try {
135
147
  const el = document.getElementById(mapId);
136
148
  if (!el) return;
@@ -156,10 +168,14 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
156
168
  }
157
169
  }
158
170
  map.setView([46.5, 2.5], 5);
171
+
172
+ setTimeout(() => {
173
+ try { map.invalidateSize(); } catch (e) {}
174
+ }, 50);
159
175
  } catch (e) {
160
176
  // ignore leaflet errors
161
177
  }
162
- }, 0);
178
+ });
163
179
  }
164
180
 
165
181
  // Click handler for map links in popups
@@ -536,9 +552,10 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
536
552
  isDialogOpen = false;
537
553
  return;
538
554
  }
539
- // Send date strings (no hours) so reservations are day-based.
540
- const startDate = new Date(info.start).toISOString().slice(0, 10);
541
- const endDate = new Date(info.end).toISOString().slice(0, 10);
555
+ // Send date-only strings from FullCalendar to avoid timezone shifts.
556
+ // For all-day selections, startStr/endStr are YYYY-MM-DD (end is exclusive).
557
+ const startDate = info.startStr;
558
+ const endDate = info.endStr;
542
559
  await requestReservation({
543
560
  start: startDate,
544
561
  end: endDate,
@@ -806,6 +823,12 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
806
823
  // Default view (France-ish)
807
824
  map.setView([46.7, 2.5], 5);
808
825
 
826
+ // Bootbox/Bootstrap modal layouts can report a zero-sized container on first paint.
827
+ // Force Leaflet to recompute sizes once the modal is fully displayed.
828
+ setTimeout(() => {
829
+ try { map.invalidateSize(); } catch (e) {}
830
+ }, 50);
831
+
809
832
  let marker = null;
810
833
  function setMarker(lat, lon, zoom) {
811
834
  const ll = [lat, lon];
@@ -903,6 +926,95 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
903
926
  // call once after current tick.
904
927
  setTimeout(() => autoInit({ template: (ajaxify && ajaxify.data && ajaxify.data.template) || { name: '' } }), 0);
905
928
 
929
+ // Render mini widgets (ACP Widgets) without inline scripts (CSP-friendly).
930
+ async function renderMiniWidgets() {
931
+ const widgets = Array.from(document.querySelectorAll('.calendar-onekite-widget'));
932
+ if (!widgets.length) return;
933
+
934
+ const now = new Date();
935
+ const year = now.getFullYear();
936
+ const month = now.getMonth();
937
+
938
+ const start = new Date(year, month, 1);
939
+ start.setDate(start.getDate() - ((start.getDay() + 6) % 7)); // Monday-start
940
+ const end = new Date(start);
941
+ end.setDate(end.getDate() + 41);
942
+
943
+ function iso(d){ return d.toISOString(); }
944
+ function ymd(d){ return d.toISOString().slice(0, 10); }
945
+
946
+ let events = [];
947
+ try {
948
+ const qs = new URLSearchParams({ start: iso(start), end: iso(end) });
949
+ events = await fetchJson(`/api/v3/plugins/calendar-onekite/events?${qs.toString()}`)
950
+ .catch(() => fetchJson(`/api/plugins/calendar-onekite/events?${qs.toString()}`));
951
+ } catch (e) {
952
+ events = [];
953
+ }
954
+
955
+ const dayMap = new Map();
956
+ for (const ev of (events || [])) {
957
+ if (!ev || !ev.start) continue;
958
+ const d = new Date(ev.start);
959
+ const key = ymd(d);
960
+ const props = ev.extendedProps || {};
961
+ const isSpecial = props.type === 'special' || ev.type === 'special' || (ev.classNames || []).includes('onekite-special');
962
+ const status = props.status || ev.status || '';
963
+ const bucket = dayMap.get(key) || { pending: 0, paid: 0, special: 0 };
964
+ if (isSpecial) bucket.special++;
965
+ else if (status === 'paid') bucket.paid++;
966
+ else bucket.pending++;
967
+ dayMap.set(key, bucket);
968
+ }
969
+
970
+ for (const widgetEl of widgets) {
971
+ const monthEl = widgetEl.querySelector('.calendar-onekite-widget__month');
972
+ if (!monthEl) continue;
973
+ const showSpecial = widgetEl.getAttribute('data-show-special') === '1';
974
+
975
+ const grid = document.createElement('div');
976
+ grid.className = 'calendar-onekite-widget__grid';
977
+ const labels = ['L','M','M','J','V','S','D'];
978
+ for (const l of labels) {
979
+ const el = document.createElement('div');
980
+ el.className = 'calendar-onekite-widget__cell calendar-onekite-widget__cell--muted';
981
+ el.textContent = l;
982
+ grid.appendChild(el);
983
+ }
984
+ for (let i = 0; i < 42; i++) {
985
+ const d = new Date(start);
986
+ d.setDate(start.getDate() + i);
987
+ const cell = document.createElement('a');
988
+ cell.href = '/calendar';
989
+ cell.className = 'calendar-onekite-widget__cell';
990
+ if (d.getMonth() !== month) cell.classList.add('calendar-onekite-widget__cell--muted');
991
+ if (ymd(d) === ymd(now)) cell.classList.add('calendar-onekite-widget__cell--today');
992
+ cell.textContent = d.getDate();
993
+
994
+ const b = dayMap.get(ymd(d));
995
+ if (b && (b.pending || b.paid || (showSpecial && b.special))) {
996
+ const badges = document.createElement('div');
997
+ badges.className = 'calendar-onekite-badges';
998
+ if (b.pending) { const s = document.createElement('span'); s.className = 'calendar-onekite-badge calendar-onekite-badge--pending'; badges.appendChild(s); }
999
+ if (b.paid) { const s = document.createElement('span'); s.className = 'calendar-onekite-badge calendar-onekite-badge--paid'; badges.appendChild(s); }
1000
+ if (showSpecial && b.special) { const s = document.createElement('span'); s.className = 'calendar-onekite-badge calendar-onekite-badge--special'; badges.appendChild(s); }
1001
+ cell.appendChild(badges);
1002
+ }
1003
+
1004
+ grid.appendChild(cell);
1005
+ }
1006
+
1007
+ monthEl.innerHTML = '';
1008
+ monthEl.appendChild(grid);
1009
+ }
1010
+ }
1011
+
1012
+ // Re-render widgets on every page load/end.
1013
+ if (hooks && typeof hooks.on === 'function') {
1014
+ hooks.on('action:ajaxify.end', () => { renderMiniWidgets().catch(() => {}); });
1015
+ }
1016
+ setTimeout(() => { renderMiniWidgets().catch(() => {}); }, 0);
1017
+
906
1018
 
907
1019
 
908
1020
  // Live refresh when a reservation changes (e.g., payment confirmed by webhook)
@@ -6,25 +6,25 @@
6
6
 
7
7
  <ul class="nav nav-tabs mt-3" role="tablist">
8
8
  <li class="nav-item" role="presentation">
9
- <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#onekite-tab-settings" type="button" role="tab">Locations</button>
9
+ <button class="nav-link active" data-bs-toggle="tab" data-bs-target="#onekite-tab-settings" type="button" role="tab" aria-controls="onekite-tab-settings" aria-selected="true">Locations</button>
10
10
  </li>
11
11
  <li class="nav-item" role="presentation">
12
- <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-events" type="button" role="tab">Évènements</button>
12
+ <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-events" type="button" role="tab" aria-controls="onekite-tab-events" aria-selected="false">Évènements</button>
13
13
  </li>
14
14
  <li class="nav-item" role="presentation">
15
- <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-pending" type="button" role="tab">Demandes en attente</button>
15
+ <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-pending" type="button" role="tab" aria-controls="onekite-tab-pending" aria-selected="false">Demandes en attente</button>
16
16
  </li>
17
17
  <li class="nav-item" role="presentation">
18
- <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-debug" type="button" role="tab">Debug HelloAsso</button>
18
+ <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-debug" type="button" role="tab" aria-controls="onekite-tab-debug" aria-selected="false">Debug HelloAsso</button>
19
19
  </li>
20
20
  <li class="nav-item" role="presentation">
21
- <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-accounting" type="button" role="tab">Comptabilisation</button>
21
+ <button class="nav-link" data-bs-toggle="tab" data-bs-target="#onekite-tab-accounting" type="button" role="tab" aria-controls="onekite-tab-accounting" aria-selected="false">Comptabilisation</button>
22
22
  </li>
23
23
  </ul>
24
24
 
25
25
  <div class="tab-content pt-3">
26
- <form id="onekite-settings-form" class="mt-1">
27
26
  <div class="tab-pane fade show active" id="onekite-tab-settings" role="tabpanel">
27
+ <div id="onekite-settings-container" class="mt-1">
28
28
  <h4>Groupes</h4>
29
29
  <div class="mb-3">
30
30
  <label class="form-label">Groupes autorisés à créer une demande (csv)</label>
@@ -87,9 +87,11 @@
87
87
  <label class="form-label">Form Slug</label>
88
88
  <input class="form-control" name="helloassoFormSlug">
89
89
  </div>
90
+ </div>
90
91
  </div>
91
92
 
92
93
  <div class="tab-pane fade" id="onekite-tab-events" role="tabpanel">
94
+ <div id="onekite-events-container" class="mt-1">
93
95
  <h4>Évènements (autre couleur)</h4>
94
96
  <div class="form-text mb-3">Permet de créer des évènements horaires (début/fin) avec adresse (Leaflet) et notes.</div>
95
97
 
@@ -110,30 +112,34 @@
110
112
  <button type="button" class="btn btn-outline-danger" id="onekite-se-purge">Purger</button>
111
113
  </div>
112
114
  <div class="form-text mt-2">Supprime définitivement tous les évènements dont la date de début est dans l'année sélectionnée.</div>
115
+ </div>
113
116
  </div>
114
117
 
115
- </form>
116
-
117
118
  <div class="tab-pane fade" id="onekite-tab-pending" role="tabpanel">
118
- <h4>Demandes en attente</h4>
119
- <div id="onekite-pending" class="list-group"></div>
119
+ <div class="mt-1">
120
+ <h4>Demandes en attente</h4>
121
+ <div id="onekite-pending" class="list-group"></div>
120
122
 
121
123
  <hr class="my-4" />
122
124
 
123
- <h4>Purge</h4>
124
- <div class="d-flex gap-2 align-items-center">
125
- <input class="form-control" style="max-width: 160px;" id="onekite-purge-year" placeholder="YYYY">
126
- <button type="button" class="btn btn-outline-danger" id="onekite-purge">Purger</button>
125
+ <h4>Purge</h4>
126
+ <div class="d-flex gap-2 align-items-center">
127
+ <input class="form-control" style="max-width: 160px;" id="onekite-purge-year" placeholder="YYYY">
128
+ <button type="button" class="btn btn-outline-danger" id="onekite-purge">Purger</button>
129
+ </div>
127
130
  </div>
128
131
  </div>
129
132
 
130
133
  <div class="tab-pane fade" id="onekite-tab-debug" role="tabpanel">
131
- <p class="text-muted">Teste la récupération du token et la liste du matériel (catalogue).</p>
132
- <button type="button" class="btn btn-secondary me-2" id="onekite-debug-run">Tester le chargement du matériel</button>
133
- <pre id="onekite-debug-output" class="mt-3 p-3 bg-light" style="max-height: 360px; overflow: auto;"></pre>
134
+ <div class="mt-1">
135
+ <p class="text-muted">Teste la récupération du token et la liste du matériel (catalogue).</p>
136
+ <button type="button" class="btn btn-secondary me-2" id="onekite-debug-run">Tester le chargement du matériel</button>
137
+ <pre id="onekite-debug-output" class="mt-3 p-3 bg-light" style="max-height: 360px; overflow: auto;"></pre>
138
+ </div>
134
139
  </div>
135
140
 
136
141
  <div class="tab-pane fade" id="onekite-tab-accounting" role="tabpanel">
142
+ <div class="mt-1">
137
143
  <h4>Comptabilisation des locations (payées)</h4>
138
144
  <div class="d-flex flex-wrap gap-2 align-items-end mb-3">
139
145
  <div>
@@ -170,6 +176,7 @@
170
176
  <tbody></tbody>
171
177
  </table>
172
178
  </div>
179
+ </div>
173
180
  </div>
174
181
  </div>
175
182
  </div>
@@ -183,4 +190,4 @@
183
190
  });
184
191
  </script>
185
192
 
186
- <!-- IMPORT admin/partials/settings/footer.tpl -->
193
+ <!-- IMPORT admin/partials/settings/footer.tpl -->