nodebb-plugin-equipment-calendar 9.1.3 → 9.1.5
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 +11 -50
- package/package.json +3 -20
- package/plugin.json +1 -6
- package/public/js/acp.js +32 -24
- package/public/js/client.js +17 -21
- package/templates/admin/plugins/equipment-calendar.tpl +2 -8
- package/templates/equipment-calendar/calendar.tpl +4 -15
- package/README.md +0 -43
- package/public/css/style.css +0 -2
- package/public/js/admin.js +0 -29
- package/public/lib/admin.js +0 -32
- package/public/lib/client.js +0 -50
- package/public/styles.css +0 -1
- package/public/templates/admin/plugins/equipment-calendar-helloasso-test.tpl +0 -54
- package/public/templates/admin/plugins/equipment-calendar-reservations.tpl +0 -99
- package/public/templates/admin/plugins/equipment-calendar.tpl +0 -83
- package/public/templates/equipment-calendar/approvals.tpl +0 -51
- package/public/templates/equipment-calendar/calendar.tpl +0 -89
- package/public/templates/equipment-calendar/payment-return.tpl +0 -15
- package/scripts/postinstall.js +0 -37
- package/templates/admin/plugins/equipment-calendar-helloasso-test.tpl +0 -54
- package/templates/admin/plugins/equipment-calendar-reservations.tpl +0 -99
- package/templates/equipment-calendar/approvals.tpl +0 -51
- package/templates/equipment-calendar/payment-return.tpl +0 -15
package/library.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const nconf = require.main.require('nconf');
|
|
4
3
|
const meta = require.main.require('./src/meta');
|
|
5
4
|
const db = require.main.require('./src/database');
|
|
6
|
-
const user = require.main.require('./src/user');
|
|
7
5
|
const groups = require.main.require('./src/groups');
|
|
8
6
|
const winston = require.main.require('winston');
|
|
9
7
|
|
|
@@ -35,16 +33,13 @@ async function getSettings() {
|
|
|
35
33
|
async function isUserInAnyGroups(uid, groupCsv) {
|
|
36
34
|
if (!uid || !groupCsv) return false;
|
|
37
35
|
const names = String(groupCsv).split(',').map(s => s.trim()).filter(Boolean);
|
|
38
|
-
if (!names.length) return false;
|
|
39
36
|
for (const name of names) {
|
|
40
|
-
|
|
41
|
-
if (inGroup) return true;
|
|
37
|
+
if (await groups.isMember(uid, name)) return true;
|
|
42
38
|
}
|
|
43
39
|
return false;
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
function
|
|
47
|
-
// UUID v4 (no dependency)
|
|
42
|
+
function genRid() {
|
|
48
43
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
49
44
|
const r = Math.random() * 16 | 0;
|
|
50
45
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
@@ -60,14 +55,13 @@ async function saveReservation(r) {
|
|
|
60
55
|
await db.sortedSetAdd(reservationsZset, r.createdAt || Date.now(), r.rid);
|
|
61
56
|
}
|
|
62
57
|
|
|
63
|
-
async function listReservations(limit=
|
|
58
|
+
async function listReservations(limit=1000) {
|
|
64
59
|
const rids = await db.getSortedSetRevRange(reservationsZset, 0, limit - 1);
|
|
65
60
|
const objs = await Promise.all(rids.map(id => db.getObject(reservationKey(id))));
|
|
66
61
|
return objs.filter(Boolean);
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
function toEvent(r) {
|
|
70
|
-
// FullCalendar allDay uses exclusive end
|
|
71
65
|
const start = String(r.startIso).slice(0,10);
|
|
72
66
|
const end = new Date(String(r.endIso).slice(0,10) + 'T00:00:00Z');
|
|
73
67
|
end.setUTCDate(end.getUTCDate() + 1);
|
|
@@ -75,42 +69,28 @@ function toEvent(r) {
|
|
|
75
69
|
|
|
76
70
|
const status = r.status || 'pending';
|
|
77
71
|
const title = status === 'paid' ? '[PAID] Reservation' : (status === 'approved' ? '[APPROVED] Reservation' : '[PENDING] Reservation');
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
id: r.rid,
|
|
81
|
-
title,
|
|
82
|
-
start,
|
|
83
|
-
end: endExcl,
|
|
84
|
-
allDay: true,
|
|
85
|
-
};
|
|
72
|
+
return { id: r.rid, title, start, end: endExcl, allDay: true };
|
|
86
73
|
}
|
|
87
74
|
|
|
88
75
|
plugin.init = async function (params) {
|
|
89
76
|
const { router, middleware } = params;
|
|
90
77
|
|
|
91
|
-
// Admin page
|
|
92
78
|
router.get('/admin/plugins/equipment-calendar', middleware.admin.buildHeader, async (req, res) => {
|
|
93
79
|
res.render('admin/plugins/equipment-calendar', {});
|
|
94
80
|
});
|
|
95
|
-
router.get('/api/admin/plugins/equipment-calendar', async (req, res) => {
|
|
96
|
-
res.json({});
|
|
97
|
-
});
|
|
81
|
+
router.get('/api/admin/plugins/equipment-calendar', async (req, res) => res.json({}));
|
|
98
82
|
|
|
99
|
-
// Calendar page
|
|
100
83
|
router.get('/equipment/calendar', middleware.buildHeader, async (req, res) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
router.get('/api/equipment/calendar', async (req, res) => {
|
|
104
|
-
res.json({});
|
|
84
|
+
const settings = await getSettings();
|
|
85
|
+
res.render('equipment-calendar/calendar', { defaultView: settings.defaultView });
|
|
105
86
|
});
|
|
87
|
+
router.get('/api/equipment/calendar', async (req, res) => res.json({}));
|
|
106
88
|
|
|
107
|
-
// API: events
|
|
108
89
|
router.get('/api/plugins/equipment-calendar/events', async (req, res) => {
|
|
109
90
|
const list = await listReservations(1000);
|
|
110
91
|
res.json({ events: list.map(toEvent) });
|
|
111
92
|
});
|
|
112
93
|
|
|
113
|
-
// API: create reservation
|
|
114
94
|
router.post('/api/plugins/equipment-calendar/reservations', middleware.applyCSRF, async (req, res) => {
|
|
115
95
|
const uid = req.uid;
|
|
116
96
|
if (!uid) return res.status(403).json({ message: 'forbidden' });
|
|
@@ -125,36 +105,17 @@ plugin.init = async function (params) {
|
|
|
125
105
|
if (!startIso || !endIso) return res.status(400).json({ message: 'dates required' });
|
|
126
106
|
if (!itemIds.length) return res.status(400).json({ message: 'items required' });
|
|
127
107
|
|
|
128
|
-
const r = {
|
|
129
|
-
rid: rid(),
|
|
130
|
-
uid,
|
|
131
|
-
startIso,
|
|
132
|
-
endIso,
|
|
133
|
-
itemIds: itemIds.map(String),
|
|
134
|
-
status: 'pending',
|
|
135
|
-
createdAt: Date.now(),
|
|
136
|
-
};
|
|
108
|
+
const r = { rid: genRid(), uid, startIso, endIso, itemIds: itemIds.map(String), status: 'pending', createdAt: Date.now() };
|
|
137
109
|
await saveReservation(r);
|
|
138
110
|
res.json({ ok: true, rid: r.rid });
|
|
139
111
|
});
|
|
140
112
|
|
|
141
|
-
winston.info('[equipment-calendar] loaded (official4x
|
|
113
|
+
winston.info('[equipment-calendar] loaded (official4x CDN)');
|
|
142
114
|
};
|
|
143
115
|
|
|
144
116
|
plugin.addAdminMenu = async function (header) {
|
|
145
|
-
header.plugins.push({
|
|
146
|
-
route: '/plugins/equipment-calendar',
|
|
147
|
-
icon: 'fa-calendar',
|
|
148
|
-
name: 'Equipment Calendar',
|
|
149
|
-
});
|
|
117
|
+
header.plugins.push({ route: '/plugins/equipment-calendar', icon: 'fa-calendar', name: 'Equipment Calendar' });
|
|
150
118
|
return header;
|
|
151
119
|
};
|
|
152
120
|
|
|
153
|
-
// Optional: allow /calendar -> /equipment/calendar without hard redirect
|
|
154
|
-
plugin.addRouteAliases = async function (hookData) {
|
|
155
|
-
// hookData: { router, middleware, controllers } - but for filter:router.page we can inject page routes elsewhere;
|
|
156
|
-
// Keep empty to avoid breaking installs. Alias handled by theme route config if needed.
|
|
157
|
-
return hookData;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
121
|
module.exports = plugin;
|
package/package.json
CHANGED
|
@@ -1,24 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-equipment-calendar",
|
|
3
|
-
"version": "9.1.
|
|
4
|
-
"description": "Equipment reservation calendar (NodeBB 4.x
|
|
3
|
+
"version": "9.1.5",
|
|
4
|
+
"description": "Equipment reservation calendar (NodeBB 4.x, official ACP settings pattern, FullCalendar via CDN)",
|
|
5
5
|
"main": "library.js",
|
|
6
|
-
"license": "MIT"
|
|
7
|
-
"keywords": [
|
|
8
|
-
"nodebb",
|
|
9
|
-
"plugin",
|
|
10
|
-
"calendar",
|
|
11
|
-
"reservations",
|
|
12
|
-
"fullcalendar"
|
|
13
|
-
],
|
|
14
|
-
"dependencies": {
|
|
15
|
-
"@fullcalendar/core": "^6.1.11",
|
|
16
|
-
"@fullcalendar/daygrid": "^6.1.11",
|
|
17
|
-
"@fullcalendar/interaction": "^6.1.11",
|
|
18
|
-
"@fullcalendar/list": "^6.1.11",
|
|
19
|
-
"@fullcalendar/timegrid": "^6.1.11"
|
|
20
|
-
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"postinstall": "node ./scripts/postinstall.js"
|
|
23
|
-
}
|
|
6
|
+
"license": "MIT"
|
|
24
7
|
}
|
package/plugin.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nodebb-plugin-equipment-calendar",
|
|
3
3
|
"name": "Equipment Calendar",
|
|
4
|
-
"description": "Equipment reservation calendar
|
|
5
|
-
"url": "https://github.com/your-org/nodebb-plugin-equipment-calendar",
|
|
4
|
+
"description": "Equipment reservation calendar (NodeBB 4.x, CDN assets)",
|
|
6
5
|
"library": "./library.js",
|
|
7
6
|
"hooks": [
|
|
8
7
|
{
|
|
@@ -12,10 +11,6 @@
|
|
|
12
11
|
{
|
|
13
12
|
"hook": "filter:admin.header.build",
|
|
14
13
|
"method": "addAdminMenu"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"hook": "filter:router.page",
|
|
18
|
-
"method": "addRouteAliases"
|
|
19
14
|
}
|
|
20
15
|
],
|
|
21
16
|
"acpScripts": [
|
package/public/js/acp.js
CHANGED
|
@@ -1,40 +1,48 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
/* globals $, ajaxify */
|
|
3
|
-
define('admin/plugins/equipment-calendar', ['settings', 'alerts'], function (Settings, alerts) {
|
|
4
|
-
const Admin = {};
|
|
2
|
+
/* globals $, ajaxify, require */
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
(function () {
|
|
5
|
+
function onEnd(Settings, alerts) {
|
|
7
6
|
const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : (window.location.pathname || '');
|
|
8
|
-
if (!url.startsWith('admin/plugins/equipment-calendar'))
|
|
9
|
-
|
|
10
|
-
}
|
|
7
|
+
if (!url.startsWith('admin/plugins/equipment-calendar')) return;
|
|
8
|
+
|
|
11
9
|
const container = $('.equipment-calendar-settings');
|
|
12
10
|
if (!container.length) return;
|
|
13
11
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
Settings.sync
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
// load
|
|
13
|
+
try {
|
|
14
|
+
if (typeof Settings.sync === 'function') {
|
|
15
|
+
Settings.sync('equipmentCalendar', container);
|
|
16
|
+
} else {
|
|
17
|
+
Settings.load('equipmentCalendar', container);
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {}
|
|
20
20
|
|
|
21
|
-
$('#save').off('click.ec').on('click.ec', function (e) {
|
|
21
|
+
$('#ec-save').off('click.ec').on('click.ec', function (e) {
|
|
22
22
|
e.preventDefault();
|
|
23
23
|
const done = function () {
|
|
24
24
|
alerts.success('[[admin/settings:settings-saved]]');
|
|
25
25
|
};
|
|
26
|
-
|
|
27
|
-
Settings.persist
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
try {
|
|
27
|
+
if (typeof Settings.persist === 'function') {
|
|
28
|
+
Settings.persist('equipmentCalendar', container, done, true);
|
|
29
|
+
} else {
|
|
30
|
+
Settings.save('equipmentCalendar', container, done);
|
|
31
|
+
}
|
|
32
|
+
} catch (err) {
|
|
33
|
+
alerts.error(err && err.message ? err.message : 'Erreur');
|
|
30
34
|
}
|
|
31
35
|
});
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
$(window).off('action:ajaxify.end.ec').on('action:ajaxify.end.ec', function () {
|
|
39
|
+
require(['settings', 'alerts'], function (Settings, alerts) {
|
|
40
|
+
onEnd(Settings, alerts);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
// run once
|
|
45
|
+
require(['settings', 'alerts'], function (Settings, alerts) {
|
|
46
|
+
onEnd(Settings, alerts);
|
|
47
|
+
});
|
|
48
|
+
})();
|
package/public/js/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
/* globals $, ajaxify
|
|
2
|
+
/* globals $, ajaxify */
|
|
3
3
|
define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
4
4
|
const Client = {};
|
|
5
5
|
|
|
@@ -8,9 +8,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
|
8
8
|
return url === 'equipment/calendar' || url === 'calendar';
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function isoDate(
|
|
12
|
-
return String(d || '').slice(0, 10);
|
|
13
|
-
}
|
|
11
|
+
function isoDate(s) { return String(s || '').slice(0, 10); }
|
|
14
12
|
|
|
15
13
|
async function loadEvents() {
|
|
16
14
|
const res = await api.get('/plugins/equipment-calendar/events', {});
|
|
@@ -21,31 +19,33 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
|
21
19
|
$('#ec-start').val(startIso);
|
|
22
20
|
$('#ec-end').val(endIso);
|
|
23
21
|
const modalEl = document.getElementById('ecModal');
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
if (!modalEl || !window.bootstrap) return;
|
|
23
|
+
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
async function createReservation(payload) {
|
|
29
|
-
|
|
30
|
-
return res;
|
|
27
|
+
return api.post('/plugins/equipment-calendar/reservations', payload);
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
async function initCalendar() {
|
|
34
31
|
const el = document.getElementById('ec-calendar');
|
|
35
|
-
if (!el || !window.FullCalendar)
|
|
32
|
+
if (!el || !window.FullCalendar || !window.FullCalendar.Calendar) {
|
|
33
|
+
// show a useful error
|
|
34
|
+
if (el) {
|
|
35
|
+
el.innerHTML = '<div class="alert alert-danger">FullCalendar non chargé (CDN)</div>';
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
const events = await loadEvents();
|
|
38
41
|
|
|
39
|
-
const calendar = new FullCalendar.Calendar(el, {
|
|
40
|
-
plugins: [ FullCalendarDayGrid, FullCalendarTimeGrid, FullCalendarList, FullCalendarInteraction ],
|
|
42
|
+
const calendar = new window.FullCalendar.Calendar(el, {
|
|
41
43
|
initialView: 'dayGridMonth',
|
|
42
44
|
headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,listWeek' },
|
|
43
45
|
selectable: true,
|
|
44
46
|
selectMirror: true,
|
|
45
|
-
|
|
46
|
-
allDaySlot: true,
|
|
47
|
+
displayEventTime: false,
|
|
47
48
|
select: function (info) {
|
|
48
|
-
// all-day selection end is exclusive -> store inclusive end
|
|
49
49
|
const start = isoDate(info.startStr);
|
|
50
50
|
const endExcl = isoDate(info.endStr);
|
|
51
51
|
let end = endExcl;
|
|
@@ -61,7 +61,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
|
61
61
|
const d = isoDate(info.dateStr);
|
|
62
62
|
openModal(d, d);
|
|
63
63
|
},
|
|
64
|
-
events
|
|
64
|
+
events,
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
calendar.render();
|
|
@@ -77,8 +77,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
|
77
77
|
await createReservation({ startIso, endIso, itemIds, _csrf: csrf });
|
|
78
78
|
alerts.success('Demande envoyée');
|
|
79
79
|
const modalEl = document.getElementById('ecModal');
|
|
80
|
-
|
|
81
|
-
if (modal) modal.hide();
|
|
80
|
+
if (modalEl && window.bootstrap) window.bootstrap.Modal.getOrCreateInstance(modalEl).hide();
|
|
82
81
|
|
|
83
82
|
const newEvents = await loadEvents();
|
|
84
83
|
calendar.removeAllEvents();
|
|
@@ -89,10 +88,7 @@ define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
function onEnd() {
|
|
93
|
-
if (!pageMatch()) return;
|
|
94
|
-
initCalendar();
|
|
95
|
-
}
|
|
91
|
+
function onEnd() { if (pageMatch()) initCalendar(); }
|
|
96
92
|
|
|
97
93
|
Client.init = function () {
|
|
98
94
|
$(window).off('action:ajaxify.end.ecCal').on('action:ajaxify.end.ecCal', onEnd);
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
54
|
<div class="card card-body mb-3">
|
|
55
|
-
<h5 class="mb-3">
|
|
55
|
+
<h5 class="mb-3">Calendrier</h5>
|
|
56
56
|
<div class="row g-3">
|
|
57
57
|
<div class="col-md-6">
|
|
58
58
|
<label class="form-label">Timeout paiement (minutes)</label>
|
|
@@ -69,11 +69,5 @@
|
|
|
69
69
|
</div>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
|
-
<button id="save" class="btn btn-primary" type="button">Enregistrer</button>
|
|
72
|
+
<button id="ec-save" class="btn btn-primary" type="button">Enregistrer</button>
|
|
73
73
|
</div>
|
|
74
|
-
|
|
75
|
-
<script>
|
|
76
|
-
require(['admin/plugins/equipment-calendar'], function (mod) {
|
|
77
|
-
mod.init();
|
|
78
|
-
});
|
|
79
|
-
</script>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<div class="container-fluid px-3 equipment-calendar-page">
|
|
2
|
-
<
|
|
3
|
-
<h1 class="mb-0">Calendrier des réservations</h1>
|
|
4
|
-
</div>
|
|
5
|
-
|
|
2
|
+
<h1 class="mb-3">Calendrier des réservations</h1>
|
|
6
3
|
<div id="ec-calendar"></div>
|
|
7
4
|
|
|
8
5
|
<div class="modal fade" id="ecModal" tabindex="-1" aria-hidden="true">
|
|
@@ -24,7 +21,7 @@
|
|
|
24
21
|
<input class="form-control" type="date" name="endIso" id="ec-end" required>
|
|
25
22
|
</div>
|
|
26
23
|
<div class="mb-3">
|
|
27
|
-
<label class="form-label">Matériel (IDs, séparés par virgule)
|
|
24
|
+
<label class="form-label">Matériel (IDs, séparés par virgule)</label>
|
|
28
25
|
<input class="form-control" type="text" name="itemIds" id="ec-items" placeholder="ex: item123,item456" required>
|
|
29
26
|
</div>
|
|
30
27
|
</div>
|
|
@@ -38,16 +35,8 @@
|
|
|
38
35
|
</div>
|
|
39
36
|
</div>
|
|
40
37
|
|
|
41
|
-
<link rel="stylesheet" href="
|
|
42
|
-
<
|
|
43
|
-
<link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/timegrid.index.global.min.css">
|
|
44
|
-
<link rel="stylesheet" href="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/list.index.global.min.css">
|
|
45
|
-
|
|
46
|
-
<script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/core.index.global.min.js"></script>
|
|
47
|
-
<script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/interaction.index.global.min.js"></script>
|
|
48
|
-
<script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/daygrid.index.global.min.js"></script>
|
|
49
|
-
<script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/timegrid.index.global.min.js"></script>
|
|
50
|
-
<script src="{config.relative_path}/plugins/nodebb-plugin-equipment-calendar/vendor/fullcalendar/list.index.global.min.js"></script>
|
|
38
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar/index.global.min.css">
|
|
39
|
+
<script src="https://cdn.jsdelivr.net/npm/fullcalendar/index.global.min.js"></script>
|
|
51
40
|
|
|
52
41
|
<script>
|
|
53
42
|
require(['equipment-calendar/client'], function (mod) { mod.init(); });
|
package/README.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# NodeBB Equipment Calendar (v0.1.0)
|
|
2
|
-
|
|
3
|
-
Plugin NodeBB (testé pour NodeBB v4.7.x) pour gérer des réservations de matériel via un calendrier (FullCalendar),
|
|
4
|
-
avec workflow : demande -> validation par un groupe -> lien de paiement HelloAsso -> statut payé/validé.
|
|
5
|
-
|
|
6
|
-
## Fonctionnement (sans "AJAX applicatif")
|
|
7
|
-
- La page calendrier est rendue côté serveur avec les évènements de la période demandée.
|
|
8
|
-
- Les actions (création, validation, refus) sont des POST classiques, suivis d'une redirection.
|
|
9
|
-
- FullCalendar est utilisé en mode "events inline" (pas de feed JSON automatique).
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
```bash
|
|
13
|
-
cd /path/to/nodebb
|
|
14
|
-
npm install /path/to/nodebb-plugin-equipment-calendar
|
|
15
|
-
./nodebb build
|
|
16
|
-
./nodebb restart
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Configuration
|
|
20
|
-
Dans l'ACP : Plugins -> Equipment Calendar
|
|
21
|
-
|
|
22
|
-
- Groupes autorisés à créer une demande
|
|
23
|
-
- Groupe validateur
|
|
24
|
-
- Groupe notifié
|
|
25
|
-
- Matériel (JSON)
|
|
26
|
-
- Paramètres HelloAsso (clientId, clientSecret, organizationSlug, returnUrl, webhookSecret, etc.)
|
|
27
|
-
|
|
28
|
-
## Webhook HelloAsso
|
|
29
|
-
Déclare l'URL :
|
|
30
|
-
`https://<ton-forum>/equipment/webhook/helloasso`
|
|
31
|
-
|
|
32
|
-
Le plugin vérifie la signature si `webhookSecret` est renseigné (exemple basique).
|
|
33
|
-
|
|
34
|
-
## Remarques
|
|
35
|
-
- Ce plugin est un squelette complet mais générique : adapte la logique de paiement HelloAsso selon ton besoin exact
|
|
36
|
-
(type de checkout, itemization, montant, etc.).
|
|
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/public/css/style.css
DELETED
package/public/js/admin.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
'use strict';
|
|
3
|
-
/* globals $, ajaxify */
|
|
4
|
-
define('admin/plugins/equipment-calendar', ['settings', 'alerts'], function (Settings, alerts) {
|
|
5
|
-
const Admin = {};
|
|
6
|
-
|
|
7
|
-
function initOnPage() {
|
|
8
|
-
const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : (window.location.pathname || '');
|
|
9
|
-
if (!url || url.indexOf('admin/plugins/equipment-calendar') !== 0) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
Settings.load('equipmentCalendar', $('.acp-page-container'));
|
|
14
|
-
|
|
15
|
-
$('#save').off('click.ec').on('click.ec', function (e) {
|
|
16
|
-
try { e.preventDefault(); } catch (err) {}
|
|
17
|
-
Settings.save('equipmentCalendar', $('.acp-page-container'), function () {
|
|
18
|
-
alerts.success('[[admin/settings:settings-saved]]');
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
Admin.init = function () {
|
|
24
|
-
$(window).off('action:ajaxify.end.ec').on('action:ajaxify.end.ec', initOnPage);
|
|
25
|
-
initOnPage();
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return Admin;
|
|
29
|
-
});
|
package/public/lib/admin.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* global $, app */
|
|
3
|
-
|
|
4
|
-
define('admin/plugins/equipment-calendar', function () {
|
|
5
|
-
const EquipmentCalendar = {};
|
|
6
|
-
|
|
7
|
-
EquipmentCalendar.init = function () {
|
|
8
|
-
$('#save').on('click', function () {
|
|
9
|
-
const payload = {
|
|
10
|
-
creatorGroups: $('#creatorGroups').val(),
|
|
11
|
-
approverGroup: $('#approverGroup').val(),
|
|
12
|
-
notifyGroup: $('#notifyGroup').val(),
|
|
13
|
-
itemsJson: $('#itemsJson').val(),
|
|
14
|
-
ha_clientId: $('#ha_clientId').val(),
|
|
15
|
-
ha_clientSecret: $('#ha_clientSecret').val(),
|
|
16
|
-
ha_organizationSlug: $('#ha_organizationSlug').val(),
|
|
17
|
-
ha_returnUrl: $('#ha_returnUrl').val(),
|
|
18
|
-
ha_webhookSecret: $('#ha_webhookSecret').val(),
|
|
19
|
-
defaultView: $('#defaultView').val(),
|
|
20
|
-
timezone: $('#timezone').val(),
|
|
21
|
-
showRequesterToAll: $('#showRequesterToAll').is(':checked') ? '1' : '0',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
socket.emit('admin.settings.save', { hash: 'equipmentCalendar', values: payload }, function (err) {
|
|
25
|
-
if (err) return app.alertError(err.message || err);
|
|
26
|
-
app.alertSuccess('Sauvegardé');
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return EquipmentCalendar;
|
|
32
|
-
});
|
package/public/lib/client.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* global $, window, document, FullCalendar */
|
|
3
|
-
|
|
4
|
-
(function () {
|
|
5
|
-
function submitCreate(startISO, endISO) {
|
|
6
|
-
const form = document.getElementById('ec-create-form');
|
|
7
|
-
if (!form) return;
|
|
8
|
-
form.querySelector('input[name="start"]').value = startISO;
|
|
9
|
-
form.querySelector('input[name="end"]').value = endISO;
|
|
10
|
-
form.submit();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
$(document).ready(function () {
|
|
14
|
-
const el = document.getElementById('equipment-calendar');
|
|
15
|
-
if (!el) return;
|
|
16
|
-
|
|
17
|
-
const events = window.EC_EVENTS || [];
|
|
18
|
-
const initialDate = window.EC_INITIAL_DATE;
|
|
19
|
-
const initialView = window.EC_INITIAL_VIEW || 'dayGridMonth';
|
|
20
|
-
|
|
21
|
-
const calendar = new FullCalendar.Calendar(el, {
|
|
22
|
-
initialView: initialView,
|
|
23
|
-
initialDate: initialDate,
|
|
24
|
-
timeZone: window.EC_TZ || 'local',
|
|
25
|
-
selectable: window.EC_CAN_CREATE === true,
|
|
26
|
-
selectMirror: true,
|
|
27
|
-
events: events,
|
|
28
|
-
select: function (info) {
|
|
29
|
-
// FullCalendar provides end exclusive for all-day; for timed selections it's fine.
|
|
30
|
-
submitCreate(info.startStr, info.endStr);
|
|
31
|
-
},
|
|
32
|
-
dateClick: function (info) {
|
|
33
|
-
// Create 1-hour slot by default
|
|
34
|
-
const start = info.date;
|
|
35
|
-
const end = new Date(start.getTime() + 60 * 60 * 1000);
|
|
36
|
-
submitCreate(start.toISOString(), end.toISOString());
|
|
37
|
-
},
|
|
38
|
-
eventClick: function (info) {
|
|
39
|
-
// optionally show details
|
|
40
|
-
},
|
|
41
|
-
headerToolbar: {
|
|
42
|
-
left: 'prev,next today',
|
|
43
|
-
center: 'title',
|
|
44
|
-
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
calendar.render();
|
|
49
|
-
});
|
|
50
|
-
}());
|
package/public/styles.css
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.equipment-calendar-page { }
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
<div class="acp-page-container">
|
|
2
|
-
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
-
<h1 class="mb-0">Equipment Calendar</h1>
|
|
4
|
-
<div class="btn-group">
|
|
5
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar">Paramètres</a>
|
|
6
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations">Réservations</a>
|
|
7
|
-
<a class="btn btn-secondary active" href="/admin/plugins/equipment-calendar/helloasso-test">Test HelloAsso</a>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<div class="card card-body mt-3">
|
|
12
|
-
<div class="d-flex flex-wrap gap-2 mb-2">
|
|
13
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test">Test (cache OK)</a>
|
|
14
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test?force=1">Test (forcer nouveau token)</a>
|
|
15
|
-
<a class="btn btn-outline-danger" href="/admin/plugins/equipment-calendar/helloasso-test?force=1&clear=1" onclick="return confirm('Supprimer le token stocké et retester ?');">Vider token + retester</a>
|
|
16
|
-
</div>
|
|
17
|
-
{{{ if ok }}}
|
|
18
|
-
<div class="alert alert-success">{message}</div>
|
|
19
|
-
{{{ else }}}
|
|
20
|
-
<div class="alert alert-danger">Échec : {message}</div>
|
|
21
|
-
{{{ end }}}
|
|
22
|
-
|
|
23
|
-
<div class="small text-muted">
|
|
24
|
-
Form: <code>{settings.ha_itemsFormType}</code> / <code>{settings.ha_itemsFormSlug}</code> — Orga: <code>{settings.ha_organizationSlug}</code>
|
|
25
|
-
</div>
|
|
26
|
-
{{{ if hasSampleItems }}}
|
|
27
|
-
<div class="card card-body mt-3">
|
|
28
|
-
<div class="d-flex flex-wrap gap-2 mb-2">
|
|
29
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test">Test (cache OK)</a>
|
|
30
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test?force=1">Test (forcer nouveau token)</a>
|
|
31
|
-
<a class="btn btn-outline-danger" href="/admin/plugins/equipment-calendar/helloasso-test?force=1&clear=1" onclick="return confirm('Supprimer le token stocké et retester ?');">Vider token + retester</a>
|
|
32
|
-
</div>
|
|
33
|
-
<h5 class="mb-2">Aperçu (10 premiers articles)</h5>
|
|
34
|
-
<div class="table-responsive">
|
|
35
|
-
<table class="table table-striped align-middle">
|
|
36
|
-
<thead>
|
|
37
|
-
<tr>
|
|
38
|
-
<th>ID</th>
|
|
39
|
-
<th>Nom</th>
|
|
40
|
-
</tr>
|
|
41
|
-
</thead>
|
|
42
|
-
<tbody>
|
|
43
|
-
{{{ each sampleItems }}}
|
|
44
|
-
<tr>
|
|
45
|
-
<td><code>{sampleItems.id}</code></td>
|
|
46
|
-
<td>{sampleItems.rawName}</td>
|
|
47
|
-
</tr>
|
|
48
|
-
{{{ end }}}
|
|
49
|
-
</tbody>
|
|
50
|
-
</table>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
{{{ end }}}
|
|
54
|
-
</div>
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
<div class="acp-page-container">
|
|
2
|
-
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
-
<h1 class="mb-0">Equipment Calendar</h1>
|
|
4
|
-
<div class="btn-group">
|
|
5
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar">Paramètres</a>
|
|
6
|
-
<a class="btn btn-secondary active" href="/admin/plugins/equipment-calendar/reservations">Réservations</a>
|
|
7
|
-
</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<form method="get" action="/admin/plugins/equipment-calendar/reservations" class="card card-body mt-3 mb-3">
|
|
11
|
-
<div class="row g-2 align-items-end">
|
|
12
|
-
<div class="col-md-3">
|
|
13
|
-
<label class="form-label">Statut</label>
|
|
14
|
-
<select class="form-select" name="status">
|
|
15
|
-
{{{ each statusOptions }}}
|
|
16
|
-
<option value="{statusOptions.id}" {{{ if statusOptions.selected }}}selected{{{ end }}}>{statusOptions.name}</option>
|
|
17
|
-
{{{ end }}}
|
|
18
|
-
</select>
|
|
19
|
-
</div>
|
|
20
|
-
<div class="col-md-4">
|
|
21
|
-
<label class="form-label">Matériel</label>
|
|
22
|
-
<select class="form-select" name="itemId">
|
|
23
|
-
{{{ each itemOptions }}}
|
|
24
|
-
<option value="{itemOptions.id}" {{{ if itemOptions.selected }}}selected{{{ end }}}>{itemOptions.name}</option>
|
|
25
|
-
{{{ end }}}
|
|
26
|
-
</select>
|
|
27
|
-
</div>
|
|
28
|
-
<div class="col-md-3">
|
|
29
|
-
<label class="form-label">Recherche</label>
|
|
30
|
-
<input class="form-control" name="q" value="{q}" placeholder="rid, uid, note">
|
|
31
|
-
</div>
|
|
32
|
-
<div class="col-md-2">
|
|
33
|
-
<button class="btn btn-primary w-100" type="submit">Filtrer</button>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</form>
|
|
37
|
-
|
|
38
|
-
<div class="text-muted small mb-2">
|
|
39
|
-
Total (filtré) : <strong>{total}</strong> - Total (global) : <strong>{totalAll}</strong>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
{{{ if hasRows }}}
|
|
43
|
-
<div class="table-responsive">
|
|
44
|
-
<table class="table table-striped align-middle">
|
|
45
|
-
<thead><tr>
|
|
46
|
-
<th>Matériel</th>
|
|
47
|
-
<th>Période</th>
|
|
48
|
-
<th>Créée le</th>
|
|
49
|
-
<th>Statut</th>
|
|
50
|
-
<th>Total</th>
|
|
51
|
-
<th class="text-end">Actions</th>
|
|
52
|
-
</tr></thead>
|
|
53
|
-
<tbody>
|
|
54
|
-
{{{ each rows }}}
|
|
55
|
-
<tr>
|
|
56
|
-
<td><code>{rows.rid}</code></td>
|
|
57
|
-
<td>{rows.itemName}</td>
|
|
58
|
-
<td>{rows.uid}</td>
|
|
59
|
-
<td><small>{rows.start}</small></td>
|
|
60
|
-
<td><small>{rows.end}</small></td>
|
|
61
|
-
<td><code>{rows.status}</code></td>
|
|
62
|
-
<td><small>{rows.createdAt}</small></td>
|
|
63
|
-
<td><small>{rows.notesUser}</small></td>
|
|
64
|
-
<td class="text-nowrap">
|
|
65
|
-
<form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/approve" class="d-inline">
|
|
66
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
67
|
-
<button class="btn btn-sm btn-success" type="submit">Approve</button>
|
|
68
|
-
</form>
|
|
69
|
-
<form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/reject" class="d-inline ms-1">
|
|
70
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
71
|
-
<button class="btn btn-sm btn-warning" type="submit">Reject</button>
|
|
72
|
-
</form>
|
|
73
|
-
<form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/delete" class="d-inline ms-1" onsubmit="return confirm('Supprimer définitivement ?');">
|
|
74
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
75
|
-
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
|
76
|
-
</form>
|
|
77
|
-
</td>
|
|
78
|
-
</tr>
|
|
79
|
-
{{{ end }}}
|
|
80
|
-
</tbody>
|
|
81
|
-
</table>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
85
|
-
<div>Page {page} / {totalPages}</div>
|
|
86
|
-
<div class="btn-group">
|
|
87
|
-
{{{ if prevPage }}}
|
|
88
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations?page={prevPage}&perPage={perPage}">Précédent</a>
|
|
89
|
-
{{{ end }}}
|
|
90
|
-
{{{ if nextPage }}}
|
|
91
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations?page={nextPage}&perPage={perPage}">Suivant</a>
|
|
92
|
-
{{{ end }}}
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
{{{ else }}}
|
|
97
|
-
<div class="alert alert-info">Aucune réservation trouvée.</div>
|
|
98
|
-
{{{ end }}}
|
|
99
|
-
</div>
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
<div class="acp-page-container">
|
|
2
|
-
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
-
<h1 class="mb-0">Equipment Calendar</h1>
|
|
4
|
-
<div class="btn-group">
|
|
5
|
-
<a class="btn btn-secondary active" href="/admin/plugins/equipment-calendar">Paramètres</a>
|
|
6
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations">Réservations</a>
|
|
7
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/helloasso-test">Test HelloAsso</a>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<form id="ec-admin-form" method="post" action="/admin/plugins/equipment-calendar/save" class="mb-3 mt-3">
|
|
12
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
13
|
-
|
|
14
|
-
<div class="card card-body mb-3">
|
|
15
|
-
<h5>Permissions</h5>
|
|
16
|
-
<div class="mb-3">
|
|
17
|
-
<label class="form-label">Groupes autorisés à créer une demande (creatorGroups)</label>
|
|
18
|
-
<input class="form-control" type="text" name="creatorGroups" value="{settings.creatorGroups}">
|
|
19
|
-
</div>
|
|
20
|
-
<div class="mb-3">
|
|
21
|
-
<label class="form-label">Groupe valideur (approverGroup)</label>
|
|
22
|
-
<input class="form-control" type="text" name="approverGroup" value="{settings.approverGroup}">
|
|
23
|
-
</div>
|
|
24
|
-
<div class="mb-0">
|
|
25
|
-
<label class="form-label">Groupe notifié (notifyGroup)</label>
|
|
26
|
-
<input class="form-control" type="text" name="notifyGroup" value="{settings.notifyGroup}">
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div class="card card-body mb-3">
|
|
31
|
-
<h5>Paiement</h5>
|
|
32
|
-
<div class="mb-0">
|
|
33
|
-
<label class="form-label">Timeout paiement (minutes)</label>
|
|
34
|
-
<input class="form-control" type="number" min="1" name="paymentTimeoutMinutes" value="{settings.paymentTimeoutMinutes}">
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<div class="card card-body mb-3">
|
|
39
|
-
<h5>HelloAsso</h5>
|
|
40
|
-
<div class="mb-3">
|
|
41
|
-
<label class="form-label">API Base URL (prod/sandbox)</label>
|
|
42
|
-
<input class="form-control" type="text" name="ha_apiBaseUrl" value="{settings.ha_apiBaseUrl}">
|
|
43
|
-
<div class="form-text">Prod: <code>https://api.helloasso.com</code> — Sandbox: <code>https://api.helloasso-sandbox.com</code></div>
|
|
44
|
-
</div>
|
|
45
|
-
<div class="mb-3">
|
|
46
|
-
<label class="form-label">Organization slug</label>
|
|
47
|
-
<input class="form-control" type="text" name="ha_organizationSlug" value="{settings.ha_organizationSlug}">
|
|
48
|
-
</div>
|
|
49
|
-
<div class="mb-3">
|
|
50
|
-
<label class="form-label">Client ID</label>
|
|
51
|
-
<input class="form-control" type="text" name="ha_clientId" value="{settings.ha_clientId}">
|
|
52
|
-
</div>
|
|
53
|
-
<div class="mb-3">
|
|
54
|
-
<label class="form-label">Client Secret</label>
|
|
55
|
-
<input class="form-control" type="password" name="ha_clientSecret" value="{settings.ha_clientSecret}">
|
|
56
|
-
</div>
|
|
57
|
-
<div class="row g-3">
|
|
58
|
-
<div class="col-md-6">
|
|
59
|
-
<label class="form-label">Form Type</label>
|
|
60
|
-
<input class="form-control" type="text" name="ha_itemsFormType" value="{settings.ha_itemsFormType}" placeholder="shop">
|
|
61
|
-
</div>
|
|
62
|
-
<div class="col-md-6">
|
|
63
|
-
<label class="form-label">Form Slug</label>
|
|
64
|
-
<input class="form-control" type="text" name="ha_itemsFormSlug" value="{settings.ha_itemsFormSlug}" placeholder="locations-materiel-2026">
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="mb-0 mt-3">
|
|
68
|
-
<label class="form-label">Préfixe itemName (checkout)</label>
|
|
69
|
-
<input class="form-control" type="text" name="ha_calendarItemNamePrefix" value="{settings.ha_calendarItemNamePrefix}">
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
<button class="btn btn-primary" type="submit">Enregistrer</button>
|
|
74
|
-
</form>
|
|
75
|
-
|
|
76
|
-
{{{ if saved }}}
|
|
77
|
-
<script>
|
|
78
|
-
if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
79
|
-
window.app.alertSuccess('Paramètres enregistrés');
|
|
80
|
-
}
|
|
81
|
-
</script>
|
|
82
|
-
{{{ end }}}
|
|
83
|
-
</div>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<div class="equipment-approvals-page">
|
|
2
|
-
<h1>Validation des réservations</h1>
|
|
3
|
-
|
|
4
|
-
{{{ if hasRows }}}
|
|
5
|
-
<div class="table-responsive">
|
|
6
|
-
<table class="table table-striped align-middle">
|
|
7
|
-
<thead>
|
|
8
|
-
<tr>
|
|
9
|
-
<th>Matériel</th>
|
|
10
|
-
<th>Demandeur</th>
|
|
11
|
-
<th>Début</th>
|
|
12
|
-
<th>Fin</th>
|
|
13
|
-
<th>Statut</th>
|
|
14
|
-
<th>Paiement</th>
|
|
15
|
-
<th>Actions</th>
|
|
16
|
-
</tr>
|
|
17
|
-
</thead>
|
|
18
|
-
<tbody>
|
|
19
|
-
{{{ each rows }}}
|
|
20
|
-
<tr>
|
|
21
|
-
<td>{rows.itemName}</td>
|
|
22
|
-
<td>{rows.requester}</td>
|
|
23
|
-
<td>{rows.start}</td>
|
|
24
|
-
<td>{rows.end}</td>
|
|
25
|
-
<td><code>{rows.status}</code></td>
|
|
26
|
-
<td>
|
|
27
|
-
{{{ if rows.paymentUrl }}}
|
|
28
|
-
<a href="{rows.paymentUrl}" target="_blank" rel="noreferrer">Lien</a>
|
|
29
|
-
{{{ else }}}
|
|
30
|
-
-
|
|
31
|
-
{{{ end }}}
|
|
32
|
-
</td>
|
|
33
|
-
<td>
|
|
34
|
-
<form method="post" action="/equipment/reservations/{rows.id}/approve" class="d-inline">
|
|
35
|
-
<input type="hidden" name="_csrf" value="{rows.csrf}">
|
|
36
|
-
<button class="btn btn-sm btn-success" type="submit">Approuver</button>
|
|
37
|
-
</form>
|
|
38
|
-
<form method="post" action="/equipment/reservations/{rows.id}/reject" class="d-inline ms-1">
|
|
39
|
-
<input type="hidden" name="_csrf" value="{rows.csrf}">
|
|
40
|
-
<button class="btn btn-sm btn-danger" type="submit">Refuser</button>
|
|
41
|
-
</form>
|
|
42
|
-
</td>
|
|
43
|
-
</tr>
|
|
44
|
-
{{{ end }}}
|
|
45
|
-
</tbody>
|
|
46
|
-
</table>
|
|
47
|
-
</div>
|
|
48
|
-
{{{ else }}}
|
|
49
|
-
<div class="alert alert-success">Aucune demande en attente 🎉</div>
|
|
50
|
-
{{{ end }}}
|
|
51
|
-
</div>
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
<div class="equipment-calendar-page">
|
|
2
|
-
<h1>Réservation de matériel</h1>
|
|
3
|
-
|
|
4
|
-
{{{ if canCreate }}}
|
|
5
|
-
<div class="alert alert-info">
|
|
6
|
-
Clique sur une date ou sélectionne une plage sur le calendrier pour faire une demande.
|
|
7
|
-
</div>
|
|
8
|
-
{{{ else }}}
|
|
9
|
-
<div class="alert alert-info">Tu peux consulter le calendrier, mais tu n’as pas les droits pour créer une demande.</div>
|
|
10
|
-
{{{ end }}}
|
|
11
|
-
|
|
12
|
-
<div class="card card-body mb-3">
|
|
13
|
-
<div id="ec-calendar"></div>
|
|
14
|
-
</div>
|
|
15
|
-
|
|
16
|
-
<!-- Modal de demande -->
|
|
17
|
-
<div class="modal fade" id="ec-create-modal" tabindex="-1" aria-hidden="true">
|
|
18
|
-
<div class="modal-dialog modal-dialog-centered">
|
|
19
|
-
<div class="modal-content">
|
|
20
|
-
<form id="ec-create-form" method="post" action="{relative_path}/equipment/reservations/create">
|
|
21
|
-
<div class="modal-header">
|
|
22
|
-
<h5 class="modal-title">Demande de réservation</h5>
|
|
23
|
-
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<div class="modal-body">
|
|
27
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
28
|
-
<input type="hidden" id="ec-start-iso" name="start" value="">
|
|
29
|
-
<input type="hidden" id="ec-end-iso" name="end" value="">
|
|
30
|
-
|
|
31
|
-
<div class="row g-3 mb-3">
|
|
32
|
-
<div class="col-6">
|
|
33
|
-
<label class="form-label">Début</label>
|
|
34
|
-
<input class="form-control" type="date" id="ec-start-date" required>
|
|
35
|
-
</div>
|
|
36
|
-
<div class="col-6">
|
|
37
|
-
<label class="form-label">Fin</label>
|
|
38
|
-
<input class="form-control" type="date" id="ec-end-date" required>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<div class="mb-3">
|
|
43
|
-
<label class="form-label">Matériel</label>
|
|
44
|
-
<select class="form-select" id="ec-item-ids" name="itemIds" multiple required>
|
|
45
|
-
<!-- BEGIN items -->
|
|
46
|
-
<option value="{items.id}" data-price="{items.price}">{items.name}</option>
|
|
47
|
-
<!-- END items -->
|
|
48
|
-
</select>
|
|
49
|
-
<div class="form-text">Tu peux sélectionner plusieurs matériels.</div>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
<div class="mb-3">
|
|
53
|
-
<label class="form-label">Notes</label>
|
|
54
|
-
<textarea class="form-control" name="notesUser" rows="3" placeholder="Infos utiles..."></textarea>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<div class="mb-0">
|
|
58
|
-
<div class="fw-semibold">Durée</div>
|
|
59
|
-
<div id="ec-total-days">1 jour</div>
|
|
60
|
-
<hr class="my-2">
|
|
61
|
-
<div class="fw-semibold">Total estimé</div>
|
|
62
|
-
<div id="ec-total-price" class="fs-5">0 EUR</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<div class="modal-footer">
|
|
67
|
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
|
68
|
-
<button type="submit" class="btn btn-primary">Envoyer la demande</button>
|
|
69
|
-
</div>
|
|
70
|
-
</form>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js"></script>
|
|
77
|
-
<script src="{relative_path}/plugins/nodebb-plugin-equipment-calendar/js/client.js"></script>
|
|
78
|
-
|
|
79
|
-
<script>
|
|
80
|
-
window.EC_EVENTS = JSON.parse(atob('{eventsB64}'));
|
|
81
|
-
window.EC_BLOCKS = JSON.parse(atob('{blocksB64}'));
|
|
82
|
-
window.EC_CAN_CREATE = {canCreateJs};
|
|
83
|
-
window.EC_TZ = '{tz}';
|
|
84
|
-
</script>
|
|
85
|
-
|
|
86
|
-
<script>
|
|
87
|
-
window.EC_CSRF = '{csrf_token}';
|
|
88
|
-
window.EC_IS_APPROVER = {isApprover};
|
|
89
|
-
</script>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<div class="card card-body">
|
|
2
|
-
<h4>Retour paiement</h4>
|
|
3
|
-
|
|
4
|
-
{{{ if statusPaid }}}
|
|
5
|
-
<div class="alert alert-success">✅ Paiement confirmé.</div>
|
|
6
|
-
{{{ end }}}
|
|
7
|
-
|
|
8
|
-
{{{ if statusError }}}
|
|
9
|
-
<div class="alert alert-danger">❌ Paiement non confirmé.</div>
|
|
10
|
-
{{{ end }}}
|
|
11
|
-
|
|
12
|
-
<p>{message}</p>
|
|
13
|
-
|
|
14
|
-
<a class="btn btn-primary" href="/equipment/calendar">Retour au calendrier</a>
|
|
15
|
-
</div>
|
package/scripts/postinstall.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
function copyFile(src, dst) {
|
|
7
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
8
|
-
fs.copyFileSync(src, dst);
|
|
9
|
-
}
|
|
10
|
-
function exists(p) { try { fs.accessSync(p); return true; } catch { return false; } }
|
|
11
|
-
|
|
12
|
-
(function main() {
|
|
13
|
-
const root = path.join(__dirname, '..');
|
|
14
|
-
const outDir = path.join(root, 'public', 'vendor', 'fullcalendar');
|
|
15
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
16
|
-
|
|
17
|
-
const candidates = [
|
|
18
|
-
// FullCalendar v6 provides "index.global.min.js" per package
|
|
19
|
-
{ pkg: '@fullcalendar/core', js: 'index.global.min.js', css: 'index.global.min.css' },
|
|
20
|
-
{ pkg: '@fullcalendar/daygrid', js: 'index.global.min.js', css: 'index.global.min.css' },
|
|
21
|
-
{ pkg: '@fullcalendar/timegrid', js: 'index.global.min.js', css: 'index.global.min.css' },
|
|
22
|
-
{ pkg: '@fullcalendar/list', js: 'index.global.min.js', css: 'index.global.min.css' },
|
|
23
|
-
{ pkg: '@fullcalendar/interaction', js: 'index.global.min.js', css: 'index.global.min.css' },
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const copied = [];
|
|
27
|
-
candidates.forEach((c) => {
|
|
28
|
-
const base = path.join(root, 'node_modules', c.pkg);
|
|
29
|
-
const js = path.join(base, c.js);
|
|
30
|
-
const css = path.join(base, c.css);
|
|
31
|
-
if (exists(js)) { copyFile(js, path.join(outDir, `${c.pkg.replace('@fullcalendar/','')}.${c.js}`)); copied.push(js); }
|
|
32
|
-
if (exists(css)) { copyFile(css, path.join(outDir, `${c.pkg.replace('@fullcalendar/','')}.${c.css}`)); copied.push(css); }
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Also write a small manifest for debugging
|
|
36
|
-
fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify({ copied }, null, 2), 'utf-8');
|
|
37
|
-
})();
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
<div class="acp-page-container">
|
|
2
|
-
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
-
<h1 class="mb-0">Equipment Calendar</h1>
|
|
4
|
-
<div class="btn-group">
|
|
5
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar">Paramètres</a>
|
|
6
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations">Réservations</a>
|
|
7
|
-
<a class="btn btn-secondary active" href="/admin/plugins/equipment-calendar/helloasso-test">Test HelloAsso</a>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<div class="card card-body mt-3">
|
|
12
|
-
<div class="d-flex flex-wrap gap-2 mb-2">
|
|
13
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test">Test (cache OK)</a>
|
|
14
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test?force=1">Test (forcer nouveau token)</a>
|
|
15
|
-
<a class="btn btn-outline-danger" href="/admin/plugins/equipment-calendar/helloasso-test?force=1&clear=1" onclick="return confirm('Supprimer le token stocké et retester ?');">Vider token + retester</a>
|
|
16
|
-
</div>
|
|
17
|
-
{{{ if ok }}}
|
|
18
|
-
<div class="alert alert-success">{message}</div>
|
|
19
|
-
{{{ else }}}
|
|
20
|
-
<div class="alert alert-danger">Échec : {message}</div>
|
|
21
|
-
{{{ end }}}
|
|
22
|
-
|
|
23
|
-
<div class="small text-muted">
|
|
24
|
-
Form: <code>{settings.ha_itemsFormType}</code> / <code>{settings.ha_itemsFormSlug}</code> — Orga: <code>{settings.ha_organizationSlug}</code>
|
|
25
|
-
</div>
|
|
26
|
-
{{{ if hasSampleItems }}}
|
|
27
|
-
<div class="card card-body mt-3">
|
|
28
|
-
<div class="d-flex flex-wrap gap-2 mb-2">
|
|
29
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test">Test (cache OK)</a>
|
|
30
|
-
<a class="btn btn-outline-primary" href="/admin/plugins/equipment-calendar/helloasso-test?force=1">Test (forcer nouveau token)</a>
|
|
31
|
-
<a class="btn btn-outline-danger" href="/admin/plugins/equipment-calendar/helloasso-test?force=1&clear=1" onclick="return confirm('Supprimer le token stocké et retester ?');">Vider token + retester</a>
|
|
32
|
-
</div>
|
|
33
|
-
<h5 class="mb-2">Aperçu (10 premiers articles)</h5>
|
|
34
|
-
<div class="table-responsive">
|
|
35
|
-
<table class="table table-striped align-middle">
|
|
36
|
-
<thead>
|
|
37
|
-
<tr>
|
|
38
|
-
<th>ID</th>
|
|
39
|
-
<th>Nom</th>
|
|
40
|
-
</tr>
|
|
41
|
-
</thead>
|
|
42
|
-
<tbody>
|
|
43
|
-
{{{ each sampleItems }}}
|
|
44
|
-
<tr>
|
|
45
|
-
<td><code>{sampleItems.id}</code></td>
|
|
46
|
-
<td>{sampleItems.rawName}</td>
|
|
47
|
-
</tr>
|
|
48
|
-
{{{ end }}}
|
|
49
|
-
</tbody>
|
|
50
|
-
</table>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
{{{ end }}}
|
|
54
|
-
</div>
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
<div class="acp-page-container">
|
|
2
|
-
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
-
<h1 class="mb-0">Equipment Calendar</h1>
|
|
4
|
-
<div class="btn-group">
|
|
5
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar">Paramètres</a>
|
|
6
|
-
<a class="btn btn-secondary active" href="/admin/plugins/equipment-calendar/reservations">Réservations</a>
|
|
7
|
-
</div>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<form method="get" action="/admin/plugins/equipment-calendar/reservations" class="card card-body mt-3 mb-3">
|
|
11
|
-
<div class="row g-2 align-items-end">
|
|
12
|
-
<div class="col-md-3">
|
|
13
|
-
<label class="form-label">Statut</label>
|
|
14
|
-
<select class="form-select" name="status">
|
|
15
|
-
{{{ each statusOptions }}}
|
|
16
|
-
<option value="{statusOptions.id}" {{{ if statusOptions.selected }}}selected{{{ end }}}>{statusOptions.name}</option>
|
|
17
|
-
{{{ end }}}
|
|
18
|
-
</select>
|
|
19
|
-
</div>
|
|
20
|
-
<div class="col-md-4">
|
|
21
|
-
<label class="form-label">Matériel</label>
|
|
22
|
-
<select class="form-select" name="itemId">
|
|
23
|
-
{{{ each itemOptions }}}
|
|
24
|
-
<option value="{itemOptions.id}" {{{ if itemOptions.selected }}}selected{{{ end }}}>{itemOptions.name}</option>
|
|
25
|
-
{{{ end }}}
|
|
26
|
-
</select>
|
|
27
|
-
</div>
|
|
28
|
-
<div class="col-md-3">
|
|
29
|
-
<label class="form-label">Recherche</label>
|
|
30
|
-
<input class="form-control" name="q" value="{q}" placeholder="rid, uid, note">
|
|
31
|
-
</div>
|
|
32
|
-
<div class="col-md-2">
|
|
33
|
-
<button class="btn btn-primary w-100" type="submit">Filtrer</button>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</form>
|
|
37
|
-
|
|
38
|
-
<div class="text-muted small mb-2">
|
|
39
|
-
Total (filtré) : <strong>{total}</strong> - Total (global) : <strong>{totalAll}</strong>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
{{{ if hasRows }}}
|
|
43
|
-
<div class="table-responsive">
|
|
44
|
-
<table class="table table-striped align-middle">
|
|
45
|
-
<thead><tr>
|
|
46
|
-
<th>Matériel</th>
|
|
47
|
-
<th>Période</th>
|
|
48
|
-
<th>Créée le</th>
|
|
49
|
-
<th>Statut</th>
|
|
50
|
-
<th>Total</th>
|
|
51
|
-
<th class="text-end">Actions</th>
|
|
52
|
-
</tr></thead>
|
|
53
|
-
<tbody>
|
|
54
|
-
{{{ each rows }}}
|
|
55
|
-
<tr>
|
|
56
|
-
<td><code>{rows.rid}</code></td>
|
|
57
|
-
<td>{rows.itemName}</td>
|
|
58
|
-
<td>{rows.uid}</td>
|
|
59
|
-
<td><small>{rows.start}</small></td>
|
|
60
|
-
<td><small>{rows.end}</small></td>
|
|
61
|
-
<td><code>{rows.status}</code></td>
|
|
62
|
-
<td><small>{rows.createdAt}</small></td>
|
|
63
|
-
<td><small>{rows.notesUser}</small></td>
|
|
64
|
-
<td class="text-nowrap">
|
|
65
|
-
<form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/approve" class="d-inline">
|
|
66
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
67
|
-
<button class="btn btn-sm btn-success" type="submit">Approve</button>
|
|
68
|
-
</form>
|
|
69
|
-
<form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/reject" class="d-inline ms-1">
|
|
70
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
71
|
-
<button class="btn btn-sm btn-warning" type="submit">Reject</button>
|
|
72
|
-
</form>
|
|
73
|
-
<form method="post" action="/admin/plugins/equipment-calendar/reservations/{rows.rid}/delete" class="d-inline ms-1" onsubmit="return confirm('Supprimer définitivement ?');">
|
|
74
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
75
|
-
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
|
|
76
|
-
</form>
|
|
77
|
-
</td>
|
|
78
|
-
</tr>
|
|
79
|
-
{{{ end }}}
|
|
80
|
-
</tbody>
|
|
81
|
-
</table>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
85
|
-
<div>Page {page} / {totalPages}</div>
|
|
86
|
-
<div class="btn-group">
|
|
87
|
-
{{{ if prevPage }}}
|
|
88
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations?page={prevPage}&perPage={perPage}">Précédent</a>
|
|
89
|
-
{{{ end }}}
|
|
90
|
-
{{{ if nextPage }}}
|
|
91
|
-
<a class="btn btn-outline-secondary" href="/admin/plugins/equipment-calendar/reservations?page={nextPage}&perPage={perPage}">Suivant</a>
|
|
92
|
-
{{{ end }}}
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
{{{ else }}}
|
|
97
|
-
<div class="alert alert-info">Aucune réservation trouvée.</div>
|
|
98
|
-
{{{ end }}}
|
|
99
|
-
</div>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
<div class="equipment-approvals-page">
|
|
2
|
-
<h1>Validation des réservations</h1>
|
|
3
|
-
|
|
4
|
-
{{{ if hasRows }}}
|
|
5
|
-
<div class="table-responsive">
|
|
6
|
-
<table class="table table-striped align-middle">
|
|
7
|
-
<thead>
|
|
8
|
-
<tr>
|
|
9
|
-
<th>Matériel</th>
|
|
10
|
-
<th>Demandeur</th>
|
|
11
|
-
<th>Début</th>
|
|
12
|
-
<th>Fin</th>
|
|
13
|
-
<th>Statut</th>
|
|
14
|
-
<th>Paiement</th>
|
|
15
|
-
<th>Actions</th>
|
|
16
|
-
</tr>
|
|
17
|
-
</thead>
|
|
18
|
-
<tbody>
|
|
19
|
-
{{{ each rows }}}
|
|
20
|
-
<tr>
|
|
21
|
-
<td>{rows.itemName}</td>
|
|
22
|
-
<td>{rows.requester}</td>
|
|
23
|
-
<td>{rows.start}</td>
|
|
24
|
-
<td>{rows.end}</td>
|
|
25
|
-
<td><code>{rows.status}</code></td>
|
|
26
|
-
<td>
|
|
27
|
-
{{{ if rows.paymentUrl }}}
|
|
28
|
-
<a href="{rows.paymentUrl}" target="_blank" rel="noreferrer">Lien</a>
|
|
29
|
-
{{{ else }}}
|
|
30
|
-
-
|
|
31
|
-
{{{ end }}}
|
|
32
|
-
</td>
|
|
33
|
-
<td>
|
|
34
|
-
<form method="post" action="/equipment/reservations/{rows.id}/approve" class="d-inline">
|
|
35
|
-
<input type="hidden" name="_csrf" value="{rows.csrf}">
|
|
36
|
-
<button class="btn btn-sm btn-success" type="submit">Approuver</button>
|
|
37
|
-
</form>
|
|
38
|
-
<form method="post" action="/equipment/reservations/{rows.id}/reject" class="d-inline ms-1">
|
|
39
|
-
<input type="hidden" name="_csrf" value="{rows.csrf}">
|
|
40
|
-
<button class="btn btn-sm btn-danger" type="submit">Refuser</button>
|
|
41
|
-
</form>
|
|
42
|
-
</td>
|
|
43
|
-
</tr>
|
|
44
|
-
{{{ end }}}
|
|
45
|
-
</tbody>
|
|
46
|
-
</table>
|
|
47
|
-
</div>
|
|
48
|
-
{{{ else }}}
|
|
49
|
-
<div class="alert alert-success">Aucune demande en attente 🎉</div>
|
|
50
|
-
{{{ end }}}
|
|
51
|
-
</div>
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<div class="card card-body">
|
|
2
|
-
<h4>Retour paiement</h4>
|
|
3
|
-
|
|
4
|
-
{{{ if statusPaid }}}
|
|
5
|
-
<div class="alert alert-success">✅ Paiement confirmé.</div>
|
|
6
|
-
{{{ end }}}
|
|
7
|
-
|
|
8
|
-
{{{ if statusError }}}
|
|
9
|
-
<div class="alert alert-danger">❌ Paiement non confirmé.</div>
|
|
10
|
-
{{{ end }}}
|
|
11
|
-
|
|
12
|
-
<p>{message}</p>
|
|
13
|
-
|
|
14
|
-
<a class="btn btn-primary" href="/equipment/calendar">Retour au calendrier</a>
|
|
15
|
-
</div>
|