nodebb-plugin-equipment-calendar 2.2.2 → 3.0.0
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/library.js
CHANGED
|
@@ -17,6 +17,19 @@ const nconf = require.main.require('nconf');
|
|
|
17
17
|
|
|
18
18
|
const winston = require.main.require('winston');
|
|
19
19
|
|
|
20
|
+
function decorateCalendarEvents(events) {
|
|
21
|
+
return (events || []).map((ev) => {
|
|
22
|
+
const status = ev.status || (ev.extendedProps && ev.extendedProps.status) || 'pending';
|
|
23
|
+
const icon = status === 'paid' ? '✅' : (status === 'approved' ? '✔️' : (status === 'pending' ? '⏳' : (status === 'cancelled' ? '❌' : '⚠️')));
|
|
24
|
+
const title = ev.title || '';
|
|
25
|
+
return Object.assign({}, ev, {
|
|
26
|
+
title: `${icon} ${title}`,
|
|
27
|
+
classNames: ['ec-status-' + status],
|
|
28
|
+
extendedProps: Object.assign({}, ev.extendedProps || {}, { status }),
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
function generateId() {
|
|
21
34
|
try {
|
|
22
35
|
// Node 14+ / modern: crypto.randomUUID
|
|
@@ -35,6 +48,11 @@ const crypto = require('crypto');
|
|
|
35
48
|
const plugin = {};
|
|
36
49
|
const SETTINGS_KEY = 'equipmentCalendar';
|
|
37
50
|
|
|
51
|
+
const EC_KEYS = {
|
|
52
|
+
reservationsZset: 'ec:reservations',
|
|
53
|
+
reservationKey: (id) => `ec:reservation:${id}`,
|
|
54
|
+
};
|
|
55
|
+
|
|
38
56
|
const DEFAULT_SETTINGS = {
|
|
39
57
|
creatorGroups: 'registered-users',
|
|
40
58
|
approverGroup: 'administrators',
|
|
@@ -681,90 +699,26 @@ plugin.addAdminRoutes = async function (params) {
|
|
|
681
699
|
};
|
|
682
700
|
|
|
683
701
|
|
|
684
|
-
async function renderAdminReservationsPage(req, res) {
|
|
685
|
-
if (!(await ensureIsAdmin(req, res))) return;
|
|
686
|
-
|
|
687
|
-
const settings = await getSettings();
|
|
688
|
-
const items = await getActiveItems(settings);
|
|
689
|
-
const itemById = {};
|
|
690
|
-
items.forEach(it => { itemById[it.id] = it; });
|
|
691
|
-
|
|
692
|
-
const status = String(req.query.status || ''); // optional filter
|
|
693
|
-
const itemId = String(req.query.itemId || ''); // optional filter
|
|
694
|
-
const q = String(req.query.q || '').trim(); // search rid/user/notes
|
|
695
|
-
|
|
696
|
-
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
|
697
|
-
const perPage = Math.min(100, Math.max(10, parseInt(req.query.perPage, 10) || 50));
|
|
698
|
-
|
|
699
|
-
const allRids = await db.getSortedSetRevRange('equipmentCalendar:reservations', 0, -1);
|
|
700
|
-
const totalAll = allRids.length;
|
|
701
|
-
|
|
702
|
-
const rows = [];
|
|
703
|
-
for (const rid of allRids) {
|
|
704
|
-
// eslint-disable-next-line no-await-in-loop
|
|
705
|
-
const r = await db.getObject(`equipmentCalendar:reservation:${rid}`);
|
|
706
|
-
if (!r || !r.rid) continue;
|
|
707
|
-
|
|
708
|
-
if (status && String(r.status) !== status) continue;
|
|
709
|
-
if (itemId && String(r.itemId) !== itemId) continue;
|
|
710
|
-
|
|
711
|
-
const notes = String(r.notesUser || '');
|
|
712
|
-
const ridStr = String(r.rid || rid);
|
|
713
|
-
if (q) {
|
|
714
|
-
const hay = (ridStr + ' ' + String(r.uid || '') + ' ' + notes).toLowerCase();
|
|
715
|
-
if (!hay.includes(q.toLowerCase())) continue;
|
|
716
|
-
}
|
|
717
702
|
|
|
703
|
+
async function renderAdminReservationsPage(req, res) {
|
|
704
|
+
const isAdmin = await user.isAdminOrGlobalMod(req.uid);
|
|
705
|
+
if (!isAdmin) return res.status(403).render('403', {});
|
|
706
|
+
const ids = await db.getSortedSetRevRange(EC_KEYS.reservationsZset, 0, 199);
|
|
707
|
+
const rowsRaw = await db.getObjects(ids.map(id => EC_KEYS.reservationKey(id)));
|
|
708
|
+
const rows = (rowsRaw || []).filter(Boolean).map((r) => {
|
|
718
709
|
const startMs = parseInt(r.startMs, 10) || 0;
|
|
719
710
|
const endMs = parseInt(r.endMs, 10) || 0;
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
end: endMs ? new Date(endMs).toISOString() : '',
|
|
730
|
-
createdAt: createdAt ? new Date(createdAt).toISOString() : '',
|
|
731
|
-
notesUser: notes,
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
const total = rows.length;
|
|
736
|
-
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
737
|
-
const safePage = Math.min(page, totalPages);
|
|
738
|
-
const startIndex = (safePage - 1) * perPage;
|
|
739
|
-
const pageRows = rows.slice(startIndex, startIndex + perPage);
|
|
740
|
-
|
|
741
|
-
const itemOptions = [{ id: '', name: 'Tous' }].concat(items.map(i => ({ id: i.id, name: i.name })));
|
|
742
|
-
const statusOptions = [
|
|
743
|
-
{ id: '', name: 'Tous' },
|
|
744
|
-
{ id: 'pending', name: 'pending' },
|
|
745
|
-
{ id: 'approved', name: 'approved' },
|
|
746
|
-
{ id: 'paid', name: 'paid' },
|
|
747
|
-
{ id: 'rejected', name: 'rejected' },
|
|
748
|
-
{ id: 'cancelled', name: 'cancelled' },
|
|
749
|
-
];
|
|
750
|
-
|
|
751
|
-
res.render('admin/plugins/equipment-calendar-reservations', {
|
|
752
|
-
title: 'Equipment Calendar - Réservations',
|
|
753
|
-
settings,
|
|
754
|
-
rows: pageRows,
|
|
755
|
-
hasRows: pageRows.length > 0,
|
|
756
|
-
itemOptions: itemOptions.map(o => ({ ...o, selected: o.id === itemId })),
|
|
757
|
-
statusOptions: statusOptions.map(o => ({ ...o, selected: o.id === status })),
|
|
758
|
-
q,
|
|
759
|
-
page: safePage,
|
|
760
|
-
perPage,
|
|
761
|
-
total,
|
|
762
|
-
totalAll,
|
|
763
|
-
totalPages,
|
|
764
|
-
prevPage: safePage > 1 ? safePage - 1 : 0,
|
|
765
|
-
nextPage: safePage < totalPages ? safePage + 1 : 0,
|
|
766
|
-
actionBase: '/admin/plugins/equipment-calendar/reservations',
|
|
711
|
+
return {
|
|
712
|
+
id: r.id,
|
|
713
|
+
itemName: r.itemName || r.itemId,
|
|
714
|
+
startIso: startMs ? new Date(startMs).toISOString().slice(0,10) : '',
|
|
715
|
+
endIso: endMs ? new Date(endMs - 24*60*60*1000).toISOString().slice(0,10) : '',
|
|
716
|
+
days: parseInt(r.days, 10) || 1,
|
|
717
|
+
status: r.status || 'pending',
|
|
718
|
+
total: r.total || '0',
|
|
719
|
+
};
|
|
767
720
|
});
|
|
721
|
+
res.render('admin/plugins/equipment-calendar-reservations', { title: 'Réservations', rows, hasRows: rows.length>0 });
|
|
768
722
|
}
|
|
769
723
|
|
|
770
724
|
async function handleAdminApprove(req, res) {
|
|
@@ -989,6 +943,8 @@ async function renderCalendarPage(req, res) {
|
|
|
989
943
|
}));
|
|
990
944
|
|
|
991
945
|
res.render('equipment-calendar/calendar', {
|
|
946
|
+
defaultView: settings.defaultView,
|
|
947
|
+
|
|
992
948
|
title: 'Réservation de matériel',
|
|
993
949
|
items,
|
|
994
950
|
view,
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/js/client.js
CHANGED
|
@@ -93,10 +93,12 @@ require(['jquery', 'bootstrap'], function ($, bootstrap) {
|
|
|
93
93
|
const events = Array.isArray(window.EC_EVENTS) ? window.EC_EVENTS : [];
|
|
94
94
|
|
|
95
95
|
const calendar = new window.FullCalendar.Calendar(calendarEl, {
|
|
96
|
-
|
|
96
|
+
headerToolbar:{left:'prev,next today',center:'title',right:'dayGridMonth,listMonth,listWeek'},
|
|
97
|
+
initialView: (window.EC_DEFAULT_VIEW || 'dayGridMonth'),
|
|
97
98
|
selectable: canCreate,
|
|
98
99
|
selectMirror: true,
|
|
99
100
|
events,
|
|
101
|
+
eventDidMount:function(info){try{const s=info.event.extendedProps&&info.event.extendedProps.status;info.el.title=(s?('Statut: '+s+' — '):'')+info.event.title;}catch(e){}},
|
|
100
102
|
dateClick: function (info) {
|
|
101
103
|
if (!canCreate) return;
|
|
102
104
|
const startMs = Date.UTC(info.date.getUTCFullYear(), info.date.getUTCMonth(), info.date.getUTCDate());
|
|
@@ -35,7 +35,20 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
</div>
|
|
37
37
|
|
|
38
|
+
|
|
38
39
|
<div class="card card-body mb-3">
|
|
40
|
+
<h5>Affichage calendrier</h5>
|
|
41
|
+
<div class="mb-0">
|
|
42
|
+
<label class="form-label">Vue par défaut</label>
|
|
43
|
+
<select class="form-select" name="defaultView">
|
|
44
|
+
<option value="dayGridMonth" {{{ if view_dayGridMonth }}}selected{{{ end }}}>Mois</option>
|
|
45
|
+
<option value="listMonth" {{{ if view_listMonth }}}selected{{{ end }}}>Liste (mois)</option>
|
|
46
|
+
<option value="listWeek" {{{ if view_listWeek }}}selected{{{ end }}}>Liste (semaine)</option>
|
|
47
|
+
</select>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="card card-body mb-3">
|
|
39
52
|
<h5>HelloAsso</h5>
|
|
40
53
|
<div class="mb-3">
|
|
41
54
|
<label class="form-label">API Base URL (prod/sandbox)</label>
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
</div>
|
|
75
75
|
|
|
76
76
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js"></script>
|
|
77
|
+
<script>window.EC_DEFAULT_VIEW = '{defaultView}';</script>
|
|
77
78
|
<script src="{relative_path}/plugins/nodebb-plugin-equipment-calendar/js/client.js"></script>
|
|
78
79
|
|
|
79
80
|
<script>
|
|
@@ -82,3 +83,5 @@
|
|
|
82
83
|
window.EC_CAN_CREATE = {canCreateJs};
|
|
83
84
|
window.EC_TZ = '{tz}';
|
|
84
85
|
</script>
|
|
86
|
+
|
|
87
|
+
<style>.ec-status-pending{opacity:.85}.ec-status-paid{font-weight:600}.ec-status-cancelled,.ec-status-expired{opacity:.6;text-decoration:line-through}</style>
|