nodebb-plugin-equipment-calendar 2.0.6 → 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
@@ -16,6 +16,30 @@ const helpers = require.main.require('./src/controllers/helpers');
16
16
  const nconf = require.main.require('nconf');
17
17
 
18
18
  const winston = require.main.require('winston');
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
+
33
+ function generateId() {
34
+ try {
35
+ // Node 14+ / modern: crypto.randomUUID
36
+ const crypto = require('crypto');
37
+ if (crypto.randomUUID) return crypto.randomUUID();
38
+ return crypto.randomBytes(16).toString('hex');
39
+ } catch (e) {
40
+ return String(Date.now()) + '-' + Math.random().toString(16).slice(2);
41
+ }
42
+ }
19
43
  const axios = require('axios');
20
44
  const { DateTime } = require('luxon');
21
45
  const { v4: uuidv4 } = require('uuid');
@@ -24,6 +48,11 @@ const crypto = require('crypto');
24
48
  const plugin = {};
25
49
  const SETTINGS_KEY = 'equipmentCalendar';
26
50
 
51
+ const EC_KEYS = {
52
+ reservationsZset: 'ec:reservations',
53
+ reservationKey: (id) => `ec:reservation:${id}`,
54
+ };
55
+
27
56
  const DEFAULT_SETTINGS = {
28
57
  creatorGroups: 'registered-users',
29
58
  approverGroup: 'administrators',
@@ -287,7 +316,7 @@ async function getActiveItems() {
287
316
  const items = (Array.isArray(rawItems) ? rawItems : []).map((it) => ({
288
317
  id: String(it.id || '').trim(),
289
318
  name: String(it.name || '').trim(),
290
- price: String(it.price || '0').trim(),
319
+ price: Number(it.price || 0) || 0,
291
320
  active: true,
292
321
  })).filter(it => it.id && it.name);
293
322
  return items;
@@ -670,90 +699,26 @@ plugin.addAdminRoutes = async function (params) {
670
699
  };
671
700
 
672
701
 
673
- async function renderAdminReservationsPage(req, res) {
674
- if (!(await ensureIsAdmin(req, res))) return;
675
-
676
- const settings = await getSettings();
677
- const items = await getActiveItems(settings);
678
- const itemById = {};
679
- items.forEach(it => { itemById[it.id] = it; });
680
-
681
- const status = String(req.query.status || ''); // optional filter
682
- const itemId = String(req.query.itemId || ''); // optional filter
683
- const q = String(req.query.q || '').trim(); // search rid/user/notes
684
-
685
- const page = Math.max(1, parseInt(req.query.page, 10) || 1);
686
- const perPage = Math.min(100, Math.max(10, parseInt(req.query.perPage, 10) || 50));
687
-
688
- const allRids = await db.getSortedSetRevRange('equipmentCalendar:reservations', 0, -1);
689
- const totalAll = allRids.length;
690
-
691
- const rows = [];
692
- for (const rid of allRids) {
693
- // eslint-disable-next-line no-await-in-loop
694
- const r = await db.getObject(`equipmentCalendar:reservation:${rid}`);
695
- if (!r || !r.rid) continue;
696
-
697
- if (status && String(r.status) !== status) continue;
698
- if (itemId && String(r.itemId) !== itemId) continue;
699
-
700
- const notes = String(r.notesUser || '');
701
- const ridStr = String(r.rid || rid);
702
- if (q) {
703
- const hay = (ridStr + ' ' + String(r.uid || '') + ' ' + notes).toLowerCase();
704
- if (!hay.includes(q.toLowerCase())) continue;
705
- }
706
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) => {
707
709
  const startMs = parseInt(r.startMs, 10) || 0;
708
710
  const endMs = parseInt(r.endMs, 10) || 0;
709
- const createdAt = parseInt(r.createdAt, 10) || 0;
710
-
711
- rows.push({
712
- rid: ridStr,
713
- itemId: String(r.itemId || ''),
714
- itemName: (itemById[String(r.itemId || '')] && itemById[String(r.itemId || '')].name) || String(r.itemId || ''),
715
- uid: String(r.uid || ''),
716
- status: String(r.status || ''),
717
- start: startMs ? new Date(startMs).toISOString() : '',
718
- end: endMs ? new Date(endMs).toISOString() : '',
719
- createdAt: createdAt ? new Date(createdAt).toISOString() : '',
720
- notesUser: notes,
721
- });
722
- }
723
-
724
- const total = rows.length;
725
- const totalPages = Math.max(1, Math.ceil(total / perPage));
726
- const safePage = Math.min(page, totalPages);
727
- const startIndex = (safePage - 1) * perPage;
728
- const pageRows = rows.slice(startIndex, startIndex + perPage);
729
-
730
- const itemOptions = [{ id: '', name: 'Tous' }].concat(items.map(i => ({ id: i.id, name: i.name })));
731
- const statusOptions = [
732
- { id: '', name: 'Tous' },
733
- { id: 'pending', name: 'pending' },
734
- { id: 'approved', name: 'approved' },
735
- { id: 'paid', name: 'paid' },
736
- { id: 'rejected', name: 'rejected' },
737
- { id: 'cancelled', name: 'cancelled' },
738
- ];
739
-
740
- res.render('admin/plugins/equipment-calendar-reservations', {
741
- title: 'Equipment Calendar - Réservations',
742
- settings,
743
- rows: pageRows,
744
- hasRows: pageRows.length > 0,
745
- itemOptions: itemOptions.map(o => ({ ...o, selected: o.id === itemId })),
746
- statusOptions: statusOptions.map(o => ({ ...o, selected: o.id === status })),
747
- q,
748
- page: safePage,
749
- perPage,
750
- total,
751
- totalAll,
752
- totalPages,
753
- prevPage: safePage > 1 ? safePage - 1 : 0,
754
- nextPage: safePage < totalPages ? safePage + 1 : 0,
755
- 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
+ };
756
720
  });
721
+ res.render('admin/plugins/equipment-calendar-reservations', { title: 'Réservations', rows, hasRows: rows.length>0 });
757
722
  }
758
723
 
759
724
  async function handleAdminApprove(req, res) {
@@ -978,6 +943,8 @@ async function renderCalendarPage(req, res) {
978
943
  }));
979
944
 
980
945
  res.render('equipment-calendar/calendar', {
946
+ defaultView: settings.defaultView,
947
+
981
948
  title: 'Réservation de matériel',
982
949
  items,
983
950
  view,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "2.0.6",
3
+ "version": "3.0.0",
4
4
  "description": "Equipment reservation calendar for NodeBB (FullCalendar, approvals, HelloAsso payments)",
5
5
  "main": "library.js",
6
6
  "scripts": {
package/plugin.json CHANGED
@@ -25,6 +25,6 @@
25
25
  "scripts": [
26
26
  "public/js/client.js"
27
27
  ],
28
- "version": "0.7.3",
28
+ "version": "0.7.5",
29
29
  "minver": "4.7.1"
30
30
  }
@@ -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
- initialView: 'dayGridMonth',
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>