nodebb-plugin-calendar-onekite 1.4.7 → 2.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/.drone.yml +62 -0
- package/README.md +21 -0
- package/helloasso.js +129 -81
- package/library.js +290 -454
- package/package.json +18 -18
- package/plugin.json +33 -21
- package/static/css/calendar-onekite.css +4 -0
- package/static/js/admin.js +184 -172
- package/static/js/calendar-my-reservations.js +55 -0
- package/static/js/calendar.js +429 -42
- package/templates/admin/calendar-planning.tpl +25 -30
- package/templates/admin/plugins/calendar-onekite.tpl +61 -34
- package/templates/calendar-my-reservations.tpl +29 -45
- package/templates/calendar.tpl +100 -91
- package/templates/emails/calendar-payment-confirmed.tpl +1 -12
- package/templates/emails/calendar-reservation-approved.tpl +1 -13
- package/templates/emails/calendar-reservation-created.tpl +1 -13
- package/templates/widgets/calendar-upcoming.tpl +14 -22
- package/static/style.css +0 -47
- package/templates/admin/plugins/calendar-planning.tpl +0 -30
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-calendar-onekite",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "https://git.worobel.net/Arnaud/nodebb-plugin-calendar-onekite.git"
|
|
8
|
-
},
|
|
9
|
-
"license": "ISC",
|
|
10
|
-
"author": "",
|
|
11
|
-
"type": "commonjs",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Calendar + equipment booking + admin approval + HelloAsso payments for NodeBB v4 (no-jQuery UI)",
|
|
12
5
|
"main": "library.js",
|
|
13
|
-
"
|
|
14
|
-
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nodebb",
|
|
9
|
+
"plugin",
|
|
10
|
+
"calendar",
|
|
11
|
+
"booking",
|
|
12
|
+
"helloasso",
|
|
13
|
+
"fullcalendar"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
15
17
|
},
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"static/"
|
|
22
|
-
]
|
|
18
|
+
"dependencies": {},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"lint": "echo \"(optional) add eslint if you want\"",
|
|
21
|
+
"test": "echo \"no tests\""
|
|
22
|
+
}
|
|
23
23
|
}
|
package/plugin.json
CHANGED
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "nodebb-plugin-calendar-onekite",
|
|
3
|
-
"name": "Calendar Onekite",
|
|
4
|
-
"description": "Calendrier + réservation
|
|
5
|
-
"url": "",
|
|
6
|
-
"version": "
|
|
7
|
-
"library": "./library.js",
|
|
8
|
-
"staticDirs": {
|
|
9
|
-
"static": "static"
|
|
10
|
-
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
{
|
|
2
|
+
"id": "nodebb-plugin-calendar-onekite",
|
|
3
|
+
"name": "Calendar Onekite",
|
|
4
|
+
"description": "Calendrier + réservation matériel + validation admin + paiement HelloAsso pour NodeBB v4 (no-jQuery UI)",
|
|
5
|
+
"url": "",
|
|
6
|
+
"version": "2.0.0",
|
|
7
|
+
"library": "./library.js",
|
|
8
|
+
"staticDirs": {
|
|
9
|
+
"static": "static"
|
|
10
|
+
},
|
|
11
|
+
"acpScripts": [
|
|
12
|
+
"static/js/admin.js"
|
|
13
|
+
],
|
|
14
|
+
"hooks": [
|
|
15
|
+
{
|
|
16
|
+
"hook": "static:app.load",
|
|
17
|
+
"method": "init"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"hook": "filter:admin.header.build",
|
|
21
|
+
"method": "addAdminNavigation"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"hook": "filter:widgets.getWidgets",
|
|
25
|
+
"method": "defineWidgets"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"hook": "filter:widget.render:calendarUpcoming",
|
|
29
|
+
"method": "renderUpcomingWidget"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"templates": "templates"
|
|
33
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
.calendar-modal{display:none;position:fixed;z-index:9999;left:0;top:0;width:100%;height:100%;overflow:auto;background:rgba(0,0,0,.45)}
|
|
2
|
+
.calendar-modal-content{background:#fff;margin:5vh auto;padding:16px;width:min(900px,92vw);border-radius:10px;box-shadow:0 10px 40px rgba(0,0,0,.2)}
|
|
3
|
+
.calendar-close{float:right;cursor:pointer;font-size:22px;line-height:1}
|
|
4
|
+
#calendar{min-height:600px}
|
package/static/js/admin.js
CHANGED
|
@@ -1,172 +1,184 @@
|
|
|
1
|
-
define('plugins/nodebb-plugin-calendar-onekite/static/js/admin', [
|
|
2
|
-
'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
1
|
+
define('plugins/nodebb-plugin-calendar-onekite/static/js/admin', ['api', 'alerts'], function (api, alerts) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const STATE = { initedSettings:false, initedPlanning:false, delegationBound:false };
|
|
5
|
+
|
|
6
|
+
function qs(sel, root=document){ return root.querySelector(sel); }
|
|
7
|
+
function qsa(sel, root=document){ return Array.from(root.querySelectorAll(sel)); }
|
|
8
|
+
|
|
9
|
+
function escapeHtml(s){
|
|
10
|
+
return String(s ?? '')
|
|
11
|
+
.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>')
|
|
12
|
+
.replaceAll('"','"').replaceAll("'","'");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function saveSettings() {
|
|
16
|
+
const settings = {
|
|
17
|
+
allowedGroups: qs('#calendar-onekite-groups')?.value || '',
|
|
18
|
+
allowedBookingGroups: qs('#calendar-onekite-book-groups')?.value || '',
|
|
19
|
+
limit: qs('#calendar-onekite-widget-limit')?.value || '',
|
|
20
|
+
helloassoApiBase: qs('#calendar-onekite-helloasso-apibase')?.value || '',
|
|
21
|
+
helloassoOrganizationSlug: qs('#calendar-onekite-helloasso-org')?.value || '',
|
|
22
|
+
helloassoFormSlug: qs('#calendar-onekite-helloasso-form')?.value || '',
|
|
23
|
+
helloassoClientId: qs('#calendar-onekite-helloasso-clientid')?.value || '',
|
|
24
|
+
helloassoClientSecret: qs('#calendar-onekite-helloasso-secret')?.value || '',
|
|
25
|
+
helloassoReturnUrl: qs('#calendar-onekite-helloasso-return')?.value || '',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await api.put('/admin/plugins/calendar-onekite', settings);
|
|
30
|
+
alerts.success('Paramètres enregistrés');
|
|
31
|
+
} catch (e) {
|
|
32
|
+
alerts.error(e?.message || e);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderPending(container, list) {
|
|
37
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
38
|
+
container.innerHTML = '<p>Aucune réservation en attente.</p>';
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
container.innerHTML = list.map(block => {
|
|
43
|
+
const ev = block.event || {};
|
|
44
|
+
const reservations = Array.isArray(block.reservations) ? block.reservations : [];
|
|
45
|
+
const rows = reservations.map(r => `
|
|
46
|
+
<div class="reservation-row mb-2 p-2 border rounded" data-rid="${escapeHtml(r.rid)}">
|
|
47
|
+
<p><strong>RID :</strong> ${escapeHtml(r.rid)}</p>
|
|
48
|
+
<p><strong>UID :</strong> ${escapeHtml(r.uid)}</p>
|
|
49
|
+
<p><strong>Matériel :</strong> ${escapeHtml(r.itemId)}</p>
|
|
50
|
+
<p><strong>Quantité :</strong> ${escapeHtml(r.quantity)}</p>
|
|
51
|
+
<p><strong>Dates :</strong> ${escapeHtml(r.dateStart)} → ${escapeHtml(r.dateEnd)} (${escapeHtml(r.days||'')} jours)</p>
|
|
52
|
+
<p><strong>Lieu de retrait :</strong> ${escapeHtml(r.pickupLocation || 'Non précisé')}</p>
|
|
53
|
+
<button class="btn btn-success btn-sm js-validate">Valider</button>
|
|
54
|
+
<button class="btn btn-danger btn-sm js-cancel">Annuler</button>
|
|
55
|
+
</div>
|
|
56
|
+
`).join('');
|
|
57
|
+
|
|
58
|
+
return `
|
|
59
|
+
<div class="card mb-3">
|
|
60
|
+
<div class="card-header">
|
|
61
|
+
<strong>${escapeHtml(ev.title)}</strong><br>
|
|
62
|
+
<small>${escapeHtml(ev.start)} → ${escapeHtml(ev.end)}</small>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="card-body">${rows}</div>
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
67
|
+
}).join('');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function loadPending() {
|
|
71
|
+
const container = qs('#pending-reservations');
|
|
72
|
+
if (!container) return;
|
|
73
|
+
try {
|
|
74
|
+
const list = await api.get('/admin/calendar/pending');
|
|
75
|
+
renderPending(container, list);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
alerts.error(e?.message || e);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function validateReservation(rid, row) {
|
|
82
|
+
try {
|
|
83
|
+
await api.post(`/admin/calendar/reservation/${encodeURIComponent(rid)}/validate`, {});
|
|
84
|
+
row.style.background = '#d4edda';
|
|
85
|
+
qsa('.js-validate, .js-cancel', row).forEach(b => b.remove());
|
|
86
|
+
row.insertAdjacentHTML('beforeend', '<p><strong>Validée.</strong> Lien de paiement envoyé.</p>');
|
|
87
|
+
} catch (e) {
|
|
88
|
+
alerts.error(e?.message || e);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function cancelReservation(rid, row) {
|
|
93
|
+
if (!window.confirm('Annuler cette réservation ?')) return;
|
|
94
|
+
try {
|
|
95
|
+
await api.post(`/admin/calendar/reservation/${encodeURIComponent(rid)}/cancel`, {});
|
|
96
|
+
row.style.background = '#f8d7da';
|
|
97
|
+
qsa('.js-validate, .js-cancel', row).forEach(b => b.remove());
|
|
98
|
+
row.insertAdjacentHTML('beforeend', '<p><strong>Annulée.</strong></p>');
|
|
99
|
+
} catch (e) {
|
|
100
|
+
alerts.error(e?.message || e);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function bindDelegationOnce() {
|
|
105
|
+
if (STATE.delegationBound) return;
|
|
106
|
+
STATE.delegationBound = true;
|
|
107
|
+
|
|
108
|
+
document.addEventListener('click', (ev) => {
|
|
109
|
+
const t = ev.target;
|
|
110
|
+
if (!(t instanceof HTMLElement)) return;
|
|
111
|
+
|
|
112
|
+
const row = t.closest('.reservation-row');
|
|
113
|
+
if (!row) return;
|
|
114
|
+
|
|
115
|
+
const rid = row.getAttribute('data-rid');
|
|
116
|
+
if (!rid) return;
|
|
117
|
+
|
|
118
|
+
if (t.classList.contains('js-validate')) validateReservation(rid, row);
|
|
119
|
+
if (t.classList.contains('js-cancel')) cancelReservation(rid, row);
|
|
120
|
+
}, { passive: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function loadPlanning() {
|
|
124
|
+
const tbody = qs('#planning-body');
|
|
125
|
+
if (!tbody) return;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const rows = await api.get('/admin/calendar/planning');
|
|
129
|
+
if (!rows || !rows.length) {
|
|
130
|
+
tbody.innerHTML = '<tr><td colspan="9">Aucune réservation future.</td></tr>';
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
tbody.innerHTML = rows.map(r => `
|
|
134
|
+
<tr>
|
|
135
|
+
<td>${escapeHtml(r.eventTitle)}</td>
|
|
136
|
+
<td>${escapeHtml(r.itemName)}</td>
|
|
137
|
+
<td>${escapeHtml(r.pickupLocation || 'Non précisé')}</td>
|
|
138
|
+
<td>${escapeHtml(r.uid)}</td>
|
|
139
|
+
<td>${escapeHtml(r.quantity)}</td>
|
|
140
|
+
<td>${escapeHtml(r.dateStart)}</td>
|
|
141
|
+
<td>${escapeHtml(r.dateEnd)}</td>
|
|
142
|
+
<td>${escapeHtml(r.days)}</td>
|
|
143
|
+
<td>${escapeHtml(r.status)}</td>
|
|
144
|
+
</tr>
|
|
145
|
+
`).join('');
|
|
146
|
+
} catch (e) {
|
|
147
|
+
alerts.error(e?.message || e);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function initSettingsPage() {
|
|
152
|
+
const root = qs('#calendar-onekite-admin');
|
|
153
|
+
if (!root || STATE.initedSettings) return;
|
|
154
|
+
STATE.initedSettings = true;
|
|
155
|
+
|
|
156
|
+
qs('#calendar-onekite-save')?.addEventListener('click', saveSettings);
|
|
157
|
+
bindDelegationOnce();
|
|
158
|
+
loadPending();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function initPlanningPage() {
|
|
162
|
+
const root = qs('#calendar-planning');
|
|
163
|
+
if (!root || STATE.initedPlanning) return;
|
|
164
|
+
STATE.initedPlanning = true;
|
|
165
|
+
loadPlanning();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resetFlagsIfLeft() {
|
|
169
|
+
if (!qs('#calendar-onekite-admin')) STATE.initedSettings = false;
|
|
170
|
+
if (!qs('#calendar-planning')) STATE.initedPlanning = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const obs = new MutationObserver(() => {
|
|
174
|
+
resetFlagsIfLeft();
|
|
175
|
+
initSettingsPage();
|
|
176
|
+
initPlanningPage();
|
|
177
|
+
});
|
|
178
|
+
obs.observe(document.body, { childList: true, subtree: true });
|
|
179
|
+
|
|
180
|
+
initSettingsPage();
|
|
181
|
+
initPlanningPage();
|
|
182
|
+
|
|
183
|
+
return {};
|
|
184
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
define('plugins/nodebb-plugin-calendar-onekite/static/js/my-reservations', ['api', 'alerts'], function (api, alerts) {
|
|
2
|
+
'use strict';
|
|
3
|
+
const STATE = { inited:false };
|
|
4
|
+
|
|
5
|
+
function qs(sel, root=document){ return root.querySelector(sel); }
|
|
6
|
+
function escapeHtml(s){
|
|
7
|
+
return String(s ?? '')
|
|
8
|
+
.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>')
|
|
9
|
+
.replaceAll('"','"').replaceAll("'","'");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function load() {
|
|
13
|
+
const root = qs('#calendar-my-reservations');
|
|
14
|
+
const tbody = qs('#my-reservations-body');
|
|
15
|
+
if (!root || !tbody) return;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const rows = await api.get('/calendar/my-reservations');
|
|
19
|
+
if (!rows || !rows.length) {
|
|
20
|
+
tbody.innerHTML = '<tr><td colspan="7">Aucune réservation.</td></tr>';
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
tbody.innerHTML = rows.map(r => `
|
|
24
|
+
<tr>
|
|
25
|
+
<td>${escapeHtml(r.eventTitle)}</td>
|
|
26
|
+
<td>${escapeHtml(r.itemName)}</td>
|
|
27
|
+
<td>${escapeHtml(r.pickupLocation || '')}</td>
|
|
28
|
+
<td>${escapeHtml(r.dateStart)}</td>
|
|
29
|
+
<td>${escapeHtml(r.dateEnd)}</td>
|
|
30
|
+
<td>${escapeHtml(r.days || '')}</td>
|
|
31
|
+
<td>${escapeHtml(r.status)}</td>
|
|
32
|
+
</tr>
|
|
33
|
+
`).join('');
|
|
34
|
+
} catch (e) {
|
|
35
|
+
alerts.error(e?.message || e);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function initOnce() {
|
|
40
|
+
if (STATE.inited) return;
|
|
41
|
+
if (!qs('#calendar-my-reservations')) return;
|
|
42
|
+
STATE.inited = true;
|
|
43
|
+
load();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resetIfLeft() {
|
|
47
|
+
if (!qs('#calendar-my-reservations')) STATE.inited = false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const obs = new MutationObserver(() => { resetIfLeft(); initOnce(); });
|
|
51
|
+
obs.observe(document.body, { childList:true, subtree:true });
|
|
52
|
+
initOnce();
|
|
53
|
+
|
|
54
|
+
return { init: initOnce };
|
|
55
|
+
});
|