nodebb-plugin-equipment-calendar 2.0.0 → 2.0.1
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 +30 -46
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/js/client.js +95 -146
- package/public/templates/equipment-calendar/calendar.tpl +62 -20
package/library.js
CHANGED
|
@@ -1158,63 +1158,47 @@ async function handleCreateReservation(req, res) {
|
|
|
1158
1158
|
return helpers.notAllowed(req, res);
|
|
1159
1159
|
}
|
|
1160
1160
|
|
|
1161
|
-
const
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1161
|
+
const itemIdsRaw = req.body.itemIds || req.body.itemId || '';
|
|
1162
|
+
const itemIds = (Array.isArray(itemIdsRaw) ? itemIdsRaw : String(itemIdsRaw))
|
|
1163
|
+
.split(',')
|
|
1164
|
+
.map(s => String(s).trim())
|
|
1165
|
+
.filter(Boolean);
|
|
1166
|
+
|
|
1167
|
+
if (!itemIds.length) {
|
|
1168
|
+
return res.status(400).send('itemIds required');
|
|
1169
|
+
}
|
|
1169
1170
|
|
|
1170
1171
|
const tz = settings.timezone || 'Europe/Paris';
|
|
1171
1172
|
const start = DateTime.fromISO(String(req.body.start || ''), { zone: tz });
|
|
1172
1173
|
const end = DateTime.fromISO(String(req.body.end || ''), { zone: tz });
|
|
1173
|
-
|
|
1174
1174
|
if (!start.isValid || !end.isValid) return res.status(400).send('Invalid dates');
|
|
1175
|
-
const startMs = start.toMillis();
|
|
1176
|
-
const endMs = end.toMillis();
|
|
1177
|
-
|
|
1178
|
-
if (endMs <= startMs) return res.status(400).send('End must be after start');
|
|
1179
|
-
if (end.diff(start, 'days').days > 31) return res.status(400).send('Range too large');
|
|
1180
1175
|
|
|
1181
|
-
//
|
|
1182
|
-
|
|
1183
|
-
|
|
1176
|
+
// Normalize to whole days (no hours). End is exclusive.
|
|
1177
|
+
const startDay = start.startOf('day');
|
|
1178
|
+
let endDay = end.startOf('day');
|
|
1179
|
+
if (endDay <= startDay) {
|
|
1180
|
+
endDay = startDay.plus({ days: 1 });
|
|
1184
1181
|
}
|
|
1185
1182
|
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
itemId,
|
|
1189
|
-
uid: req.uid,
|
|
1190
|
-
startMs,
|
|
1191
|
-
endMs,
|
|
1192
|
-
status: 'pending',
|
|
1193
|
-
createdAtMs: Date.now(),
|
|
1194
|
-
updatedAtMs: Date.now(),
|
|
1195
|
-
validatorUid: 0,
|
|
1196
|
-
notesUser: String(req.body.notesUser || '').slice(0, 2000),
|
|
1197
|
-
notesAdmin: '',
|
|
1198
|
-
ha_checkoutIntentId: '',
|
|
1199
|
-
ha_paymentUrl: '',
|
|
1200
|
-
ha_paymentStatus: '',
|
|
1201
|
-
ha_paidAtMs: 0,
|
|
1202
|
-
};
|
|
1183
|
+
const startMs = startDay.toMillis();
|
|
1184
|
+
const endMs = endDay.toMillis();
|
|
1203
1185
|
|
|
1204
|
-
|
|
1186
|
+
const notesUser = String(req.body.notesUser || '').trim();
|
|
1205
1187
|
|
|
1206
|
-
|
|
1207
|
-
const link = `/equipment/approvals`;
|
|
1208
|
-
await sendGroupNotificationAndEmail(
|
|
1209
|
-
settings.notifyGroup || settings.approverGroup,
|
|
1210
|
-
'Nouvelle demande de réservation',
|
|
1211
|
-
`Une demande de réservation est en attente (matériel: ${item.name}).`,
|
|
1212
|
-
link
|
|
1213
|
-
);
|
|
1188
|
+
const bookingId = `${req.uid}:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;
|
|
1214
1189
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1190
|
+
// Create one reservation per item, linked by bookingId
|
|
1191
|
+
const created = [];
|
|
1192
|
+
for (const itemId of itemIds) {
|
|
1193
|
+
const rid = await createReservationForItem(req, res, settings, itemId, startMs, endMs, notesUser, bookingId);
|
|
1194
|
+
created.push(rid);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Redirect to calendar with a success flag
|
|
1198
|
+
return res.redirect('/equipment/calendar?requested=1');
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
winston.error(err);
|
|
1201
|
+
return res.status(500).send(err.message || 'Error');
|
|
1218
1202
|
}
|
|
1219
1203
|
}
|
|
1220
1204
|
|
package/package.json
CHANGED
package/plugin.json
CHANGED
package/public/js/client.js
CHANGED
|
@@ -1,179 +1,128 @@
|
|
|
1
1
|
require(['jquery'], function ($) {
|
|
2
2
|
'use strict';
|
|
3
|
-
/* global window, document, FullCalendar
|
|
3
|
+
/* global window, document, FullCalendar */
|
|
4
4
|
|
|
5
5
|
(function () {
|
|
6
|
-
function
|
|
7
|
-
|
|
6
|
+
function toIsoDateUTC(ms) {
|
|
7
|
+
const d = new Date(ms);
|
|
8
|
+
const y = d.getUTCFullYear();
|
|
9
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, '0');
|
|
10
|
+
const day = String(d.getUTCDate()).padStart(2, '0');
|
|
11
|
+
return `${y}-${m}-${day}`;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
14
|
+
function parseDateInputToMs(value) {
|
|
15
|
+
// value is YYYY-MM-DD
|
|
16
|
+
if (!value) return 0;
|
|
17
|
+
const d = new Date(value + 'T00:00:00Z');
|
|
18
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const available = [];
|
|
29
|
-
for (const it of items) {
|
|
30
|
-
const bks = blockedByItem[it.id] || [];
|
|
31
|
-
const hasOverlap = bks.some(b => overlaps(startMs, endMs, Number(b.startMs), Number(b.endMs)));
|
|
32
|
-
if (!hasOverlap) available.push(it);
|
|
33
|
-
}
|
|
34
|
-
return available;
|
|
21
|
+
function getReservationDays() {
|
|
22
|
+
const startVal = document.getElementById('ec-start-date')?.value;
|
|
23
|
+
const endVal = document.getElementById('ec-end-date')?.value;
|
|
24
|
+
const startMs = parseDateInputToMs(startVal);
|
|
25
|
+
const endMs = parseDateInputToMs(endVal);
|
|
26
|
+
if (!startMs || !endMs) return 1;
|
|
27
|
+
const days = Math.round((endMs - startMs) / (24 * 60 * 60 * 1000)) + 1;
|
|
28
|
+
return days > 0 ? days : 1;
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
function
|
|
38
|
-
const
|
|
39
|
-
|
|
31
|
+
function updateTotalPrice() {
|
|
32
|
+
const sel = document.getElementById('ec-item-ids');
|
|
33
|
+
const out = document.getElementById('ec-total-price');
|
|
34
|
+
const daysEl = document.getElementById('ec-total-days');
|
|
35
|
+
if (!sel || !out) return;
|
|
36
|
+
|
|
37
|
+
let unitTotal = 0;
|
|
38
|
+
Array.from(sel.selectedOptions || []).forEach((opt) => {
|
|
39
|
+
const p = parseFloat(opt.getAttribute('data-price') || '0');
|
|
40
|
+
if (!Number.isNaN(p)) unitTotal += p;
|
|
41
|
+
});
|
|
40
42
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const itemInput = form.querySelector('input[name="itemIds"]');
|
|
44
|
-
const notesInput = form.querySelector('input[name="notesUser"]');
|
|
43
|
+
const days = getReservationDays();
|
|
44
|
+
if (daysEl) daysEl.textContent = days + (days > 1 ? ' jours' : ' jour');
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const finalTotal = unitTotal * days;
|
|
47
|
+
const txt = Number.isInteger(finalTotal) ? String(finalTotal) : finalTotal.toFixed(2);
|
|
48
|
+
out.textContent = txt + ' €';
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
function syncHiddenIsoFields() {
|
|
52
|
+
const s = document.getElementById('ec-start-date')?.value;
|
|
53
|
+
const e = document.getElementById('ec-end-date')?.value;
|
|
54
|
+
const startIsoEl = document.getElementById('ec-start-iso');
|
|
55
|
+
const endIsoEl = document.getElementById('ec-end-iso');
|
|
56
|
+
if (!s || !e || !startIsoEl || !endIsoEl) return;
|
|
57
|
+
|
|
58
|
+
// Start inclusive at 00:00Z; End exclusive = day after end date at 00:00Z
|
|
59
|
+
const startMs = parseDateInputToMs(s);
|
|
60
|
+
const endMsExclusive = parseDateInputToMs(e) + 24 * 60 * 60 * 1000;
|
|
61
|
+
startIsoEl.value = new Date(startMs).toISOString();
|
|
62
|
+
endIsoEl.value = new Date(endMsExclusive).toISOString();
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const endMs = endDate.getTime();
|
|
59
|
-
|
|
60
|
-
const available = buildAvailability(startMs, endMs);
|
|
61
|
-
|
|
62
|
-
if (!available.length) {
|
|
63
|
-
if (typeof bootbox !== 'undefined') {
|
|
64
|
-
bootbox.alert('Aucun matériel n’est disponible sur cette plage.');
|
|
65
|
-
} else {
|
|
66
|
-
alert('Aucun matériel n’est disponible sur cette plage.');
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
65
|
+
function openModalWithRange(startMs, endMsExclusive) {
|
|
66
|
+
// Convert to date inputs: end date = endExclusive -1 day
|
|
67
|
+
const startDate = toIsoDateUTC(startMs);
|
|
68
|
+
const endDate = toIsoDateUTC(endMsExclusive - 24 * 60 * 60 * 1000);
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
optionsHtml += `<option value="${it.id}">${label}</option>`;
|
|
76
|
-
}
|
|
70
|
+
const startEl = document.getElementById('ec-start-date');
|
|
71
|
+
const endEl = document.getElementById('ec-end-date');
|
|
72
|
+
if (startEl) startEl.value = startDate;
|
|
73
|
+
if (endEl) endEl.value = endDate;
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<label class="form-label">Début</label>
|
|
81
|
-
<input class="form-control" type="text" value="${fmt(startDate)}" readonly>
|
|
82
|
-
</div>
|
|
83
|
-
<div class="mb-3">
|
|
84
|
-
<label class="form-label">Fin</label>
|
|
85
|
-
<input class="form-control" type="text" value="${fmt(endDate)}" readonly>
|
|
86
|
-
</div>
|
|
87
|
-
<div class="mb-3">
|
|
88
|
-
<label class="form-label">Matériel (Ctrl/Cmd pour multi-sélection)</label>
|
|
89
|
-
<select class="form-select" id="ec-modal-item" multiple size="6">
|
|
90
|
-
${optionsHtml}
|
|
91
|
-
</select>
|
|
92
|
-
</div>
|
|
93
|
-
<div class="mb-3">
|
|
94
|
-
<label class="form-label">Note (optionnel)</label>
|
|
95
|
-
<input class="form-control" type="text" id="ec-modal-notes" maxlength="2000" placeholder="Ex: besoin de trépied, etc.">
|
|
96
|
-
</div>
|
|
97
|
-
`;
|
|
98
|
-
|
|
99
|
-
if (typeof bootbox === 'undefined') {
|
|
100
|
-
// Fallback without bootbox (should be rare on NodeBB pages)
|
|
101
|
-
const itemId = available[0].id;
|
|
102
|
-
submitReservation(startDate.toISOString(), endDate.toISOString(), itemId, '');
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
75
|
+
syncHiddenIsoFields();
|
|
76
|
+
updateTotalPrice();
|
|
105
77
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
cancel: {
|
|
111
|
-
label: 'Annuler',
|
|
112
|
-
className: 'btn-outline-secondary',
|
|
113
|
-
},
|
|
114
|
-
ok: {
|
|
115
|
-
label: 'Envoyer la demande',
|
|
116
|
-
className: 'btn-primary',
|
|
117
|
-
callback: function () {
|
|
118
|
-
const itemEl = document.getElementById('ec-modal-item');
|
|
119
|
-
const notesEl = document.getElementById('ec-modal-notes');
|
|
120
|
-
let ids = [];
|
|
121
|
-
if (itemEl && itemEl.options) {
|
|
122
|
-
for (const opt of itemEl.options) { if (opt.selected) ids.push(opt.value); }
|
|
123
|
-
}
|
|
124
|
-
if (!ids.length) ids = [available[0].id];
|
|
125
|
-
const notes = notesEl ? notesEl.value : '';
|
|
126
|
-
submitReservation(startDate.toISOString(), endDate.toISOString(), ids.join(','), notes);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
});
|
|
78
|
+
const modalEl = document.getElementById('ec-create-modal');
|
|
79
|
+
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
|
|
80
|
+
window.bootstrap.Modal.getOrCreateInstance(modalEl).show();
|
|
81
|
+
}
|
|
131
82
|
}
|
|
132
83
|
|
|
133
84
|
function initCalendar() {
|
|
134
|
-
const
|
|
135
|
-
if (!
|
|
136
|
-
|
|
137
|
-
const events = window.EC_EVENTS || [];
|
|
138
|
-
const initialDate = window.EC_INITIAL_DATE;
|
|
139
|
-
const initialView = window.EC_INITIAL_VIEW || 'dayGridMonth';
|
|
140
|
-
|
|
141
|
-
const calendar = new FullCalendar.Calendar(el, {
|
|
142
|
-
initialView: initialView,
|
|
143
|
-
initialDate: initialDate,
|
|
144
|
-
timeZone: window.EC_TZ || 'local',
|
|
145
|
-
selectable: window.EC_CAN_CREATE === true,
|
|
146
|
-
selectMirror: true,
|
|
147
|
-
events: events,
|
|
85
|
+
const calendarEl = document.getElementById('ec-calendar');
|
|
86
|
+
if (!calendarEl || !window.FullCalendar) return;
|
|
148
87
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
openNodeBBModal(info.startStr, info.endStr);
|
|
152
|
-
},
|
|
88
|
+
const canCreate = !!window.EC_CAN_CREATE;
|
|
89
|
+
const events = Array.isArray(window.EC_EVENTS) ? window.EC_EVENTS : [];
|
|
153
90
|
|
|
154
|
-
|
|
91
|
+
const calendar = new window.FullCalendar.Calendar(calendarEl, {
|
|
92
|
+
initialView: 'dayGridMonth',
|
|
93
|
+
selectable: canCreate,
|
|
94
|
+
selectMirror: true,
|
|
95
|
+
events,
|
|
155
96
|
dateClick: function (info) {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
97
|
+
if (!canCreate) return;
|
|
98
|
+
const startMs = Date.UTC(info.date.getUTCFullYear(), info.date.getUTCMonth(), info.date.getUTCDate());
|
|
99
|
+
const endMs = startMs + 24 * 60 * 60 * 1000;
|
|
100
|
+
openModalWithRange(startMs, endMs);
|
|
160
101
|
},
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
102
|
+
select: function (info) {
|
|
103
|
+
if (!canCreate) return;
|
|
104
|
+
const startMs = Date.UTC(info.start.getUTCFullYear(), info.start.getUTCMonth(), info.start.getUTCDate());
|
|
105
|
+
const endMs = Date.UTC(info.end.getUTCFullYear(), info.end.getUTCMonth(), info.end.getUTCDate());
|
|
106
|
+
openModalWithRange(startMs, endMs);
|
|
166
107
|
},
|
|
167
108
|
});
|
|
168
109
|
|
|
169
110
|
calendar.render();
|
|
170
|
-
}
|
|
171
111
|
|
|
172
|
-
|
|
173
|
-
document.addEventListener('
|
|
174
|
-
|
|
175
|
-
|
|
112
|
+
document.getElementById('ec-item-ids')?.addEventListener('change', updateTotalPrice);
|
|
113
|
+
document.getElementById('ec-start-date')?.addEventListener('change', function () {
|
|
114
|
+
syncHiddenIsoFields();
|
|
115
|
+
updateTotalPrice();
|
|
116
|
+
});
|
|
117
|
+
document.getElementById('ec-end-date')?.addEventListener('change', function () {
|
|
118
|
+
syncHiddenIsoFields();
|
|
119
|
+
updateTotalPrice();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
updateTotalPrice();
|
|
176
123
|
}
|
|
177
|
-
}());
|
|
178
124
|
|
|
125
|
+
$(window).off('action:ajaxify.end.ec').on('action:ajaxify.end.ec', initCalendar);
|
|
126
|
+
$(initCalendar);
|
|
127
|
+
})();
|
|
179
128
|
});
|
|
@@ -9,34 +9,76 @@
|
|
|
9
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
10
|
{{{ end }}}
|
|
11
11
|
|
|
12
|
-
<div class="card card-body">
|
|
13
|
-
<div id="
|
|
12
|
+
<div class="card card-body mb-3">
|
|
13
|
+
<div id="ec-calendar"></div>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
-
<!--
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
{{{ each items }}}
|
|
46
|
+
<option value="{@value.id}" data-price="{@value.price}">{@value.name} — {@value.price} €</option>
|
|
47
|
+
{{{ end }}}
|
|
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 €</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>
|
|
24
74
|
</div>
|
|
25
75
|
|
|
26
76
|
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.19/index.global.min.js"></script>
|
|
27
|
-
<script src="/plugins/nodebb-plugin-equipment-calendar/js/client.js"></script>
|
|
77
|
+
<script src="{relative_path}/plugins/nodebb-plugin-equipment-calendar/js/client.js"></script>
|
|
28
78
|
|
|
29
79
|
<script>
|
|
30
80
|
window.EC_EVENTS = JSON.parse(atob('{eventsB64}'));
|
|
31
81
|
window.EC_BLOCKS = JSON.parse(atob('{blocksB64}'));
|
|
32
|
-
window.EC_ITEMS = JSON.parse(atob('{itemsB64}'));
|
|
33
|
-
window.EC_INITIAL_DATE = "{initialDateISO}";
|
|
34
|
-
window.EC_INITIAL_VIEW = "{view}";
|
|
35
|
-
window.EC_TZ = "{tz}";
|
|
36
82
|
window.EC_CAN_CREATE = {canCreateJs};
|
|
83
|
+
window.EC_TZ = '{tz}';
|
|
37
84
|
</script>
|
|
38
|
-
|
|
39
|
-
<style>
|
|
40
|
-
.ec-status-pending .fc-event-title { font-weight: 600; }
|
|
41
|
-
.ec-status-valid .fc-event-title { font-weight: 700; }
|
|
42
|
-
</style>
|