nodebb-plugin-equipment-calendar 2.0.0 → 2.0.3
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 +103 -148
- 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,134 @@
|
|
|
1
|
-
require(['jquery'], function (
|
|
1
|
+
require(['jquery', 'bootstrap'], function ($, bootstrap) {
|
|
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;
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
});
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
if (itemInput) itemInput.value = itemId;
|
|
49
|
-
if (notesInput) notesInput.value = notes || '';
|
|
43
|
+
const days = getReservationDays();
|
|
44
|
+
if (daysEl) daysEl.textContent = days + (days > 1 ? ' jours' : ' jour');
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
const finalTotal = unitTotal * days;
|
|
47
|
+
const txt = Number.isInteger(finalTotal) ? String(finalTotal) : finalTotal.toFixed(2);
|
|
48
|
+
out.textContent = txt + ' €';
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
function
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Build modal HTML
|
|
72
|
-
let optionsHtml = '';
|
|
73
|
-
for (const it of available) {
|
|
74
|
-
const label = it.location ? `${it.name} — ${it.location}` : it.name;
|
|
75
|
-
optionsHtml += `<option value="${it.id}">${label}</option>`;
|
|
76
|
-
}
|
|
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();
|
|
63
|
+
}
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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;
|
|
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);
|
|
69
|
+
|
|
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;
|
|
74
|
+
|
|
75
|
+
syncHiddenIsoFields();
|
|
76
|
+
updateTotalPrice();
|
|
77
|
+
|
|
78
|
+
const modalEl = document.getElementById('ec-create-modal');
|
|
79
|
+
const BS = (bootstrap && bootstrap.Modal) ? bootstrap : (window.bootstrap && window.bootstrap.Modal ? window.bootstrap : null);
|
|
80
|
+
if (modalEl && BS && BS.Modal) {
|
|
81
|
+
BS.Modal.getOrCreateInstance(modalEl).show();
|
|
82
|
+
} else {
|
|
83
|
+
// Fallback: if bootstrap modal isn't available, scroll to form area
|
|
84
|
+
modalEl?.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
104
85
|
}
|
|
105
|
-
|
|
106
|
-
bootbox.dialog({
|
|
107
|
-
title: 'Demande de réservation',
|
|
108
|
-
message: body,
|
|
109
|
-
buttons: {
|
|
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
|
-
});
|
|
131
86
|
}
|
|
132
87
|
|
|
133
88
|
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,
|
|
89
|
+
const calendarEl = document.getElementById('ec-calendar');
|
|
90
|
+
if (!calendarEl || !window.FullCalendar) return;
|
|
148
91
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
openNodeBBModal(info.startStr, info.endStr);
|
|
152
|
-
},
|
|
92
|
+
const canCreate = !!window.EC_CAN_CREATE;
|
|
93
|
+
const events = Array.isArray(window.EC_EVENTS) ? window.EC_EVENTS : [];
|
|
153
94
|
|
|
154
|
-
|
|
95
|
+
const calendar = new window.FullCalendar.Calendar(calendarEl, {
|
|
96
|
+
initialView: 'dayGridMonth',
|
|
97
|
+
selectable: canCreate,
|
|
98
|
+
selectMirror: true,
|
|
99
|
+
events,
|
|
155
100
|
dateClick: function (info) {
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
101
|
+
if (!canCreate) return;
|
|
102
|
+
const startMs = Date.UTC(info.date.getUTCFullYear(), info.date.getUTCMonth(), info.date.getUTCDate());
|
|
103
|
+
const endMs = startMs + 24 * 60 * 60 * 1000;
|
|
104
|
+
console.debug('[equipment-calendar] open modal', { startMs, endMs });
|
|
105
|
+
openModalWithRange(startMs, endMs);
|
|
160
106
|
},
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
107
|
+
select: function (info) {
|
|
108
|
+
if (!canCreate) return;
|
|
109
|
+
const startMs = Date.UTC(info.start.getUTCFullYear(), info.start.getUTCMonth(), info.start.getUTCDate());
|
|
110
|
+
const endMs = Date.UTC(info.end.getUTCFullYear(), info.end.getUTCMonth(), info.end.getUTCDate());
|
|
111
|
+
console.debug('[equipment-calendar] open modal', { startMs, endMs });
|
|
112
|
+
openModalWithRange(startMs, endMs);
|
|
166
113
|
},
|
|
167
114
|
});
|
|
168
115
|
|
|
169
116
|
calendar.render();
|
|
170
|
-
}
|
|
171
117
|
|
|
172
|
-
|
|
173
|
-
document.addEventListener('
|
|
174
|
-
|
|
175
|
-
|
|
118
|
+
document.getElementById('ec-item-ids')?.addEventListener('change', updateTotalPrice);
|
|
119
|
+
document.getElementById('ec-start-date')?.addEventListener('change', function () {
|
|
120
|
+
syncHiddenIsoFields();
|
|
121
|
+
updateTotalPrice();
|
|
122
|
+
});
|
|
123
|
+
document.getElementById('ec-end-date')?.addEventListener('change', function () {
|
|
124
|
+
syncHiddenIsoFields();
|
|
125
|
+
updateTotalPrice();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
updateTotalPrice();
|
|
176
129
|
}
|
|
177
|
-
}());
|
|
178
130
|
|
|
131
|
+
$(window).off('action:ajaxify.end.ec').on('action:ajaxify.end.ec', initCalendar);
|
|
132
|
+
$(initCalendar);
|
|
133
|
+
})();
|
|
179
134
|
});
|
|
@@ -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>
|