nodebb-plugin-onekite-calendar 2.0.49 → 2.0.51
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/plugin.json +1 -1
- package/public/client.js +326 -218
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/client.js
CHANGED
|
@@ -84,12 +84,12 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
84
84
|
|
|
85
85
|
/* Mobile FAB date range picker (single calendar) */
|
|
86
86
|
.onekite-range-picker { user-select: none; }
|
|
87
|
-
.onekite-range-header { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:
|
|
87
|
+
.onekite-range-header { display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:4px; }
|
|
88
88
|
.onekite-range-header .btn { padding:.25rem .5rem; }
|
|
89
89
|
.onekite-range-month { font-weight:600; }
|
|
90
90
|
.onekite-range-weekdays, .onekite-range-grid { display:grid; grid-template-columns:repeat(7,1fr); gap:4px; }
|
|
91
|
-
.onekite-range-weekdays div { text-align:center; font-size:.8rem; opacity:.7; padding:
|
|
92
|
-
.onekite-range-day { border:1px solid var(--bs-border-color, rgba(0,0,0,.18)); border-radius:10px; padding:
|
|
91
|
+
.onekite-range-weekdays div { text-align:center; font-size:.8rem; opacity:.7; padding:1px 0; }
|
|
92
|
+
.onekite-range-day { border:1px solid var(--bs-border-color, rgba(0,0,0,.18)); border-radius:10px; padding:6px 0; text-align:center; cursor:pointer; background:var(--bs-body-bg,#fff); color:var(--bs-body-color,#212529); }
|
|
93
93
|
.onekite-range-day.is-empty { border-color:transparent; background:transparent; cursor:default; }
|
|
94
94
|
.onekite-range-day.is-disabled { opacity:.35; cursor:not-allowed; }
|
|
95
95
|
.onekite-range-day.is-start, .onekite-range-day.is-end {
|
|
@@ -111,7 +111,11 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
111
111
|
.onekite-range-day.is-inrange { background: rgba(255,255,255,.08); }
|
|
112
112
|
.onekite-range-day.is-start, .onekite-range-day.is-end { background: rgba(255,255,255,.14); color: var(--bs-body-color,#f8f9fa); border-color: var(--bs-border-color, rgba(255,255,255,.22)); }
|
|
113
113
|
}
|
|
114
|
-
.onekite-range-summary { margin-top:
|
|
114
|
+
.onekite-range-summary { margin-top:6px; font-size:.9rem; }
|
|
115
|
+
|
|
116
|
+
/* Compact spacing inside the FAB creation wizard */
|
|
117
|
+
.onekite-wiz-type .text-muted { margin-bottom: .25rem !important; }
|
|
118
|
+
.onekite-wiz-type .btn { padding: .35rem .75rem; }
|
|
115
119
|
|
|
116
120
|
`;
|
|
117
121
|
document.head.appendChild(style);
|
|
@@ -134,6 +138,8 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
|
|
|
134
138
|
// calendar instance and capability flags). The mobile FAB is mounted outside
|
|
135
139
|
// init(), so we store the handler here.
|
|
136
140
|
let createFromSelectionHandler = null;
|
|
141
|
+
// Unified creation wizard (date range + type) used by the mobile FAB.
|
|
142
|
+
let openCreateWizardHandler = null;
|
|
137
143
|
|
|
138
144
|
// Mobile FAB (mounted only on the calendar page)
|
|
139
145
|
let fabEl = null;
|
|
@@ -1335,116 +1341,162 @@ function toDatetimeLocalValue(date) {
|
|
|
1335
1341
|
}
|
|
1336
1342
|
}
|
|
1337
1343
|
|
|
1338
|
-
//
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
const startDate = toLocalYmd(info.start);
|
|
1354
|
-
const endDate = (chosen && chosen.endDate) ? String(chosen.endDate) : toLocalYmd(info.end);
|
|
1355
|
-
const resp = await requestReservation({
|
|
1356
|
-
title: (chosen && chosen.title) ? String(chosen.title) : '',
|
|
1357
|
-
start: startDate,
|
|
1358
|
-
end: endDate,
|
|
1359
|
-
itemIds: chosen.itemIds,
|
|
1360
|
-
itemNames: chosen.itemNames,
|
|
1361
|
-
total: chosen.total,
|
|
1362
|
-
});
|
|
1363
|
-
if (resp && (resp.autoPaid || String(resp.status) === 'paid')) {
|
|
1364
|
-
showAlert('success', 'Réservation confirmée.');
|
|
1365
|
-
} else {
|
|
1366
|
-
showAlert('success', 'Demande envoyée (en attente de validation).');
|
|
1367
|
-
}
|
|
1368
|
-
invalidateEventsCache();
|
|
1369
|
-
scheduleRefetch(calendar);
|
|
1370
|
-
} catch (e) {
|
|
1371
|
-
handleCreateError(e);
|
|
1372
|
-
} finally {
|
|
1373
|
-
try { calendar.unselect(); } catch (e) {}
|
|
1374
|
-
isDialogOpen = false;
|
|
1344
|
+
// Single entry modal: pick date(s) + choose type (FAB & calendar),
|
|
1345
|
+
// then open the dedicated form dialog.
|
|
1346
|
+
await openCreateWizardModal({
|
|
1347
|
+
initialSelection: info,
|
|
1348
|
+
// When the user clicks/selects on the main calendar, we already have
|
|
1349
|
+
// the date(s) and should not show the mini date picker (FAB only).
|
|
1350
|
+
showDatePicker: false,
|
|
1351
|
+
canCreateReservation,
|
|
1352
|
+
canCreateOuting,
|
|
1353
|
+
canCreateSpecial,
|
|
1354
|
+
onCreateLocation: async (sel) => {
|
|
1355
|
+
try {
|
|
1356
|
+
if (!items || !items.length) {
|
|
1357
|
+
showAlert('error', 'Aucun matériel disponible (items HelloAsso non chargés).');
|
|
1358
|
+
return;
|
|
1375
1359
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
showAlert('success', 'Prévision de sortie créée.');
|
|
1393
|
-
invalidateEventsCache();
|
|
1394
|
-
scheduleRefetch(calendar);
|
|
1395
|
-
} catch (e) {
|
|
1396
|
-
handleCreateError(e);
|
|
1397
|
-
} finally {
|
|
1398
|
-
try { calendar.unselect(); } catch (e) {}
|
|
1399
|
-
isDialogOpen = false;
|
|
1360
|
+
const chosen = await openReservationDialog(sel, items);
|
|
1361
|
+
if (!chosen || !chosen.itemIds || !chosen.itemIds.length) return;
|
|
1362
|
+
const startDate = toLocalYmd(sel.start);
|
|
1363
|
+
const endDate = (chosen && chosen.endDate) ? String(chosen.endDate) : toLocalYmd(sel.end);
|
|
1364
|
+
const resp = await requestReservation({
|
|
1365
|
+
title: (chosen && chosen.title) ? String(chosen.title) : '',
|
|
1366
|
+
start: startDate,
|
|
1367
|
+
end: endDate,
|
|
1368
|
+
itemIds: chosen.itemIds,
|
|
1369
|
+
itemNames: chosen.itemNames,
|
|
1370
|
+
total: chosen.total,
|
|
1371
|
+
});
|
|
1372
|
+
if (resp && (resp.autoPaid || String(resp.status) === 'paid')) {
|
|
1373
|
+
showAlert('success', 'Réservation confirmée.');
|
|
1374
|
+
} else {
|
|
1375
|
+
showAlert('success', 'Demande envoyée (en attente de validation).');
|
|
1400
1376
|
}
|
|
1401
|
-
|
|
1377
|
+
invalidateEventsCache();
|
|
1378
|
+
scheduleRefetch(calendar);
|
|
1379
|
+
} catch (e) {
|
|
1380
|
+
handleCreateError(e);
|
|
1381
|
+
} finally {
|
|
1382
|
+
try { calendar.unselect(); } catch (e) {}
|
|
1383
|
+
}
|
|
1384
|
+
},
|
|
1385
|
+
onCreateOuting: async (sel) => {
|
|
1386
|
+
try {
|
|
1387
|
+
const payload = await openOutingDialog(sel);
|
|
1388
|
+
if (!payload) return;
|
|
1389
|
+
await fetchJson('/api/v3/plugins/calendar-onekite/outings', {
|
|
1390
|
+
method: 'POST',
|
|
1391
|
+
body: JSON.stringify(payload),
|
|
1392
|
+
});
|
|
1393
|
+
showAlert('success', 'Prévision de sortie créée.');
|
|
1394
|
+
invalidateEventsCache();
|
|
1395
|
+
scheduleRefetch(calendar);
|
|
1396
|
+
} catch (e) {
|
|
1397
|
+
handleCreateError(e);
|
|
1398
|
+
} finally {
|
|
1399
|
+
try { calendar.unselect(); } catch (e) {}
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
onCreateSpecial: async (sel) => {
|
|
1403
|
+
try {
|
|
1404
|
+
const payload = await openSpecialEventDialog(sel);
|
|
1405
|
+
if (!payload) return;
|
|
1406
|
+
await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1407
|
+
method: 'POST',
|
|
1408
|
+
body: JSON.stringify(payload),
|
|
1409
|
+
});
|
|
1410
|
+
showAlert('success', 'Évènement créé.');
|
|
1411
|
+
invalidateEventsCache();
|
|
1412
|
+
scheduleRefetch(calendar);
|
|
1413
|
+
} catch (e) {
|
|
1414
|
+
handleCreateError(e);
|
|
1415
|
+
} finally {
|
|
1416
|
+
try { calendar.unselect(); } catch (e) {}
|
|
1417
|
+
}
|
|
1402
1418
|
},
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
|
-
if (canCreateSpecial) {
|
|
1406
|
-
buttons.special = {
|
|
1407
|
-
label: 'Évènement',
|
|
1408
|
-
className: 'btn-onekite-special',
|
|
1409
|
-
callback: async () => {
|
|
1410
|
-
try {
|
|
1411
|
-
isDialogOpen = true;
|
|
1412
|
-
const payload = await openSpecialEventDialog(info);
|
|
1413
|
-
if (!payload) return;
|
|
1414
|
-
await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1415
|
-
method: 'POST',
|
|
1416
|
-
body: JSON.stringify(payload),
|
|
1417
|
-
}).catch(async () => {
|
|
1418
|
-
return await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1419
|
-
method: 'POST',
|
|
1420
|
-
body: JSON.stringify(payload),
|
|
1421
|
-
});
|
|
1422
|
-
});
|
|
1423
|
-
showAlert('success', 'Évènement créé.');
|
|
1424
|
-
invalidateEventsCache();
|
|
1425
|
-
scheduleRefetch(calendar);
|
|
1426
|
-
} catch (e) {
|
|
1427
|
-
handleCreateError(e);
|
|
1428
|
-
} finally {
|
|
1429
|
-
try { calendar.unselect(); } catch (e) {}
|
|
1430
|
-
isDialogOpen = false;
|
|
1431
|
-
}
|
|
1432
|
-
return true;
|
|
1433
|
-
},
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
buttons.cancel = { label: 'Annuler', className: 'btn-danger' };
|
|
1438
|
-
|
|
1439
|
-
bootbox.dialog({
|
|
1440
|
-
title: 'Créer',
|
|
1441
|
-
message: '<div class="text-muted">Que veux-tu créer sur ces dates ?</div>',
|
|
1442
|
-
buttons,
|
|
1443
1419
|
});
|
|
1444
1420
|
}
|
|
1445
1421
|
|
|
1446
1422
|
// Expose to the mobile FAB (mounted outside init).
|
|
1447
1423
|
createFromSelectionHandler = handleCreateFromSelection;
|
|
1424
|
+
openCreateWizardHandler = async () => {
|
|
1425
|
+
// Same pre-checks as calendar creation, but with no initial selection.
|
|
1426
|
+
if (!canCreateReservation && !canCreateOuting && !canCreateSpecial) {
|
|
1427
|
+
showAlert('error', 'Vous devez être adhérent Onekite');
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
await openCreateWizardModal({
|
|
1431
|
+
initialSelection: null,
|
|
1432
|
+
canCreateReservation,
|
|
1433
|
+
canCreateOuting,
|
|
1434
|
+
canCreateSpecial,
|
|
1435
|
+
onCreateLocation: async (sel) => {
|
|
1436
|
+
try {
|
|
1437
|
+
if (!items || !items.length) {
|
|
1438
|
+
showAlert('error', 'Aucun matériel disponible (items HelloAsso non chargés).');
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
const chosen = await openReservationDialog(sel, items);
|
|
1442
|
+
if (!chosen || !chosen.itemIds || !chosen.itemIds.length) return;
|
|
1443
|
+
const startDate = toLocalYmd(sel.start);
|
|
1444
|
+
const endDate = (chosen && chosen.endDate) ? String(chosen.endDate) : toLocalYmd(sel.end);
|
|
1445
|
+
const resp = await requestReservation({
|
|
1446
|
+
title: (chosen && chosen.title) ? String(chosen.title) : '',
|
|
1447
|
+
start: startDate,
|
|
1448
|
+
end: endDate,
|
|
1449
|
+
itemIds: chosen.itemIds,
|
|
1450
|
+
itemNames: chosen.itemNames,
|
|
1451
|
+
total: chosen.total,
|
|
1452
|
+
});
|
|
1453
|
+
if (resp && (resp.autoPaid || String(resp.status) === 'paid')) {
|
|
1454
|
+
showAlert('success', 'Réservation confirmée.');
|
|
1455
|
+
} else {
|
|
1456
|
+
showAlert('success', 'Demande envoyée (en attente de validation).');
|
|
1457
|
+
}
|
|
1458
|
+
invalidateEventsCache();
|
|
1459
|
+
scheduleRefetch(calendar);
|
|
1460
|
+
} catch (e) {
|
|
1461
|
+
// Reuse calendar handler (simplified): show payload message when present
|
|
1462
|
+
const msg = (e && e.payload && (e.payload.message || e.payload.error || e.payload.msg)) ? String(e.payload.message || e.payload.error || e.payload.msg) : '';
|
|
1463
|
+
showAlert('error', msg || 'Erreur lors de la création.');
|
|
1464
|
+
}
|
|
1465
|
+
},
|
|
1466
|
+
onCreateOuting: async (sel) => {
|
|
1467
|
+
try {
|
|
1468
|
+
const payload = await openOutingDialog(sel);
|
|
1469
|
+
if (!payload) return;
|
|
1470
|
+
await fetchJson('/api/v3/plugins/calendar-onekite/outings', {
|
|
1471
|
+
method: 'POST',
|
|
1472
|
+
body: JSON.stringify(payload),
|
|
1473
|
+
});
|
|
1474
|
+
showAlert('success', 'Prévision de sortie créée.');
|
|
1475
|
+
invalidateEventsCache();
|
|
1476
|
+
scheduleRefetch(calendar);
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
const msg = (e && e.payload && (e.payload.message || e.payload.error || e.payload.msg)) ? String(e.payload.message || e.payload.error || e.payload.msg) : '';
|
|
1479
|
+
showAlert('error', msg || 'Erreur lors de la création.');
|
|
1480
|
+
}
|
|
1481
|
+
},
|
|
1482
|
+
onCreateSpecial: async (sel) => {
|
|
1483
|
+
try {
|
|
1484
|
+
const payload = await openSpecialEventDialog(sel);
|
|
1485
|
+
if (!payload) return;
|
|
1486
|
+
await fetchJson('/api/v3/plugins/calendar-onekite/special-events', {
|
|
1487
|
+
method: 'POST',
|
|
1488
|
+
body: JSON.stringify(payload),
|
|
1489
|
+
});
|
|
1490
|
+
showAlert('success', 'Évènement créé.');
|
|
1491
|
+
invalidateEventsCache();
|
|
1492
|
+
scheduleRefetch(calendar);
|
|
1493
|
+
} catch (e) {
|
|
1494
|
+
const msg = (e && e.payload && (e.payload.message || e.payload.error || e.payload.msg)) ? String(e.payload.message || e.payload.error || e.payload.msg) : '';
|
|
1495
|
+
showAlert('error', msg || 'Erreur lors de la création.');
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
});
|
|
1499
|
+
};
|
|
1448
1500
|
|
|
1449
1501
|
calendar = new FullCalendar.Calendar(el, {
|
|
1450
1502
|
initialView: 'dayGridMonth',
|
|
@@ -2206,10 +2258,21 @@ function toDatetimeLocalValue(date) {
|
|
|
2206
2258
|
}
|
|
2207
2259
|
|
|
2208
2260
|
|
|
2209
|
-
async function
|
|
2210
|
-
if (!lockAction('
|
|
2261
|
+
async function openCreateWizardModal(opts) {
|
|
2262
|
+
if (!lockAction('create-wizard', 700)) return;
|
|
2263
|
+
|
|
2264
|
+
const options = opts || {};
|
|
2265
|
+
const canCreateReservation = !!options.canCreateReservation;
|
|
2266
|
+
const canCreateOuting = !!options.canCreateOuting;
|
|
2267
|
+
const canCreateSpecial = !!options.canCreateSpecial;
|
|
2211
2268
|
|
|
2212
|
-
//
|
|
2269
|
+
// Only the mobile FAB should show the mini range picker calendar.
|
|
2270
|
+
// For calendar interactions (date click / range select), we already know
|
|
2271
|
+
// the selected dates, so we can skip the mini calendar and jump straight
|
|
2272
|
+
// to the type chooser.
|
|
2273
|
+
const showDatePicker = options.showDatePicker !== false;
|
|
2274
|
+
|
|
2275
|
+
// Cannot create in the past (wizard). Same-day is allowed.
|
|
2213
2276
|
const today = new Date();
|
|
2214
2277
|
today.setHours(0, 0, 0, 0);
|
|
2215
2278
|
const minStart = new Date(today);
|
|
@@ -2225,86 +2288,86 @@ async function openFabDatePicker(onSelection) {
|
|
|
2225
2288
|
end: null, // inclusive
|
|
2226
2289
|
};
|
|
2227
2290
|
|
|
2228
|
-
|
|
2229
|
-
|
|
2291
|
+
// No auto-scroll: keep the modal stable and compact.
|
|
2292
|
+
|
|
2293
|
+
// Pre-fill from an existing FullCalendar selection (end is exclusive)
|
|
2294
|
+
try {
|
|
2295
|
+
const initial = options.initialSelection;
|
|
2296
|
+
if (initial && initial.start) {
|
|
2297
|
+
const s = new Date(initial.start);
|
|
2298
|
+
s.setHours(0, 0, 0, 0);
|
|
2299
|
+
state.start = s;
|
|
2300
|
+
if (initial.end) {
|
|
2301
|
+
const eEx = new Date(initial.end);
|
|
2302
|
+
eEx.setHours(0, 0, 0, 0);
|
|
2303
|
+
// end inclusive = end exclusive - 1 day
|
|
2304
|
+
const e = new Date(eEx);
|
|
2305
|
+
e.setDate(e.getDate() - 1);
|
|
2306
|
+
if (e.getTime() >= s.getTime()) {
|
|
2307
|
+
state.end = e;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
state.cursor = new Date(s);
|
|
2311
|
+
state.cursor.setDate(1);
|
|
2312
|
+
}
|
|
2313
|
+
} catch (e) {}
|
|
2314
|
+
|
|
2315
|
+
function summaryText() {
|
|
2316
|
+
if (!state.start) return 'Aucune date sélectionnée.';
|
|
2317
|
+
const startTxt = formatDdMmYyyy(state.start);
|
|
2318
|
+
const endTxt = formatDdMmYyyy(state.end || state.start);
|
|
2319
|
+
return (state.end && !sameDay(state.start, state.end))
|
|
2320
|
+
? `Du ${startTxt} au ${endTxt}`
|
|
2321
|
+
: `Le ${startTxt}`;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const datePickerHtml = showDatePicker ? `
|
|
2230
2325
|
<div class="onekite-range-header">
|
|
2231
|
-
<button type="button" class="btn btn-outline-secondary btn-sm" id="onekite-
|
|
2232
|
-
<div class="onekite-range-month" id="onekite-
|
|
2233
|
-
<button type="button" class="btn btn-outline-secondary btn-sm" id="onekite-
|
|
2326
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" id="onekite-wiz-prev" aria-label="Mois précédent">‹</button>
|
|
2327
|
+
<div class="onekite-range-month" id="onekite-wiz-month"></div>
|
|
2328
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" id="onekite-wiz-next" aria-label="Mois suivant">›</button>
|
|
2234
2329
|
</div>
|
|
2235
2330
|
<div class="onekite-range-weekdays">
|
|
2236
2331
|
<div>L</div><div>M</div><div>M</div><div>J</div><div>V</div><div>S</div><div>D</div>
|
|
2237
2332
|
</div>
|
|
2238
|
-
<div class="onekite-range-grid" id="onekite-
|
|
2239
|
-
<div class="onekite-range-summary" id="onekite-
|
|
2240
|
-
|
|
2333
|
+
<div class="onekite-range-grid" id="onekite-wiz-grid"></div>
|
|
2334
|
+
<div class="onekite-range-summary" id="onekite-wiz-summary"></div>
|
|
2335
|
+
|
|
2336
|
+
<hr class="my-2" />
|
|
2337
|
+
` : `
|
|
2338
|
+
<div class="onekite-range-summary" id="onekite-wiz-summary-static"><strong>${escapeHtml(summaryText())}</strong></div>
|
|
2339
|
+
<hr class="my-2" />
|
|
2340
|
+
`;
|
|
2341
|
+
|
|
2342
|
+
const html = `
|
|
2343
|
+
<div class="onekite-range-picker" id="onekite-wiz-root">
|
|
2344
|
+
${datePickerHtml}
|
|
2345
|
+
|
|
2346
|
+
<div id="onekite-wiz-type" class="onekite-wiz-type">
|
|
2347
|
+
<div class="text-muted mb-1">Que veux-tu créer sur cette/ces date(s) ?</div>
|
|
2348
|
+
<div class="d-flex flex-wrap gap-2 justify-content-center">
|
|
2349
|
+
${canCreateReservation ? '<button type="button" class="btn btn-onekite-location" id="onekite-wiz-btn-location">Location</button>' : ''}
|
|
2350
|
+
${canCreateOuting ? '<button type="button" class="btn btn-onekite-outing" id="onekite-wiz-btn-outing">Prévision de sortie</button>' : ''}
|
|
2351
|
+
${canCreateSpecial ? '<button type="button" class="btn btn-onekite-special" id="onekite-wiz-btn-special">Évènement</button>' : ''}
|
|
2352
|
+
</div>
|
|
2353
|
+
</div>
|
|
2241
2354
|
</div>
|
|
2242
2355
|
`;
|
|
2243
2356
|
|
|
2244
2357
|
const dlg = bootbox.dialog({
|
|
2245
|
-
title: '
|
|
2358
|
+
title: 'Créer',
|
|
2246
2359
|
message: html,
|
|
2247
2360
|
buttons: {
|
|
2248
|
-
cancel: { label: 'Annuler', className: 'btn-
|
|
2249
|
-
ok: {
|
|
2250
|
-
label: 'Continuer',
|
|
2251
|
-
className: 'btn-primary',
|
|
2252
|
-
callback: function () {
|
|
2253
|
-
const s = state.start;
|
|
2254
|
-
const e = state.end || state.start;
|
|
2255
|
-
if (!s) {
|
|
2256
|
-
alerts.error('Choisis une date de début.');
|
|
2257
|
-
return false;
|
|
2258
|
-
}
|
|
2259
|
-
if (!e) {
|
|
2260
|
-
alerts.error('Choisis une date de fin.');
|
|
2261
|
-
return false;
|
|
2262
|
-
}
|
|
2263
|
-
// Convert end inclusive -> end exclusive (FullCalendar rule)
|
|
2264
|
-
const endExcl = new Date(e);
|
|
2265
|
-
endExcl.setDate(endExcl.getDate() + 1);
|
|
2266
|
-
|
|
2267
|
-
// If a callback is provided, delegate the next step (chooser).
|
|
2268
|
-
if (typeof onSelection === 'function') {
|
|
2269
|
-
try { onSelection({ start: s, end: endExcl, allDay: true }); } catch (e) {}
|
|
2270
|
-
return true;
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
(async () => {
|
|
2274
|
-
try {
|
|
2275
|
-
if (isDialogOpen) return;
|
|
2276
|
-
isDialogOpen = true;
|
|
2277
|
-
const items = cachedItems || (await loadItems());
|
|
2278
|
-
const chosen = await openReservationDialog({ start: s, end: endExcl }, items);
|
|
2279
|
-
if (chosen && chosen.itemIds && chosen.itemIds.length) {
|
|
2280
|
-
const startDate = toLocalYmd(s);
|
|
2281
|
-
const endDate = (chosen && chosen.endDate) ? String(chosen.endDate) : toLocalYmd(endExcl);
|
|
2282
|
-
const resp = await requestReservation({
|
|
2283
|
-
start: startDate,
|
|
2284
|
-
end: endDate,
|
|
2285
|
-
itemIds: chosen.itemIds,
|
|
2286
|
-
itemNames: chosen.itemNames,
|
|
2287
|
-
total: chosen.total,
|
|
2288
|
-
});
|
|
2289
|
-
if (resp && (resp.autoPaid || String(resp.status) === 'paid')) {
|
|
2290
|
-
showAlert('success', 'Réservation confirmée.');
|
|
2291
|
-
} else {
|
|
2292
|
-
showAlert('success', 'Demande envoyée (en attente de validation).');
|
|
2293
|
-
}
|
|
2294
|
-
invalidateEventsCache();
|
|
2295
|
-
if (currentCalendar) scheduleRefetch(currentCalendar);
|
|
2296
|
-
}
|
|
2297
|
-
} catch (err) {
|
|
2298
|
-
// ignore
|
|
2299
|
-
}
|
|
2300
|
-
})();
|
|
2301
|
-
|
|
2302
|
-
return true;
|
|
2303
|
-
},
|
|
2304
|
-
},
|
|
2361
|
+
cancel: { label: 'Annuler', className: 'btn-danger' },
|
|
2305
2362
|
},
|
|
2306
2363
|
});
|
|
2307
2364
|
|
|
2365
|
+
// Mark as open to prevent duplicate modals (select/dateClick on mobile)
|
|
2366
|
+
isDialogOpen = true;
|
|
2367
|
+
try {
|
|
2368
|
+
dlg.on('hidden.bs.modal', () => { isDialogOpen = false; });
|
|
2369
|
+
} catch (e) {}
|
|
2370
|
+
|
|
2308
2371
|
function sameDay(a, b) {
|
|
2309
2372
|
return a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
2310
2373
|
}
|
|
@@ -2324,9 +2387,10 @@ async function openFabDatePicker(onSelection) {
|
|
|
2324
2387
|
}
|
|
2325
2388
|
|
|
2326
2389
|
function render() {
|
|
2327
|
-
|
|
2328
|
-
const
|
|
2329
|
-
const
|
|
2390
|
+
if (!showDatePicker) return;
|
|
2391
|
+
const monthEl = document.getElementById('onekite-wiz-month');
|
|
2392
|
+
const gridEl = document.getElementById('onekite-wiz-grid');
|
|
2393
|
+
const sumEl = document.getElementById('onekite-wiz-summary');
|
|
2330
2394
|
if (!monthEl || !gridEl || !sumEl) return;
|
|
2331
2395
|
|
|
2332
2396
|
monthEl.textContent = monthLabel(state.cursor);
|
|
@@ -2417,48 +2481,92 @@ async function openFabDatePicker(onSelection) {
|
|
|
2417
2481
|
|
|
2418
2482
|
dlg.on('shown.bs.modal', () => {
|
|
2419
2483
|
try {
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2484
|
+
if (showDatePicker) {
|
|
2485
|
+
const prevBtn = document.getElementById('onekite-wiz-prev');
|
|
2486
|
+
const nextBtn = document.getElementById('onekite-wiz-next');
|
|
2487
|
+
if (prevBtn) {
|
|
2488
|
+
prevBtn.addEventListener('click', () => {
|
|
2489
|
+
state.cursor.setMonth(state.cursor.getMonth() - 1);
|
|
2490
|
+
render();
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
if (nextBtn) {
|
|
2494
|
+
nextBtn.addEventListener('click', () => {
|
|
2495
|
+
state.cursor.setMonth(state.cursor.getMonth() + 1);
|
|
2496
|
+
render();
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// Swipe left/right to change month
|
|
2501
|
+
try {
|
|
2502
|
+
const root = document.getElementById('onekite-wiz-root');
|
|
2503
|
+
if (root) {
|
|
2504
|
+
let x0 = null;
|
|
2505
|
+
root.addEventListener('touchstart', (ev) => {
|
|
2506
|
+
try { x0 = ev.touches && ev.touches[0] ? ev.touches && ev.touches[0] ? ev.touches[0].clientX : null : null; } catch (e) { x0 = null; }
|
|
2507
|
+
}, { passive: true });
|
|
2508
|
+
root.addEventListener('touchend', (ev) => {
|
|
2509
|
+
try {
|
|
2510
|
+
if (x0 === null) return;
|
|
2511
|
+
const x1 = ev.changedTouches && ev.changedTouches[0] ? ev.changedTouches[0].clientX : null;
|
|
2512
|
+
if (x1 === null) return;
|
|
2513
|
+
const dx = x1 - x0;
|
|
2514
|
+
if (Math.abs(dx) < 40) return;
|
|
2515
|
+
if (dx < 0) {
|
|
2516
|
+
state.cursor.setMonth(state.cursor.getMonth() + 1);
|
|
2517
|
+
} else {
|
|
2518
|
+
state.cursor.setMonth(state.cursor.getMonth() - 1);
|
|
2519
|
+
}
|
|
2520
|
+
render();
|
|
2521
|
+
} catch (e) {}
|
|
2522
|
+
finally { x0 = null; }
|
|
2523
|
+
}, { passive: true });
|
|
2524
|
+
}
|
|
2525
|
+
} catch (e) {}
|
|
2526
|
+
} else {
|
|
2527
|
+
// Keep the static summary in sync (pre-filled by calendar click/selection).
|
|
2528
|
+
try {
|
|
2529
|
+
const el = document.getElementById('onekite-wiz-summary-static');
|
|
2530
|
+
if (el) el.innerHTML = `<strong>${escapeHtml(summaryText())}</strong>`;
|
|
2531
|
+
} catch (e) {}
|
|
2427
2532
|
}
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2533
|
+
|
|
2534
|
+
// Wire type buttons
|
|
2535
|
+
function ensureDatesOrToast() {
|
|
2536
|
+
if (!state.start) {
|
|
2537
|
+
showAlert('error', 'Choisis d\'abord une date.');
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
const s = new Date(state.start);
|
|
2541
|
+
const eIncl = state.end || state.start;
|
|
2542
|
+
const endExcl = new Date(eIncl);
|
|
2543
|
+
endExcl.setDate(endExcl.getDate() + 1);
|
|
2544
|
+
return { start: s, end: endExcl, allDay: true };
|
|
2433
2545
|
}
|
|
2434
2546
|
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
const dx = x1 - x0;
|
|
2449
|
-
if (Math.abs(dx) < 40) return;
|
|
2450
|
-
if (dx < 0) {
|
|
2451
|
-
state.cursor.setMonth(state.cursor.getMonth() + 1);
|
|
2452
|
-
} else {
|
|
2453
|
-
state.cursor.setMonth(state.cursor.getMonth() - 1);
|
|
2454
|
-
}
|
|
2455
|
-
render();
|
|
2456
|
-
} catch (e) {}
|
|
2457
|
-
finally { x0 = null; }
|
|
2458
|
-
}, { passive: true });
|
|
2547
|
+
async function run(kind) {
|
|
2548
|
+
const sel = ensureDatesOrToast();
|
|
2549
|
+
if (!sel) return;
|
|
2550
|
+
try { dlg.modal('hide'); } catch (e) { try { bootbox.hideAll(); } catch (e2) {} }
|
|
2551
|
+
|
|
2552
|
+
// Delegate to the existing flows (forms) while keeping the wizard
|
|
2553
|
+
// as the single entry-point for date+type selection.
|
|
2554
|
+
if (kind === 'location' && typeof options.onCreateLocation === 'function') {
|
|
2555
|
+
await options.onCreateLocation(sel);
|
|
2556
|
+
} else if (kind === 'outing' && typeof options.onCreateOuting === 'function') {
|
|
2557
|
+
await options.onCreateOuting(sel);
|
|
2558
|
+
} else if (kind === 'special' && typeof options.onCreateSpecial === 'function') {
|
|
2559
|
+
await options.onCreateSpecial(sel);
|
|
2459
2560
|
}
|
|
2460
|
-
}
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
document.getElementById('onekite-wiz-btn-location')?.addEventListener('click', () => { run('location'); });
|
|
2564
|
+
document.getElementById('onekite-wiz-btn-outing')?.addEventListener('click', () => { run('outing'); });
|
|
2565
|
+
document.getElementById('onekite-wiz-btn-special')?.addEventListener('click', () => { run('special'); });
|
|
2566
|
+
|
|
2461
2567
|
render();
|
|
2568
|
+
|
|
2569
|
+
// No auto-scroll: keep the wizard compact and stable.
|
|
2462
2570
|
} catch (e) {}
|
|
2463
2571
|
});
|
|
2464
2572
|
}
|
|
@@ -2490,10 +2598,10 @@ function parseYmdDate(ymdStr) {
|
|
|
2490
2598
|
fabEl.innerHTML = '<i class="fa fa-plus"></i>';
|
|
2491
2599
|
|
|
2492
2600
|
fabHandler = () => {
|
|
2493
|
-
// init() sets
|
|
2601
|
+
// init() sets openCreateWizardHandler. If the calendar has not
|
|
2494
2602
|
// finished initialising, do nothing.
|
|
2495
|
-
if (typeof
|
|
2496
|
-
|
|
2603
|
+
if (typeof openCreateWizardHandler !== 'function') return;
|
|
2604
|
+
openCreateWizardHandler();
|
|
2497
2605
|
};
|
|
2498
2606
|
fabEl.addEventListener('click', fabHandler);
|
|
2499
2607
|
|