nodebb-plugin-calendar-onekite 11.1.87 → 11.1.89

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-calendar-onekite",
3
- "version": "11.1.87",
3
+ "version": "11.1.89",
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
@@ -51,7 +51,7 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
51
51
  `;
52
52
 
53
53
  return await new Promise((resolve) => {
54
- bootbox.dialog({
54
+ const dialog = bootbox.dialog({
55
55
  title: 'Créer un évènement',
56
56
  message: html,
57
57
  buttons: {
@@ -79,13 +79,17 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
79
79
  });
80
80
 
81
81
  // init leaflet
82
+ dialog.on('shown.bs.modal', () => {
82
83
  setTimeout(async () => {
84
+
83
85
  try {
84
86
  const mapEl = document.getElementById('onekite-se-map');
85
87
  if (!mapEl) return;
86
88
  const L = await loadLeaflet();
87
89
  const map = L.map(mapEl).setView([46.5, 2.5], 5);
88
90
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
91
+ setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 100);
92
+ setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 100);
89
93
  let marker = null;
90
94
  function setMarker(lat, lon) {
91
95
  if (marker) map.removeLayer(marker);
@@ -114,7 +118,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
114
118
  } catch (e) {
115
119
  // ignore leaflet errors
116
120
  }
117
- }, 0);
121
+ }, 50);
122
+ });
118
123
  });
119
124
  }
120
125
 
@@ -125,18 +130,21 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
125
130
  ${safeAddr ? `<div class="mb-2">${escapeHtml(safeAddr)}</div>` : ''}
126
131
  <div id="${mapId}" style="height:260px; border:1px solid #ddd; border-radius:6px;"></div>
127
132
  `;
128
- bootbox.dialog({
133
+ const dialog = bootbox.dialog({
129
134
  title: title || 'Carte',
130
135
  message: html,
131
136
  buttons: { close: { label: 'Fermer', className: 'btn-secondary' } },
132
137
  });
138
+ dialog.on('shown.bs.modal', () => {
133
139
  setTimeout(async () => {
140
+
134
141
  try {
135
142
  const el = document.getElementById(mapId);
136
143
  if (!el) return;
137
144
  const L = await loadLeaflet();
138
145
  const map = L.map(el);
139
146
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '&copy; OpenStreetMap' }).addTo(map);
147
+ setTimeout(() => { try { map.invalidateSize(); } catch (e) {} }, 100);
140
148
 
141
149
  async function setAt(lat2, lon2) {
142
150
  map.setView([lat2, lon2], 14);
@@ -159,7 +167,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
159
167
  } catch (e) {
160
168
  // ignore leaflet errors
161
169
  }
162
- }, 0);
170
+ }, 50);
171
+ });
163
172
  }
164
173
 
165
174
  // Click handler for map links in popups
@@ -342,7 +351,14 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
342
351
  }
343
352
 
344
353
 
