nodebb-plugin-equipment-calendar 3.0.0 → 4.9.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
@@ -20,11 +20,8 @@ const winston = require.main.require('winston');
20
20
  function decorateCalendarEvents(events) {
21
21
  return (events || []).map((ev) => {
22
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
23
  return Object.assign({}, ev, {
26
- title: `${icon} ${title}`,
27
- classNames: ['ec-status-' + status],
24
+ classNames: (ev.classNames || []).concat(['ec-status-' + status]),
28
25
  extendedProps: Object.assign({}, ev.extendedProps || {}, { status }),
29
26
  });
30
27
  });
@@ -363,9 +360,13 @@ async function getBookingRids(bookingId) {
363
360
  return await db.getSetMembers(`equipmentCalendar:booking:${bookingId}:rids`) || [];
364
361
  }
365
362
 
363
+ const GLOBAL_INDEX_KEY = 'ec:reservations';
364
+
366
365
  async function saveReservation(res) {
367
366
  await db.setObject(resKey(res.id), res);
368
367
  await db.sortedSetAdd(itemIndexKey(res.itemId), res.startMs, res.id);
368
+ // Global index for ACP listing
369
+ await db.sortedSetAdd(GLOBAL_INDEX_KEY, res.startMs, res.id);
369
370
  }
370
371
 
371
372
  async function getReservation(id) {
@@ -703,19 +704,22 @@ plugin.addAdminRoutes = async function (params) {
703
704
  async function renderAdminReservationsPage(req, res) {
704
705
  const isAdmin = await user.isAdminOrGlobalMod(req.uid);
705
706
  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)));
707
+ const ids = await db.getSortedSetRevRange(GLOBAL_INDEX_KEY, 0, 199);
708
+ const rowsRaw = await db.getObjects(ids.map(id => resKey(id)));
708
709
  const rows = (rowsRaw || []).filter(Boolean).map((r) => {
709
710
  const startMs = parseInt(r.startMs, 10) || 0;
710
711
  const endMs = parseInt(r.endMs, 10) || 0;
711
712
  return {
712
713
  id: r.id,
714
+ bookingId: r.bookingId || '',
713
715
  itemName: r.itemName || r.itemId,
714
716
  startIso: startMs ? new Date(startMs).toISOString().slice(0,10) : '',
715
717
  endIso: endMs ? new Date(endMs - 24*60*60*1000).toISOString().slice(0,10) : '',
716
- days: parseInt(r.days, 10) || 1,
718
+ days: Math.max(1, Math.round((endMs - startMs) / (24*60*60*1000))),
717
719
  status: r.status || 'pending',
718
720
  total: r.total || '0',
721
+ uid: r.uid,
722
+ createdAt: r.createdAt || 0,
719
723
  };
720
724
  });
721
725
  res.render('admin/plugins/equipment-calendar-reservations', { title: 'Réservations', rows, hasRows: rows.length>0 });
@@ -1159,7 +1163,7 @@ async function handleCreateReservation(req, res) {
1159
1163
  const created = [];
1160
1164
  for (const itemId of itemIds) {
1161
1165
  const rid = await createReservationForItem(req, res, settings, itemId, startMs, endMs, notesUser, bookingId);
1162
- created.push(rid);
1166
+ created.push(rid.id || rid);
1163
1167
  }
1164
1168
 
1165
1169
  // Redirect to calendar with a success flag
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "3.0.0",
3
+ "version": "4.9.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.5",
28
+ "version": "0.7.7",
29
29
  "minver": "4.7.1"
30
30
  }
@@ -93,11 +93,25 @@ 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
- headerToolbar:{left:'prev,next today',center:'title',right:'dayGridMonth,listMonth,listWeek'},
96
+ headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,dayGridWeek,timeGridWeek,listMonth,listWeek' },
97
97
  initialView: (window.EC_DEFAULT_VIEW || 'dayGridMonth'),
98
98
  selectable: canCreate,
99
99
  selectMirror: true,
100
100
  events,
101
+ eventContent: function(arg) {
102
+ const status = (arg.event.extendedProps && arg.event.extendedProps.status) || 'pending';
103
+ const wrap = document.createElement('div');
104
+ wrap.className = 'ec-event';
105
+ const dot = document.createElement('span');
106
+ dot.className = 'ec-dot';
107
+ const title = document.createElement('span');
108
+ title.className = 'ec-title';
109
+ title.textContent = arg.event.title || '';
110
+ wrap.appendChild(dot);
111
+ wrap.appendChild(title);
112
+ return { domNodes: [wrap] };
113
+ },
114
+ eventClassNames:function(arg){return (arg.event.classNames||[]);},
101
115
  eventDidMount:function(info){try{const s=info.event.extendedProps&&info.event.extendedProps.status;info.el.title=(s?('Statut: '+s+' — '):'')+info.event.title;}catch(e){}},
102
116
  dateClick: function (info) {
103
117
  if (!canCreate) return;
@@ -42,6 +42,8 @@
42
42
  <label class="form-label">Vue par défaut</label>
43
43
  <select class="form-select" name="defaultView">
44
44
  <option value="dayGridMonth" {{{ if view_dayGridMonth }}}selected{{{ end }}}>Mois</option>
45
+ <option value="dayGridWeek" {{{ if view_dayGridWeek }}}selected{{{ end }}}>Semaine</option>
46
+ <option value="timeGridWeek" {{{ if view_timeGridWeek }}}selected{{{ end }}}>Semaine (grille)</option>
45
47
  <option value="listMonth" {{{ if view_listMonth }}}selected{{{ end }}}>Liste (mois)</option>
46
48
  <option value="listWeek" {{{ if view_listWeek }}}selected{{{ end }}}>Liste (semaine)</option>
47
49
  </select>
@@ -1,3 +1,24 @@
1
+
2
+ <style>
3
+ /* Professional status styling */
4
+ .fc .ec-event { display:flex; align-items:center; gap:.4rem; padding:.05rem .35rem; border-radius:.5rem; }
5
+ .fc .ec-dot { width:.55rem; height:.55rem; border-radius:999px; display:inline-block; }
6
+ .fc .ec-title { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
7
+
8
+ .fc-event.ec-status-pending { border-color: rgba(108,117,125,.35); }
9
+ .fc-event.ec-status-approved { border-color: rgba(13,110,253,.35); }
10
+ .fc-event.ec-status-paid { border-color: rgba(25,135,84,.35); }
11
+ .fc-event.ec-status-cancelled { opacity:.55; text-decoration: line-through; }
12
+
13
+ .fc-event.ec-status-pending .ec-dot { background: #6c757d; }
14
+ .fc-event.ec-status-approved .ec-dot { background: #0d6efd; }
15
+ .fc-event.ec-status-paid .ec-dot { background: #198754; }
16
+ .fc-event.ec-status-cancelled .ec-dot { background: #dc3545; }
17
+
18
+ /* list view rows */
19
+ .fc .fc-list-event.ec-status-cancelled { opacity:.6; text-decoration: line-through; }
20
+ </style>
21
+
1
22
  <div class="equipment-calendar-page">
2
23
  <h1>Réservation de matériel</h1>
3
24
 
@@ -10,7 +31,15 @@
10
31
  {{{ end }}}
11
32
 
12
33
  <div class="card card-body mb-3">
13
- <div id="ec-calendar"></div>
34
+
35
+ <div class="d-flex flex-wrap gap-2 align-items-center mb-2" id="ec-legend">
36
+ <span class="badge text-bg-secondary"><i class="fa fa-hourglass-half me-1"></i>En attente</span>
37
+ <span class="badge text-bg-primary"><i class="fa fa-check me-1"></i>Validée</span>
38
+ <span class="badge text-bg-success"><i class="fa fa-check-double me-1"></i>Payée</span>
39
+ <span class="badge text-bg-danger"><i class="fa fa-times me-1"></i>Annulée</span>
40
+ </div>
41
+
42
+ <div id="ec-calendar"></div>
14
43
  </div>
15
44
 
16
45
  <!-- Modal de demande -->