nodebb-plugin-equipment-calendar 0.1.0 → 0.2.2

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/README.md CHANGED
@@ -35,3 +35,9 @@ Le plugin vérifie la signature si `webhookSecret` est renseigné (exemple basiq
35
35
  - Ce plugin est un squelette complet mais générique : adapte la logique de paiement HelloAsso selon ton besoin exact
36
36
  (type de checkout, itemization, montant, etc.).
37
37
  - Pour un contrôle d'overlap strict : le plugin empêche les réservations qui chevauchent (même item) pour les statuts bloquants.
38
+
39
+
40
+ ## URLs
41
+ - Calendrier: `/equipment/calendar` (alias `/calendar`)
42
+ - Validations: `/equipment/approvals`
43
+ - ACP: `/admin/plugins/equipment-calendar`
package/library.js CHANGED
@@ -306,7 +306,18 @@ function clampRange(startStr, endStr, tz) {
306
306
 
307
307
  // --- Routes ---
308
308
  plugin.init = async function (params) {
309
- const { router } = params;
309
+ const { router } = params;
310
+ const mid = params.middleware;
311
+
312
+ // Admin (ACP) routes
313
+ if (mid && mid.admin) {
314
+ router.get('/admin/plugins/equipment-calendar', mid.admin.buildHeader, renderAdminPage);
315
+ router.get('/api/admin/plugins/equipment-calendar', renderAdminPage);
316
+ }
317
+
318
+ // Convenience alias (optional): /calendar -> /equipment/calendar
319
+ router.get('/calendar', (req, res) => res.redirect('/equipment/calendar'));
320
+
310
321
 
311
322
  // To verify webhook signature we need raw body; add a rawBody collector for this route only
312
323
  router.post('/equipment/webhook/helloasso',
@@ -350,6 +361,7 @@ plugin.init = async function (params) {
350
361
  title: 'Paiement',
351
362
  rid: req.query.rid || '',
352
363
  status: req.query.status || 'ok',
364
+ statusError: String(req.query.status || '') === 'error',
353
365
  });
354
366
  });
355
367
 
@@ -389,6 +401,10 @@ async function renderAdminPage(req, res) {
389
401
  res.render('admin/plugins/equipment-calendar', {
390
402
  title: 'Equipment Calendar',
391
403
  settings,
404
+ view_dayGridMonth: (settings.defaultView || 'dayGridMonth') === 'dayGridMonth',
405
+ view_timeGridWeek: (settings.defaultView || '') === 'timeGridWeek',
406
+ view_timeGridDay: (settings.defaultView || '') === 'timeGridDay',
407
+ view_showRequesterToAll: String(settings.showRequesterToAll || '0') === '1',
392
408
  });
393
409
  }
394
410
 
@@ -397,10 +413,13 @@ async function renderCalendarPage(req, res) {
397
413
  const settings = await getSettings();
398
414
  const items = parseItems(settings.itemsJson).filter(i => i.active);
399
415
 
416
+ const rawItems = items;
417
+
400
418
  const tz = settings.timezone || 'Europe/Paris';
401
419
 
402
- const itemId = String(req.query.itemId || (items[0]?.id || '')).trim();
403
- const chosenItem = items.find(i => i.id === itemId) || items[0] || null;
420
+ const itemId = String(req.query.itemId || (rawItems[0]?.id || '')).trim();
421
+ const chosenItem = rawItems.find(i => i.id === itemId) || rawItems[0] || null;
422
+ const itemsView = rawItems.map(it => ({ ...it, selected: chosenItem ? it.id === chosenItem.id : false }));
404
423
 
405
424
  // Determine range to render
406
425
  const now = DateTime.now().setZone(tz);
@@ -437,7 +456,7 @@ async function renderCalendarPage(req, res) {
437
456
 
438
457
  res.render('equipment-calendar/calendar', {
439
458
  title: 'Réservation de matériel',
440
- items,
459
+ items: itemsView,
441
460
  chosenItemId: chosenItem ? chosenItem.id : '',
442
461
  chosenItemName: chosenItem ? chosenItem.name : '',
443
462
  chosenItemPriceCents: chosenItem ? chosenItem.priceCents : 0,
@@ -499,6 +518,7 @@ async function renderApprovalsPage(req, res) {
499
518
  res.render('equipment-calendar/approvals', {
500
519
  title: 'Validation des réservations',
501
520
  rows,
521
+ hasRows: Array.isArray(rows) && rows.length > 0,
502
522
  csrf: req.csrfToken,
503
523
  });
504
524
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-equipment-calendar",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Equipment reservation calendar for NodeBB (FullCalendar, approvals, HelloAsso payments)",
5
5
  "main": "library.js",
6
6
  "scripts": {
package/plugin.json CHANGED
@@ -1,9 +1,16 @@
1
1
  {
2
- "id": "nodebb-plugin-equipment-calendar",
3
- "name": "Equipment Calendar",
4
- "description": "Calendar-based equipment reservations with group approvals and HelloAsso payments.",
5
- "url": "https://example.invalid",
2
+ "id": "nodebb-plugin-calendar-onekite",
3
+ "name": "Calendar Onekite",
4
+ "description": "Calendrier + réservation matériel + validation admin + paiement HelloAsso pour NodeBB v4",
5
+ "url": "",
6
+ "version": "0.2.2",
6
7
  "library": "./library.js",
8
+ "staticDirs": {
9
+ "static": "static"
10
+ },
11
+ "acpScripts": [
12
+ "static/js/admin.js"
13
+ ],
7
14
  "hooks": [
8
15
  {
9
16
  "hook": "static:app.load",
@@ -14,12 +21,13 @@
14
21
  "method": "addAdminNavigation"
15
22
  },
16
23
  {
17
- "hook": "filter:router.page",
18
- "method": "addPageRoutes"
24
+ "hook": "filter:widgets.getWidgets",
25
+ "method": "defineWidgets"
26
+ },
27
+ {
28
+ "hook": "filter:widget.render:calendarUpcoming",
29
+ "method": "renderUpcomingWidget"
19
30
  }
20
31
  ],
21
- "staticDirs": {
22
- "public": "./public"
23
- },
24
- "templates": "./public/templates"
32
+ "templates": "templates"
25
33
  }
@@ -26,7 +26,7 @@
26
26
  <input id="notifyGroup" class="form-control" value="{{settings.notifyGroup}}">
27
27
  </div>
28
28
  <div class="form-check">
29
- <input class="form-check-input" type="checkbox" id="showRequesterToAll" {{#if (eq settings.showRequesterToAll "1")}}checked{{/if}}>
29
+ <input class="form-check-input" type="checkbox" id="showRequesterToAll" {{#if view_showRequesterToAll}}checked{{/if}}>
30
30
  <label class="form-check-label" for="showRequesterToAll">Afficher le demandeur à tout le monde</label>
31
31
  </div>
32
32
  </div>
@@ -49,9 +49,9 @@
49
49
  <h5>Calendrier</h5>
50
50
  <div class="mb-3"><label class="form-label">Vue par défaut</label>
51
51
  <select id="defaultView" class="form-select">
52
- <option value="dayGridMonth" {{#if (eq settings.defaultView "dayGridMonth")}}selected{{/if}}>Mois</option>
53
- <option value="timeGridWeek" {{#if (eq settings.defaultView "timeGridWeek")}}selected{{/if}}>Semaine</option>
54
- <option value="timeGridDay" {{#if (eq settings.defaultView "timeGridDay")}}selected{{/if}}>Jour</option>
52
+ <option value="dayGridMonth" {{#if view_dayGridMonth}}selected{{/if}}>Mois</option>
53
+ <option value="timeGridWeek" {{#if view_timeGridWeek}}selected{{/if}}>Semaine</option>
54
+ <option value="timeGridDay" {{#if view_timeGridDay}}selected{{/if}}>Jour</option>
55
55
  </select>
56
56
  </div>
57
57
  <div class="mb-3"><label class="form-label">Timezone</label><input id="timezone" class="form-control" value="{{settings.timezone}}"></div>
@@ -1,7 +1,7 @@
1
1
  <div class="equipment-approvals-page">
2
2
  <h1>Validation des réservations</h1>
3
3
 
4
- {{#if rows.length}}
4
+ {{#if hasRows}}
5
5
  <div class="table-responsive">
6
6
  <table class="table table-striped align-middle">
7
7
  <thead>
@@ -16,7 +16,7 @@
16
16
  </tr>
17
17
  </thead>
18
18
  <tbody>
19
- {{#rows}}
19
+ {{#each rows}}
20
20
  <tr>
21
21
  <td>{{itemName}}</td>
22
22
  <td>{{requester}}</td>
@@ -41,7 +41,7 @@
41
41
  </form>
42
42
  </td>
43
43
  </tr>
44
- {{/rows}}
44
+ {{/each}}
45
45
  </tbody>
46
46
  </table>
47
47
  </div>
@@ -6,9 +6,9 @@
6
6
  <div>
7
7
  <label class="form-label">Matériel</label>
8
8
  <select name="itemId" class="form-select" onchange="this.form.submit()">
9
- {{#items}}
10
- <option value="{{id}}" {{#if (eq ../chosenItemId id)}}selected{{/if}}>{{name}} — {{location}}</option>
11
- {{/items}}
9
+ {{#each items}}
10
+ <option value="{{id}}" {{#if selected}}selected{{/if}}>{{name}} — {{location}}</option>
11
+ {{/each}}
12
12
  </select>
13
13
  </div>
14
14
  <div class="text-muted small">
@@ -55,13 +55,16 @@
55
55
  <script src="/plugins/nodebb-plugin-equipment-calendar/lib/client.js"></script>
56
56
 
57
57
  <script>
58
- window.EC_EVENTS = {{{eventsJson}}};
58
+ // eventsJson is already JSON, we output it unescaped by using data attribute trick
59
+ window.EC_EVENTS = JSON.parse(document.getElementById('ec-events-json').textContent);
59
60
  window.EC_INITIAL_DATE = "{{initialDateISO}}";
60
61
  window.EC_INITIAL_VIEW = "{{view}}";
61
62
  window.EC_TZ = "{{tz}}";
62
63
  window.EC_CAN_CREATE = {{#if canCreate}}true{{else}}false{{/if}};
63
64
  </script>
64
65
 
66
+ <script type="application/json" id="ec-events-json">{{{eventsJson}}}</script>
67
+
65
68
  <style>
66
69
  .ec-status-pending .fc-event-title { font-weight: 600; }
67
70
  .ec-status-valid .fc-event-title { font-weight: 700; }
@@ -1,6 +1,6 @@
1
1
  <div class="equipment-payment-return">
2
2
  <h1>Paiement</h1>
3
- {{#if (eq status "error")}}
3
+ {{#if statusError}}
4
4
  <div class="alert alert-danger">Le paiement semble avoir échoué. Référence réservation: <code>{{rid}}</code></div>
5
5
  {{else}}
6
6
  <div class="alert alert-info">Merci. Si le paiement est confirmé, la réservation passera en "validée". Référence: <code>{{rid}}</code></div>