templates/index.html (view raw)
1<!DOCTYPE html>
2<html>
3
4<head>
5 <title>Ricorrenze</title>
6 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%2210 0 100 100%22><text y=%22.90em%22 font-size=%2290%22>📅</text></svg>"></link>
7 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
8 <style>
9 :root {
10 color-scheme: light dark;
11 --text: #000;
12 --bg: #fff;
13 --green: #4CAF50;
14 --hover-green: #45a049;
15 --red: #af4c4c;
16 --hover-red: #a14545;
17 }
18
19 @media (prefers-color-scheme: dark) {
20 :root {
21 color-scheme: dark light;
22 --text: #fff;
23 --bg: #121212;
24 --green: #265929;
25 --hover-green: #214d23;
26 --red: #592626;
27 --hover-red: #4d2121;
28 }
29 }
30
31 body {
32 font-family: Arial, sans-serif;
33 background-color: var(--bg);
34 color: var(--text);
35 }
36
37 table {
38 width: 100%;
39 border-collapse: collapse;
40 }
41
42 table,
43 th,
44 td {
45 border: 1px solid var(--text);
46 }
47
48 th,
49 td {
50 padding: 10px;
51 text-align: left;
52 }
53
54 th {
55 background-color: var(--bg);
56 }
57
58 .next {
59 background-color: var(--red);
60 color: white;
61 }
62
63 .date-inputs {
64 display: flex;
65 align-items: center;
66 border: none;
67 gap: 2px;
68 }
69 .small-input {
70 width: 32px;
71 }
72 .big-input {
73 width: 100%;
74 }
75
76 .actions i {
77 cursor: pointer;
78 padding-right: 5px;
79 }
80
81 .cute-button {
82 margin-top: 20px;
83 background-color: var(--green);
84 border: none;
85 color: white;
86 padding: 10px 20px;
87 text-align: center;
88 text-decoration: none;
89 display: inline-block;
90 font-size: 16px;
91 margin: 4px 2px;
92 transition-duration: 0.4s;
93 cursor: pointer;
94 border-radius: 12px;
95 }
96
97 .cute-button:hover {
98 background-color: var(--hover-green);
99 }
100
101 .cute-button-red {
102 background-color: var(--red);
103 }
104
105 .cute-button-red:hover {
106 background-color: var(--hover-red);
107 }
108
109 .hidden {
110 display: none;
111 }
112 </style>
113</head>
114
115<body>
116 <h1>Ricorrenze</h1>
117 <table id="main-table">
118 <tr>
119 <th data-field="name">Nome</th>
120 <th data-field="description">Descrizione</th>
121 <th data-field="date">Data (gg/mm)</th>
122 <th data-field="notify">Notifica</th>
123 <th data-field="notified">Inviata</th>
124 <th data-field="actions">Azioni</th>
125 </tr>
126 {{ range .Occurrences }}
127 <tr id="occurrence-{{ .ID }}">
128 <td data-field="name">{{ .Name }}</td>
129 <td data-field="description">{{ .Description }}</td>
130 <td data-field="date">{{ padZero .Day }}/{{ padZero .Month }}</td>
131 <td data-field="notify"><input type="checkbox" {{if .Notify}}checked{{end}} disabled></td>
132 <td data-field="notified"><input type="checkbox" {{if .Notified}}checked{{end}} disabled></td>
133 <td data-field="actions" class="actions">
134 <i class="fas fa-edit" title="Modifica" onclick="editOccurrence('{{ .ID }}')"></i>
135 <i class="fas fa-trash-alt" title="Elimina" onclick="deleteOccurrence('{{ .ID }}')"></i>
136 </td>
137 </tr>
138 {{ end }}
139 <tr id="occurrence-none" class="hidden">
140 <td colspan="6">Nessuna ricorrenza.</td>
141 </tr>
142 </table>
143 <div style="margin-top: 10px; text-align: center;">
144 <button id="add-row-button" class="cute-button" onclick="addNewOccurrenceRow()">
145 <i class="fas fa-plus"></i> Aggiungi</button>
146 <button id="save-row-button" class="cute-button hidden" onclick="saveOccurrence('0')">
147 <i class="fas fa-save"></i> Salva
148 </button>
149 <button id="cancel-row-button" class="cute-button cute-button-red hidden" onclick="cancelNewOccurrence()">
150 <i class="fas fa-times"></i> Annulla
151 </button>
152 </div>
153
154 <script>
155 const hiddenClass = 'hidden';
156 const addButton = document.getElementById('add-row-button');
157 const saveButton = document.getElementById('save-row-button');
158 const cancelButton = document.getElementById('cancel-row-button');
159 const mainTable = document.getElementById('main-table');
160 const noneRow = document.getElementById('occurrence-none');
161
162 const dataError = 'Controlla che i dati (e le date) siano corretti.';
163
164 let currentNext = null;
165
166 function updateRowDisplay() {
167 const rows = mainTable.getElementsByTagName('tr');
168 const l = rows.length
169 if (l === 2) {
170 noneRow.classList.remove(hiddenClass);
171 return;
172 }
173 noneRow.classList.add(hiddenClass);
174 findNextOccurrence();
175 }
176
177 function deleteOccurrence(id) {
178 if (confirm('Sei sicuro di voler eliminare questa ricorrenza?')) {
179 fetch(`/occurrences/${id}`, {
180 method: 'DELETE'
181 })
182 .then(response => {
183 if (!response.ok) {
184 console.error('Error:', response.status);
185 alert('Eliminazione fallita.');
186 return;
187 }
188 const deletedRow = document.getElementById(`occurrence-${id}`);
189 deletedRow.parentElement.removeChild(deletedRow);
190 updateRowDisplay();
191 })
192 .catch(error => {
193 console.error('Error:', error);
194 alert(dataError);
195 });
196 }
197 }
198
199 function padNumber(input, n=2) {
200 return Math.max(1, Math.min(input, 12)).toString().padStart(n, '0');
201 }
202
203 function createRow(id, name, description, day, month, notify, notified) {
204 return `
205 <td data-field="name">${name}</td>
206 <td data-field="description">${description}</td>
207 <td data-field="date">${padNumber(day)}/${padNumber(month)}</td>
208 <td data-field="notify"><input type="checkbox" ${notify ? 'checked' : ''} disabled></td>
209 <td data-field="notified"><input type="checkbox" ${notified ? 'checked' : ''} disabled></td>
210 <td data-field="actions" class="actions">
211 <i class="fas fa-edit" title="Edit" onclick="editOccurrence(${id})"></i>
212 <i class="fas fa-trash-alt" title="Delete" onclick="deleteOccurrence(${id})"></i>
213 </td>
214 `;
215 }
216
217 function createInputFields(id, name, description, day, month, notify, notified, isNew) {
218 return `
219 <td><input class="big-input" type="text" value="${name || ''}" id="name-${id}"></td>
220 <td><input class="big-input" type="text" value="${description || ''}" id="description-${id}"></td>
221 <td class="date-inputs"><input type="number" value="${day || ''}" id="day-${id}" class="small-input" min="1" max="31" onchange="this.value = padNumber(this.value);"> /<input type="number" value="${month || ''}" id="month-${id}" class="small-input" min="1" max="12" onchange="this.value = padNumber(this.value);"></td>
222 <td><input type="checkbox" id="notify-${id}" ${notify ? 'checked' : ''}></td>
223 <td><input type="checkbox" id="notified-${id}" ${notified ? 'checked' : 'disabled'}></td>
224 <td class="actions">
225 ${isNew ? '' : `
226 <i class="fas fa-save" title="Save" onclick="saveOccurrence(${id})"></i>
227 <i class="fas fa-times" title="Cancel" onclick="cancelEdit(${id}, '${name}', '${description}', ${day}, ${month}, ${notify})"></i>
228 `}
229 </td>
230 `;
231 }
232
233 function editOccurrence(id) {
234 const row = document.getElementById(`occurrence-${id}`);
235 const cells = row.getElementsByTagName('td');
236
237 const name = cells[0].innerText;
238 const description = cells[1].innerText;
239 const [day, month] = cells[2].innerText.split('/');
240 const notify = cells[3].getElementsByTagName('input')[0].checked;
241 const notified = cells[4].getElementsByTagName('input')[0].checked;
242
243 row.innerHTML = createInputFields(id, name, description, day, month, notify, notified, false);
244 }
245
246 function cancelEdit(id, name, description, day, month, notify) {
247 const row = document.getElementById(`occurrence-${id}`);
248 row.innerHTML = createRow(id, name, description, day, month, notify);
249 }
250
251 function saveOccurrence(id) {
252 const name = document.getElementById(`name-${id}`).value;
253 const description = document.getElementById(`description-${id}`).value;
254 const day = parseInt(document.getElementById(`day-${id}`).value);
255 const month = parseInt(document.getElementById(`month-${id}`).value);
256 const notify = document.getElementById(`notify-${id}`).checked;
257 const notified = document.getElementById(`notified-${id}`).checked;
258
259 const isNew = id === '0';
260 const updatedData = {
261 id: isNew ? undefined : id,
262 name: name,
263 description: description,
264 month: month,
265 day: day,
266 notify: notify,
267 notified: notified
268 };
269
270 fetch('/occurrences', {
271 method: 'POST',
272 headers: {
273 'Content-Type': 'application/json'
274 },
275 body: JSON.stringify(updatedData)
276 })
277 .then(response => {
278 if (!response.ok) {
279 console.error('Error:', response.status);
280 alert('Controlla che i campi siano validi.');
281 return;
282 }
283 if (isNew) {
284 cancelNewOccurrence();
285 mainTable.insertRow(-1).id = `occurrence-${id}`
286 }
287 updateRow(`occurrence-${id}`, response);
288 })
289 .catch(error => {
290 console.error('Error:', error);
291 alert(dataError);
292 });
293 }
294
295 function addNewOccurrenceRow() {
296 const newRow = mainTable.insertRow(-1);
297 newRow.id = 'new-occurrence';
298 newRow.innerHTML = createInputFields('0', '', '', '', '', true, false, true);
299
300 hideAddButton();
301 updateRowDisplay();
302 }
303 function hideAddButton() {
304 addButton.classList.add(hiddenClass);
305 saveButton.classList.remove(hiddenClass);
306 cancelButton.classList.remove(hiddenClass);
307 }
308
309 function showAddButton() {
310 addButton.classList.remove(hiddenClass);
311 saveButton.classList.add(hiddenClass);
312 cancelButton.classList.add(hiddenClass);
313 }
314
315 function updateRow(rowElementId, response) {
316 const newRow = document.getElementById(rowElementId);
317 response.json().then((res) => {
318 newRow.id = `occurrence-${res.id}`;
319 newRow.innerHTML = createRow(res.id, res.name, res.description, res.day, res.month, res.notify, res.notified);
320 updateRowDisplay()
321 });
322 }
323
324 function cancelNewOccurrence() {
325 const newRow = document.getElementById('new-occurrence');
326 newRow.parentNode.removeChild(newRow);
327 showAddButton();
328 updateRowDisplay();
329 }
330
331 function findNextOccurrence() {
332 if (currentNext !== null) {
333 currentNext.classList.remove('next');
334 }
335 const now = new Date();
336 const occurrenceRows = Array.from(mainTable.querySelectorAll("tr[id]:not(#occurrence-none):not(#new-occurrence)"));
337 const occurrences = occurrenceRows.map((row) => {
338 const tds = Array.from(row.getElementsByTagName('td'));
339
340 const id = row.id.split('-')[1];
341
342 const dateString = tds.find((td) => td.getAttribute('data-field') === 'date').innerText;
343 const [day, month] = dateString.split('/').map((x) => Number(x));
344 const date = new Date(now.getFullYear(), month - 1, day, 23, 59, 59);
345
346 return {id, date}
347 });
348
349 const deltas = occurrences.map((x) => ({...x, distance: x.date - now})).filter((x) => x.distance > 0);
350 if (deltas.length == 0) return;
351
352 const distances = deltas.map((x) => x.distance);
353 const minDistance = Math.min(...distances);
354 const minDelta = deltas.find((x) => x.distance == minDistance);
355
356 currentNext = occurrenceRows.find((row) => row.id === `occurrence-${minDelta.id}`);
357 currentNext.classList.add('next');
358 }
359
360 updateRowDisplay();
361 </script>
362
363</body>
364
365</html>