Codice commentato
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gestione Ore e Costi - Didattica</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
/* --- RESET E IMPOSTAZIONI GENERALI --- */
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { background-color: #f4f6f7; padding: 20px; }
/* --- NAVIGAZIONE --- */
.top-nav { margin-bottom: 20px; }
.top-nav .btn-home {
text-decoration: none;
background-color: #3498db;
color: white;
padding: 10px 20px;
border-radius: 5px;
transition: background 0.3s;
display: inline-block;
font-weight: bold;
}
.top-nav .btn-home:hover { background-color: #2980b9; }
/* --- LAYOUT PRINCIPALE --- */
.container { max-width: 1000px; margin: 0 auto; background: white; padding: 25px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
h1 { color: #2c3e50; margin-bottom: 25px; text-align: center; border-bottom: 2px solid #ddd; padding-bottom: 15px; }
/* --- SEZIONE INPUT (CARD) --- */
.card { background: #fff; border: 1px solid #ddd; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.card h3 { margin-bottom: 15px; color: #2c3e50; }
/* Grid per allineare i campi di input in modo responsive */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; align-items: end; }
.form-group { display: flex; flex-direction: column; }
.form-group label { font-size: 0.9em; margin-bottom: 5px; font-weight: bold; }
.form-group input { padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
.form-group input:focus { border-color: #3498db; outline: none; }
/* --- PULSANTI --- */
button { cursor: pointer; padding: 10px 15px; border: none; border-radius: 4px; color: white; font-weight: bold; transition: opacity 0.2s; font-size: 14px; }
button:hover { opacity: 0.9; }
.btn-add { background-color: #3498db; width: 100%; }
.btn-success { background-color: #27ae60; }
.btn-danger { background-color: #e74c3c; }
/* --- BARRA DI RICERCA E FILTRI --- */
.controls-bar { display: flex; justify-content: space-between; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; }
.filter-box { flex-grow: 1; position: relative; }
.filter-box input { width: 100%; max-width: 300px; padding: 10px 10px 10px 35px; border: 1px solid #ddd; border-radius: 20px; }
.filter-box i { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #999; }
/* --- TABELLA DATI --- */
.table-responsive { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
thead { background-color: #2c3e50; color: white; }
th { padding: 12px; text-align: left; }
tbody tr { border-bottom: 1px solid #ddd; }
tbody tr:nth-child(even) { background-color: #f9f9f9; }
tbody tr:hover { background-color: #f1f1f1; }
td { padding: 10px; }
/* --- FOOTER PER IL CALCOLO DEI TOTALI --- */
.summary-footer { display: flex; justify-content: flex-end; gap: 20px; padding-top: 20px; border-top: 2px solid #2c3e50; }
.summary-item { font-size: 1.1em; }
.summary-item .amount { font-weight: bold; font-size: 1.3em; margin-left: 10px; }
.summary-item.highlight .amount { color: #3498db; }
</style>
</head>
<body>
<nav class="top-nav">
<a href="index.html" class="btn-home"><i class="fas fa-home"></i> Torna alla Home</a>
</nav>
<div class="container">
<h1><i class="fas fa-table"></i> Registro Attività e Costi</h1>
<div class="card input-section">
<h3>Inserisci Nuova Voce</h3>
<div class="form-grid">
<div class="form-group">
<label>Data</label>
<input type="date" id="inputData">
</div>
<div class="form-group">
<label>Operazione</label>
<input type="text" id="inputOperazione" placeholder="Es. Manutenzione">
</div>
<div class="form-group">
<label>Ditta (Nominativo)</label>
<input type="text" id="inputDitta" placeholder="Es. Rossi S.r.l.">
</div>
<div class="form-group">
<label>Ore</label>
<input type="number" id="inputOre" step="0.5" placeholder="0.0">
</div>
<div class="form-group">
<label>Costo Orario (€)</label>
<input type="number" id="inputCosto" step="0.01" placeholder="0.00" onkeyup="checkInvio(event)">
</div>
<div class="form-group button-group">
<button type="button" onclick="aggiungiRiga()" class="btn-add"><i class="fas fa-plus"></i> Aggiungi</button>
</div>
</div>
</div>
<div class="controls-bar">
<div class="filter-box">
<i class="fas fa-search"></i>
<input type="text" id="filtroDitta" onkeyup="filtraTabella()" placeholder="Filtra per Ditta/Nominativo...">
</div>
<div class="actions-box">
<button type="button" onclick="svuotaTabella()" class="btn-danger">Svuota Tabella</button>
<button type="button" onclick="esportaCSV()" class="btn-success"><i class="fas fa-file-csv"></i> Scarica CSV</button>
</div>
</div>
<div class="table-responsive">
<table id="tabellaDati">
<thead>
<tr>
<th>Data</th>
<th>Operazione</th>
<th>Ditta</th>
<th>Ore</th>
<th>Costo Orario</th>
<th>Totale (€)</th>
<th>Azioni</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="summary-footer">
<div class="summary-item">
<span>Totale Generale:</span>
<span id="totaleGenerale" class="amount">0,00 €</span>
</div>
<div class="summary-item highlight">
<span>Totale Filtrato:</span>
<span id="totaleFiltrato" class="amount">0,00 €</span>
</div>
</div>
</div>
<script>
/**
* LOGICA DI GESTIONE DATI
* Carica l'array 'registroLavori' dal localStorage o crea un array vuoto se non esiste.
*/
let elencoDati = JSON.parse(localStorage.getItem('registroLavori')) || [];
// Al caricamento della pagina: disegna la tabella e imposta la data odierna nell'input
document.addEventListener('DOMContentLoaded', () => {
renderizzaTabella();
document.getElementById('inputData').valueAsDate = new Date();
});
// Funzione per salvare l'elenco nel browser in formato stringa JSON
function salvaDati() {
localStorage.setItem('registroLavori', JSON.stringify(elencoDati));
}
// Permette l'invio dei dati premendo il tasto "Invio" sulla tastiera
function checkInvio(event) {
if (event.key === "Enter") aggiungiRiga();
}
// Recupera i valori dai campi input e crea un nuovo oggetto riga
function aggiungiRiga() {
const data = document.getElementById("inputData").value;
const operazione = document.getElementById("inputOperazione").value;
const ditta = document.getElementById("inputDitta").value;
const ore = parseFloat(document.getElementById("inputOre").value);
const costo = parseFloat(document.getElementById("inputCosto").value);
// Validazione: controlla che tutti i campi siano compilati
if (!data || !operazione || !ditta || isNaN(ore) || isNaN(costo)) {
alert("Compila tutti i campi correttamente!");
return;
}
// Calcolo del costo totale per questa specifica riga
const totaleRiga = ore * costo;
// Creazione oggetto con ID univoco basato sul timestamp
const nuovaRiga = {
id: Date.now(),
data: data,
operazione: operazione,
ditta: ditta,
ore: ore,
costo: costo,
totale: totaleRiga
};
elencoDati.push(nuovaRiga); // Aggiunge all'array
salvaDati(); // Persiste i dati
pulisciInput(); // Reset dei campi input
renderizzaTabella(); // Aggiorna la vista
}
// Svuota i campi input e riporta il focus sul campo "Operazione"
function pulisciInput() {
document.getElementById("inputData").valueAsDate = new Date();
document.getElementById("inputOperazione").value = "";
document.getElementById("inputDitta").value = "";
document.getElementById("inputOre").value = "";
document.getElementById("inputCosto").value = "";
document.getElementById("inputOperazione").focus();
}
/**
* FUNZIONE DI RENDERING (DISEGNO TABELLA)
* Questa funzione svuota la tabella e la ricostruisce in base ai dati e ai filtri attivi.
*/
function renderizzaTabella() {
const tbody = document.querySelector("#tabellaDati tbody");
tbody.innerHTML = ""; // Svuota il corpo della tabella
const filtroTesto = document.getElementById("filtroDitta").value.toLowerCase();
let sommaGenerale = 0;
let sommaFiltrata = 0;
elencoDati.forEach(riga => {
sommaGenerale += riga.totale;
// Controlla se la riga deve essere mostrata (filtro ricerca)
if (riga.ditta.toLowerCase().includes(filtroTesto)) {
sommaFiltrata += riga.totale;
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${formattaData(riga.data)}</td>
<td>${riga.operazione}</td>
<td><strong>${riga.ditta}</strong></td>
<td>${riga.ore}</td>
<td>${formattaValuta(riga.costo)}</td>
<td style="color: green; font-weight:bold;">${formattaValuta(riga.totale)}</td>
<td>
<button onclick="eliminaRiga(${riga.id})" class="btn-danger" style="padding: 5px 10px;">
<i class="fas fa-trash"></i>
</button>
</td>
`;
tbody.appendChild(tr);
}
});
// Aggiorna i valori dei totali nel footer
document.getElementById("totaleGenerale").textContent = formattaValuta(sommaGenerale);
document.getElementById("totaleFiltrato").textContent = formattaValuta(sommaFiltrata);
}
// Rimuove una riga specifica tramite il suo ID univoco
function eliminaRiga(id) {
if(confirm("Eliminare questa riga?")) {
elencoDati = elencoDati.filter(r => r.id !== id);
salvaDati();
renderizzaTabella();
}
}
// Cancella l'intero archivio dal localStorage
function svuotaTabella() {
if (confirm("ATTENZIONE: Cancellare tutto il database locale?")) {
elencoDati = [];
localStorage.removeItem('registroLavori');
renderizzaTabella();
}
}
// Funzione di trigger per la ricerca istantanea
function filtraTabella() { renderizzaTabella(); }
/**
* ESPORTAZIONE CSV
* Trasforma i dati in un file testuale separato da punto e virgola.
*/
function esportaCSV() {
if (elencoDati.length === 0) { alert("Nessun dato da esportare."); return; }
const filtroTesto = document.getElementById("filtroDitta").value.toLowerCase();
const datiDaEsportare = elencoDati.filter(r => r.ditta.toLowerCase().includes(filtroTesto));
// Inizio costruzione file CSV (con BOM per Excel)
let csvContent = "data:text/csv;charset=utf-8,\uFEFF";
csvContent += "Data;Operazione;Ditta;Ore;Costo Orario;Totale\n";
datiDaEsportare.forEach(riga => {
// Formattazione per Excel italiano (virgola per i decimali)
const costoIt = riga.costo.toString().replace('.', ',');
const totaleIt = riga.totale.toString().replace('.', ',');
const row = `${riga.data};${riga.operazione};${riga.ditta};${riga.ore};${costoIt};${totaleIt}`;
csvContent += row + "\n";
});
// Creazione dinamica del nome file con timestamp (es. Report_2026_01_21.csv)
const now = new Date();
const anno = now.getFullYear();
const mese = String(now.getMonth() + 1).padStart(2, '0');
const giorno = String(now.getDate()).padStart(2, '0');
const ora = String(now.getHours()).padStart(2, '0');
const minuti = String(now.getMinutes()).padStart(2, '0');
const nomeFile = `Report_${anno}_${mese}_${giorno}_${ora}_${minuti}.csv`;
// Creazione link di download "invisibile"
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", nomeFile);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Formatta numeri in formato valuta Euro (€)
function formattaValuta(numero) {
return new Intl.NumberFormat('it-IT', { style: 'currency', currency: 'EUR' }).format(numero);
}
// Formatta la stringa data nel formato italiano (GG/MM/AAAA)
function formattaData(dataString) {
const data = new Date(dataString);
return data.toLocaleDateString('it-IT');
}
</script>
</body>
</html>