nodebb-plugin-equipment-calendar 0.9.9 → 1.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/package.json +1 -1
- package/plugin.json +1 -1
- package/public/js/client.js +46 -11
- package/public/templates/admin/plugins/equipment-calendar.tpl +81 -104
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/js/client.js
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
/* global window, document, FullCalendar, bootbox */
|
|
3
3
|
|
|
4
4
|
(function () {
|
|
5
|
+
|
|
6
|
+
function toUtcMidnightMs(dateObj) {
|
|
7
|
+
return Date.UTC(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatDateInputValue(dateObj) {
|
|
11
|
+
// YYYY-MM-DD in UTC
|
|
12
|
+
const y = dateObj.getUTCFullYear();
|
|
13
|
+
const m = String(dateObj.getUTCMonth() + 1).padStart(2, '0');
|
|
14
|
+
const d = String(dateObj.getUTCDate()).padStart(2, '0');
|
|
15
|
+
return `${y}-${m}-${d}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function openCreateModal(startMs, endMs) {
|
|
19
|
+
const startDateEl = document.getElementById('ec-start-date');
|
|
20
|
+
const endDateEl = document.getElementById('ec-end-date');
|
|
21
|
+
const startMsEl = document.getElementById('ec-start-ms');
|
|
22
|
+
const endMsEl = document.getElementById('ec-end-ms');
|
|
23
|
+
|
|
24
|
+
if (startMsEl) startMsEl.value = String(startMs);
|
|
25
|
+
if (endMsEl) endMsEl.value = String(endMs);
|
|
26
|
+
|
|
27
|
+
// set date inputs
|
|
28
|
+
if (startDateEl) startDateEl.value = formatDateInputValue(new Date(startMs));
|
|
29
|
+
if (endDateEl) endDateEl.value = formatDateInputValue(new Date(endMs - 24*60*60*1000));
|
|
30
|
+
|
|
31
|
+
if (typeof updateTotalPrice === 'function') updateTotalPrice();
|
|
32
|
+
|
|
33
|
+
const modalEl = document.getElementById('ec-create-modal');
|
|
34
|
+
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
|
|
35
|
+
const modal = window.bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
36
|
+
modal.show();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
5
41
|
function overlaps(aStartMs, aEndMs, bStartMs, bEndMs) {
|
|
6
42
|
return aStartMs < bEndMs && aEndMs > bStartMs;
|
|
7
43
|
}
|
|
@@ -20,8 +56,8 @@
|
|
|
20
56
|
|
|
21
57
|
const blockedByItem = {};
|
|
22
58
|
for (const b of blocks) {
|
|
23
|
-
if (!blockedByItem[b.
|
|
24
|
-
blockedByItem[b.
|
|
59
|
+
if (!blockedByItem[b.itemIds]) blockedByItem[b.itemIds] = [];
|
|
60
|
+
blockedByItem[b.itemIds].push(b);
|
|
25
61
|
}
|
|
26
62
|
|
|
27
63
|
const available = [];
|
|
@@ -33,7 +69,7 @@
|
|
|
33
69
|
return available;
|
|
34
70
|
}
|
|
35
71
|
|
|
36
|
-
function submitReservation(startISO, endISO,
|
|
72
|
+
function submitReservation(startISO, endISO, itemIds, notes) {
|
|
37
73
|
const form = document.getElementById('ec-create-form');
|
|
38
74
|
if (!form) return;
|
|
39
75
|
|
|
@@ -44,7 +80,7 @@
|
|
|
44
80
|
|
|
45
81
|
if (startInput) startInput.value = startISO;
|
|
46
82
|
if (endInput) endInput.value = endISO;
|
|
47
|
-
if (itemInput) itemInput.value =
|
|
83
|
+
if (itemInput) itemInput.value = itemIds;
|
|
48
84
|
if (notesInput) notesInput.value = notes || '';
|
|
49
85
|
|
|
50
86
|
form.submit();
|
|
@@ -97,8 +133,8 @@
|
|
|
97
133
|
|
|
98
134
|
if (typeof bootbox === 'undefined') {
|
|
99
135
|
// Fallback without bootbox (should be rare on NodeBB pages)
|
|
100
|
-
const
|
|
101
|
-
submitReservation(startDate.toISOString(), endDate.toISOString(),
|
|
136
|
+
const itemIds = available[0].id;
|
|
137
|
+
submitReservation(startDate.toISOString(), endDate.toISOString(), itemIds, '');
|
|
102
138
|
return;
|
|
103
139
|
}
|
|
104
140
|
|
|
@@ -151,11 +187,10 @@
|
|
|
151
187
|
},
|
|
152
188
|
|
|
153
189
|
// Single date click
|
|
154
|
-
dateClick: function
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
openNodeBBModal(start.toISOString(), end.toISOString());
|
|
190
|
+
dateClick: function(info) {
|
|
191
|
+
const startMs = toUtcMidnightMs(info.date);
|
|
192
|
+
const endMs = startMs + 24*60*60*1000;
|
|
193
|
+
openCreateModal(startMs, endMs);
|
|
159
194
|
},
|
|
160
195
|
|
|
161
196
|
headerToolbar: {
|
|
@@ -1,135 +1,112 @@
|
|
|
1
1
|
<div class="acp-page-container">
|
|
2
2
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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>
|
|
8
9
|
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
{{{ if saved }}}
|
|
12
|
-
<div class="alert alert-success">Paramètres enregistrés.</div>
|
|
13
|
-
<script>
|
|
14
|
-
(function () {
|
|
15
|
-
try {
|
|
16
|
-
if (window.app && window.app.alertSuccess) {
|
|
17
|
-
window.app.alertSuccess('Paramètres enregistrés');
|
|
18
|
-
}
|
|
19
|
-
} catch (e) {}
|
|
20
|
-
}());
|
|
21
|
-
</script>
|
|
22
|
-
{{{ end }}}
|
|
23
10
|
|
|
24
|
-
|
|
25
|
-
<form id="ec-admin-form" method="post" action="/admin/plugins/equipment-calendar/save" class="mb-3">
|
|
11
|
+
<form id="ec-admin-form" method="post" action="/admin/plugins/equipment-calendar/save" class="mb-3 mt-3">
|
|
26
12
|
<input type="hidden" name="_csrf" value="{config.csrf_token}">
|
|
27
13
|
|
|
28
|
-
<div class="alert alert-warning">
|
|
29
|
-
Le champ "Matériel" doit être un JSON valide (array). Exemple :
|
|
30
|
-
<pre class="mb-0">[
|
|
31
|
-
{ "id": "cam1", "name": "Caméra A", "price": 50, "": "Stock A", "active": true },
|
|
32
|
-
{ "id": "light1", "name": "Projecteur", "price": 20, "": "Stock B", "active": true }
|
|
33
|
-
]</pre>
|
|
34
|
-
<div class="mt-2">Note : <strong>price est en euros</strong> dans ce plugin (ex: 50 = 50€).</div>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
14
|
<div class="card card-body mb-3">
|
|
38
15
|
<h5>Permissions</h5>
|
|
16
|
+
|
|
17
|
+
<div class="mb-3">
|
|
18
|
+
<label class="form-label">Groupes autorisés à créer une demande (creatorGroups)</label>
|
|
19
|
+
<input class="form-control" type="text" name="creatorGroups" value="{settings.creatorGroups}">
|
|
20
|
+
<div class="form-text">Nom(s) de groupes NodeBB. Pour plusieurs groupes, sépare par des virgules. Ex: <code>registered-users, members</code></div>
|
|
39
21
|
</div>
|
|
40
22
|
|
|
41
23
|
<div class="mb-3">
|
|
42
|
-
<label class="form-label">
|
|
43
|
-
<
|
|
24
|
+
<label class="form-label">Groupe valideur (approverGroup)</label>
|
|
25
|
+
<input class="form-control" type="text" name="approverGroup" value="{settings.approverGroup}">
|
|
26
|
+
<div class="form-text">Les membres de ce groupe voient /equipment/approvals et peuvent valider/refuser.</div>
|
|
44
27
|
</div>
|
|
45
|
-
</div>
|
|
46
28
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<div class="mb-3"><label class="form-label">Return URL</label><input name="ha_returnUrl" class="form-control" value="{settings.ha_returnUrl}"></div>
|
|
53
|
-
<div class="mb-3"><label class="form-label">Webhook Secret (HMAC SHA256)</label><input name="ha_webhookSecret" class="form-control" value="{settings.ha_webhookSecret}"></div>
|
|
29
|
+
<div class="mb-0">
|
|
30
|
+
<label class="form-label">Groupe notifié (notifyGroup)</label>
|
|
31
|
+
<input class="form-control" type="text" name="notifyGroup" value="{settings.notifyGroup}">
|
|
32
|
+
<div class="form-text">Envoi d’un email/notification au groupe lors d’une nouvelle demande.</div>
|
|
33
|
+
</div>
|
|
54
34
|
</div>
|
|
55
35
|
|
|
56
36
|
<div class="card card-body mb-3">
|
|
57
|
-
<h5>
|
|
37
|
+
<h5>Matériel</h5>
|
|
38
|
+
|
|
39
|
+
<div class="mb-3">
|
|
40
|
+
<label class="form-label">Source du matériel</label>
|
|
41
|
+
<select class="form-select" name="itemsSource">
|
|
42
|
+
<option value="manual" {{{ if view_itemsSourceManual }}}selected{{{ end }}}>Manuel (JSON)</option>
|
|
43
|
+
<option value="helloasso" {{{ if view_itemsSourceHelloasso }}}selected{{{ end }}}>HelloAsso (catalogue)</option>
|
|
44
|
+
</select>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
58
47
|
<div class="mb-3">
|
|
48
|
+
<label class="form-label">Matériel (JSON) — utilisé si Source = Manuel</label>
|
|
49
|
+
<textarea class="form-control" name="itemsJson" rows="8">{settings.itemsJson}</textarea>
|
|
50
|
+
<div class="form-text">Format: <code>[{"id":"cam1","name":"Caméra","price":50,"active":true}]</code> — <code>price</code> en euros (unitaire / jour).</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="mb-0">
|
|
59
54
|
<label class="form-label">Timeout paiement (minutes)</label>
|
|
60
55
|
<input class="form-control" type="number" min="1" name="paymentTimeoutMinutes" value="{settings.paymentTimeoutMinutes}">
|
|
61
56
|
<div class="form-text">Après validation, si le paiement n’est pas confirmé dans ce délai, la réservation est annulée et le matériel est débloqué.</div>
|
|
62
57
|
</div>
|
|
63
|
-
<div class="mb-3">
|
|
64
|
-
<label class="form-label">Vue par défaut</label>
|
|
65
|
-
<select name="defaultView" class="form-select">
|
|
66
|
-
<option value="dayGridMonth" {{{ if view_dayGridMonth }}}selected{{{ end }}}>Mois</option>
|
|
67
|
-
<option value="timeGridWeek" {{{ if view_timeGridWeek }}}selected{{{ end }}}>Semaine</option>
|
|
68
|
-
<option value="timeGridDay" {{{ if view_timeGridDay }}}selected{{{ end }}}>Jour</option>
|
|
69
|
-
</select>
|
|
70
|
-
</div>
|
|
71
|
-
<div class="mb-3"><label class="form-label">Timezone</label><input name="timezone" class="form-control" value="{settings.timezone}"></div>
|
|
72
58
|
</div>
|
|
73
59
|
|
|
74
|
-
|
|
75
60
|
<div class="card card-body mb-3">
|
|
76
|
-
<h5>
|
|
77
|
-
<p class="text-muted mb-2">Supprime des réservations de la base (action irréversible).</p>
|
|
78
|
-
|
|
79
|
-
<div class="d-flex flex-wrap gap-2">
|
|
80
|
-
<button type="submit" class="btn btn-outline-danger" formaction="/admin/plugins/equipment-calendar/purge" formmethod="post" name="mode" value="nonblocking"
|
|
81
|
-
onclick="return confirm('Supprimer toutes les réservations refusées/annulées ?');">
|
|
82
|
-
Purger refusées/annulées
|
|
83
|
-
</button>
|
|
84
|
-
|
|
85
|
-
<div class="d-flex align-items-center gap-2">
|
|
86
|
-
<input class="form-control" style="max-width: 140px" type="number" min="1" name="olderThanDays" placeholder="Jours">
|
|
87
|
-
<button type="submit" class="btn btn-outline-danger" formaction="/admin/plugins/equipment-calendar/purge" formmethod="post" name="mode" value="olderThan"
|
|
88
|
-
onclick="return confirm('Supprimer les réservations plus anciennes que N jours ?');">
|
|
89
|
-
Purger plus anciennes que…
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
61
|
+
<h5>HelloAsso</h5>
|
|
92
62
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
</
|
|
63
|
+
<div class="mb-3">
|
|
64
|
+
<label class="form-label">API Base URL (prod/sandbox)</label>
|
|
65
|
+
<input class="form-control" type="text" name="ha_apiBaseUrl" value="{settings.ha_apiBaseUrl}">
|
|
66
|
+
<div class="form-text">Production: <code>https://api.helloasso.com</code> — Sandbox: <code>https://api.helloasso-sandbox.com</code></div>
|
|
97
67
|
</div>
|
|
98
68
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
if (window.app && window.app.alertSuccess) {
|
|
105
|
-
window.app.alertSuccess('Purge effectuée : {purged} réservation(s) supprimée(s).');
|
|
106
|
-
}
|
|
107
|
-
} catch (e) {}
|
|
108
|
-
}());
|
|
109
|
-
</script>
|
|
110
|
-
{{{ end }}}
|
|
111
|
-
</div>
|
|
69
|
+
<div class="mb-3">
|
|
70
|
+
<label class="form-label">Organization slug</label>
|
|
71
|
+
<input class="form-control" type="text" name="ha_organizationSlug" value="{settings.ha_organizationSlug}">
|
|
72
|
+
</div>
|
|
112
73
|
|
|
113
|
-
|
|
74
|
+
<div class="mb-3">
|
|
75
|
+
<label class="form-label">Client ID</label>
|
|
76
|
+
<input class="form-control" type="text" name="ha_clientId" value="{settings.ha_clientId}">
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="mb-3">
|
|
80
|
+
<label class="form-label">Client Secret</label>
|
|
81
|
+
<input class="form-control" type="password" name="ha_clientSecret" value="{settings.ha_clientSecret}">
|
|
82
|
+
</div>
|
|
114
83
|
|
|
84
|
+
<div class="row g-3">
|
|
85
|
+
<div class="col-md-6">
|
|
86
|
+
<label class="form-label">Form Type</label>
|
|
87
|
+
<input class="form-control" type="text" name="ha_itemsFormType" value="{settings.ha_itemsFormType}" placeholder="shop">
|
|
88
|
+
<div class="form-text">Ex: <code>shop</code></div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="col-md-6">
|
|
91
|
+
<label class="form-label">Form Slug</label>
|
|
92
|
+
<input class="form-control" type="text" name="ha_itemsFormSlug" value="{settings.ha_itemsFormSlug}" placeholder="locations-materiel-2026">
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="mb-0 mt-3">
|
|
97
|
+
<label class="form-label">Préfixe itemName (checkout)</label>
|
|
98
|
+
<input class="form-control" type="text" name="ha_calendarItemNamePrefix" value="{settings.ha_calendarItemNamePrefix}">
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<button class="btn btn-primary" type="submit">Enregistrer</button>
|
|
115
103
|
</form>
|
|
116
|
-
</div>
|
|
117
104
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
form.submit();
|
|
128
|
-
} catch (e) {
|
|
129
|
-
console.error(e);
|
|
130
|
-
alert('Erreur lors de la soumission du formulaire (voir console).');
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
}());
|
|
135
|
-
</script>
|
|
105
|
+
{{{ if saved }}}
|
|
106
|
+
<script>
|
|
107
|
+
if (window.app && typeof window.app.alertSuccess === 'function') {
|
|
108
|
+
window.app.alertSuccess('Paramètres enregistrés');
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
{{{ end }}}
|
|
112
|
+
</div>
|