nodebb-plugin-calendar-onekite 11.2.13 → 11.2.15
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 +1 -1
- package/public/admin.js +156 -185
- package/public/client.js +36 -12
- package/templates/admin/plugins/calendar-onekite.tpl +8 -8
package/package.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts, bootbox) {
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
// Cache of pending reservations keyed by rid so delegated click handlers
|
|
6
|
+
// can open rich modals without embedding large JSON blobs into the DOM.
|
|
7
|
+
const pendingCache = new Map();
|
|
8
|
+
|
|
5
9
|
function showAlert(type, msg) {
|
|
6
10
|
try {
|
|
7
11
|
if (alerts && typeof alerts[type] === 'function') {
|
|
@@ -187,6 +191,8 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
187
191
|
if (!wrap) return;
|
|
188
192
|
wrap.innerHTML = '';
|
|
189
193
|
|
|
194
|
+
pendingCache.clear();
|
|
195
|
+
|
|
190
196
|
if (!list || !list.length) {
|
|
191
197
|
wrap.innerHTML = '<div class="text-muted">Aucune demande.</div>';
|
|
192
198
|
return;
|
|
@@ -203,11 +209,12 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
203
209
|
};
|
|
204
210
|
|
|
205
211
|
for (const r of list) {
|
|
212
|
+
if (r && r.rid) pendingCache.set(String(r.rid), r);
|
|
206
213
|
const created = r.createdAt ? fmtFR(r.createdAt) : '';
|
|
207
214
|
const itemNames = Array.isArray(r.itemNames) && r.itemNames.length ? r.itemNames : [r.itemName || r.itemId].filter(Boolean);
|
|
208
215
|
const itemsHtml = `<ul style="margin: 0 0 10px 18px;">${itemNames.map(n => `<li>${escapeHtml(String(n))}</li>`).join('')}</ul>`;
|
|
209
216
|
const div = document.createElement('div');
|
|
210
|
-
div.className = 'list-group-item';
|
|
217
|
+
div.className = 'list-group-item onekite-pending-row';
|
|
211
218
|
div.innerHTML = `
|
|
212
219
|
<div class="d-flex justify-content-between align-items-start gap-2">
|
|
213
220
|
<div style="min-width: 0;">
|
|
@@ -216,149 +223,11 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
216
223
|
<div class="text-muted" style="font-size: 12px;">Période: ${escapeHtml(new Date(parseInt(r.start, 10)).toLocaleDateString('fr-FR'))} → ${escapeHtml(new Date(parseInt(r.end, 10)).toLocaleDateString('fr-FR'))}</div>
|
|
217
224
|
</div>
|
|
218
225
|
<div class="d-flex gap-2">
|
|
219
|
-
<button class="btn btn-outline-danger btn-sm">
|
|
220
|
-
<button class="btn btn-success btn-sm">Valider</button>
|
|
226
|
+
<button class="btn btn-outline-danger btn-sm" data-action="refuse" data-rid="${escapeHtml(String(r.rid || ''))}">Refuser</button>
|
|
227
|
+
<button class="btn btn-success btn-sm" data-action="approve" data-rid="${escapeHtml(String(r.rid || ''))}">Valider</button>
|
|
221
228
|
</div>
|
|
222
229
|
</div>
|
|
223
230
|
`;
|
|
224
|
-
const [refuseBtn, approveBtn] = div.querySelectorAll('button');
|
|
225
|
-
refuseBtn.addEventListener('click', async () => {
|
|
226
|
-
if (!confirm('Rejeter cette demande ?')) return;
|
|
227
|
-
try {
|
|
228
|
-
// Server route: PUT /api/v3/admin/plugins/calendar-onekite/reservations/:rid/refuse
|
|
229
|
-
await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${encodeURIComponent(r.rid)}/refuse`, { method: 'PUT' });
|
|
230
|
-
showAlert('success', 'Demande rejetée.');
|
|
231
|
-
await loadPending();
|
|
232
|
-
} catch (e) {
|
|
233
|
-
showAlert('error', 'Rejet impossible.');
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
approveBtn.addEventListener('click', async () => {
|
|
238
|
-
const itemNamesModal = Array.isArray(r.itemNames) && r.itemNames.length ? r.itemNames : [r.itemName || r.itemId].filter(Boolean);
|
|
239
|
-
const itemsHtmlModal = `<ul style="margin: 0 0 10px 18px;">${itemNamesModal.map(n => `<li>${escapeHtml(String(n))}</li>`).join('')}</ul>`;
|
|
240
|
-
|
|
241
|
-
const opts = (() => {
|
|
242
|
-
const out = [];
|
|
243
|
-
for (let h = 7; h < 24; h++) {
|
|
244
|
-
for (let m = 0; m < 60; m += 5) {
|
|
245
|
-
out.push(`${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return out;
|
|
249
|
-
})().map(t => `<option value="${t}">${t}</option>`).join('');
|
|
250
|
-
|
|
251
|
-
const html = `
|
|
252
|
-
<div class="mb-2"><strong>Matériel</strong>${itemsHtmlModal}</div>
|
|
253
|
-
<div class="mb-3">
|
|
254
|
-
<label class="form-label">Adresse de récupération</label>
|
|
255
|
-
<div class="input-group">
|
|
256
|
-
<input type="text" class="form-control" id="onekite-pickup-address" placeholder="Adresse complète" />
|
|
257
|
-
<button class="btn btn-outline-secondary" type="button" id="onekite-geocode">Rechercher</button>
|
|
258
|
-
</div>
|
|
259
|
-
<div id="onekite-map" style="height:220px; border:1px solid #ddd; border-radius:6px; margin-top:0.5rem;"></div>
|
|
260
|
-
<div class="form-text">Vous pouvez déplacer le marqueur pour ajuster la position.</div>
|
|
261
|
-
<input type="hidden" id="onekite-pickup-lat" />
|
|
262
|
-
<input type="hidden" id="onekite-pickup-lon" />
|
|
263
|
-
</div>
|
|
264
|
-
<div class="mb-3">
|
|
265
|
-
<label class="form-label">Notes (facultatif)</label>
|
|
266
|
-
<textarea class="form-control" id="onekite-notes" rows="3" placeholder="Ex: code portail, personne à contacter, horaires..."></textarea>
|
|
267
|
-
</div>
|
|
268
|
-
<div class="mb-2">
|
|
269
|
-
<label class="form-label">Heure de récupération</label>
|
|
270
|
-
<select class="form-select" id="onekite-pickup-time">${opts}</select>
|
|
271
|
-
</div>
|
|
272
|
-
`;
|
|
273
|
-
|
|
274
|
-
const dlg = bootbox.dialog({
|
|
275
|
-
title: 'Valider la demande',
|
|
276
|
-
message: html,
|
|
277
|
-
buttons: {
|
|
278
|
-
cancel: { label: 'Annuler', className: 'btn-secondary' },
|
|
279
|
-
ok: {
|
|
280
|
-
label: 'Valider',
|
|
281
|
-
className: 'btn-success',
|
|
282
|
-
callback: async () => {
|
|
283
|
-
try {
|
|
284
|
-
const pickupAddress = (document.getElementById('onekite-pickup-address')?.value || '').trim();
|
|
285
|
-
const notes = (document.getElementById('onekite-notes')?.value || '').trim();
|
|
286
|
-
const pickupTime = (document.getElementById('onekite-pickup-time')?.value || '').trim();
|
|
287
|
-
const pickupLat = (document.getElementById('onekite-pickup-lat')?.value || '').trim();
|
|
288
|
-
const pickupLon = (document.getElementById('onekite-pickup-lon')?.value || '').trim();
|
|
289
|
-
// Server route: PUT /api/v3/admin/plugins/calendar-onekite/reservations/:rid/approve
|
|
290
|
-
await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${encodeURIComponent(r.rid)}/approve`, { method: 'PUT', body: JSON.stringify({ pickupAddress, notes, pickupTime, pickupLat, pickupLon }) });
|
|
291
|
-
showAlert('success', 'Demande validée.');
|
|
292
|
-
await loadPending();
|
|
293
|
-
} catch (e) {
|
|
294
|
-
showAlert('error', 'Validation impossible.');
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
},
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
dlg.on('shown.bs.modal', async () => {
|
|
302
|
-
try {
|
|
303
|
-
const L = await loadLeaflet();
|
|
304
|
-
const mapEl = document.getElementById('onekite-map');
|
|
305
|
-
if (!mapEl) return;
|
|
306
|
-
const map = L.map(mapEl, { scrollWheelZoom: false });
|
|
307
|
-
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
308
|
-
maxZoom: 19,
|
|
309
|
-
attribution: '© OpenStreetMap',
|
|
310
|
-
}).addTo(map);
|
|
311
|
-
map.setView([46.7, 2.5], 5);
|
|
312
|
-
let marker = null;
|
|
313
|
-
function setMarker(lat, lon, zoom) {
|
|
314
|
-
const ll = [lat, lon];
|
|
315
|
-
if (!marker) {
|
|
316
|
-
marker = L.marker(ll, { draggable: true }).addTo(map);
|
|
317
|
-
marker.on('dragend', () => {
|
|
318
|
-
const p2 = marker.getLatLng();
|
|
319
|
-
document.getElementById('onekite-pickup-lat').value = String(p2.lat);
|
|
320
|
-
document.getElementById('onekite-pickup-lon').value = String(p2.lng);
|
|
321
|
-
});
|
|
322
|
-
} else {
|
|
323
|
-
marker.setLatLng(ll);
|
|
324
|
-
}
|
|
325
|
-
document.getElementById('onekite-pickup-lat').value = String(lat);
|
|
326
|
-
document.getElementById('onekite-pickup-lon').value = String(lon);
|
|
327
|
-
if (zoom) map.setView(ll, zoom); else map.panTo(ll);
|
|
328
|
-
}
|
|
329
|
-
map.on('click', (e) => {
|
|
330
|
-
if (e && e.latlng) setMarker(e.latlng.lat, e.latlng.lng, map.getZoom());
|
|
331
|
-
});
|
|
332
|
-
const geocodeBtn = document.getElementById('onekite-geocode');
|
|
333
|
-
const addrInput = document.getElementById('onekite-pickup-address');
|
|
334
|
-
async function runGeocode() {
|
|
335
|
-
try {
|
|
336
|
-
const addr = (addrInput?.value || '').trim();
|
|
337
|
-
if (!addr) return;
|
|
338
|
-
const hit = await geocodeAddress(addr);
|
|
339
|
-
if (!hit) {
|
|
340
|
-
showAlert('error', 'Adresse introuvable.');
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
setMarker(hit.lat, hit.lon, 16);
|
|
344
|
-
} catch (e) {
|
|
345
|
-
showAlert('error', 'Recherche adresse impossible.');
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (geocodeBtn) geocodeBtn.addEventListener('click', runGeocode);
|
|
349
|
-
if (addrInput) {
|
|
350
|
-
addrInput.addEventListener('keydown', (e) => {
|
|
351
|
-
if (e.key === 'Enter') {
|
|
352
|
-
e.preventDefault();
|
|
353
|
-
runGeocode();
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
} catch (e) {}
|
|
358
|
-
});
|
|
359
|
-
return false;
|
|
360
|
-
});
|
|
361
|
-
|
|
362
231
|
wrap.appendChild(div);
|
|
363
232
|
}
|
|
364
233
|
}
|
|
@@ -552,53 +421,14 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
552
421
|
const rowEl = btn.closest('tr') || btn.closest('.onekite-pending-row');
|
|
553
422
|
|
|
554
423
|
try {
|
|
555
|
-
if (action === '
|
|
556
|
-
const opts = timeOptions(5).map(t => `<option value="${t}">${t}</option>`).join('');
|
|
424
|
+
if (action === 'refuse') {
|
|
557
425
|
const html = `
|
|
558
426
|
<div class="mb-3">
|
|
559
|
-
<label class="form-label">
|
|
560
|
-
<textarea class="form-control" id="onekite-admin-note" rows="3" placeholder="Matériel à récupérer à l'adresse : ..."></textarea>
|
|
561
|
-
</div>
|
|
562
|
-
<div class="mb-2">
|
|
563
|
-
<label class="form-label">Heure de récupération</label>
|
|
564
|
-
<select class="form-select" id="onekite-pickup-time">${opts}</select>
|
|
565
|
-
</div>
|
|
566
|
-
`;
|
|
567
|
-
await new Promise((resolve) => {
|
|
568
|
-
bootbox.dialog({
|
|
569
|
-
title: 'Valider la réservation',
|
|
570
|
-
message: html,
|
|
571
|
-
buttons: {
|
|
572
|
-
cancel: { label: 'Annuler', className: 'btn-secondary', callback: () => resolve(false) },
|
|
573
|
-
ok: {
|
|
574
|
-
label: 'Valider',
|
|
575
|
-
className: 'btn-success',
|
|
576
|
-
callback: async () => {
|
|
577
|
-
try {
|
|
578
|
-
const adminNote = (document.getElementById('onekite-admin-note')?.value || '').trim();
|
|
579
|
-
const pickupTime = (document.getElementById('onekite-pickup-time')?.value || '').trim();
|
|
580
|
-
await approve(rid, { adminNote, pickupTime });
|
|
581
|
-
if (rowEl && rowEl.parentNode) rowEl.parentNode.removeChild(rowEl);
|
|
582
|
-
bootbox.alert('Réservation validée.');
|
|
583
|
-
resolve(true);
|
|
584
|
-
} catch (e) {
|
|
585
|
-
showAlert('error', 'Validation impossible.');
|
|
586
|
-
resolve(false);
|
|
587
|
-
}
|
|
588
|
-
return false;
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
},
|
|
592
|
-
});
|
|
593
|
-
});
|
|
594
|
-
} else if (action === 'refuse') {
|
|
595
|
-
const html = `
|
|
596
|
-
<div class="mb-3">
|
|
597
|
-
<label class="form-label">Raison du refus (incluse dans l'email)</label>
|
|
427
|
+
<label class="form-label">Raison du refus</label>
|
|
598
428
|
<textarea class="form-control" id="onekite-refuse-reason" rows="3" placeholder="Ex: matériel indisponible, dates impossibles, dossier incomplet..."></textarea>
|
|
599
429
|
</div>
|
|
600
430
|
`;
|
|
601
|
-
await new Promise((resolve) => {
|
|
431
|
+
const ok = await new Promise((resolve) => {
|
|
602
432
|
bootbox.dialog({
|
|
603
433
|
title: 'Refuser la réservation',
|
|
604
434
|
message: html,
|
|
@@ -612,7 +442,7 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
612
442
|
const reason = (document.getElementById('onekite-refuse-reason')?.value || '').trim();
|
|
613
443
|
await refuse(rid, { reason });
|
|
614
444
|
if (rowEl && rowEl.parentNode) rowEl.parentNode.removeChild(rowEl);
|
|
615
|
-
|
|
445
|
+
showAlert('success', 'Demande refusée.');
|
|
616
446
|
resolve(true);
|
|
617
447
|
} catch (e) {
|
|
618
448
|
showAlert('error', 'Refus impossible.');
|
|
@@ -624,8 +454,149 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
|
|
|
624
454
|
},
|
|
625
455
|
});
|
|
626
456
|
});
|
|
457
|
+
if (ok) {
|
|
458
|
+
await refreshPending();
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (action === 'approve') {
|
|
464
|
+
const r = pendingCache.get(String(rid)) || {};
|
|
465
|
+
const itemNames = Array.isArray(r.itemNames) && r.itemNames.length
|
|
466
|
+
? r.itemNames
|
|
467
|
+
: (typeof r.itemNames === 'string' && r.itemNames.trim()
|
|
468
|
+
? r.itemNames.split(',').map(s => s.trim()).filter(Boolean)
|
|
469
|
+
: ([r.itemName || r.itemId].filter(Boolean)));
|
|
470
|
+
const itemsListHtml = itemNames.length
|
|
471
|
+
? `<div class="mb-2"><strong>Matériel</strong><ul style="margin:0.25rem 0 0 1.1rem; padding:0;">${itemNames.map(n => `<li>${escapeHtml(String(n))}</li>`).join('')}</ul></div>`
|
|
472
|
+
: '';
|
|
473
|
+
const opts = timeOptions(5).map(t => `<option value="${t}" ${t === '07:00' ? 'selected' : ''}>${t}</option>`).join('');
|
|
474
|
+
|
|
475
|
+
const html = `
|
|
476
|
+
${itemsListHtml}
|
|
477
|
+
<div class="mb-3">
|
|
478
|
+
<label class="form-label">Adresse de récupération</label>
|
|
479
|
+
<div class="input-group">
|
|
480
|
+
<input type="text" class="form-control" id="onekite-pickup-address" placeholder="Adresse complète" />
|
|
481
|
+
<button class="btn btn-outline-secondary" type="button" id="onekite-geocode">Rechercher</button>
|
|
482
|
+
</div>
|
|
483
|
+
<div id="onekite-map" style="height:220px; border:1px solid #ddd; border-radius:6px; margin-top:0.5rem;"></div>
|
|
484
|
+
<div class="form-text" id="onekite-map-help">Vous pouvez déplacer le marqueur pour ajuster la position.</div>
|
|
485
|
+
<input type="hidden" id="onekite-pickup-lat" />
|
|
486
|
+
<input type="hidden" id="onekite-pickup-lon" />
|
|
487
|
+
</div>
|
|
488
|
+
<div class="mb-3">
|
|
489
|
+
<label class="form-label">Notes (facultatif)</label>
|
|
490
|
+
<textarea class="form-control" id="onekite-notes" rows="3" placeholder="Ex: code portail, personne à contacter, horaires..."></textarea>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="mb-2">
|
|
493
|
+
<label class="form-label">Heure de récupération</label>
|
|
494
|
+
<select class="form-select" id="onekite-pickup-time">${opts}</select>
|
|
495
|
+
</div>
|
|
496
|
+
`;
|
|
497
|
+
|
|
498
|
+
const dlg = bootbox.dialog({
|
|
499
|
+
title: 'Valider la demande',
|
|
500
|
+
message: html,
|
|
501
|
+
buttons: {
|
|
502
|
+
cancel: { label: 'Annuler', className: 'btn-secondary' },
|
|
503
|
+
ok: {
|
|
504
|
+
label: 'Valider',
|
|
505
|
+
className: 'btn-success',
|
|
506
|
+
callback: async () => {
|
|
507
|
+
try {
|
|
508
|
+
const pickupAddress = (document.getElementById('onekite-pickup-address')?.value || '').trim();
|
|
509
|
+
const notes = (document.getElementById('onekite-notes')?.value || '').trim();
|
|
510
|
+
const pickupTime = (document.getElementById('onekite-pickup-time')?.value || '').trim();
|
|
511
|
+
const pickupLat = (document.getElementById('onekite-pickup-lat')?.value || '').trim();
|
|
512
|
+
const pickupLon = (document.getElementById('onekite-pickup-lon')?.value || '').trim();
|
|
513
|
+
await approve(rid, { pickupAddress, notes, pickupTime, pickupLat, pickupLon });
|
|
514
|
+
if (rowEl && rowEl.parentNode) rowEl.parentNode.removeChild(rowEl);
|
|
515
|
+
showAlert('success', 'Demande validée.');
|
|
516
|
+
await refreshPending();
|
|
517
|
+
} catch (e) {
|
|
518
|
+
showAlert('error', 'Validation impossible.');
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Init Leaflet map once the modal is visible.
|
|
527
|
+
dlg.on('shown.bs.modal', async () => {
|
|
528
|
+
try {
|
|
529
|
+
const L = await loadLeaflet();
|
|
530
|
+
const mapEl = document.getElementById('onekite-map');
|
|
531
|
+
if (!mapEl) return;
|
|
532
|
+
|
|
533
|
+
const map = L.map(mapEl, { scrollWheelZoom: false });
|
|
534
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
535
|
+
maxZoom: 19,
|
|
536
|
+
attribution: '© OpenStreetMap',
|
|
537
|
+
}).addTo(map);
|
|
538
|
+
|
|
539
|
+
// Default view (France-ish)
|
|
540
|
+
map.setView([46.7, 2.5], 5);
|
|
541
|
+
|
|
542
|
+
let marker = null;
|
|
543
|
+
function setMarker(lat, lon, zoom) {
|
|
544
|
+
const ll = [lat, lon];
|
|
545
|
+
if (!marker) {
|
|
546
|
+
marker = L.marker(ll, { draggable: true }).addTo(map);
|
|
547
|
+
marker.on('dragend', () => {
|
|
548
|
+
const p2 = marker.getLatLng();
|
|
549
|
+
document.getElementById('onekite-pickup-lat').value = String(p2.lat);
|
|
550
|
+
document.getElementById('onekite-pickup-lon').value = String(p2.lng);
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
marker.setLatLng(ll);
|
|
554
|
+
}
|
|
555
|
+
document.getElementById('onekite-pickup-lat').value = String(lat);
|
|
556
|
+
document.getElementById('onekite-pickup-lon').value = String(lon);
|
|
557
|
+
if (zoom) {
|
|
558
|
+
map.setView(ll, zoom);
|
|
559
|
+
} else {
|
|
560
|
+
map.panTo(ll);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
map.on('click', (e) => {
|
|
565
|
+
if (e && e.latlng) {
|
|
566
|
+
setMarker(e.latlng.lat, e.latlng.lng, map.getZoom());
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const geocodeBtn = document.getElementById('onekite-geocode');
|
|
571
|
+
const addrInput = document.getElementById('onekite-pickup-address');
|
|
572
|
+
async function runGeocode() {
|
|
573
|
+
try {
|
|
574
|
+
const addr = (addrInput?.value || '').trim();
|
|
575
|
+
if (!addr) return;
|
|
576
|
+
const hit = await geocodeAddress(addr);
|
|
577
|
+
if (!hit) {
|
|
578
|
+
showAlert('error', 'Adresse introuvable.');
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
setMarker(hit.lat, hit.lon, 16);
|
|
582
|
+
} catch (e) {
|
|
583
|
+
showAlert('error', 'Recherche adresse impossible.');
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (geocodeBtn) geocodeBtn.addEventListener('click', runGeocode);
|
|
587
|
+
if (addrInput) {
|
|
588
|
+
addrInput.addEventListener('keydown', (e) => {
|
|
589
|
+
if (e.key === 'Enter') {
|
|
590
|
+
e.preventDefault();
|
|
591
|
+
runGeocode();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
} catch (e) {
|
|
596
|
+
// ignore
|
|
597
|
+
}
|
|
598
|
+
});
|
|
627
599
|
}
|
|
628
|
-
await refreshPending();
|
|
629
600
|
} catch (e) {
|
|
630
601
|
showAlert('error', 'Action impossible.');
|
|
631
602
|
}
|
package/public/client.js
CHANGED
|
@@ -25,7 +25,18 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
25
25
|
text-overflow: clip;
|
|
26
26
|
white-space: nowrap;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/* FullCalendar: keep the clock emoji aligned like other inline icons */
|
|
31
|
+
.fc .fc-event-time .okc-clock {
|
|
32
|
+
line-height: 1;
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
}
|
|
36
|
+
.fc .fc-event-time .okc-time-dash {
|
|
37
|
+
opacity: 0.8;
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
29
40
|
document.head.appendChild(style);
|
|
30
41
|
} catch (e) {
|
|
31
42
|
// ignore
|
|
@@ -947,17 +958,30 @@ function toDatetimeLocalValue(date) {
|
|
|
947
958
|
el2.style.color = '#ffffff';
|
|
948
959
|
}
|
|
949
960
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
+
|
|
962
|
+
// Improve readability: show a clock icon before the time and a dash after it.
|
|
963
|
+
// We use a tiny inline wrapper to keep the clock aligned with other inline icons (like locations).
|
|
964
|
+
try {
|
|
965
|
+
const timeEl = arg.el && arg.el.querySelector && arg.el.querySelector('.fc-event-time');
|
|
966
|
+
|
|
967
|
+
if (timeEl && typeof timeEl.textContent === 'string') {
|
|
968
|
+
const t = timeEl.textContent.trim();
|
|
969
|
+
if (t) {
|
|
970
|
+
// Inline-flex alignment fixes baseline issues caused by emoji glyphs.
|
|
971
|
+
timeEl.style.display = 'inline-flex';
|
|
972
|
+
timeEl.style.alignItems = 'center';
|
|
973
|
+
timeEl.style.gap = '4px';
|
|
974
|
+
timeEl.style.whiteSpace = 'nowrap';
|
|
975
|
+
|
|
976
|
+
// Avoid duplicating the marker if rerendered.
|
|
977
|
+
if (!timeEl.dataset.okcClock) {
|
|
978
|
+
timeEl.dataset.okcClock = '1';
|
|
979
|
+
const safeText = (t || '').replace(/</g, '<').replace(/>/g, '>');
|
|
980
|
+
timeEl.innerHTML = `<span class="okc-clock" aria-hidden="true">🕒</span><span class="okc-time-text">${safeText}</span><span class="okc-time-dash" aria-hidden="true">-</span>`;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
} catch (e2) {}
|
|
961
985
|
}
|
|
962
986
|
} catch (e) {}
|
|
963
987
|
},
|
|
@@ -95,6 +95,14 @@
|
|
|
95
95
|
<input class="form-control" name="helloassoFormSlug">
|
|
96
96
|
</div>
|
|
97
97
|
</form>
|
|
98
|
+
|
|
99
|
+
<hr class="my-4" />
|
|
100
|
+
<h4>Purge des locations</h4>
|
|
101
|
+
<div class="d-flex gap-2 align-items-center">
|
|
102
|
+
<input class="form-control" style="max-width: 160px;" id="onekite-purge-year" placeholder="YYYY">
|
|
103
|
+
<button type="button" class="btn btn-outline-danger" id="onekite-purge">Purger</button>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="form-text mt-2">Supprime définitivement toutes les locations (réservations) dont la date de début est dans l'année sélectionnée.</div>
|
|
98
106
|
</div>
|
|
99
107
|
|
|
100
108
|
<div class="tab-pane fade" id="onekite-tab-events" role="tabpanel">
|
|
@@ -125,14 +133,6 @@
|
|
|
125
133
|
<div class="tab-pane fade" id="onekite-tab-pending" role="tabpanel">
|
|
126
134
|
<h4>Demandes en attente</h4>
|
|
127
135
|
<div id="onekite-pending" class="list-group"></div>
|
|
128
|
-
|
|
129
|
-
<hr class="my-4" />
|
|
130
|
-
|
|
131
|
-
<h4>Purge</h4>
|
|
132
|
-
<div class="d-flex gap-2 align-items-center">
|
|
133
|
-
<input class="form-control" style="max-width: 160px;" id="onekite-purge-year" placeholder="YYYY">
|
|
134
|
-
<button type="button" class="btn btn-outline-danger" id="onekite-purge">Purger</button>
|
|
135
|
-
</div>
|
|
136
136
|
</div>
|
|
137
137
|
|
|
138
138
|
<div class="tab-pane fade" id="onekite-tab-debug" role="tabpanel">
|