nodebb-plugin-onekite-calendar 2.0.9 → 2.0.11
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/CHANGELOG.md +6 -0
- package/lib/api.js +34 -8
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/client.js +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog – calendar-onekite
|
|
2
2
|
|
|
3
|
+
## 1.2.18
|
|
4
|
+
- API events + anti double booking : les tests de chevauchement utilisent désormais en priorité startDate/endDate (YYYY-MM-DD) quand disponibles (logique calendaire pure, endDate exclusive). Cela supprime définitivement les faux chevauchements liés aux timestamps/fuseaux/DST, notamment sur mobile et « Durée rapide ».
|
|
5
|
+
|
|
6
|
+
## 1.2.17
|
|
7
|
+
- Modale réservation : la requête de disponibilité initiale utilise aussi des dates calendaires (YYYY-MM-DD) au lieu de startStr/endStr/toISOString(), ce qui corrige le grisé erroné (mobile + durée rapide).
|
|
8
|
+
|
|
3
9
|
## 1.2.16
|
|
4
10
|
- Modale réservation (Durée rapide) : correction du grisé erroné des matériels réservés la veille. Les requêtes de disponibilité utilisent désormais des dates calendaires (YYYY-MM-DD) au lieu de toISOString() (UTC), pour éviter tout faux chevauchement lié au fuseau/DST.
|
|
5
11
|
|
package/lib/api.js
CHANGED
|
@@ -445,8 +445,17 @@ function computeEtag(payload) {
|
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
api.getEvents = async function (req, res) {
|
|
448
|
-
const
|
|
449
|
-
const
|
|
448
|
+
const qStartRaw = (req && req.query && req.query.start !== undefined) ? String(req.query.start).trim() : '';
|
|
449
|
+
const qEndRaw = (req && req.query && req.query.end !== undefined) ? String(req.query.end).trim() : '';
|
|
450
|
+
|
|
451
|
+
// If the client provides date-only strings (YYYY-MM-DD), prefer purely calendar-based
|
|
452
|
+
// overlap checks. This avoids any dependency on server timezone, user timezone, DST,
|
|
453
|
+
// or how JS Date() parses inputs.
|
|
454
|
+
const qStartYmd = (/^\d{4}-\d{2}-\d{2}$/.test(qStartRaw)) ? qStartRaw : null;
|
|
455
|
+
const qEndYmd = (/^\d{4}-\d{2}-\d{2}$/.test(qEndRaw)) ? qEndRaw : null;
|
|
456
|
+
|
|
457
|
+
const startTs = toTs(qStartRaw) || 0;
|
|
458
|
+
const endTs = toTs(qEndRaw) || (Date.now() + 365 * 24 * 3600 * 1000);
|
|
450
459
|
|
|
451
460
|
const settings = await meta.settings.get('calendar-onekite');
|
|
452
461
|
const canMod = req.uid ? await canValidate(req.uid, settings) : false;
|
|
@@ -494,9 +503,19 @@ api.getEvents = async function (req, res) {
|
|
|
494
503
|
}
|
|
495
504
|
// Only show active statuses
|
|
496
505
|
if (!['pending', 'awaiting_payment', 'paid'].includes(r.status)) continue;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
506
|
+
// Overlap check
|
|
507
|
+
// Prefer date-only strings (YYYY-MM-DD) for 100% reliable calendar-day logic.
|
|
508
|
+
const rStartYmd = (r.startDate && /^\d{4}-\d{2}-\d{2}$/.test(String(r.startDate))) ? String(r.startDate) : null;
|
|
509
|
+
const rEndYmd = (r.endDate && /^\d{4}-\d{2}-\d{2}$/.test(String(r.endDate))) ? String(r.endDate) : null;
|
|
510
|
+
|
|
511
|
+
if (qStartYmd && qEndYmd && rStartYmd && rEndYmd) {
|
|
512
|
+
// endDate is EXCLUSIVE (FullCalendar rule): overlap iff aStart < bEnd && bStart < aEnd
|
|
513
|
+
if (!(rStartYmd < qEndYmd && qStartYmd < rEndYmd)) continue;
|
|
514
|
+
} else {
|
|
515
|
+
const rStart = parseInt(r.start, 10);
|
|
516
|
+
const rEnd = parseInt(r.end, 10);
|
|
517
|
+
if (!(rStart < endTs && startTs < rEnd)) continue;
|
|
518
|
+
}
|
|
500
519
|
const evs = eventsFor(r);
|
|
501
520
|
for (const ev of evs) {
|
|
502
521
|
const p = ev.extendedProps || {};
|
|
@@ -822,9 +841,16 @@ api.createReservation = async function (req, res) {
|
|
|
822
841
|
const existingRows = await dbLayer.getReservations(candidateIds);
|
|
823
842
|
for (const existing of (existingRows || [])) {
|
|
824
843
|
if (!existing || !blocking.has(existing.status)) continue;
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
-
if (
|
|
844
|
+
const exStartYmd = (existing.startDate && /^\d{4}-\d{2}-\d{2}$/.test(String(existing.startDate))) ? String(existing.startDate) : null;
|
|
845
|
+
const exEndYmd = (existing.endDate && /^\d{4}-\d{2}-\d{2}$/.test(String(existing.endDate))) ? String(existing.endDate) : null;
|
|
846
|
+
if (startDate && endDate && exStartYmd && exEndYmd) {
|
|
847
|
+
// endDate is EXCLUSIVE: overlap iff aStart < bEnd && bStart < aEnd
|
|
848
|
+
if (!(exStartYmd < endDate && startDate < exEndYmd)) continue;
|
|
849
|
+
} else {
|
|
850
|
+
const exStart = parseInt(existing.start, 10);
|
|
851
|
+
const exEnd = parseInt(existing.end, 10);
|
|
852
|
+
if (!(exStart < end && start < exEnd)) continue;
|
|
853
|
+
}
|
|
828
854
|
const exItemIds = Array.isArray(existing.itemIds) ? existing.itemIds : (existing.itemId ? [existing.itemId] : []);
|
|
829
855
|
const shared = exItemIds.filter(x => itemIds.includes(String(x)));
|
|
830
856
|
if (shared.length) {
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/client.js
CHANGED
|
@@ -837,6 +837,13 @@ function toDatetimeLocalValue(date) {
|
|
|
837
837
|
const start = selectionInfo.start;
|
|
838
838
|
let end = selectionInfo.end;
|
|
839
839
|
|
|
840
|
+
// In some FullCalendar flows (notably on mobile), selectionInfo.end can be undefined.
|
|
841
|
+
// For all-day bookings, end is always treated as EXCLUSIVE.
|
|
842
|
+
if (!end) {
|
|
843
|
+
end = new Date(start);
|
|
844
|
+
end.setDate(end.getDate() + 1);
|
|
845
|
+
}
|
|
846
|
+
|
|
840
847
|
// Days (end is exclusive in FullCalendar) — compute in calendar days only
|
|
841
848
|
// (no dependency on hours, timezone or DST).
|
|
842
849
|
let days = calendarDaysExclusive(start, end);
|
|
@@ -844,9 +851,13 @@ function toDatetimeLocalValue(date) {
|
|
|
844
851
|
// Fetch existing events overlapping the selection to disable already reserved items.
|
|
845
852
|
let blocked = new Set();
|
|
846
853
|
try {
|
|
854
|
+
// IMPORTANT: never use toISOString() for availability checks.
|
|
855
|
+
// Local midnight in Europe/Paris becomes 23:00Z/22:00Z depending on DST, which makes
|
|
856
|
+
// consecutive all-day ranges appear to overlap and wrongly greys out items.
|
|
857
|
+
// Always query using calendar dates (YYYY-MM-DD) with end exclusive.
|
|
847
858
|
const qs = new URLSearchParams({
|
|
848
|
-
start: (
|
|
849
|
-
end: (
|
|
859
|
+
start: toLocalYmd(start),
|
|
860
|
+
end: toLocalYmd(end),
|
|
850
861
|
});
|
|
851
862
|
const evs = await fetchJson(`/api/v3/plugins/calendar-onekite/events?${qs.toString()}`);
|
|
852
863
|
(evs || []).forEach((ev) => {
|