345
- function toDatetimeLocalValue(date) {
354
+
355
+ function toLocalYmd(date) {
356
+ const d = new Date(date);
357
+ const pad = (n) => String(n).padStart(2, '0');
358
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
359
+ }
360
+
361
+ function toDatetimeLocalValue(date) {
346
362
  const d = new Date(date);
347
363
  const pad = (n) => String(n).padStart(2, '0');
348
364
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
@@ -474,14 +490,70 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
474
490
 
475
491
  let mode = 'reservation'; // or 'special'
476
492
 
493
+ // Inject lightweight responsive CSS once.
494
+ try {
495
+ const styleId = 'onekite-responsive-css';
496
+ if (!document.getElementById(styleId)) {
497
+ const st = document.createElement('style');
498
+ st.id = styleId;
499
+ st.textContent = `
500
+ /* Prevent accidental horizontal scroll on small screens */
501
+ #onekite-calendar, #onekite-calendar * { box-sizing: border-box; }
502
+ #onekite-calendar { width: 100%; max-width: 100%; overflow-x: hidden; }
503
+
504
+ @media (max-width: 576px) {
505
+ #onekite-calendar .fc .fc-toolbar-title { font-size: 1.05rem; }
506
+ #onekite-calendar .fc .fc-button { padding: .25rem .45rem; font-size: .85rem; }
507
+ #onekite-calendar .fc .fc-daygrid-day-number { font-size: .8rem; }
508
+ #onekite-calendar .fc .fc-daygrid-event { font-size: .75rem; }
509
+ #onekite-calendar .fc .fc-timegrid-event { font-size: .75rem; }
510
+ #onekite-calendar .fc .fc-col-header-cell-cushion { font-size: .8rem; }
511
+ }
512
+
513
+ @media (max-width: 576px) and (orientation: landscape) {
514
+ #onekite-calendar .fc .fc-toolbar-title { font-size: 1rem; }
515
+ #onekite-calendar .fc .fc-button { padding: .2rem .4rem; font-size: .8rem; }
516
+ }
517
+ `;
518
+ document.head.appendChild(st);
519
+ }
520
+ } catch (e) {}
521
+
522
+ function isMobileNow() {
523
+ return !!(window.matchMedia && window.matchMedia('(max-width: 576px)').matches);
524
+ }
525
+
526
+ function isLandscapeNow() {
527
+ return !!(window.matchMedia && window.matchMedia('(orientation: landscape)').matches);
528
+ }
529
+
530
+ function computeAspectRatio() {
531
+ const mobile = isMobileNow();
532
+ if (!mobile) return 1.35;
533
+ return isLandscapeNow() ? 1.6 : 0.9;
534
+ }
535
+
536
+ const headerToolbar = isMobileNow() ? {
537
+ left: 'prev,next',
538
+ center: 'title',
539
+ right: 'today',
540
+ } : {
541
+ left: 'prev,next today',
542
+ center: 'title',
543
+ // Only month + week (no day view)
544
+ right: (canCreateSpecial ? 'newSpecial ' : '') + 'dayGridMonth,timeGridWeek',
545
+ };
546
+
477
547
  const calendar = new FullCalendar.Calendar(el, {
478
548
  initialView: 'dayGridMonth',
549
+ height: 'auto',
550
+ contentHeight: 'auto',
551
+ aspectRatio: computeAspectRatio(),
552
+ dayMaxEvents: true,
479
553
  locale: 'fr',
480
- headerToolbar: {
481
- left: 'prev,next today',
482
- center: 'title',
483
- right: (canCreateSpecial ? 'newSpecial ' : '') + 'dayGridMonth,timeGridWeek,timeGridDay',
484
- },
554
+ headerToolbar: headerToolbar,
555
+ // Keep titles short on mobile to avoid horizontal overflow
556
+ titleFormat: isMobileNow() ? { year: 'numeric', month: 'short' } : undefined,
485
557
  customButtons: canCreateSpecial ? {
486
558
  newSpecial: {
487
559
  text: 'Évènement',
@@ -547,8 +619,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
547
619
  return;
548
620
  }
549
621
  // Send date strings (no hours) so reservations are day-based.
550
- const startDate = new Date(info.start).toISOString().slice(0, 10);
551
- const endDate = new Date(info.end).toISOString().slice(0, 10);
622
+ const startDate = toLocalYmd(info.start);
623
+ const endDate = toLocalYmd(info.end);
552
624
  await requestReservation({
553
625
  start: startDate,
554
626
  end: endDate,
@@ -893,6 +965,92 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
893
965
  try { window.oneKiteCalendar = calendar; } catch (e) {}
894
966
 
895
967
  calendar.render();
968
+
969
+ // Mobile controls: view (month/week) + mode (reservation/event) without bloating the header.
970
+ try {
971
+ const controlsId = 'onekite-mobile-controls';
972
+ const old = document.getElementById(controlsId);
973
+ if (old) old.remove();
974
+
975
+ if (isMobileNow()) {
976
+ const controls = document.createElement('div');
977
+ controls.id = controlsId;
978
+ controls.className = 'mt-2 d-flex flex-wrap gap-2 align-items-center';
979
+
980
+ const viewGroup = document.createElement('div');
981
+ viewGroup.className = 'btn-group btn-group-sm';
982
+ viewGroup.setAttribute('role', 'group');
983
+
984
+ const btnMonth = document.createElement('button');
985
+ btnMonth.type = 'button';
986
+ btnMonth.className = 'btn btn-outline-secondary';
987
+ btnMonth.textContent = 'Mois';
988
+
989
+ const btnWeek = document.createElement('button');
990
+ btnWeek.type = 'button';
991
+ btnWeek.className = 'btn btn-outline-secondary';
992
+ btnWeek.textContent = 'Semaine';
993
+
994
+ function refreshViewButtons() {
995
+ const v = calendar.view && calendar.view.type;
996
+ btnMonth.classList.toggle('active', v === 'dayGridMonth');
997
+ btnWeek.classList.toggle('active', v === 'timeGridWeek');
998
+ }
999
+
1000
+ btnMonth.addEventListener('click', () => {
1001
+ calendar.changeView('dayGridMonth');
1002
+ refreshViewButtons();
1003
+ });
1004
+ btnWeek.addEventListener('click', () => {
1005
+ calendar.changeView('timeGridWeek');
1006
+ refreshViewButtons();
1007
+ });
1008
+
1009
+ viewGroup.appendChild(btnMonth);
1010
+ viewGroup.appendChild(btnWeek);
1011
+ controls.appendChild(viewGroup);
1012
+
1013
+ if (canCreateSpecial) {
1014
+ const modeBtn = document.createElement('button');
1015
+ modeBtn.type = 'button';
1016
+ modeBtn.className = 'btn btn-sm btn-outline-primary';
1017
+ function refreshModeBtn() {
1018
+ const isSpecial = mode === 'special';
1019
+ modeBtn.textContent = isSpecial ? 'Mode évènement ✓' : 'Mode évènement';
1020
+ modeBtn.classList.toggle('active', isSpecial);
1021
+ }
1022
+ modeBtn.addEventListener('click', () => {
1023
+ mode = (mode === 'special') ? 'reservation' : 'special';
1024
+ refreshModeBtn();
1025
+ if (mode === 'special') {
1026
+ showAlert('success', 'Mode évènement : sélectionne une plage (date/heure) sur le calendrier.');
1027
+ }
1028
+ });
1029
+ refreshModeBtn();
1030
+ controls.appendChild(modeBtn);
1031
+ }
1032
+
1033
+ el.parentNode && el.parentNode.insertBefore(controls, el.nextSibling);
1034
+ refreshViewButtons();
1035
+ }
1036
+ } catch (e) {}
1037
+
1038
+ // Update sizing when rotating/resizing (especially on mobile landscape).
1039
+ try {
1040
+ let lastMobile = isMobileNow();
1041
+ let lastLandscape = isLandscapeNow();
1042
+ window.addEventListener('resize', () => {
1043
+ const mobile = isMobileNow();
1044
+ const landscape = isLandscapeNow();
1045
+ if (mobile !== lastMobile || landscape !== lastLandscape) {
1046
+ lastMobile = mobile;
1047
+ lastLandscape = landscape;
1048
+ calendar.setOption('aspectRatio', computeAspectRatio());
1049
+ calendar.setOption('titleFormat', mobile ? { year: 'numeric', month: 'short' } : undefined);
1050
+ }
1051
+ try { calendar.updateSize(); } catch (err) {}
1052
+ });
1053
+ } catch (e) {}
896
1054
  }
897
1055
 
898
1056
  // Auto-init on /calendar when ajaxify finishes rendering.
@@ -48,6 +48,12 @@
48
48
  <input class="form-control" name="pendingHoldMinutes" placeholder="5">
49
49
  </div>
50
50
 
51
+ <div class="mb-3">
52
+ <label class="form-label">Délai rappel paiement (minutes)</label>
53
+ <input class="form-control" name="paymentHoldMinutes" placeholder="60">
54
+ <div class="form-text">Après validation (statut <code>paiement en attente</code>), un rappel est envoyé après ce délai. La réservation est ensuite expirée après <strong>2×</strong> ce délai.</div>
55
+ </div>
56
+
51
57
  <h4 class="mt-4">HelloAsso</h4>
52
58
 
53
59
  <div class="mb-3">
@@ -15,3 +15,22 @@
15
15
  No inline require() here.
16
16
  The plugin's forum script auto-initialises on the calendar page via ajaxify.
17
17
  -->
18
+
19
+
20
+ <style>
21
+ /* Mobile tweaks for FullCalendar */
22
+ @media (max-width: 576px) {
23
+ #onekite-calendar { margin-top: .5rem !important; }
24
+ .fc .fc-toolbar { flex-wrap: wrap; gap: .25rem; }
25
+ .fc .fc-toolbar-title { font-size: 1.05rem; line-height: 1.2; }
26
+ .fc .fc-button { padding: .25rem .45rem; font-size: .75rem; }
27
+ .fc .fc-button .fc-icon { font-size: .9em; }
28
+ .fc .fc-daygrid-day-number { font-size: .75rem; padding: 2px; }
29
+ .fc .fc-col-header-cell-cushion { font-size: .75rem; padding: 4px 0; }
30
+ .fc .fc-event-title { font-size: .72rem; }
31
+ /* Avoid horizontal scroll */
32
+ .fc { overflow-x: hidden; }
33
+ .fc .fc-scrollgrid, .fc .fc-scrollgrid table { width: 100% !important; }
34
+ }
35
+ </style>
36
+