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 +16 -22
- package/library.js +2 -83
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/client.js +123 -11
- package/templates/admin/plugins/calendar-onekite.tpl +25 -18
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
|
-
//
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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
package/plugin.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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: '© 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
|
-
}
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
540
|
-
|
|
541
|
-
const
|
|
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
|
-
<
|
|
119
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
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 -->
|