Il problema
I pacchetti ore prepagati dei miei clienti finivano gestiti su fogli Excel o via email. Il risultato? Il cliente non sapeva mai quante ore gli restavano, gli aggiornamenti erano manuali e gli errori frequenti.
Mi serviva qualcosa di semplice: un pannello dove io registro le lavorazioni e il cliente controlla il suo saldo in autonomia, senza account e senza password.

Come funziona
L’app è volutamente minimale: tre file PHP, un database MySQL con due tabelle, nessun framework.
clients (id, company_name, contact_name, email, total_hours_purchased)
time_entries (id, client_id, hours, website, date, description)
La distinzione tra admin e cliente è gestita in un unico file: se sei loggato come admin vedi tutti i clienti; se accedi tramite un link univoco (?client=c_abc123) vedi solo i tuoi dati, senza bisogno di fare login.
Cosa fa
Lato admin
- Card per ogni cliente con progress bar che diventa rossa oltre il 90% di utilizzo
- Aggiunta clienti e registrazione lavorazioni (sito, ore, data, descrizione)
- Modifica inline del pacchetto ore: click → modifica → Enter per salvare
- Link univoco per ogni cliente, copiabile in un click
- Eliminazione cliente con cancellazione automatica di tutte le lavorazioni
Lato cliente
- Accesso diretto via link, senza login
- Ore acquistate, consumate e residue sempre in evidenza
- Tabella con lo storico completo delle lavorazioni
- Grafico a ciambella con la ripartizione ore per sito web
Sicurezza
Ho rafforzato ogni punto di ingresso dell’applicazione:
- CSRF — token random a 32 byte su ogni form, verificato con confronto timing-safe
- Session fixation — rigenerazione ID sessione al login e al logout
- Input validation — regex su tutti gli ID, validazione email e date con funzioni native PHP
- Prepared statements — nessuna concatenazione di input utente nelle query
- ID opachi — gli ID cliente sono stringhe random, non sequenze prevedibili
- Auth gate — ogni azione admin è protetta con controllo sessione e risposta 403 immediata
Un bug trovato in code review: il cast (int) sugli ID stringa li convertiva a 0, bloccando l’accesso a tutti i clienti. Risolto con una funzione di validazione centralizzata basata su regex.
Interfaccia
Dark mode con Tailwind CSS e componenti glass-morphism. Card con hover che mostra le azioni, modali con backdrop blur, progress bar animate e editing inline riservato all’admin.
