nodebb-plugin-equipment-calendar 9.1.6 → 9.1.8
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 +31 -18
- package/package.json +1 -1
- package/plugin.json +0 -3
- package/templates/admin/plugins/equipment-calendar.tpl +70 -63
- package/templates/equipment-calendar/calendar.tpl +102 -4
- package/public/js/acp.js +0 -48
- package/public/js/admin.js +0 -38
- package/public/js/client.js +0 -98
package/library.js
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
const meta = require.main.require('./src/meta');
|
|
4
4
|
const db = require.main.require('./src/database');
|
|
5
5
|
const groups = require.main.require('./src/groups');
|
|
6
|
-
const winston = require.main.require('winston');
|
|
7
6
|
|
|
8
|
-
const plugin = {};
|
|
9
7
|
const SETTINGS_KEY = 'equipmentCalendar';
|
|
10
8
|
|
|
11
9
|
const DEFAULT_SETTINGS = {
|
|
@@ -27,9 +25,19 @@ async function getSettings() {
|
|
|
27
25
|
const raw = await meta.settings.get(SETTINGS_KEY);
|
|
28
26
|
const s = Object.assign({}, DEFAULT_SETTINGS, raw || {});
|
|
29
27
|
s.paymentTimeoutMinutes = parseInt(s.paymentTimeoutMinutes, 10) || DEFAULT_SETTINGS.paymentTimeoutMinutes;
|
|
28
|
+
s.defaultView = s.defaultView || DEFAULT_SETTINGS.defaultView;
|
|
30
29
|
return s;
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
async function saveSettingsFromBody(body) {
|
|
33
|
+
const current = await getSettings();
|
|
34
|
+
const next = Object.assign({}, current, body || {});
|
|
35
|
+
next.paymentTimeoutMinutes = parseInt(next.paymentTimeoutMinutes, 10) || DEFAULT_SETTINGS.paymentTimeoutMinutes;
|
|
36
|
+
next.defaultView = next.defaultView || DEFAULT_SETTINGS.defaultView;
|
|
37
|
+
await meta.settings.set(SETTINGS_KEY, next);
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
async function isUserInAnyGroups(uid, groupCsv) {
|
|
34
42
|
if (!uid || !groupCsv) return false;
|
|
35
43
|
const names = String(groupCsv).split(',').map(s => s.trim()).filter(Boolean);
|
|
@@ -66,39 +74,46 @@ function toEvent(r) {
|
|
|
66
74
|
const end = new Date(String(r.endIso).slice(0,10) + 'T00:00:00Z');
|
|
67
75
|
end.setUTCDate(end.getUTCDate() + 1);
|
|
68
76
|
const endExcl = end.toISOString().slice(0,10);
|
|
69
|
-
|
|
70
77
|
const status = r.status || 'pending';
|
|
71
78
|
const title = status === 'paid' ? '[PAID] Reservation' : (status === 'approved' ? '[APPROVED] Reservation' : '[PENDING] Reservation');
|
|
72
79
|
return { id: r.rid, title, start, end: endExcl, allDay: true };
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
const plugin = {};
|
|
83
|
+
|
|
75
84
|
plugin.init = async function (params) {
|
|
76
85
|
const { router, middleware } = params;
|
|
77
86
|
|
|
78
|
-
// ACP
|
|
79
87
|
router.get('/admin/plugins/equipment-calendar', middleware.admin.buildHeader, async (req, res) => {
|
|
80
|
-
|
|
88
|
+
const settings = await getSettings();
|
|
89
|
+
res.render('admin/plugins/equipment-calendar', {
|
|
90
|
+
settings,
|
|
91
|
+
savedFlag: (req.query && req.query.saved === '1') ? 'true' : 'false',
|
|
92
|
+
selected_dayGridMonth: settings.defaultView === 'dayGridMonth' ? 'selected' : '',
|
|
93
|
+
selected_timeGridWeek: settings.defaultView === 'timeGridWeek' ? 'selected' : '',
|
|
94
|
+
selected_listWeek: settings.defaultView === 'listWeek' ? 'selected' : '',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
router.post('/admin/plugins/equipment-calendar', middleware.admin.checkPrivileges, middleware.applyCSRF, async (req, res) => {
|
|
99
|
+
await saveSettingsFromBody(req.body || {});
|
|
100
|
+
res.redirect(`${req.baseUrl}${req.path}?saved=1`);
|
|
81
101
|
});
|
|
82
|
-
router.get('/api/admin/plugins/equipment-calendar', async (req, res) => res.json({}));
|
|
83
102
|
|
|
84
|
-
|
|
85
|
-
async function renderCal(req, res) {
|
|
103
|
+
router.get('/equipment/calendar', middleware.buildHeader, async (req, res) => {
|
|
86
104
|
const settings = await getSettings();
|
|
87
105
|
res.render('equipment-calendar/calendar', { defaultView: settings.defaultView });
|
|
88
|
-
}
|
|
89
|
-
router.get('/
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
router.get('/api/calendar', async (req, res) => res.json({}));
|
|
106
|
+
});
|
|
107
|
+
router.get('/calendar', middleware.buildHeader, async (req, res) => {
|
|
108
|
+
const settings = await getSettings();
|
|
109
|
+
res.render('equipment-calendar/calendar', { defaultView: settings.defaultView });
|
|
110
|
+
});
|
|
94
111
|
|
|
95
|
-
// Events
|
|
96
112
|
router.get('/api/plugins/equipment-calendar/events', async (req, res) => {
|
|
97
113
|
const list = await listReservations(1000);
|
|
98
114
|
res.json({ events: list.map(toEvent) });
|
|
99
115
|
});
|
|
100
116
|
|
|
101
|
-
// Create reservation
|
|
102
117
|
router.post('/api/plugins/equipment-calendar/reservations', middleware.applyCSRF, async (req, res) => {
|
|
103
118
|
const uid = req.uid;
|
|
104
119
|
if (!uid) return res.status(403).json({ message: 'forbidden' });
|
|
@@ -117,8 +132,6 @@ plugin.init = async function (params) {
|
|
|
117
132
|
await saveReservation(r);
|
|
118
133
|
res.json({ ok: true, rid: r.rid });
|
|
119
134
|
});
|
|
120
|
-
|
|
121
|
-
winston.info('[equipment-calendar] loaded (official4x CDN fix)');
|
|
122
135
|
};
|
|
123
136
|
|
|
124
137
|
plugin.addAdminMenu = async function (header) {
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,79 +1,86 @@
|
|
|
1
|
-
<div class="acp-page-container
|
|
1
|
+
<div class="acp-page-container">
|
|
2
2
|
<h1 class="mb-3">Equipment Calendar</h1>
|
|
3
3
|
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
<div class="mb-3">
|
|
7
|
-
<label class="form-label">Groupes autorisés à créer (creatorGroups, séparés par virgule)</label>
|
|
8
|
-
<input class="form-control" type="text" data-field="creatorGroups">
|
|
9
|
-
</div>
|
|
10
|
-
<div class="mb-3">
|
|
11
|
-
<label class="form-label">Groupe valideur (approverGroup)</label>
|
|
12
|
-
<input class="form-control" type="text" data-field="approverGroup">
|
|
13
|
-
</div>
|
|
14
|
-
<div class="mb-0">
|
|
15
|
-
<label class="form-label">Groupe notifié (notifyGroup)</label>
|
|
16
|
-
<input class="form-control" type="text" data-field="notifyGroup">
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
4
|
+
<form method="post" action="{config.relative_path}/admin/plugins/equipment-calendar">
|
|
5
|
+
<input type="hidden" name="_csrf" value="{csrf_token}">
|
|
19
6
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<
|
|
25
|
-
<input class="form-control" type="text" data-field="ha_apiBaseUrl" placeholder="https://api.helloasso.com ou https://api.helloasso-sandbox.com">
|
|
26
|
-
</div>
|
|
27
|
-
<div class="col-md-6">
|
|
28
|
-
<label class="form-label">Organization slug</label>
|
|
29
|
-
<input class="form-control" type="text" data-field="ha_organizationSlug">
|
|
30
|
-
</div>
|
|
31
|
-
<div class="col-md-6">
|
|
32
|
-
<label class="form-label">Client ID</label>
|
|
33
|
-
<input class="form-control" type="text" data-field="ha_clientId">
|
|
7
|
+
<div class="card card-body mb-3">
|
|
8
|
+
<h5 class="mb-3">Permissions</h5>
|
|
9
|
+
<div class="mb-3">
|
|
10
|
+
<label class="form-label">Groupes autorisés à créer (creatorGroups, séparés par virgule)</label>
|
|
11
|
+
<input class="form-control" type="text" name="creatorGroups" value="{settings.creatorGroups}">
|
|
34
12
|
</div>
|
|
35
|
-
<div class="
|
|
36
|
-
<label class="form-label">
|
|
37
|
-
<input class="form-control" type="
|
|
13
|
+
<div class="mb-3">
|
|
14
|
+
<label class="form-label">Groupe valideur (approverGroup)</label>
|
|
15
|
+
<input class="form-control" type="text" name="approverGroup" value="{settings.approverGroup}">
|
|
38
16
|
</div>
|
|
39
|
-
<div class="
|
|
40
|
-
<label class="form-label">
|
|
41
|
-
<input class="form-control" type="text"
|
|
42
|
-
</div>
|
|
43
|
-
<div class="col-md-6">
|
|
44
|
-
<label class="form-label">Form Slug</label>
|
|
45
|
-
<input class="form-control" type="text" data-field="ha_itemsFormSlug" placeholder="locations-materiel-2026">
|
|
46
|
-
</div>
|
|
47
|
-
<div class="col-12">
|
|
48
|
-
<label class="form-label">Return URL base (callback)</label>
|
|
49
|
-
<input class="form-control" type="text" data-field="ha_returnUrl" placeholder="https://www.onekite.com">
|
|
17
|
+
<div class="mb-0">
|
|
18
|
+
<label class="form-label">Groupe notifié (notifyGroup)</label>
|
|
19
|
+
<input class="form-control" type="text" name="notifyGroup" value="{settings.notifyGroup}">
|
|
50
20
|
</div>
|
|
51
21
|
</div>
|
|
52
|
-
</div>
|
|
53
22
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
23
|
+
<div class="card card-body mb-3">
|
|
24
|
+
<h5 class="mb-3">HelloAsso</h5>
|
|
25
|
+
<div class="row g-3">
|
|
26
|
+
<div class="col-md-6">
|
|
27
|
+
<label class="form-label">API Base URL (prod/sandbox)</label>
|
|
28
|
+
<input class="form-control" type="text" name="ha_apiBaseUrl" value="{settings.ha_apiBaseUrl}" placeholder="https://api.helloasso.com ou https://api.helloasso-sandbox.com">
|
|
29
|
+
</div>
|
|
30
|
+
<div class="col-md-6">
|
|
31
|
+
<label class="form-label">Organization slug</label>
|
|
32
|
+
<input class="form-control" type="text" name="ha_organizationSlug" value="{settings.ha_organizationSlug}">
|
|
33
|
+
</div>
|
|
34
|
+
<div class="col-md-6">
|
|
35
|
+
<label class="form-label">Client ID</label>
|
|
36
|
+
<input class="form-control" type="text" name="ha_clientId" value="{settings.ha_clientId}">
|
|
37
|
+
</div>
|
|
38
|
+
<div class="col-md-6">
|
|
39
|
+
<label class="form-label">Client Secret</label>
|
|
40
|
+
<input class="form-control" type="password" name="ha_clientSecret" value="{settings.ha_clientSecret}">
|
|
41
|
+
</div>
|
|
42
|
+
<div class="col-md-6">
|
|
43
|
+
<label class="form-label">Form Type</label>
|
|
44
|
+
<input class="form-control" type="text" name="ha_itemsFormType" value="{settings.ha_itemsFormType}" placeholder="shop">
|
|
45
|
+
</div>
|
|
46
|
+
<div class="col-md-6">
|
|
47
|
+
<label class="form-label">Form Slug</label>
|
|
48
|
+
<input class="form-control" type="text" name="ha_itemsFormSlug" value="{settings.ha_itemsFormSlug}" placeholder="locations-materiel-2026">
|
|
49
|
+
</div>
|
|
50
|
+
<div class="col-12">
|
|
51
|
+
<label class="form-label">Return URL base (callback)</label>
|
|
52
|
+
<input class="form-control" type="text" name="ha_returnUrl" value="{settings.ha_returnUrl}" placeholder="https://www.onekite.com">
|
|
53
|
+
</div>
|
|
60
54
|
</div>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="card card-body mb-3">
|
|
58
|
+
<h5 class="mb-3">Calendrier</h5>
|
|
59
|
+
<div class="row g-3">
|
|
60
|
+
<div class="col-md-6">
|
|
61
|
+
<label class="form-label">Timeout paiement (minutes)</label>
|
|
62
|
+
<input class="form-control" type="number" min="1" name="paymentTimeoutMinutes" value="{settings.paymentTimeoutMinutes}">
|
|
63
|
+
</div>
|
|
64
|
+
<div class="col-md-6">
|
|
65
|
+
<label class="form-label">Vue par défaut</label>
|
|
66
|
+
<select class="form-select" name="defaultView">
|
|
67
|
+
<option value="dayGridMonth" {selected_dayGridMonth}>Mois</option>
|
|
68
|
+
<option value="timeGridWeek" {selected_timeGridWeek}>Semaine</option>
|
|
69
|
+
<option value="listWeek" {selected_listWeek}>Liste</option>
|
|
70
|
+
</select>
|
|
71
|
+
</div>
|
|
68
72
|
</div>
|
|
69
73
|
</div>
|
|
70
|
-
</div>
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
<button class="btn btn-primary" type="submit">Enregistrer</button>
|
|
76
|
+
</form>
|
|
73
77
|
</div>
|
|
74
78
|
|
|
75
79
|
<script>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
(function () {
|
|
81
|
+
var saved = {savedFlag};
|
|
82
|
+
if (saved && window.app && typeof window.app.alertSuccess === 'function') {
|
|
83
|
+
window.app.alertSuccess('[[admin/settings:settings-saved]]');
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
79
86
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<div class="container-fluid px-3 equipment-calendar-page">
|
|
2
2
|
<h1 class="mb-3">Calendrier des réservations</h1>
|
|
3
|
+
|
|
3
4
|
<div id="ec-calendar"></div>
|
|
4
5
|
|
|
5
6
|
<div class="modal fade" id="ecModal" tabindex="-1" aria-hidden="true">
|
|
@@ -14,15 +15,15 @@
|
|
|
14
15
|
<input type="hidden" name="_csrf" value="{csrf_token}">
|
|
15
16
|
<div class="mb-3">
|
|
16
17
|
<label class="form-label">Du</label>
|
|
17
|
-
<input class="form-control" type="date"
|
|
18
|
+
<input class="form-control" type="date" id="ec-start" required>
|
|
18
19
|
</div>
|
|
19
20
|
<div class="mb-3">
|
|
20
21
|
<label class="form-label">Au</label>
|
|
21
|
-
<input class="form-control" type="date"
|
|
22
|
+
<input class="form-control" type="date" id="ec-end" required>
|
|
22
23
|
</div>
|
|
23
24
|
<div class="mb-3">
|
|
24
25
|
<label class="form-label">Matériel (IDs, séparés par virgule)</label>
|
|
25
|
-
<input class="form-control" type="text"
|
|
26
|
+
<input class="form-control" type="text" id="ec-items" placeholder="ex: item123,item456" required>
|
|
26
27
|
</div>
|
|
27
28
|
</div>
|
|
28
29
|
<div class="modal-footer">
|
|
@@ -39,5 +40,102 @@
|
|
|
39
40
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar/index.global.min.js"></script>
|
|
40
41
|
|
|
41
42
|
<script>
|
|
42
|
-
|
|
43
|
+
(function () {
|
|
44
|
+
function isoDate(s) { return String(s || '').slice(0, 10); }
|
|
45
|
+
function alertSuccess(msg){ if (window.app && app.alertSuccess) app.alertSuccess(msg); }
|
|
46
|
+
function alertError(msg){ if (window.app && app.alertError) app.alertError(msg); else console.error(msg); }
|
|
47
|
+
|
|
48
|
+
async function apiGet(path) {
|
|
49
|
+
var res = await fetch(config.relative_path + path, { credentials: 'same-origin' });
|
|
50
|
+
if (!res.ok) throw new Error('API error');
|
|
51
|
+
return res.json();
|
|
52
|
+
}
|
|
53
|
+
async function apiPost(path, body, csrf) {
|
|
54
|
+
var res = await fetch(config.relative_path + path, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
credentials: 'same-origin',
|
|
57
|
+
headers: { 'Content-Type': 'application/json', 'x-csrf-token': csrf || '' },
|
|
58
|
+
body: JSON.stringify(body || {}),
|
|
59
|
+
});
|
|
60
|
+
var data = {};
|
|
61
|
+
try { data = await res.json(); } catch (e) {}
|
|
62
|
+
if (!res.ok) throw new Error(data.message || 'Erreur');
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function openModal(startIso, endIso) {
|
|
67
|
+
document.getElementById('ec-start').value = startIso;
|
|
68
|
+
document.getElementById('ec-end').value = endIso;
|
|
69
|
+
var modalEl = document.getElementById('ecModal');
|
|
70
|
+
if (window.bootstrap && modalEl) {
|
|
71
|
+
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function init() {
|
|
76
|
+
var el = document.getElementById('ec-calendar');
|
|
77
|
+
if (!el) return;
|
|
78
|
+
|
|
79
|
+
if (!window.FullCalendar || !window.FullCalendar.Calendar) {
|
|
80
|
+
el.innerHTML = '<div class="alert alert-danger">FullCalendar non chargé (CDN). Vérifie la CSP et l’accès à cdn.jsdelivr.net.</div>';
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var data = await apiGet('/api/plugins/equipment-calendar/events');
|
|
85
|
+
var events = (data && data.events) ? data.events : [];
|
|
86
|
+
|
|
87
|
+
var calendar = new window.FullCalendar.Calendar(el, {
|
|
88
|
+
initialView: '{defaultView}',
|
|
89
|
+
headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,listWeek' },
|
|
90
|
+
selectable: true,
|
|
91
|
+
selectMirror: true,
|
|
92
|
+
displayEventTime: false,
|
|
93
|
+
select: function (info) {
|
|
94
|
+
var start = isoDate(info.startStr);
|
|
95
|
+
var endExcl = isoDate(info.endStr);
|
|
96
|
+
var end = endExcl;
|
|
97
|
+
if (endExcl) {
|
|
98
|
+
var d = new Date(endExcl + 'T00:00:00Z');
|
|
99
|
+
d.setUTCDate(d.getUTCDate() - 1);
|
|
100
|
+
end = d.toISOString().slice(0, 10);
|
|
101
|
+
}
|
|
102
|
+
openModal(start, end);
|
|
103
|
+
calendar.unselect();
|
|
104
|
+
},
|
|
105
|
+
dateClick: function (info) {
|
|
106
|
+
var d = isoDate(info.dateStr);
|
|
107
|
+
openModal(d, d);
|
|
108
|
+
},
|
|
109
|
+
events: events,
|
|
110
|
+
});
|
|
111
|
+
calendar.render();
|
|
112
|
+
|
|
113
|
+
var form = document.getElementById('ec-form');
|
|
114
|
+
if (form && !form.__bound) {
|
|
115
|
+
form.__bound = true;
|
|
116
|
+
form.addEventListener('submit', async function (e) {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
var startIso = document.getElementById('ec-start').value;
|
|
119
|
+
var endIso = document.getElementById('ec-end').value;
|
|
120
|
+
var itemIds = String(document.getElementById('ec-items').value || '').split(',').map(function (s) { return s.trim(); }).filter(Boolean);
|
|
121
|
+
var csrf = form.querySelector('input[name="_csrf"]').value;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
await apiPost('/api/plugins/equipment-calendar/reservations', { startIso: startIso, endIso: endIso, itemIds: itemIds }, csrf);
|
|
125
|
+
alertSuccess('Demande envoyée');
|
|
126
|
+
var modalEl = document.getElementById('ecModal');
|
|
127
|
+
if (window.bootstrap && modalEl) window.bootstrap.Modal.getOrCreateInstance(modalEl).hide();
|
|
128
|
+
|
|
129
|
+
var data2 = await apiGet('/api/plugins/equipment-calendar/events');
|
|
130
|
+
calendar.removeAllEvents();
|
|
131
|
+
(data2.events || []).forEach(function (ev) { calendar.addEvent(ev); });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
alertError(err.message || 'Erreur');
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
init().catch(function () {});
|
|
140
|
+
})();
|
|
43
141
|
</script>
|
package/public/js/acp.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* globals $, ajaxify, require */
|
|
3
|
-
|
|
4
|
-
(function () {
|
|
5
|
-
function onEnd(Settings, alerts) {
|
|
6
|
-
const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : (window.location.pathname || '');
|
|
7
|
-
if (!url.startsWith('admin/plugins/equipment-calendar')) return;
|
|
8
|
-
|
|
9
|
-
const container = $('.equipment-calendar-settings');
|
|
10
|
-
if (!container.length) return;
|
|
11
|
-
|
|
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
|
-
|
|
21
|
-
$('#ec-save').off('click.ec').on('click.ec', function (e) {
|
|
22
|
-
e.preventDefault();
|
|
23
|
-
const done = function () {
|
|
24
|
-
alerts.success('[[admin/settings:settings-saved]]');
|
|
25
|
-
};
|
|
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');
|
|
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
|
-
});
|
|
43
|
-
|
|
44
|
-
// run once
|
|
45
|
-
require(['settings', 'alerts'], function (Settings, alerts) {
|
|
46
|
-
onEnd(Settings, alerts);
|
|
47
|
-
});
|
|
48
|
-
})();
|
package/public/js/admin.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* globals $, ajaxify */
|
|
3
|
-
define('admin/plugins/equipment-calendar', ['settings', 'alerts'], function (Settings, alerts) {
|
|
4
|
-
const Admin = {};
|
|
5
|
-
|
|
6
|
-
function onPage() {
|
|
7
|
-
const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : (window.location.pathname || '');
|
|
8
|
-
if (!url.startsWith('admin/plugins/equipment-calendar')) return;
|
|
9
|
-
|
|
10
|
-
const container = $('.equipment-calendar-settings');
|
|
11
|
-
if (!container.length) return;
|
|
12
|
-
|
|
13
|
-
if (typeof Settings.sync === 'function') {
|
|
14
|
-
Settings.sync('equipmentCalendar', container);
|
|
15
|
-
} else {
|
|
16
|
-
Settings.load('equipmentCalendar', container);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
$('#save').off('click.ec').on('click.ec', function (e) {
|
|
20
|
-
e.preventDefault();
|
|
21
|
-
const done = function () {
|
|
22
|
-
alerts.success('[[admin/settings:settings-saved]]');
|
|
23
|
-
};
|
|
24
|
-
if (typeof Settings.persist === 'function') {
|
|
25
|
-
Settings.persist('equipmentCalendar', container, done, true);
|
|
26
|
-
} else {
|
|
27
|
-
Settings.save('equipmentCalendar', container, done);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Admin.init = function () {
|
|
33
|
-
$(window).off('action:ajaxify.end.ec').on('action:ajaxify.end.ec', onPage);
|
|
34
|
-
onPage();
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
return Admin;
|
|
38
|
-
});
|
package/public/js/client.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* globals $, ajaxify */
|
|
3
|
-
define('equipment-calendar/client', ['api', 'alerts'], function (api, alerts) {
|
|
4
|
-
const Client = {};
|
|
5
|
-
|
|
6
|
-
function pageMatch() {
|
|
7
|
-
const url = (ajaxify && ajaxify.data && ajaxify.data.url) ? ajaxify.data.url : '';
|
|
8
|
-
return url === 'equipment/calendar' || url === 'calendar';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function isoDate(s) { return String(s || '').slice(0, 10); }
|
|
12
|
-
|
|
13
|
-
async function loadEvents() {
|
|
14
|
-
const res = await api.get('/plugins/equipment-calendar/events', {});
|
|
15
|
-
return (res && res.events) ? res.events : [];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function openModal(startIso, endIso) {
|
|
19
|
-
$('#ec-start').val(startIso);
|
|
20
|
-
$('#ec-end').val(endIso);
|
|
21
|
-
const modalEl = document.getElementById('ecModal');
|
|
22
|
-
if (!modalEl || !window.bootstrap) return;
|
|
23
|
-
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function createReservation(payload) {
|
|
27
|
-
return api.post('/plugins/equipment-calendar/reservations', payload);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function initCalendar() {
|
|
31
|
-
const el = document.getElementById('ec-calendar');
|
|
32
|
-
if (!el) return;
|
|
33
|
-
|
|
34
|
-
if (!window.FullCalendar || !window.FullCalendar.Calendar) {
|
|
35
|
-
el.innerHTML = '<div class="alert alert-danger">FullCalendar non chargé (CDN). Vérifie la CSP (Content-Security-Policy) et l’accès à cdn.jsdelivr.net.</div>';
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const events = await loadEvents();
|
|
40
|
-
|
|
41
|
-
const calendar = new window.FullCalendar.Calendar(el, {
|
|
42
|
-
initialView: 'dayGridMonth',
|
|
43
|
-
headerToolbar: { left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,listWeek' },
|
|
44
|
-
selectable: true,
|
|
45
|
-
selectMirror: true,
|
|
46
|
-
displayEventTime: false,
|
|
47
|
-
select: function (info) {
|
|
48
|
-
const start = isoDate(info.startStr);
|
|
49
|
-
const endExcl = isoDate(info.endStr);
|
|
50
|
-
let end = endExcl;
|
|
51
|
-
if (endExcl) {
|
|
52
|
-
const d = new Date(endExcl + 'T00:00:00Z');
|
|
53
|
-
d.setUTCDate(d.getUTCDate() - 1);
|
|
54
|
-
end = d.toISOString().slice(0, 10);
|
|
55
|
-
}
|
|
56
|
-
openModal(start, end);
|
|
57
|
-
calendar.unselect();
|
|
58
|
-
},
|
|
59
|
-
dateClick: function (info) {
|
|
60
|
-
const d = isoDate(info.dateStr);
|
|
61
|
-
openModal(d, d);
|
|
62
|
-
},
|
|
63
|
-
events,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
calendar.render();
|
|
67
|
-
|
|
68
|
-
$('#ec-form').off('submit.ec').on('submit.ec', async function (e) {
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
const startIso = $('#ec-start').val();
|
|
71
|
-
const endIso = $('#ec-end').val();
|
|
72
|
-
const itemIds = String($('#ec-items').val() || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
73
|
-
const csrf = $(this).find('input[name="_csrf"]').val();
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await createReservation({ startIso, endIso, itemIds, _csrf: csrf });
|
|
77
|
-
alerts.success('Demande envoyée');
|
|
78
|
-
const modalEl = document.getElementById('ecModal');
|
|
79
|
-
if (modalEl && window.bootstrap) window.bootstrap.Modal.getOrCreateInstance(modalEl).hide();
|
|
80
|
-
|
|
81
|
-
const newEvents = await loadEvents();
|
|
82
|
-
calendar.removeAllEvents();
|
|
83
|
-
newEvents.forEach(ev => calendar.addEvent(ev));
|
|
84
|
-
} catch (err) {
|
|
85
|
-
alerts.error(err && err.message ? err.message : 'Erreur');
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function onEnd() { if (pageMatch()) initCalendar(); }
|
|
91
|
-
|
|
92
|
-
Client.init = function () {
|
|
93
|
-
$(window).off('action:ajaxify.end.ecCal').on('action:ajaxify.end.ecCal', onEnd);
|
|
94
|
-
onEnd();
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
return Client;
|
|
98
|
-
});
|