first upload
This commit is contained in:
56
.htaccess.example
Normal file
56
.htaccess.example
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* File di esempio .htaccess
|
||||||
|
* Territory Manager
|
||||||
|
*
|
||||||
|
* Rinomina questo file in .htaccess per usarlo con Apache
|
||||||
|
*/
|
||||||
|
|
||||||
|
# Abilita RewriteEngine
|
||||||
|
# RewriteEngine On
|
||||||
|
# RewriteBase /
|
||||||
|
|
||||||
|
# Redirect da HTTP a HTTPS (decommentare in produzione)
|
||||||
|
# RewriteCond %{HTTPS} off
|
||||||
|
# RewriteRule ^(.*)$ https://%{HTTP_HOST%{REQUEST_URI} [L,R=301]
|
||||||
|
|
||||||
|
# Protezione file sensibili
|
||||||
|
<FilesMatch "^(config\.php|db\.php|database\.sql)$">
|
||||||
|
Order allow,deny
|
||||||
|
Deny from all
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# Impostazioni PHP
|
||||||
|
php_value upload_max_filesize 10M
|
||||||
|
php_value post_max_size 10M
|
||||||
|
php_value max_execution_time 300
|
||||||
|
php_value session.gc_maxlifetime 28800
|
||||||
|
|
||||||
|
# Protezione directory uploads
|
||||||
|
<Directory "uploads">
|
||||||
|
Options -Indexes
|
||||||
|
php_flag engine off
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# Compressione GZIP
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Cache statica
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresByType image/jpg "access plus 1 year"
|
||||||
|
ExpiresByType image/jpeg "access plus 1 year"
|
||||||
|
ExpiresByType image/gif "access plus 1 year"
|
||||||
|
ExpiresByType image/png "access plus 1 year"
|
||||||
|
ExpiresByType text/css "access plus 1 month"
|
||||||
|
ExpiresByType application/javascript "access plus 1 month"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Sicurezza Headers
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
Header set X-Content-Type-Options "nosniff"
|
||||||
|
Header set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header set X-XSS-Protection "1; mode=block"
|
||||||
|
</IfModule>
|
||||||
308
README.md
Normal file
308
README.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# Territory Manager
|
||||||
|
|
||||||
|
Sistema di gestione territori per cartoline della città. Web application PHP semplice, moderna e facilmente manutenibile.
|
||||||
|
|
||||||
|
## Caratteristiche
|
||||||
|
|
||||||
|
- ✅ Gestione completa territori (CRUD)
|
||||||
|
- ✅ Sistema di assegnazioni con tracking
|
||||||
|
- ✅ Link temporanei per condivisione territori
|
||||||
|
- ✅ Dashboard con liste automatiche:
|
||||||
|
- Territori da assegnare
|
||||||
|
- Territori prioritari
|
||||||
|
- Territori da riconsegnare
|
||||||
|
- ✅ Statistiche in tempo reale
|
||||||
|
- ✅ Media percorrenza mensile e annuale
|
||||||
|
- ✅ Upload immagini piantine territori
|
||||||
|
- ✅ Export PDF delle liste
|
||||||
|
- ✅ Sistema di autenticazione
|
||||||
|
- ✅ Pannello amministrazione
|
||||||
|
- ✅ Design moderno e responsive
|
||||||
|
|
||||||
|
## Requisiti
|
||||||
|
|
||||||
|
- PHP 7.4 o superiore
|
||||||
|
- MySQL 5.7 o superiore
|
||||||
|
- Web Server (Apache/Nginx)
|
||||||
|
- Estensioni PHP:
|
||||||
|
- PDO
|
||||||
|
- pdo_mysql
|
||||||
|
- GD (per gestione immagini)
|
||||||
|
|
||||||
|
## Installazione
|
||||||
|
|
||||||
|
### 1. Copia i file
|
||||||
|
|
||||||
|
Copia tutti i file nella cartella del tuo web server (es. `htdocs`, `www`, `public_html`).
|
||||||
|
|
||||||
|
### 2. Crea il database
|
||||||
|
|
||||||
|
Accedi a phpMyAdmin o dalla riga di comando MySQL:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE territory_manager CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
Importa lo schema del database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysql -u root -p territory_manager < database.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Oppure copia il contenuto di `database.sql` ed eseguilo in phpMyAdmin.
|
||||||
|
|
||||||
|
### 3. Configura la connessione
|
||||||
|
|
||||||
|
Modifica il file `config.php` con i tuoi dati:
|
||||||
|
|
||||||
|
```php
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_NAME', 'territory_manager');
|
||||||
|
define('DB_USER', 'tuo_utente');
|
||||||
|
define('DB_PASS', 'tua_password');
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Crea la cartella uploads
|
||||||
|
|
||||||
|
Assicurati che la cartella `uploads` esista e abbia i permessi corretti:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir uploads
|
||||||
|
chmod 755 uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Accedi al sistema
|
||||||
|
|
||||||
|
Apri il browser e vai all'URL della tua installazione:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://tuosito.com/territory-manager/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Credenziali di default:**
|
||||||
|
- Username: `admin`
|
||||||
|
- Password: `admin123`
|
||||||
|
|
||||||
|
⚠️ **IMPORTANTE**: Cambia immediatamente la password dopo il primo accesso!
|
||||||
|
|
||||||
|
## Struttura File
|
||||||
|
|
||||||
|
```
|
||||||
|
territory-manager/
|
||||||
|
├── config.php # Configurazione applicazione
|
||||||
|
├── db.php # Gestione database
|
||||||
|
├── functions.php # Funzioni helper
|
||||||
|
├── database.sql # Schema database
|
||||||
|
├── index.php # Dashboard principale
|
||||||
|
├── login.php # Pagina login
|
||||||
|
├── logout.php # Logout
|
||||||
|
├── header.php # Template header
|
||||||
|
├── footer.php # Template footer
|
||||||
|
├── territories.php # Gestione territori
|
||||||
|
├── assignments.php # Gestione assegnazioni
|
||||||
|
├── statistics.php # Statistiche
|
||||||
|
├── settings.php # Impostazioni (admin)
|
||||||
|
├── export_pdf.php # Export PDF
|
||||||
|
├── view_territory.php # Visualizzazione pubblica territorio
|
||||||
|
├── style.css # Stili CSS
|
||||||
|
├── script.js # JavaScript
|
||||||
|
├── uploads/ # Cartella immagini (da creare)
|
||||||
|
└── README.md # Questo file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilizzo
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
|
||||||
|
La dashboard mostra tre liste principali:
|
||||||
|
- **Territori da Assegnare**: Territori in reparto da più di X giorni
|
||||||
|
- **Territori Prioritari**: Territori in reparto da più di Y giorni
|
||||||
|
- **Territori da Riconsegnare**: Territori assegnati da più di Z giorni
|
||||||
|
|
||||||
|
I tempi sono configurabili nelle Impostazioni.
|
||||||
|
|
||||||
|
### Gestione Territori
|
||||||
|
|
||||||
|
1. Vai su "Territori" nel menu
|
||||||
|
2. Clicca "+ Nuovo Territorio"
|
||||||
|
3. Compila i campi:
|
||||||
|
- Numero territorio
|
||||||
|
- Zona
|
||||||
|
- Tipologia
|
||||||
|
- Carica piantina (opzionale)
|
||||||
|
- Note (opzionale)
|
||||||
|
|
||||||
|
### Assegnazione Territorio
|
||||||
|
|
||||||
|
1. Dalla dashboard, clicca "Assegna" su un territorio disponibile
|
||||||
|
2. Oppure vai su "Assegnazioni" → "+ Nuova Assegnazione"
|
||||||
|
3. Compila:
|
||||||
|
- Seleziona territorio
|
||||||
|
- Nome assegnatario
|
||||||
|
- Data assegnazione
|
||||||
|
- Priorità (opzionale)
|
||||||
|
4. Viene generato automaticamente un link temporaneo
|
||||||
|
|
||||||
|
### Link Temporanei
|
||||||
|
|
||||||
|
Quando assegni un territorio, viene creato un link unico che permette di visualizzare il territorio senza login.
|
||||||
|
|
||||||
|
Il link scade dopo X giorni (configurabile) e mostra:
|
||||||
|
- Dettagli territorio
|
||||||
|
- Piantina
|
||||||
|
- Informazioni assegnazione
|
||||||
|
|
||||||
|
### Riconsegna
|
||||||
|
|
||||||
|
1. Vai su "Assegnazioni"
|
||||||
|
2. Clicca "Riconsegna" sul territorio da restituire
|
||||||
|
3. Conferma la data di restituzione
|
||||||
|
|
||||||
|
### Statistiche
|
||||||
|
|
||||||
|
La pagina Statistiche mostra:
|
||||||
|
- Totale territori
|
||||||
|
- Territori assegnati/disponibili
|
||||||
|
- Attività ultimi 7 giorni
|
||||||
|
- Media percorrenza mensile
|
||||||
|
- Media percorrenza annuale
|
||||||
|
- Statistiche per zona
|
||||||
|
- Top 10 territori più assegnati
|
||||||
|
- Top 10 persone
|
||||||
|
- Territori mai assegnati
|
||||||
|
|
||||||
|
### Export PDF
|
||||||
|
|
||||||
|
Puoi esportare in PDF:
|
||||||
|
- Lista territori da assegnare
|
||||||
|
- Lista territori prioritari
|
||||||
|
- Lista territori da riconsegnare
|
||||||
|
|
||||||
|
Clicca sul pulsante "📄 Export PDF" nella dashboard.
|
||||||
|
|
||||||
|
### Impostazioni (Solo Admin)
|
||||||
|
|
||||||
|
Configura:
|
||||||
|
- **Giorni validità link**: Durata dei link temporanei
|
||||||
|
- **Giorni territori da assegnare**: Soglia per "da assegnare"
|
||||||
|
- **Giorni territori prioritari**: Soglia per "prioritari"
|
||||||
|
- **Giorni da riconsegnare**: Soglia per "da riconsegnare"
|
||||||
|
|
||||||
|
Gestisci anche:
|
||||||
|
- Cambio password
|
||||||
|
- Utenti del sistema
|
||||||
|
|
||||||
|
## Manutenzione
|
||||||
|
|
||||||
|
### Backup Database
|
||||||
|
|
||||||
|
Esegui regolarmente backup del database:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mysqldump -u root -p territory_manager > backup_$(date +%Y%m%d).sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup File
|
||||||
|
|
||||||
|
Copia periodicamente la cartella `uploads` con le immagini.
|
||||||
|
|
||||||
|
### Pulizia Link Scaduti
|
||||||
|
|
||||||
|
I link scaduti vengono controllati automaticamente. Non è necessaria pulizia manuale.
|
||||||
|
|
||||||
|
### Log Errori
|
||||||
|
|
||||||
|
Gli errori PHP vengono salvati nel log del server. In produzione, disattiva la visualizzazione errori in `config.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
error_reporting(0);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Personalizzazione
|
||||||
|
|
||||||
|
### Modificare i Colori
|
||||||
|
|
||||||
|
Modifica le variabili CSS in `style.css`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--primary: #3498db; /* Colore principale */
|
||||||
|
--success: #27ae60; /* Verde */
|
||||||
|
--danger: #e74c3c; /* Rosso */
|
||||||
|
--warning: #f39c12; /* Arancione */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aggiungere Campi ai Territori
|
||||||
|
|
||||||
|
1. Modifica la tabella `territories` in MySQL:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE territories ADD COLUMN nuovo_campo VARCHAR(100);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Aggiungi il campo nei form in `territories.php`
|
||||||
|
|
||||||
|
3. Aggiorna le query di INSERT/UPDATE
|
||||||
|
|
||||||
|
### Modificare Template Email
|
||||||
|
|
||||||
|
Attualmente non ci sono notifiche email. Per aggiungerle:
|
||||||
|
|
||||||
|
1. Installa PHPMailer o usa `mail()`
|
||||||
|
2. Crea funzioni in `functions.php`
|
||||||
|
3. Chiama le funzioni dopo assegnazioni/riconsegne
|
||||||
|
|
||||||
|
## Risoluzione Problemi
|
||||||
|
|
||||||
|
### Errore connessione database
|
||||||
|
- Verifica credenziali in `config.php`
|
||||||
|
- Controlla che MySQL sia avviato
|
||||||
|
- Verifica che il database esista
|
||||||
|
|
||||||
|
### Upload immagini non funziona
|
||||||
|
- Controlla permessi cartella `uploads` (755)
|
||||||
|
- Verifica `upload_max_filesize` in `php.ini`
|
||||||
|
- Controlla `post_max_size` in `php.ini`
|
||||||
|
|
||||||
|
### Sessione scade troppo presto
|
||||||
|
- Modifica `SESSION_LIFETIME` in `config.php`
|
||||||
|
- Controlla `session.gc_maxlifetime` in `php.ini`
|
||||||
|
|
||||||
|
### Link temporanei non funzionano
|
||||||
|
- Verifica che `HTTP_HOST` sia configurato correttamente
|
||||||
|
- Controlla che `view_territory.php` sia accessibile
|
||||||
|
|
||||||
|
## Sicurezza
|
||||||
|
|
||||||
|
- ✅ Password criptate con bcrypt
|
||||||
|
- ✅ Protezione SQL injection (prepared statements)
|
||||||
|
- ✅ Sanitizzazione input
|
||||||
|
- ✅ Validazione file upload
|
||||||
|
- ✅ Controllo sessioni
|
||||||
|
- ✅ Token CSRF non implementato (da aggiungere se necessario)
|
||||||
|
|
||||||
|
**Raccomandazioni:**
|
||||||
|
1. Cambia password admin dopo installazione
|
||||||
|
2. Usa HTTPS in produzione
|
||||||
|
3. Limita accesso alla cartella uploads
|
||||||
|
4. Aggiorna PHP regolarmente
|
||||||
|
|
||||||
|
## Supporto e Contributi
|
||||||
|
|
||||||
|
Questo è un progetto semplice e facilmente estendibile. Puoi:
|
||||||
|
- Modificare il codice secondo le tue esigenze
|
||||||
|
- Aggiungere nuove funzionalità
|
||||||
|
- Migliorare la grafica
|
||||||
|
|
||||||
|
## Licenza
|
||||||
|
|
||||||
|
Questo progetto è fornito "così com'è" per uso personale e interno.
|
||||||
|
|
||||||
|
## Versione
|
||||||
|
|
||||||
|
**v1.0.0** - 6 dicembre 2025
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Buona gestione dei territori! 🗺️**
|
||||||
170
TODO.md
Normal file
170
TODO.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Territory Manager - To Do e Suggerimenti per Miglioramenti Futuri
|
||||||
|
|
||||||
|
## Funzionalità Base Implementate ✅
|
||||||
|
|
||||||
|
Tutte le funzionalità richieste sono state implementate:
|
||||||
|
- ✅ Sistema di autenticazione
|
||||||
|
- ✅ Gestione territori (CRUD completo)
|
||||||
|
- ✅ Upload immagini piantine
|
||||||
|
- ✅ Assegnazioni con tracking
|
||||||
|
- ✅ Link temporanei configurabili
|
||||||
|
- ✅ Dashboard con 3 liste (da assegnare, prioritari, da riconsegnare)
|
||||||
|
- ✅ Espansione liste e visualizzazione completa
|
||||||
|
- ✅ Export PDF delle liste
|
||||||
|
- ✅ Statistiche tempo reale
|
||||||
|
- ✅ Media percorrenza mensile e annuale
|
||||||
|
- ✅ Design moderno e responsive
|
||||||
|
|
||||||
|
## Suggerimenti per Miglioramenti Futuri
|
||||||
|
|
||||||
|
### 1. Notifiche Email
|
||||||
|
- Inviare email quando un territorio è assegnato
|
||||||
|
- Notificare quando un territorio è da riconsegnare
|
||||||
|
- Reminder automatici
|
||||||
|
|
||||||
|
### 2. Miglioramenti Export PDF
|
||||||
|
- Usare libreria FPDF o TCPDF per veri PDF
|
||||||
|
- Aggiungere grafici alle statistiche
|
||||||
|
- Logo personalizzato nei report
|
||||||
|
|
||||||
|
### 3. Ricerca Avanzata
|
||||||
|
- Filtri multipli combinati
|
||||||
|
- Ricerca full-text nelle note
|
||||||
|
- Storico ricerche
|
||||||
|
|
||||||
|
### 4. Calendario
|
||||||
|
- Vista calendario assegnazioni
|
||||||
|
- Visualizzazione scadenze
|
||||||
|
- Pianificazione futura
|
||||||
|
|
||||||
|
### 5. Note e Commenti
|
||||||
|
- Sistema commenti sulle assegnazioni
|
||||||
|
- Note private per amministratori
|
||||||
|
- Storico modifiche
|
||||||
|
|
||||||
|
### 6. Gestione Permessi
|
||||||
|
- Ruoli utente più granulari
|
||||||
|
- Permessi per zona
|
||||||
|
- Visualizzazione sola lettura
|
||||||
|
|
||||||
|
### 7. API REST
|
||||||
|
- API per integrazioni esterne
|
||||||
|
- App mobile companion
|
||||||
|
- Sincronizzazione dati
|
||||||
|
|
||||||
|
### 8. Dashboard Grafici
|
||||||
|
- Grafici statistiche con Chart.js
|
||||||
|
- Heatmap assegnazioni
|
||||||
|
- Timeline territori
|
||||||
|
|
||||||
|
### 9. Backup Automatico
|
||||||
|
- Backup database automatico
|
||||||
|
- Export/Import dati
|
||||||
|
- Restore point
|
||||||
|
|
||||||
|
### 10. Multi-lingua
|
||||||
|
- Interfaccia in più lingue
|
||||||
|
- Configurazione lingua per utente
|
||||||
|
|
||||||
|
## Note per Programmatori Neofiti
|
||||||
|
|
||||||
|
### Come Aggiungere un Campo a Territori
|
||||||
|
|
||||||
|
1. **Database**: Aggiungi colonna in MySQL
|
||||||
|
```sql
|
||||||
|
ALTER TABLE territories ADD COLUMN descrizione_estesa TEXT;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Form**: Modifica `territories.php`, sezione form:
|
||||||
|
```php
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="descrizione_estesa">Descrizione</label>
|
||||||
|
<textarea id="descrizione_estesa" name="descrizione_estesa"
|
||||||
|
class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Salvataggio**: Modifica query INSERT/UPDATE:
|
||||||
|
```php
|
||||||
|
$db->query(
|
||||||
|
"INSERT INTO territories (numero, zona, tipologia, descrizione_estesa)
|
||||||
|
VALUES (?, ?, ?, ?)",
|
||||||
|
[$numero, $zona, $tipologia, $descrizione_estesa]
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Come Modificare i Colori
|
||||||
|
|
||||||
|
Apri `style.css` e modifica le variabili:
|
||||||
|
```css
|
||||||
|
:root {
|
||||||
|
--primary: #3498db; /* Cambia questo valore */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Come Aggiungere una Pagina
|
||||||
|
|
||||||
|
1. Crea file `mia_pagina.php`
|
||||||
|
2. Includi header e footer:
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$page_title = 'Mia Pagina';
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Il tuo contenuto qui -->
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Aggiungi link nel menu in `header.php`
|
||||||
|
|
||||||
|
## File Importanti da Conoscere
|
||||||
|
|
||||||
|
- `config.php`: Configurazioni generali
|
||||||
|
- `db.php`: Connessione database e query helper
|
||||||
|
- `functions.php`: Funzioni utili (auth, sanitize, date)
|
||||||
|
- `header.php` / `footer.php`: Template layout
|
||||||
|
- `style.css`: Tutti gli stili
|
||||||
|
- `database.sql`: Schema database
|
||||||
|
|
||||||
|
## Comandi MySQL Utili
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Vedere tutti i territori
|
||||||
|
SELECT * FROM territories;
|
||||||
|
|
||||||
|
-- Vedere assegnazioni correnti
|
||||||
|
SELECT * FROM assignments WHERE returned_date IS NULL;
|
||||||
|
|
||||||
|
-- Vedere statistiche
|
||||||
|
SELECT COUNT(*) FROM territories;
|
||||||
|
|
||||||
|
-- Reset password admin (dalla riga di comando MySQL)
|
||||||
|
UPDATE users SET password = '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
|
||||||
|
WHERE username = 'admin';
|
||||||
|
-- Password diventa: admin123
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Sempre usa Prepared Statements** per evitare SQL injection
|
||||||
|
2. **Sanitizza l'input** con `sanitize()` prima di visualizzare
|
||||||
|
3. **Controlla i permessi** con `requireLogin()` o `requireAdmin()`
|
||||||
|
4. **Gestisci gli errori** con try-catch
|
||||||
|
5. **Commenta il codice** per capirlo in futuro
|
||||||
|
6. **Fai backup** prima di modifiche importanti
|
||||||
|
|
||||||
|
## Contatti e Supporto
|
||||||
|
|
||||||
|
Per domande o problemi, verifica:
|
||||||
|
1. Log errori PHP
|
||||||
|
2. Console browser (F12)
|
||||||
|
3. Permessi file/cartelle
|
||||||
|
4. Credenziali database
|
||||||
|
|
||||||
|
Buon lavoro! 🚀
|
||||||
454
assignments.php
Normal file
454
assignments.php
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Gestione Assegnazioni
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$page_title = 'Gestione Assegnazioni';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? 'list';
|
||||||
|
$territory_id = $_GET['territory_id'] ?? null;
|
||||||
|
|
||||||
|
// Gestione delle azioni POST
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action'])) {
|
||||||
|
switch ($_POST['action']) {
|
||||||
|
case 'assign':
|
||||||
|
$territory_id = (int)$_POST['territory_id'];
|
||||||
|
$assigned_to = sanitize($_POST['assigned_to']);
|
||||||
|
$assigned_date = $_POST['assigned_date'];
|
||||||
|
$is_priority = isset($_POST['is_priority']) ? 1 : 0;
|
||||||
|
$note = sanitize($_POST['note'] ?? '');
|
||||||
|
|
||||||
|
// Verifica che il territorio non sia già assegnato
|
||||||
|
$existing = $db->fetchOne(
|
||||||
|
"SELECT id FROM assignments WHERE territory_id = ? AND returned_date IS NULL",
|
||||||
|
[$territory_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
setFlashMessage('Questo territorio è già assegnato', 'error');
|
||||||
|
header('Location: assignments.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genera link temporaneo
|
||||||
|
$link_token = bin2hex(random_bytes(32));
|
||||||
|
$link_expiry_days = (int)$db->getConfig('link_expiry_days', 7);
|
||||||
|
$link_expires_at = date('Y-m-d H:i:s', strtotime("+$link_expiry_days days"));
|
||||||
|
|
||||||
|
$result = $db->query(
|
||||||
|
"INSERT INTO assignments (territory_id, assigned_to, assigned_date, is_priority, note, link_token, link_expires_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
[$territory_id, $assigned_to, $assigned_date, $is_priority, $note, $link_token, $link_expires_at]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$assignment_id = $db->getConnection()->lastInsertId();
|
||||||
|
setFlashMessage('Territorio assegnato con successo', 'success');
|
||||||
|
header("Location: assignments.php?action=view&id=$assignment_id");
|
||||||
|
} else {
|
||||||
|
setFlashMessage('Errore durante l\'assegnazione', 'error');
|
||||||
|
header('Location: assignments.php');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'return':
|
||||||
|
$territory_id = (int)$_POST['territory_id'];
|
||||||
|
$returned_date = $_POST['returned_date'];
|
||||||
|
|
||||||
|
$result = $db->query(
|
||||||
|
"UPDATE assignments SET returned_date = ? WHERE territory_id = ? AND returned_date IS NULL",
|
||||||
|
[$returned_date, $territory_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
setFlashMessage('Territorio riconsegnato con successo', 'success');
|
||||||
|
} else {
|
||||||
|
setFlashMessage('Errore durante la riconsegna', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: assignments.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lista assegnazioni
|
||||||
|
if ($action === 'list') {
|
||||||
|
$filter = $_GET['filter'] ?? 'current';
|
||||||
|
|
||||||
|
if ($filter === 'current') {
|
||||||
|
$assignments = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
a.*,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
DATEDIFF(CURDATE(), a.assigned_date) as days_assigned
|
||||||
|
FROM assignments a
|
||||||
|
INNER JOIN territories t ON a.territory_id = t.id
|
||||||
|
WHERE a.returned_date IS NULL
|
||||||
|
ORDER BY a.assigned_date ASC
|
||||||
|
");
|
||||||
|
} else {
|
||||||
|
$assignments = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
a.*,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
DATEDIFF(a.returned_date, a.assigned_date) as days_assigned
|
||||||
|
FROM assignments a
|
||||||
|
INNER JOIN territories t ON a.territory_id = t.id
|
||||||
|
WHERE a.returned_date IS NOT NULL
|
||||||
|
ORDER BY a.returned_date DESC
|
||||||
|
LIMIT 100
|
||||||
|
");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagina per nuova assegnazione
|
||||||
|
if ($action === 'assign') {
|
||||||
|
if ($territory_id) {
|
||||||
|
$territory = $db->fetchOne("SELECT * FROM territories WHERE id = ?", [$territory_id]);
|
||||||
|
if (!$territory) {
|
||||||
|
setFlashMessage('Territorio non trovato', 'error');
|
||||||
|
header('Location: assignments.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Carica tutti i territori disponibili
|
||||||
|
$available_territories = $db->fetchAll("
|
||||||
|
SELECT t.*
|
||||||
|
FROM territories t
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
ORDER BY t.numero ASC
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_priority = isset($_GET['priority']) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagina per riconsegna
|
||||||
|
if ($action === 'return' && $territory_id) {
|
||||||
|
$assignment = $db->fetchOne("
|
||||||
|
SELECT a.*, t.numero, t.zona, t.tipologia
|
||||||
|
FROM assignments a
|
||||||
|
INNER JOIN territories t ON a.territory_id = t.id
|
||||||
|
WHERE a.territory_id = ? AND a.returned_date IS NULL
|
||||||
|
", [$territory_id]);
|
||||||
|
|
||||||
|
if (!$assignment) {
|
||||||
|
setFlashMessage('Assegnazione non trovata', 'error');
|
||||||
|
header('Location: assignments.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visualizzazione dettaglio assegnazione
|
||||||
|
if ($action === 'view') {
|
||||||
|
$assignment_id = $_GET['id'] ?? null;
|
||||||
|
if ($assignment_id) {
|
||||||
|
$assignment = $db->fetchOne("
|
||||||
|
SELECT a.*, t.numero, t.zona, t.tipologia, t.image_path
|
||||||
|
FROM assignments a
|
||||||
|
INNER JOIN territories t ON a.territory_id = t.id
|
||||||
|
WHERE a.id = ?
|
||||||
|
", [$assignment_id]);
|
||||||
|
|
||||||
|
if (!$assignment) {
|
||||||
|
setFlashMessage('Assegnazione non trovata', 'error');
|
||||||
|
header('Location: assignments.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($action === 'list'): ?>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Gestione Assegnazioni</h1>
|
||||||
|
<a href="?action=assign" class="btn btn-primary">+ Nuova Assegnazione</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="tabs">
|
||||||
|
<a href="?filter=current" class="tab <?php echo $filter === 'current' ? 'active' : ''; ?>">
|
||||||
|
Assegnazioni Correnti
|
||||||
|
</a>
|
||||||
|
<a href="?filter=history" class="tab <?php echo $filter === 'history' ? 'active' : ''; ?>">
|
||||||
|
Storico
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($assignments) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Territorio</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>Assegnato a</th>
|
||||||
|
<th>Data Assegnazione</th>
|
||||||
|
<?php if ($filter === 'current'): ?>
|
||||||
|
<th>Giorni</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
<?php else: ?>
|
||||||
|
<th>Data Restituzione</th>
|
||||||
|
<th>Durata</th>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($assignments as $a): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong><?php echo htmlspecialchars($a['numero']); ?></strong>
|
||||||
|
<?php if ($a['is_priority']): ?>
|
||||||
|
<span class="badge badge-danger">Priorità</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo htmlspecialchars($a['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($a['tipologia']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($a['assigned_to']); ?></td>
|
||||||
|
<td><?php echo formatDate($a['assigned_date']); ?></td>
|
||||||
|
<?php if ($filter === 'current'): ?>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-info"><?php echo $a['days_assigned']; ?> giorni</span>
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<a href="?action=view&id=<?php echo $a['id']; ?>" class="btn btn-sm btn-secondary">Dettagli</a>
|
||||||
|
<a href="?action=return&territory_id=<?php echo $a['territory_id']; ?>" class="btn btn-sm btn-primary">Riconsegna</a>
|
||||||
|
</td>
|
||||||
|
<?php else: ?>
|
||||||
|
<td><?php echo formatDate($a['returned_date']); ?></td>
|
||||||
|
<td><?php echo $a['days_assigned']; ?> giorni</td>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">
|
||||||
|
<?php echo $filter === 'current' ? 'Nessuna assegnazione corrente' : 'Nessuno storico disponibile'; ?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($action === 'assign'): ?>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Nuova Assegnazione</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="assign">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="territory_id">Territorio *</label>
|
||||||
|
<?php if ($territory_id): ?>
|
||||||
|
<input type="hidden" name="territory_id" value="<?php echo $territory_id; ?>">
|
||||||
|
<input type="text" class="form-control" readonly
|
||||||
|
value="<?php echo htmlspecialchars($territory['numero'] . ' - ' . $territory['zona']); ?>">
|
||||||
|
<?php else: ?>
|
||||||
|
<select id="territory_id" name="territory_id" required class="form-control">
|
||||||
|
<option value="">Seleziona un territorio</option>
|
||||||
|
<?php foreach ($available_territories as $t): ?>
|
||||||
|
<option value="<?php echo $t['id']; ?>">
|
||||||
|
<?php echo htmlspecialchars($t['numero'] . ' - ' . $t['zona'] . ' (' . $t['tipologia'] . ')'); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="assigned_to">Assegnato a *</label>
|
||||||
|
<input type="text" id="assigned_to" name="assigned_to" required class="form-control"
|
||||||
|
placeholder="Nome della persona">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="assigned_date">Data Assegnazione *</label>
|
||||||
|
<input type="date" id="assigned_date" name="assigned_date" required
|
||||||
|
value="<?php echo date('Y-m-d'); ?>" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" name="is_priority" value="1" <?php echo $is_priority ? 'checked' : ''; ?>>
|
||||||
|
Assegnazione Prioritaria
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="note">Note</label>
|
||||||
|
<textarea id="note" name="note" rows="3" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">Assegna Territorio</button>
|
||||||
|
<a href="assignments.php" class="btn btn-secondary">Annulla</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($action === 'return'): ?>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Riconsegna Territorio</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Territorio:</strong> <?php echo htmlspecialchars($assignment['numero']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Zona:</strong> <?php echo htmlspecialchars($assignment['zona']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Assegnato a:</strong> <?php echo htmlspecialchars($assignment['assigned_to']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Data Assegnazione:</strong> <?php echo formatDate($assignment['assigned_date']); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" style="margin-top: 20px;">
|
||||||
|
<input type="hidden" name="action" value="return">
|
||||||
|
<input type="hidden" name="territory_id" value="<?php echo $territory_id; ?>">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="returned_date">Data Restituzione *</label>
|
||||||
|
<input type="date" id="returned_date" name="returned_date" required
|
||||||
|
value="<?php echo date('Y-m-d'); ?>" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">Conferma Riconsegna</button>
|
||||||
|
<a href="assignments.php" class="btn btn-secondary">Annulla</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($action === 'view'): ?>
|
||||||
|
<?php
|
||||||
|
$base_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") .
|
||||||
|
"://" . $_SERVER['HTTP_NAME'];
|
||||||
|
$share_url = $base_url . "/view_territory.php?token=" . $assignment['link_token'];
|
||||||
|
$is_link_valid = strtotime($assignment['link_expires_at']) > time();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Dettagli Assegnazione</h1>
|
||||||
|
<a href="assignments.php" class="btn btn-secondary">Torna alla Lista</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Informazioni Territorio</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Numero:</strong> <?php echo htmlspecialchars($assignment['numero']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Zona:</strong> <?php echo htmlspecialchars($assignment['zona']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Tipologia:</strong> <?php echo htmlspecialchars($assignment['tipologia']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Assegnato a:</strong> <?php echo htmlspecialchars($assignment['assigned_to']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Data Assegnazione:</strong> <?php echo formatDate($assignment['assigned_date']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Priorità:</strong>
|
||||||
|
<?php if ($assignment['is_priority']): ?>
|
||||||
|
<span class="badge badge-danger">Prioritaria</span>
|
||||||
|
<?php else: ?>
|
||||||
|
Normale
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($assignment['note']): ?>
|
||||||
|
<div class="detail-item" style="margin-top: 15px;">
|
||||||
|
<strong>Note:</strong>
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($assignment['note'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Link Condivisione Territorio</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="help-text">
|
||||||
|
Questo link permette di visualizzare il territorio senza accedere al sistema.
|
||||||
|
<?php if ($is_link_valid): ?>
|
||||||
|
Il link è valido fino al <strong><?php echo formatDateTime($assignment['link_expires_at']); ?></strong>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge badge-danger">Il link è scaduto</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="link-box">
|
||||||
|
<input type="text" id="share_link" value="<?php echo htmlspecialchars($share_url); ?>"
|
||||||
|
class="form-control" readonly>
|
||||||
|
<button onclick="copyLink()" class="btn btn-primary">Copia Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!$is_link_valid): ?>
|
||||||
|
<p class="alert alert-warning" style="margin-top: 15px;">
|
||||||
|
Per generare un nuovo link, è necessario creare una nuova assegnazione.
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!$assignment['returned_date']): ?>
|
||||||
|
<div class="form-actions">
|
||||||
|
<a href="?action=return&territory_id=<?php echo $assignment['territory_id']; ?>"
|
||||||
|
class="btn btn-primary">Riconsegna Territorio</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyLink() {
|
||||||
|
const linkInput = document.getElementById('share_link');
|
||||||
|
linkInput.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
alert('Link copiato negli appunti!');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
42
config.php
Normal file
42
config.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* File di Configurazione
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Configurazione Database
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_NAME', 'territoryassigner');
|
||||||
|
define('DB_USER', 'demo-termanager');
|
||||||
|
define('DB_PASS', 'Z.p8ibwg4jri');
|
||||||
|
define('DB_CHARSET', 'utf8mb4');
|
||||||
|
|
||||||
|
// Configurazione Applicazione
|
||||||
|
define('APP_NAME', 'Territory Manager');
|
||||||
|
define('APP_VERSION', '1.0.0');
|
||||||
|
define('TIMEZONE', 'Europe/Rome');
|
||||||
|
|
||||||
|
// Percorsi
|
||||||
|
define('BASE_PATH', __DIR__);
|
||||||
|
define('UPLOAD_PATH', BASE_PATH . '/uploads');
|
||||||
|
define('UPLOAD_URL', '/uploads');
|
||||||
|
|
||||||
|
// Configurazione Upload Immagini
|
||||||
|
define('MAX_FILE_SIZE', 5 * 1024 * 1024); // 5 MB
|
||||||
|
define('ALLOWED_EXTENSIONS', ['jpg', 'jpeg', 'png', 'gif', 'pdf']);
|
||||||
|
|
||||||
|
// Configurazione Sessione
|
||||||
|
define('SESSION_LIFETIME', 3600 * 8); // 8 ore
|
||||||
|
|
||||||
|
// Impostazioni Timezone
|
||||||
|
date_default_timezone_set(TIMEZONE);
|
||||||
|
|
||||||
|
// Gestione Errori (disattivare in produzione)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// Avvio Sessione
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
102
database.sql
Normal file
102
database.sql
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
-- Database per Gestione Territori
|
||||||
|
-- Creato il 6 dicembre 2025
|
||||||
|
|
||||||
|
CREATE DATABASE IF NOT EXISTS territoryassigner CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
USE territoryassigner;
|
||||||
|
|
||||||
|
-- Tabella utenti
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(100),
|
||||||
|
is_admin TINYINT(1) DEFAULT 0,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Tabella territori
|
||||||
|
CREATE TABLE IF NOT EXISTS territories (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
numero VARCHAR(20) NOT NULL,
|
||||||
|
zona VARCHAR(100) NOT NULL,
|
||||||
|
tipologia VARCHAR(50) NOT NULL,
|
||||||
|
image_path VARCHAR(255),
|
||||||
|
note TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY unique_territory (numero, zona)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Tabella assegnazioni
|
||||||
|
CREATE TABLE IF NOT EXISTS assignments (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
territory_id INT NOT NULL,
|
||||||
|
assigned_to VARCHAR(100) NOT NULL,
|
||||||
|
assigned_date DATE NOT NULL,
|
||||||
|
returned_date DATE NULL,
|
||||||
|
link_token VARCHAR(64) UNIQUE,
|
||||||
|
link_expires_at DATETIME NULL,
|
||||||
|
is_priority TINYINT(1) DEFAULT 0,
|
||||||
|
note TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (territory_id) REFERENCES territories(id) ON DELETE CASCADE,
|
||||||
|
INDEX idx_territory (territory_id),
|
||||||
|
INDEX idx_token (link_token),
|
||||||
|
INDEX idx_dates (assigned_date, returned_date)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Tabella configurazione
|
||||||
|
CREATE TABLE IF NOT EXISTS config (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
config_key VARCHAR(50) NOT NULL UNIQUE,
|
||||||
|
config_value VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Inserimento configurazioni di default
|
||||||
|
INSERT INTO config (config_key, config_value, description) VALUES
|
||||||
|
('link_expiry_days', '7', 'Giorni di validità dei link temporanei'),
|
||||||
|
('warning_days_normal', '90', 'Giorni dopo i quali un territorio è da assegnare'),
|
||||||
|
('warning_days_priority', '180', 'Giorni dopo i quali un territorio è prioritario'),
|
||||||
|
('warning_days_return', '120', 'Giorni dopo i quali un territorio è da riconsegnare');
|
||||||
|
|
||||||
|
-- Utente amministratore di default (password: admin123)
|
||||||
|
-- IMPORTANTE: Cambiare la password dopo la prima installazione!
|
||||||
|
INSERT INTO users (username, password, email, is_admin) VALUES
|
||||||
|
('admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin@example.com', 1);
|
||||||
|
|
||||||
|
-- Vista per territori attualmente assegnati
|
||||||
|
CREATE OR REPLACE VIEW current_assignments AS
|
||||||
|
SELECT
|
||||||
|
t.id as territory_id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
a.id as assignment_id,
|
||||||
|
a.assigned_to,
|
||||||
|
a.assigned_date,
|
||||||
|
a.is_priority,
|
||||||
|
DATEDIFF(CURDATE(), a.assigned_date) as days_assigned
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE a.returned_date IS NULL
|
||||||
|
ORDER BY a.assigned_date ASC;
|
||||||
|
|
||||||
|
-- Vista per territori disponibili (in reparto)
|
||||||
|
CREATE OR REPLACE VIEW available_territories AS
|
||||||
|
SELECT
|
||||||
|
t.id as territory_id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
t.image_path,
|
||||||
|
MAX(a.returned_date) as last_returned_date,
|
||||||
|
DATEDIFF(CURDATE(), MAX(a.returned_date)) as days_in_depot
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id, t.numero, t.zona, t.tipologia, t.image_path
|
||||||
|
ORDER BY last_returned_date ASC NULLS FIRST;
|
||||||
84
db.php
Normal file
84
db.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Gestione Connessione Database
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
class Database {
|
||||||
|
private static $instance = null;
|
||||||
|
private $connection;
|
||||||
|
|
||||||
|
private function __construct() {
|
||||||
|
try {
|
||||||
|
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||||
|
$options = [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->connection = new PDO($dsn, DB_USER, DB_PASS, $options);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Errore di connessione al database: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance() {
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnection() {
|
||||||
|
return $this->connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metodo helper per query semplici
|
||||||
|
public function query($sql, $params = []) {
|
||||||
|
try {
|
||||||
|
$stmt = $this->connection->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("Errore query: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metodo helper per ottenere una singola riga
|
||||||
|
public function fetchOne($sql, $params = []) {
|
||||||
|
$stmt = $this->query($sql, $params);
|
||||||
|
return $stmt ? $stmt->fetch() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metodo helper per ottenere tutte le righe
|
||||||
|
public function fetchAll($sql, $params = []) {
|
||||||
|
$stmt = $this->query($sql, $params);
|
||||||
|
return $stmt ? $stmt->fetchAll() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metodo helper per ottenere valore configurazione
|
||||||
|
public function getConfig($key, $default = null) {
|
||||||
|
$result = $this->fetchOne(
|
||||||
|
"SELECT config_value FROM config WHERE config_key = ?",
|
||||||
|
[$key]
|
||||||
|
);
|
||||||
|
return $result ? $result['config_value'] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metodo helper per aggiornare configurazione
|
||||||
|
public function updateConfig($key, $value) {
|
||||||
|
return $this->query(
|
||||||
|
"UPDATE config SET config_value = ? WHERE config_key = ?",
|
||||||
|
[$value, $key]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funzione helper globale per ottenere il database
|
||||||
|
function getDB() {
|
||||||
|
return Database::getInstance();
|
||||||
|
}
|
||||||
203
export_pdf.php
Normal file
203
export_pdf.php
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Export PDF
|
||||||
|
* Territory Manager
|
||||||
|
* Utilizza FPDF per generare PDF
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$type = $_GET['type'] ?? '';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Configurazioni
|
||||||
|
$warning_days_normal = (int)$db->getConfig('warning_days_normal', 90);
|
||||||
|
$warning_days_priority = (int)$db->getConfig('warning_days_priority', 180);
|
||||||
|
$warning_days_return = (int)$db->getConfig('warning_days_return', 120);
|
||||||
|
|
||||||
|
// Semplice classe PDF
|
||||||
|
class PDF {
|
||||||
|
private $content = '';
|
||||||
|
|
||||||
|
public function addTitle($title) {
|
||||||
|
$this->content .= "<h1 style='color: #2c3e50; margin-bottom: 20px;'>$title</h1>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addText($text) {
|
||||||
|
$this->content .= "<p style='margin: 10px 0;'>$text</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addTable($headers, $rows) {
|
||||||
|
$this->content .= "<table style='width: 100%; border-collapse: collapse; margin: 20px 0;'>";
|
||||||
|
$this->content .= "<thead><tr style='background-color: #34495e; color: white;'>";
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
$this->content .= "<th style='padding: 10px; border: 1px solid #ddd; text-align: left;'>$header</th>";
|
||||||
|
}
|
||||||
|
$this->content .= "</tr></thead><tbody>";
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$this->content .= "<tr>";
|
||||||
|
foreach ($row as $cell) {
|
||||||
|
$this->content .= "<td style='padding: 8px; border: 1px solid #ddd;'>$cell</td>";
|
||||||
|
}
|
||||||
|
$this->content .= "</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->content .= "</tbody></table>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function output($filename) {
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
header('Content-Disposition: inline; filename="' . $filename . '.html"');
|
||||||
|
|
||||||
|
echo "<!DOCTYPE html>
|
||||||
|
<html lang='it'>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<title>$filename</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||||
|
@media print {
|
||||||
|
body { padding: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>";
|
||||||
|
echo $this->content;
|
||||||
|
echo "<div style='margin-top: 40px; text-align: center; color: #7f8c8d;'>";
|
||||||
|
echo "<p>Generato il " . date('d/m/Y H:i') . " - " . APP_NAME . "</p>";
|
||||||
|
echo "</div>";
|
||||||
|
echo "<script>window.print();</script>";
|
||||||
|
echo "</body></html>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf = new PDF();
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'to_assign':
|
||||||
|
// Territori da assegnare
|
||||||
|
$territories = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
MAX(a.returned_date) as last_returned_date,
|
||||||
|
DATEDIFF(CURDATE(), MAX(a.returned_date)) as days_in_depot
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id, t.numero, t.zona, t.tipologia
|
||||||
|
HAVING days_in_depot >= ? OR MAX(a.returned_date) IS NULL
|
||||||
|
ORDER BY last_returned_date ASC NULLS FIRST
|
||||||
|
", [$warning_days_normal]);
|
||||||
|
|
||||||
|
$pdf->addTitle('Territori da Assegnare');
|
||||||
|
$pdf->addText('Territori in reparto da più di ' . $warning_days_normal . ' giorni');
|
||||||
|
$pdf->addText('Totale: ' . count($territories) . ' territori');
|
||||||
|
|
||||||
|
$headers = ['Numero', 'Zona', 'Tipologia', 'Ultima Restituzione', 'Giorni in Reparto'];
|
||||||
|
$rows = [];
|
||||||
|
foreach ($territories as $t) {
|
||||||
|
$rows[] = [
|
||||||
|
htmlspecialchars($t['numero']),
|
||||||
|
htmlspecialchars($t['zona']),
|
||||||
|
htmlspecialchars($t['tipologia']),
|
||||||
|
formatDate($t['last_returned_date']),
|
||||||
|
($t['days_in_depot'] ?? 'Mai assegnato')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->addTable($headers, $rows);
|
||||||
|
$pdf->output('Territori_da_Assegnare_' . date('Y-m-d'));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'priority':
|
||||||
|
// Territori prioritari
|
||||||
|
$territories = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
MAX(a.returned_date) as last_returned_date,
|
||||||
|
DATEDIFF(CURDATE(), MAX(a.returned_date)) as days_in_depot
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id, t.numero, t.zona, t.tipologia
|
||||||
|
HAVING days_in_depot >= ?
|
||||||
|
ORDER BY last_returned_date ASC NULLS FIRST
|
||||||
|
", [$warning_days_priority]);
|
||||||
|
|
||||||
|
$pdf->addTitle('Territori Prioritari');
|
||||||
|
$pdf->addText('Territori in reparto da più di ' . $warning_days_priority . ' giorni');
|
||||||
|
$pdf->addText('Totale: ' . count($territories) . ' territori');
|
||||||
|
|
||||||
|
$headers = ['Numero', 'Zona', 'Tipologia', 'Ultima Restituzione', 'Giorni in Reparto'];
|
||||||
|
$rows = [];
|
||||||
|
foreach ($territories as $t) {
|
||||||
|
$rows[] = [
|
||||||
|
htmlspecialchars($t['numero']),
|
||||||
|
htmlspecialchars($t['zona']),
|
||||||
|
htmlspecialchars($t['tipologia']),
|
||||||
|
formatDate($t['last_returned_date']),
|
||||||
|
($t['days_in_depot'] ?? 'Mai assegnato')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->addTable($headers, $rows);
|
||||||
|
$pdf->output('Territori_Prioritari_' . date('Y-m-d'));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'to_return':
|
||||||
|
// Territori da riconsegnare
|
||||||
|
$territories = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
a.assigned_to,
|
||||||
|
a.assigned_date,
|
||||||
|
DATEDIFF(CURDATE(), a.assigned_date) as days_assigned
|
||||||
|
FROM territories t
|
||||||
|
INNER JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE a.returned_date IS NULL
|
||||||
|
AND DATEDIFF(CURDATE(), a.assigned_date) >= ?
|
||||||
|
ORDER BY a.assigned_date ASC
|
||||||
|
", [$warning_days_return]);
|
||||||
|
|
||||||
|
$pdf->addTitle('Territori da Riconsegnare');
|
||||||
|
$pdf->addText('Territori assegnati da più di ' . $warning_days_return . ' giorni');
|
||||||
|
$pdf->addText('Totale: ' . count($territories) . ' territori');
|
||||||
|
|
||||||
|
$headers = ['Numero', 'Zona', 'Tipologia', 'Assegnato a', 'Data Assegnazione', 'Giorni'];
|
||||||
|
$rows = [];
|
||||||
|
foreach ($territories as $t) {
|
||||||
|
$rows[] = [
|
||||||
|
htmlspecialchars($t['numero']),
|
||||||
|
htmlspecialchars($t['zona']),
|
||||||
|
htmlspecialchars($t['tipologia']),
|
||||||
|
htmlspecialchars($t['assigned_to']),
|
||||||
|
formatDate($t['assigned_date']),
|
||||||
|
$t['days_assigned'] . ' giorni'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdf->addTable($headers, $rows);
|
||||||
|
$pdf->output('Territori_da_Riconsegnare_' . date('Y-m-d'));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
die('Tipo di export non valido');
|
||||||
|
}
|
||||||
11
footer.php
Normal file
11
footer.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>© <?php echo date('Y'); ?> <?php echo APP_NAME; ?> - Versione <?php echo APP_VERSION; ?></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
112
functions.php
Normal file
112
functions.php
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Funzioni Helper di Autenticazione
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
// Verifica se l'utente è loggato
|
||||||
|
function isLoggedIn() {
|
||||||
|
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica se l'utente è amministratore
|
||||||
|
function isAdmin() {
|
||||||
|
return isLoggedIn() && isset($_SESSION['is_admin']) && $_SESSION['is_admin'] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottieni l'utente corrente
|
||||||
|
function getCurrentUser() {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = getDB();
|
||||||
|
return $db->fetchOne(
|
||||||
|
"SELECT id, username, email, is_admin FROM users WHERE id = ?",
|
||||||
|
[$_SESSION['user_id']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login utente
|
||||||
|
function login($username, $password) {
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
$user = $db->fetchOne(
|
||||||
|
"SELECT id, username, password, email, is_admin FROM users WHERE username = ?",
|
||||||
|
[$username]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
$_SESSION['is_admin'] = $user['is_admin'];
|
||||||
|
$_SESSION['login_time'] = time();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout utente
|
||||||
|
function logout() {
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Richiedi autenticazione (redirect a login se non loggato)
|
||||||
|
function requireLogin() {
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Richiedi privilegi admin
|
||||||
|
function requireAdmin() {
|
||||||
|
requireLogin();
|
||||||
|
if (!isAdmin()) {
|
||||||
|
header('Location: index.php?error=access_denied');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genera un messaggio flash
|
||||||
|
function setFlashMessage($message, $type = 'info') {
|
||||||
|
$_SESSION['flash_message'] = $message;
|
||||||
|
$_SESSION['flash_type'] = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottieni e pulisci il messaggio flash
|
||||||
|
function getFlashMessage() {
|
||||||
|
if (isset($_SESSION['flash_message'])) {
|
||||||
|
$message = [
|
||||||
|
'text' => $_SESSION['flash_message'],
|
||||||
|
'type' => $_SESSION['flash_type'] ?? 'info'
|
||||||
|
];
|
||||||
|
unset($_SESSION['flash_message']);
|
||||||
|
unset($_SESSION['flash_type']);
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitizza input
|
||||||
|
function sanitize($input) {
|
||||||
|
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatta data
|
||||||
|
function formatDate($date) {
|
||||||
|
if (empty($date)) return '-';
|
||||||
|
return date('d/m/Y', strtotime($date));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatta data e ora
|
||||||
|
function formatDateTime($datetime) {
|
||||||
|
if (empty($datetime)) return '-';
|
||||||
|
return date('d/m/Y H:i', strtotime($datetime));
|
||||||
|
}
|
||||||
47
header.php
Normal file
47
header.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Header Template
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
$current_page = basename($_SERVER['PHP_SELF'], '.php');
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo isset($page_title) ? $page_title . ' - ' : ''; ?><?php echo APP_NAME; ?></title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a href="index.php"><?php echo APP_NAME; ?></a>
|
||||||
|
</div>
|
||||||
|
<ul class="navbar-menu">
|
||||||
|
<li><a href="index.php" class="<?php echo $current_page === 'index' ? 'active' : ''; ?>">Dashboard</a></li>
|
||||||
|
<li><a href="territories.php" class="<?php echo $current_page === 'territories' ? 'active' : ''; ?>">Territori</a></li>
|
||||||
|
<li><a href="assignments.php" class="<?php echo $current_page === 'assignments' ? 'active' : ''; ?>">Assegnazioni</a></li>
|
||||||
|
<li><a href="statistics.php" class="<?php echo $current_page === 'statistics' ? 'active' : ''; ?>">Statistiche</a></li>
|
||||||
|
<?php if (isAdmin()): ?>
|
||||||
|
<li><a href="settings.php" class="<?php echo $current_page === 'settings' ? 'active' : ''; ?>">Impostazioni</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-user">
|
||||||
|
<span>Ciao, <strong><?php echo htmlspecialchars($_SESSION['username']); ?></strong></span>
|
||||||
|
<a href="logout.php" class="btn btn-sm btn-secondary">Esci</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<?php
|
||||||
|
$flash = getFlashMessage();
|
||||||
|
if ($flash):
|
||||||
|
?>
|
||||||
|
<div class="alert alert-<?php echo $flash['type']; ?>">
|
||||||
|
<?php echo htmlspecialchars($flash['text']); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
298
index.php
Normal file
298
index.php
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pagina Dashboard (Home)
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$page_title = 'Dashboard';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Carica configurazioni
|
||||||
|
$warning_days_normal = (int)$db->getConfig('warning_days_normal', 90);
|
||||||
|
$warning_days_priority = (int)$db->getConfig('warning_days_priority', 180);
|
||||||
|
$warning_days_return = (int)$db->getConfig('warning_days_return', 120);
|
||||||
|
|
||||||
|
// Ottieni il limite (default 10, se expanded = all)
|
||||||
|
$limit_normal = isset($_GET['expand_normal']) ? '' : 'LIMIT 10';
|
||||||
|
$limit_priority = isset($_GET['expand_priority']) ? '' : 'LIMIT 10';
|
||||||
|
$limit_return = isset($_GET['expand_return']) ? '' : 'LIMIT 10';
|
||||||
|
|
||||||
|
// Territori da assegnare (in reparto da più di X giorni)
|
||||||
|
$territories_to_assign = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
MAX(a.returned_date) as last_returned_date,
|
||||||
|
DATEDIFF(CURDATE(), MAX(a.returned_date)) as days_in_depot
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id, t.numero, t.zona, t.tipologia
|
||||||
|
HAVING days_in_depot >= ? OR MAX(a.returned_date) IS NULL
|
||||||
|
ORDER BY last_returned_date ASC NULLS FIRST
|
||||||
|
$limit_normal
|
||||||
|
", [$warning_days_normal]);
|
||||||
|
|
||||||
|
// Territori prioritari (in reparto da più di X giorni)
|
||||||
|
$territories_priority = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
MAX(a.returned_date) as last_returned_date,
|
||||||
|
DATEDIFF(CURDATE(), MAX(a.returned_date)) as days_in_depot
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id, t.numero, t.zona, t.tipologia
|
||||||
|
HAVING days_in_depot >= ?
|
||||||
|
ORDER BY last_returned_date ASC NULLS FIRST
|
||||||
|
$limit_priority
|
||||||
|
", [$warning_days_priority]);
|
||||||
|
|
||||||
|
// Territori da riconsegnare (assegnati da più di X giorni)
|
||||||
|
$territories_to_return = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.id,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
a.assigned_to,
|
||||||
|
a.assigned_date,
|
||||||
|
DATEDIFF(CURDATE(), a.assigned_date) as days_assigned
|
||||||
|
FROM territories t
|
||||||
|
INNER JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE a.returned_date IS NULL
|
||||||
|
AND DATEDIFF(CURDATE(), a.assigned_date) >= ?
|
||||||
|
ORDER BY a.assigned_date ASC
|
||||||
|
$limit_return
|
||||||
|
", [$warning_days_return]);
|
||||||
|
|
||||||
|
// Conteggi totali
|
||||||
|
$count_to_assign = $db->fetchOne("
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM (
|
||||||
|
SELECT t.id
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id
|
||||||
|
HAVING DATEDIFF(CURDATE(), MAX(a.returned_date)) >= ? OR MAX(a.returned_date) IS NULL
|
||||||
|
) as subquery
|
||||||
|
", [$warning_days_normal])['total'];
|
||||||
|
|
||||||
|
$count_priority = $db->fetchOne("
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM (
|
||||||
|
SELECT t.id
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE t.id NOT IN (
|
||||||
|
SELECT territory_id FROM assignments WHERE returned_date IS NULL
|
||||||
|
)
|
||||||
|
GROUP BY t.id
|
||||||
|
HAVING DATEDIFF(CURDATE(), MAX(a.returned_date)) >= ?
|
||||||
|
) as subquery
|
||||||
|
", [$warning_days_priority])['total'];
|
||||||
|
|
||||||
|
$count_to_return = $db->fetchOne("
|
||||||
|
SELECT COUNT(*) as total
|
||||||
|
FROM assignments
|
||||||
|
WHERE returned_date IS NULL
|
||||||
|
AND DATEDIFF(CURDATE(), assigned_date) >= ?
|
||||||
|
", [$warning_days_return])['total'];
|
||||||
|
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="dashboard-header">
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<div class="dashboard-actions">
|
||||||
|
<a href="territories.php?action=add" class="btn btn-primary">+ Nuovo Territorio</a>
|
||||||
|
<a href="assignments.php?action=assign" class="btn btn-success">+ Nuova Assegnazione</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Territori da Assegnare -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Territori da Assegnare (<?php echo $count_to_assign; ?>)</h2>
|
||||||
|
<div class="card-actions">
|
||||||
|
<?php if (count($territories_to_assign) > 0): ?>
|
||||||
|
<a href="export_pdf.php?type=to_assign" class="btn btn-sm btn-secondary" target="_blank">📄 Export PDF</a>
|
||||||
|
<?php if (!isset($_GET['expand_normal'])): ?>
|
||||||
|
<a href="?expand_normal=1" class="btn btn-sm btn-primary">Mostra Tutti</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="index.php" class="btn btn-sm btn-secondary">Mostra Meno</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="help-text">Territori in reparto da più di <?php echo $warning_days_normal; ?> giorni</p>
|
||||||
|
<?php if (count($territories_to_assign) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Numero</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>Ultima Restituzione</th>
|
||||||
|
<th>Giorni in Reparto</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($territories_to_assign as $territory): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($territory['numero']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['tipologia']); ?></td>
|
||||||
|
<td><?php echo formatDate($territory['last_returned_date']); ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-warning">
|
||||||
|
<?php echo $territory['days_in_depot'] ?? 'Mai assegnato'; ?> giorni
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="assignments.php?action=assign&territory_id=<?php echo $territory['id']; ?>"
|
||||||
|
class="btn btn-sm btn-success">Assegna</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun territorio da assegnare</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Territori Prioritari -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Territori Prioritari (<?php echo $count_priority; ?>)</h2>
|
||||||
|
<div class="card-actions">
|
||||||
|
<?php if (count($territories_priority) > 0): ?>
|
||||||
|
<a href="export_pdf.php?type=priority" class="btn btn-sm btn-secondary" target="_blank">📄 Export PDF</a>
|
||||||
|
<?php if (!isset($_GET['expand_priority'])): ?>
|
||||||
|
<a href="?expand_priority=1" class="btn btn-sm btn-primary">Mostra Tutti</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="index.php" class="btn btn-sm btn-secondary">Mostra Meno</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="help-text">Territori in reparto da più di <?php echo $warning_days_priority; ?> giorni</p>
|
||||||
|
<?php if (count($territories_priority) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Numero</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>Ultima Restituzione</th>
|
||||||
|
<th>Giorni in Reparto</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($territories_priority as $territory): ?>
|
||||||
|
<tr class="priority-row">
|
||||||
|
<td><strong><?php echo htmlspecialchars($territory['numero']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['tipologia']); ?></td>
|
||||||
|
<td><?php echo formatDate($territory['last_returned_date']); ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-danger">
|
||||||
|
<?php echo $territory['days_in_depot'] ?? 'Mai assegnato'; ?> giorni
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="assignments.php?action=assign&territory_id=<?php echo $territory['id']; ?>&priority=1"
|
||||||
|
class="btn btn-sm btn-success">Assegna</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun territorio prioritario</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Territori da Riconsegnare -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Territori da Riconsegnare (<?php echo $count_to_return; ?>)</h2>
|
||||||
|
<div class="card-actions">
|
||||||
|
<?php if (count($territories_to_return) > 0): ?>
|
||||||
|
<a href="export_pdf.php?type=to_return" class="btn btn-sm btn-secondary" target="_blank">📄 Export PDF</a>
|
||||||
|
<?php if (!isset($_GET['expand_return'])): ?>
|
||||||
|
<a href="?expand_return=1" class="btn btn-sm btn-primary">Mostra Tutti</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="index.php" class="btn btn-sm btn-secondary">Mostra Meno</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="help-text">Territori assegnati da più di <?php echo $warning_days_return; ?> giorni</p>
|
||||||
|
<?php if (count($territories_to_return) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Numero</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>Assegnato a</th>
|
||||||
|
<th>Data Assegnazione</th>
|
||||||
|
<th>Giorni Assegnato</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($territories_to_return as $territory): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($territory['numero']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['tipologia']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['assigned_to']); ?></td>
|
||||||
|
<td><?php echo formatDate($territory['assigned_date']); ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-warning">
|
||||||
|
<?php echo $territory['days_assigned']; ?> giorni
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="assignments.php?action=return&territory_id=<?php echo $territory['id']; ?>"
|
||||||
|
class="btn btn-sm btn-primary">Riconsegna</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun territorio da riconsegnare</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
73
login.php
Normal file
73
login.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pagina di Login
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
|
||||||
|
// Se già loggato, redirect alla home
|
||||||
|
if (isLoggedIn()) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = sanitize($_POST['username'] ?? '');
|
||||||
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
$error = 'Inserire username e password';
|
||||||
|
} else {
|
||||||
|
if (login($username, $password)) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Credenziali non valide';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login - <?php echo APP_NAME; ?></title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body class="login-page">
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="login-box">
|
||||||
|
<h1><?php echo APP_NAME; ?></h1>
|
||||||
|
<p class="subtitle">Gestione Territori</p>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-error"><?php echo $error; ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST" action="">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" required autofocus
|
||||||
|
value="<?php echo isset($_POST['username']) ? htmlspecialchars($_POST['username']) : ''; ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">Accedi</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="login-footer">
|
||||||
|
<small>Versione <?php echo APP_VERSION; ?></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
logout.php
Normal file
12
logout.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Logout
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
|
||||||
|
logout();
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
62
script.js
Normal file
62
script.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* JavaScript Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Conferma prima di eliminare
|
||||||
|
document.querySelectorAll('form[onsubmit*="confirm"]').forEach(form => {
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
if (!confirm('Sei sicuro di voler procedere?')) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-hide flash messages dopo 5 secondi
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const alerts = document.querySelectorAll('.alert');
|
||||||
|
alerts.forEach(alert => {
|
||||||
|
setTimeout(() => {
|
||||||
|
alert.style.transition = 'opacity 0.5s';
|
||||||
|
alert.style.opacity = '0';
|
||||||
|
setTimeout(() => alert.remove(), 500);
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copia link negli appunti
|
||||||
|
function copyLink() {
|
||||||
|
const linkInput = document.getElementById('share_link');
|
||||||
|
if (linkInput) {
|
||||||
|
linkInput.select();
|
||||||
|
linkInput.setSelectionRange(0, 99999); // Per mobile
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
alert('Link copiato negli appunti!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Errore nella copia:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validazione form
|
||||||
|
document.querySelectorAll('form').forEach(form => {
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
const requiredFields = form.querySelectorAll('[required]');
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
requiredFields.forEach(field => {
|
||||||
|
if (!field.value.trim()) {
|
||||||
|
isValid = false;
|
||||||
|
field.style.borderColor = 'var(--danger)';
|
||||||
|
} else {
|
||||||
|
field.style.borderColor = 'var(--border)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('Compila tutti i campi obbligatori');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
269
settings.php
Normal file
269
settings.php
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Impostazioni (Solo Admin)
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireAdmin();
|
||||||
|
|
||||||
|
$page_title = 'Impostazioni';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Gestione salvataggio configurazioni
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action'])) {
|
||||||
|
switch ($_POST['action']) {
|
||||||
|
case 'update_config':
|
||||||
|
$link_expiry_days = (int)$_POST['link_expiry_days'];
|
||||||
|
$warning_days_normal = (int)$_POST['warning_days_normal'];
|
||||||
|
$warning_days_priority = (int)$_POST['warning_days_priority'];
|
||||||
|
$warning_days_return = (int)$_POST['warning_days_return'];
|
||||||
|
|
||||||
|
$db->updateConfig('link_expiry_days', $link_expiry_days);
|
||||||
|
$db->updateConfig('warning_days_normal', $warning_days_normal);
|
||||||
|
$db->updateConfig('warning_days_priority', $warning_days_priority);
|
||||||
|
$db->updateConfig('warning_days_return', $warning_days_return);
|
||||||
|
|
||||||
|
setFlashMessage('Configurazioni salvate con successo', 'success');
|
||||||
|
header('Location: settings.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'change_password':
|
||||||
|
$current_password = $_POST['current_password'];
|
||||||
|
$new_password = $_POST['new_password'];
|
||||||
|
$confirm_password = $_POST['confirm_password'];
|
||||||
|
|
||||||
|
$user = getCurrentUser();
|
||||||
|
$db_user = $db->fetchOne("SELECT password FROM users WHERE id = ?", [$user['id']]);
|
||||||
|
|
||||||
|
if (!password_verify($current_password, $db_user['password'])) {
|
||||||
|
setFlashMessage('Password corrente non corretta', 'error');
|
||||||
|
} elseif ($new_password !== $confirm_password) {
|
||||||
|
setFlashMessage('Le nuove password non coincidono', 'error');
|
||||||
|
} elseif (strlen($new_password) < 6) {
|
||||||
|
setFlashMessage('La password deve essere di almeno 6 caratteri', 'error');
|
||||||
|
} else {
|
||||||
|
$hashed = password_hash($new_password, PASSWORD_DEFAULT);
|
||||||
|
$db->query("UPDATE users SET password = ? WHERE id = ?", [$hashed, $user['id']]);
|
||||||
|
setFlashMessage('Password modificata con successo', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: settings.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'add_user':
|
||||||
|
$username = sanitize($_POST['username']);
|
||||||
|
$email = sanitize($_POST['email']);
|
||||||
|
$password = $_POST['password'];
|
||||||
|
$is_admin = isset($_POST['is_admin']) ? 1 : 0;
|
||||||
|
|
||||||
|
if (strlen($password) < 6) {
|
||||||
|
setFlashMessage('La password deve essere di almeno 6 caratteri', 'error');
|
||||||
|
} else {
|
||||||
|
$hashed = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$result = $db->query(
|
||||||
|
"INSERT INTO users (username, email, password, is_admin) VALUES (?, ?, ?, ?)",
|
||||||
|
[$username, $email, $hashed, $is_admin]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
setFlashMessage('Utente aggiunto con successo', 'success');
|
||||||
|
} else {
|
||||||
|
setFlashMessage('Errore: username già esistente', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: settings.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete_user':
|
||||||
|
$user_id = (int)$_POST['user_id'];
|
||||||
|
|
||||||
|
// Non permettere di eliminare se stesso
|
||||||
|
if ($user_id == $_SESSION['user_id']) {
|
||||||
|
setFlashMessage('Non puoi eliminare il tuo account', 'error');
|
||||||
|
} else {
|
||||||
|
$db->query("DELETE FROM users WHERE id = ?", [$user_id]);
|
||||||
|
setFlashMessage('Utente eliminato con successo', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: settings.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carica configurazioni
|
||||||
|
$config = [
|
||||||
|
'link_expiry_days' => $db->getConfig('link_expiry_days', 7),
|
||||||
|
'warning_days_normal' => $db->getConfig('warning_days_normal', 90),
|
||||||
|
'warning_days_priority' => $db->getConfig('warning_days_priority', 180),
|
||||||
|
'warning_days_return' => $db->getConfig('warning_days_return', 120)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Carica utenti
|
||||||
|
$users = $db->fetchAll("SELECT id, username, email, is_admin, created_at FROM users ORDER BY username");
|
||||||
|
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Impostazioni</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Configurazioni Generali -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Configurazioni Generali</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="update_config">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="link_expiry_days">Giorni validità link temporanei</label>
|
||||||
|
<input type="number" id="link_expiry_days" name="link_expiry_days"
|
||||||
|
value="<?php echo $config['link_expiry_days']; ?>"
|
||||||
|
min="1" max="365" required class="form-control">
|
||||||
|
<small class="form-help">Numero di giorni per cui i link di condivisione territorio sono validi</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="warning_days_normal">Giorni per territori da assegnare</label>
|
||||||
|
<input type="number" id="warning_days_normal" name="warning_days_normal"
|
||||||
|
value="<?php echo $config['warning_days_normal']; ?>"
|
||||||
|
min="1" max="999" required class="form-control">
|
||||||
|
<small class="form-help">Giorni dopo i quali un territorio in reparto è considerato da assegnare</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="warning_days_priority">Giorni per territori prioritari</label>
|
||||||
|
<input type="number" id="warning_days_priority" name="warning_days_priority"
|
||||||
|
value="<?php echo $config['warning_days_priority']; ?>"
|
||||||
|
min="1" max="999" required class="form-control">
|
||||||
|
<small class="form-help">Giorni dopo i quali un territorio in reparto è considerato prioritario</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="warning_days_return">Giorni per territori da riconsegnare</label>
|
||||||
|
<input type="number" id="warning_days_return" name="warning_days_return"
|
||||||
|
value="<?php echo $config['warning_days_return']; ?>"
|
||||||
|
min="1" max="999" required class="form-control">
|
||||||
|
<small class="form-help">Giorni dopo i quali un territorio assegnato è da riconsegnare</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Salva Configurazioni</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cambio Password -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Cambia Password</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="change_password">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="current_password">Password Corrente</label>
|
||||||
|
<input type="password" id="current_password" name="current_password" required class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new_password">Nuova Password</label>
|
||||||
|
<input type="password" id="new_password" name="new_password" required class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="confirm_password">Conferma Nuova Password</label>
|
||||||
|
<input type="password" id="confirm_password" name="confirm_password" required class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Cambia Password</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gestione Utenti -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Gestione Utenti</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Ruolo</th>
|
||||||
|
<th>Data Creazione</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($user['username']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($user['email']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($user['is_admin']): ?>
|
||||||
|
<span class="badge badge-danger">Admin</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge badge-info">Utente</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo formatDate($user['created_at']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($user['id'] != $_SESSION['user_id']): ?>
|
||||||
|
<form method="POST" style="display:inline;"
|
||||||
|
onsubmit="return confirm('Sei sicuro di voler eliminare questo utente?');">
|
||||||
|
<input type="hidden" name="action" value="delete_user">
|
||||||
|
<input type="hidden" name="user_id" value="<?php echo $user['id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger">Elimina</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge badge-secondary">Tu</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 style="margin-top: 30px;">Aggiungi Nuovo Utente</h3>
|
||||||
|
<form method="POST" style="margin-top: 20px;">
|
||||||
|
<input type="hidden" name="action" value="add_user">
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="username" placeholder="Username" required class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="email" name="email" placeholder="Email" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="password" name="password" placeholder="Password" required class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" name="is_admin" value="1">
|
||||||
|
Amministratore
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Aggiungi Utente</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
414
statistics.php
Normal file
414
statistics.php
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Statistiche
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$page_title = 'Statistiche';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
// Anno corrente per default
|
||||||
|
$selected_year = $_GET['year'] ?? date('Y');
|
||||||
|
$selected_month = $_GET['month'] ?? null;
|
||||||
|
|
||||||
|
// Statistiche generali
|
||||||
|
$total_territories = $db->fetchOne("SELECT COUNT(*) as total FROM territories")['total'];
|
||||||
|
$assigned_territories = $db->fetchOne("
|
||||||
|
SELECT COUNT(DISTINCT territory_id) as total
|
||||||
|
FROM assignments
|
||||||
|
WHERE returned_date IS NULL
|
||||||
|
")['total'];
|
||||||
|
$available_territories = $total_territories - $assigned_territories;
|
||||||
|
|
||||||
|
// Media percorrenza mensile (anno corrente)
|
||||||
|
$monthly_stats = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
MONTH(assigned_date) as month,
|
||||||
|
COUNT(*) as total_assignments,
|
||||||
|
AVG(DATEDIFF(COALESCE(returned_date, CURDATE()), assigned_date)) as avg_duration
|
||||||
|
FROM assignments
|
||||||
|
WHERE YEAR(assigned_date) = ?
|
||||||
|
GROUP BY MONTH(assigned_date)
|
||||||
|
ORDER BY month
|
||||||
|
", [$selected_year]);
|
||||||
|
|
||||||
|
// Media percorrenza annuale
|
||||||
|
$yearly_stats = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
YEAR(assigned_date) as year,
|
||||||
|
COUNT(*) as total_assignments,
|
||||||
|
AVG(DATEDIFF(COALESCE(returned_date, CURDATE()), assigned_date)) as avg_duration
|
||||||
|
FROM assignments
|
||||||
|
GROUP BY YEAR(assigned_date)
|
||||||
|
ORDER BY year DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
// Territori mai assegnati
|
||||||
|
$never_assigned = $db->fetchAll("
|
||||||
|
SELECT t.*
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
WHERE a.id IS NULL
|
||||||
|
ORDER BY t.numero
|
||||||
|
");
|
||||||
|
|
||||||
|
// Top 10 territori più assegnati
|
||||||
|
$most_assigned = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
COUNT(a.id) as assignment_count,
|
||||||
|
AVG(DATEDIFF(COALESCE(a.returned_date, CURDATE()), a.assigned_date)) as avg_duration
|
||||||
|
FROM territories t
|
||||||
|
INNER JOIN assignments a ON t.id = a.territory_id
|
||||||
|
GROUP BY t.id, t.numero, t.zona, t.tipologia
|
||||||
|
ORDER BY assignment_count DESC
|
||||||
|
LIMIT 10
|
||||||
|
");
|
||||||
|
|
||||||
|
// Statistiche per persona (chi ha avuto più territori)
|
||||||
|
$person_stats = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
assigned_to,
|
||||||
|
COUNT(*) as total_assignments,
|
||||||
|
COUNT(CASE WHEN returned_date IS NULL THEN 1 END) as current_assignments,
|
||||||
|
AVG(DATEDIFF(COALESCE(returned_date, CURDATE()), assigned_date)) as avg_duration
|
||||||
|
FROM assignments
|
||||||
|
GROUP BY assigned_to
|
||||||
|
ORDER BY total_assignments DESC
|
||||||
|
LIMIT 10
|
||||||
|
");
|
||||||
|
|
||||||
|
// Anni disponibili per filtro
|
||||||
|
$available_years = $db->fetchAll("
|
||||||
|
SELECT DISTINCT YEAR(assigned_date) as year
|
||||||
|
FROM assignments
|
||||||
|
ORDER BY year DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
// Statistiche per zona
|
||||||
|
$zone_stats = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.zona,
|
||||||
|
COUNT(DISTINCT t.id) as total_territories,
|
||||||
|
COUNT(a.id) as total_assignments,
|
||||||
|
AVG(DATEDIFF(COALESCE(a.returned_date, CURDATE()), a.assigned_date)) as avg_duration
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id
|
||||||
|
GROUP BY t.zona
|
||||||
|
ORDER BY total_territories DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
// Statistiche tempo reale (ultimi 7 giorni)
|
||||||
|
$recent_activity = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
DATE(assigned_date) as date,
|
||||||
|
COUNT(*) as assignments
|
||||||
|
FROM assignments
|
||||||
|
WHERE assigned_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||||
|
GROUP BY DATE(assigned_date)
|
||||||
|
ORDER BY date DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
$recent_returns = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
DATE(returned_date) as date,
|
||||||
|
COUNT(*) as returns
|
||||||
|
FROM assignments
|
||||||
|
WHERE returned_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||||
|
GROUP BY DATE(returned_date)
|
||||||
|
ORDER BY date DESC
|
||||||
|
");
|
||||||
|
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Statistiche</h1>
|
||||||
|
<div class="header-actions">
|
||||||
|
<form method="GET" style="display: inline-flex; gap: 10px;">
|
||||||
|
<select name="year" class="form-control" onchange="this.form.submit()">
|
||||||
|
<?php foreach ($available_years as $y): ?>
|
||||||
|
<option value="<?php echo $y['year']; ?>" <?php echo $selected_year == $y['year'] ? 'selected' : ''; ?>>
|
||||||
|
<?php echo $y['year']; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Statistiche Generali -->
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value"><?php echo $total_territories; ?></div>
|
||||||
|
<div class="stat-label">Totale Territori</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value"><?php echo $assigned_territories; ?></div>
|
||||||
|
<div class="stat-label">Territori Assegnati</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value"><?php echo $available_territories; ?></div>
|
||||||
|
<div class="stat-label">Territori Disponibili</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value"><?php echo count($never_assigned); ?></div>
|
||||||
|
<div class="stat-label">Mai Assegnati</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Attività Recente (7 giorni) -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Attività Ultimi 7 Giorni</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="activity-grid">
|
||||||
|
<div>
|
||||||
|
<h3>Assegnazioni</h3>
|
||||||
|
<?php if (count($recent_activity) > 0): ?>
|
||||||
|
<table class="table table-sm">
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($recent_activity as $activity): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo formatDate($activity['date']); ?></td>
|
||||||
|
<td><strong><?php echo $activity['assignments']; ?></strong> assegnazioni</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessuna assegnazione recente</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Riconsegne</h3>
|
||||||
|
<?php if (count($recent_returns) > 0): ?>
|
||||||
|
<table class="table table-sm">
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($recent_returns as $returns): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo formatDate($returns['date']); ?></td>
|
||||||
|
<td><strong><?php echo $returns['returns']; ?></strong> riconsegne</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessuna riconsegna recente</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Media Percorrenza Mensile -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Media Percorrenza Mensile - <?php echo $selected_year; ?></h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($monthly_stats) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Mese</th>
|
||||||
|
<th>Totale Assegnazioni</th>
|
||||||
|
<th>Durata Media</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$month_names = ['', 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno',
|
||||||
|
'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'];
|
||||||
|
foreach ($monthly_stats as $stat):
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $month_names[$stat['month']]; ?></td>
|
||||||
|
<td><?php echo $stat['total_assignments']; ?></td>
|
||||||
|
<td><?php echo round($stat['avg_duration']); ?> giorni</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun dato per l'anno selezionato</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Media Percorrenza Annuale -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Media Percorrenza Annuale</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($yearly_stats) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Anno</th>
|
||||||
|
<th>Totale Assegnazioni</th>
|
||||||
|
<th>Durata Media</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($yearly_stats as $stat): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo $stat['year']; ?></strong></td>
|
||||||
|
<td><?php echo $stat['total_assignments']; ?></td>
|
||||||
|
<td><?php echo round($stat['avg_duration']); ?> giorni</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun dato disponibile</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Statistiche per Zona -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Statistiche per Zona</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($zone_stats) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Totale Territori</th>
|
||||||
|
<th>Totale Assegnazioni</th>
|
||||||
|
<th>Durata Media</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($zone_stats as $stat): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($stat['zona']); ?></strong></td>
|
||||||
|
<td><?php echo $stat['total_territories']; ?></td>
|
||||||
|
<td><?php echo $stat['total_assignments']; ?></td>
|
||||||
|
<td><?php echo $stat['avg_duration'] ? round($stat['avg_duration']) . ' giorni' : '-'; ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun dato disponibile</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Top 10 Territori Più Assegnati -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Top 10 Territori Più Assegnati</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($most_assigned) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Territorio</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>N° Assegnazioni</th>
|
||||||
|
<th>Durata Media</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($most_assigned as $territory): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($territory['numero']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['tipologia']); ?></td>
|
||||||
|
<td><?php echo $territory['assignment_count']; ?></td>
|
||||||
|
<td><?php echo round($territory['avg_duration']); ?> giorni</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun dato disponibile</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Statistiche per Persona -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Top 10 Persone per Assegnazioni</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($person_stats) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Totale Assegnazioni</th>
|
||||||
|
<th>Assegnazioni Correnti</th>
|
||||||
|
<th>Durata Media</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($person_stats as $person): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($person['assigned_to']); ?></strong></td>
|
||||||
|
<td><?php echo $person['total_assignments']; ?></td>
|
||||||
|
<td><?php echo $person['current_assignments']; ?></td>
|
||||||
|
<td><?php echo round($person['avg_duration']); ?> giorni</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun dato disponibile</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Territori Mai Assegnati -->
|
||||||
|
<?php if (count($never_assigned) > 0): ?>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Territori Mai Assegnati (<?php echo count($never_assigned); ?>)</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Numero</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($never_assigned as $territory): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($territory['numero']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($territory['tipologia']); ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="assignments.php?action=assign&territory_id=<?php echo $territory['id']; ?>"
|
||||||
|
class="btn btn-sm btn-success">Assegna</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
607
style.css
Normal file
607
style.css
Normal file
@@ -0,0 +1,607 @@
|
|||||||
|
/**
|
||||||
|
* Stylesheet Territory Manager
|
||||||
|
* Design moderno e minimale
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Reset e Base */
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #3498db;
|
||||||
|
--primary-dark: #2980b9;
|
||||||
|
--secondary: #95a5a6;
|
||||||
|
--success: #27ae60;
|
||||||
|
--danger: #e74c3c;
|
||||||
|
--warning: #f39c12;
|
||||||
|
--info: #3498db;
|
||||||
|
--dark: #2c3e50;
|
||||||
|
--light: #ecf0f1;
|
||||||
|
--white: #ffffff;
|
||||||
|
--border: #dfe6e9;
|
||||||
|
--text: #2c3e50;
|
||||||
|
--text-muted: #7f8c8d;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text);
|
||||||
|
background-color: #f5f6fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navbar */
|
||||||
|
.navbar {
|
||||||
|
background: var(--white);
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand a {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu a {
|
||||||
|
padding: 10px 20px;
|
||||||
|
color: var(--text);
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu a:hover,
|
||||||
|
.navbar-menu a.active {
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-user {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
background: var(--white);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||||
|
margin-bottom: 25px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 20px 25px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--secondary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #7f8c8d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: var(--success);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background: #229954;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--danger);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-block {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-help {
|
||||||
|
display: block;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row .form-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead tr {
|
||||||
|
background: var(--light);
|
||||||
|
border-bottom: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-sm td,
|
||||||
|
.table-sm th {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-primary {
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background: var(--success);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background: var(--danger);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background: var(--warning);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
background: var(--info);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-secondary {
|
||||||
|
background: var(--secondary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts */
|
||||||
|
.alert {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: #d4edda;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: #f8d7da;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: #d1ecf1;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dashboard */
|
||||||
|
.dashboard-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page Header */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Grid */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: var(--white);
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Activity Grid */
|
||||||
|
.activity-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Details */
|
||||||
|
.detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link Box */
|
||||||
|
.link-box {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-box input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active,
|
||||||
|
.tab:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
border-bottom-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-text {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Priority Row */
|
||||||
|
.priority-row {
|
||||||
|
background: #fff5f5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter Form */
|
||||||
|
.filter-form .form-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px 20px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Login Page */
|
||||||
|
.login-page {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
background: var(--white);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box .subtitle {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public Page */
|
||||||
|
.public-page {
|
||||||
|
background: #f5f6fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar .container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu {
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-menu a {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header,
|
||||||
|
.page-header {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td.actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print Styles */
|
||||||
|
@media print {
|
||||||
|
.navbar,
|
||||||
|
.btn,
|
||||||
|
.card-actions,
|
||||||
|
.form-actions,
|
||||||
|
.footer {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
}
|
||||||
468
territories.php
Normal file
468
territories.php
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Gestione Territori
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'functions.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
requireLogin();
|
||||||
|
|
||||||
|
$page_title = 'Gestione Territori';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? 'list';
|
||||||
|
$territory_id = $_GET['id'] ?? null;
|
||||||
|
|
||||||
|
// Gestione delle azioni POST
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_POST['action'])) {
|
||||||
|
switch ($_POST['action']) {
|
||||||
|
case 'add':
|
||||||
|
$numero = sanitize($_POST['numero']);
|
||||||
|
$zona = sanitize($_POST['zona']);
|
||||||
|
$tipologia = sanitize($_POST['tipologia']);
|
||||||
|
$note = sanitize($_POST['note'] ?? '');
|
||||||
|
|
||||||
|
// Gestione upload immagine
|
||||||
|
$image_path = null;
|
||||||
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$file = $_FILES['image'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (in_array($ext, ALLOWED_EXTENSIONS) && $file['size'] <= MAX_FILE_SIZE) {
|
||||||
|
$filename = 'territory_' . time() . '_' . uniqid() . '.' . $ext;
|
||||||
|
$upload_dir = UPLOAD_PATH;
|
||||||
|
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $upload_dir . '/' . $filename)) {
|
||||||
|
$image_path = UPLOAD_URL . '/' . $filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $db->query(
|
||||||
|
"INSERT INTO territories (numero, zona, tipologia, image_path, note) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
[$numero, $zona, $tipologia, $image_path, $note]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
setFlashMessage('Territorio aggiunto con successo', 'success');
|
||||||
|
} else {
|
||||||
|
setFlashMessage('Errore durante l\'aggiunta del territorio', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: territories.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit':
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$numero = sanitize($_POST['numero']);
|
||||||
|
$zona = sanitize($_POST['zona']);
|
||||||
|
$tipologia = sanitize($_POST['tipologia']);
|
||||||
|
$note = sanitize($_POST['note'] ?? '');
|
||||||
|
|
||||||
|
// Gestione upload nuova immagine
|
||||||
|
$image_path = $_POST['current_image'] ?? null;
|
||||||
|
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$file = $_FILES['image'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (in_array($ext, ALLOWED_EXTENSIONS) && $file['size'] <= MAX_FILE_SIZE) {
|
||||||
|
// Elimina vecchia immagine
|
||||||
|
if ($image_path && file_exists(BASE_PATH . $image_path)) {
|
||||||
|
unlink(BASE_PATH . $image_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = 'territory_' . time() . '_' . uniqid() . '.' . $ext;
|
||||||
|
$upload_dir = UPLOAD_PATH;
|
||||||
|
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $upload_dir . '/' . $filename)) {
|
||||||
|
$image_path = UPLOAD_URL . '/' . $filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $db->query(
|
||||||
|
"UPDATE territories SET numero = ?, zona = ?, tipologia = ?, image_path = ?, note = ? WHERE id = ?",
|
||||||
|
[$numero, $zona, $tipologia, $image_path, $note, $id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
setFlashMessage('Territorio modificato con successo', 'success');
|
||||||
|
} else {
|
||||||
|
setFlashMessage('Errore durante la modifica del territorio', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: territories.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
|
||||||
|
// Recupera l'immagine per eliminarla
|
||||||
|
$territory = $db->fetchOne("SELECT image_path FROM territories WHERE id = ?", [$id]);
|
||||||
|
if ($territory && $territory['image_path']) {
|
||||||
|
$file_path = BASE_PATH . $territory['image_path'];
|
||||||
|
if (file_exists($file_path)) {
|
||||||
|
unlink($file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $db->query("DELETE FROM territories WHERE id = ?", [$id]);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
setFlashMessage('Territorio eliminato con successo', 'success');
|
||||||
|
} else {
|
||||||
|
setFlashMessage('Errore durante l\'eliminazione del territorio', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: territories.php');
|
||||||
|
exit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera dati per modifica
|
||||||
|
$territory = null;
|
||||||
|
if ($action === 'edit' && $territory_id) {
|
||||||
|
$territory = $db->fetchOne("SELECT * FROM territories WHERE id = ?", [$territory_id]);
|
||||||
|
if (!$territory) {
|
||||||
|
setFlashMessage('Territorio non trovato', 'error');
|
||||||
|
header('Location: territories.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lista territori con informazioni sullo stato
|
||||||
|
if ($action === 'list') {
|
||||||
|
$search = $_GET['search'] ?? '';
|
||||||
|
$filter_zona = $_GET['zona'] ?? '';
|
||||||
|
$filter_tipologia = $_GET['tipologia'] ?? '';
|
||||||
|
|
||||||
|
$where = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ($search) {
|
||||||
|
$where[] = "(t.numero LIKE ? OR t.zona LIKE ? OR t.tipologia LIKE ?)";
|
||||||
|
$search_term = "%$search%";
|
||||||
|
$params[] = $search_term;
|
||||||
|
$params[] = $search_term;
|
||||||
|
$params[] = $search_term;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filter_zona) {
|
||||||
|
$where[] = "t.zona = ?";
|
||||||
|
$params[] = $filter_zona;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filter_tipologia) {
|
||||||
|
$where[] = "t.tipologia = ?";
|
||||||
|
$params[] = $filter_tipologia;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_clause = $where ? "WHERE " . implode(" AND ", $where) : "";
|
||||||
|
|
||||||
|
$territories = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
t.*,
|
||||||
|
a.id as current_assignment_id,
|
||||||
|
a.assigned_to,
|
||||||
|
a.assigned_date,
|
||||||
|
DATEDIFF(CURDATE(), a.assigned_date) as days_assigned
|
||||||
|
FROM territories t
|
||||||
|
LEFT JOIN assignments a ON t.id = a.territory_id AND a.returned_date IS NULL
|
||||||
|
$where_clause
|
||||||
|
ORDER BY t.numero ASC
|
||||||
|
", $params);
|
||||||
|
|
||||||
|
// Ottieni liste per filtri
|
||||||
|
$zones = $db->fetchAll("SELECT DISTINCT zona FROM territories ORDER BY zona");
|
||||||
|
$types = $db->fetchAll("SELECT DISTINCT tipologia FROM territories ORDER BY tipologia");
|
||||||
|
}
|
||||||
|
|
||||||
|
include 'header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($action === 'list'): ?>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Gestione Territori</h1>
|
||||||
|
<a href="?action=add" class="btn btn-primary">+ Nuovo Territorio</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filtri di ricerca -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="GET" action="" class="filter-form">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="search" placeholder="Cerca numero, zona o tipologia..."
|
||||||
|
value="<?php echo htmlspecialchars($search); ?>" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<select name="zona" class="form-control">
|
||||||
|
<option value="">Tutte le zone</option>
|
||||||
|
<?php foreach ($zones as $z): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($z['zona']); ?>"
|
||||||
|
<?php echo $filter_zona === $z['zona'] ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($z['zona']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<select name="tipologia" class="form-control">
|
||||||
|
<option value="">Tutte le tipologie</option>
|
||||||
|
<?php foreach ($types as $t): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($t['tipologia']); ?>"
|
||||||
|
<?php echo $filter_tipologia === $t['tipologia'] ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($t['tipologia']); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Filtra</button>
|
||||||
|
<a href="territories.php" class="btn btn-secondary">Reset</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabella territori -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($territories) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Numero</th>
|
||||||
|
<th>Zona</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
<th>Assegnato a</th>
|
||||||
|
<th>Giorni</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($territories as $t): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($t['numero']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($t['zona']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($t['tipologia']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($t['current_assignment_id']): ?>
|
||||||
|
<span class="badge badge-info">Assegnato</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge badge-success">Disponibile</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $t['assigned_to'] ? htmlspecialchars($t['assigned_to']) : '-'; ?></td>
|
||||||
|
<td><?php echo $t['days_assigned'] ?? '-'; ?></td>
|
||||||
|
<td class="actions">
|
||||||
|
<a href="?action=view&id=<?php echo $t['id']; ?>" class="btn btn-sm btn-secondary">Dettagli</a>
|
||||||
|
<a href="?action=edit&id=<?php echo $t['id']; ?>" class="btn btn-sm btn-primary">Modifica</a>
|
||||||
|
<?php if (!$t['current_assignment_id']): ?>
|
||||||
|
<form method="POST" style="display:inline;" onsubmit="return confirm('Sei sicuro di voler eliminare questo territorio?');">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="id" value="<?php echo $t['id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger">Elimina</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessun territorio trovato</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($action === 'add' || $action === 'edit'): ?>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1><?php echo $action === 'add' ? 'Nuovo Territorio' : 'Modifica Territorio'; ?></h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" name="action" value="<?php echo $action; ?>">
|
||||||
|
<?php if ($action === 'edit'): ?>
|
||||||
|
<input type="hidden" name="id" value="<?php echo $territory['id']; ?>">
|
||||||
|
<input type="hidden" name="current_image" value="<?php echo htmlspecialchars($territory['image_path'] ?? ''); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="numero">Numero Territorio *</label>
|
||||||
|
<input type="text" id="numero" name="numero" required
|
||||||
|
value="<?php echo $territory ? htmlspecialchars($territory['numero']) : ''; ?>" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="zona">Zona *</label>
|
||||||
|
<input type="text" id="zona" name="zona" required
|
||||||
|
value="<?php echo $territory ? htmlspecialchars($territory['zona']) : ''; ?>" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tipologia">Tipologia *</label>
|
||||||
|
<input type="text" id="tipologia" name="tipologia" required
|
||||||
|
value="<?php echo $territory ? htmlspecialchars($territory['tipologia']) : ''; ?>" class="form-control">
|
||||||
|
<small class="form-help">Es: Residenziale, Commerciale, Rurale, ecc.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="image">Piantina Territorio</label>
|
||||||
|
<?php if ($territory && $territory['image_path']): ?>
|
||||||
|
<div class="current-image">
|
||||||
|
<img src="<?php echo htmlspecialchars($territory['image_path']); ?>" alt="Piantina" style="max-width: 300px; margin-bottom: 10px;">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" id="image" name="image" accept="image/*,.pdf" class="form-control">
|
||||||
|
<small class="form-help">Formati supportati: JPG, PNG, GIF, PDF. Dimensione massima: 5 MB</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="note">Note</label>
|
||||||
|
<textarea id="note" name="note" rows="4" class="form-control"><?php echo $territory ? htmlspecialchars($territory['note']) : ''; ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<?php echo $action === 'add' ? 'Aggiungi Territorio' : 'Salva Modifiche'; ?>
|
||||||
|
</button>
|
||||||
|
<a href="territories.php" class="btn btn-secondary">Annulla</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif ($action === 'view' && $territory_id): ?>
|
||||||
|
<?php
|
||||||
|
$territory = $db->fetchOne("SELECT * FROM territories WHERE id = ?", [$territory_id]);
|
||||||
|
if (!$territory) {
|
||||||
|
setFlashMessage('Territorio non trovato', 'error');
|
||||||
|
header('Location: territories.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storico assegnazioni
|
||||||
|
$history = $db->fetchAll("
|
||||||
|
SELECT
|
||||||
|
assigned_to,
|
||||||
|
assigned_date,
|
||||||
|
returned_date,
|
||||||
|
DATEDIFF(COALESCE(returned_date, CURDATE()), assigned_date) as days_duration,
|
||||||
|
is_priority,
|
||||||
|
note
|
||||||
|
FROM assignments
|
||||||
|
WHERE territory_id = ?
|
||||||
|
ORDER BY assigned_date DESC
|
||||||
|
", [$territory_id]);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Dettagli Territorio: <?php echo htmlspecialchars($territory['numero']); ?></h1>
|
||||||
|
<div>
|
||||||
|
<a href="?action=edit&id=<?php echo $territory['id']; ?>" class="btn btn-primary">Modifica</a>
|
||||||
|
<a href="territories.php" class="btn btn-secondary">Torna alla Lista</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Numero:</strong> <?php echo htmlspecialchars($territory['numero']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Zona:</strong> <?php echo htmlspecialchars($territory['zona']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Tipologia:</strong> <?php echo htmlspecialchars($territory['tipologia']); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($territory['note']): ?>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Note:</strong>
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($territory['note'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($territory['image_path']): ?>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Piantina:</strong><br>
|
||||||
|
<a href="<?php echo htmlspecialchars($territory['image_path']); ?>" target="_blank">
|
||||||
|
<img src="<?php echo htmlspecialchars($territory['image_path']); ?>"
|
||||||
|
alt="Piantina" style="max-width: 500px; margin-top: 10px; border: 1px solid #ddd;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Storico Assegnazioni</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($history) > 0): ?>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Assegnato a</th>
|
||||||
|
<th>Data Assegnazione</th>
|
||||||
|
<th>Data Restituzione</th>
|
||||||
|
<th>Durata</th>
|
||||||
|
<th>Priorità</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($history as $h): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo htmlspecialchars($h['assigned_to']); ?></td>
|
||||||
|
<td><?php echo formatDate($h['assigned_date']); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
if ($h['returned_date']) {
|
||||||
|
echo formatDate($h['returned_date']);
|
||||||
|
} else {
|
||||||
|
echo '<span class="badge badge-info">In corso</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $h['days_duration']; ?> giorni</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($h['is_priority']): ?>
|
||||||
|
<span class="badge badge-danger">Priorità</span>
|
||||||
|
<?php else: ?>
|
||||||
|
-
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="empty-state">Nessuna assegnazione storica</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php include 'footer.php'; ?>
|
||||||
12
uploads/README.md
Normal file
12
uploads/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# uploads/
|
||||||
|
|
||||||
|
Questa cartella contiene le immagini delle piantine dei territori caricate dall'amministratore.
|
||||||
|
|
||||||
|
**Nota**: Assicurati che questa cartella abbia i permessi corretti (755).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod 755 uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
Le immagini vengono nominate automaticamente con il formato:
|
||||||
|
`territory_[timestamp]_[id-unico].[estensione]`
|
||||||
131
view_territory.php
Normal file
131
view_territory.php
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Visualizzazione Territorio tramite Link Temporaneo
|
||||||
|
* Territory Manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
require_once 'db.php';
|
||||||
|
|
||||||
|
$token = $_GET['token'] ?? '';
|
||||||
|
$db = getDB();
|
||||||
|
|
||||||
|
if (empty($token)) {
|
||||||
|
die('Link non valido');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera l'assegnazione
|
||||||
|
$assignment = $db->fetchOne("
|
||||||
|
SELECT
|
||||||
|
a.*,
|
||||||
|
t.numero,
|
||||||
|
t.zona,
|
||||||
|
t.tipologia,
|
||||||
|
t.image_path,
|
||||||
|
t.note as territory_note
|
||||||
|
FROM assignments a
|
||||||
|
INNER JOIN territories t ON a.territory_id = t.id
|
||||||
|
WHERE a.link_token = ?
|
||||||
|
", [$token]);
|
||||||
|
|
||||||
|
if (!$assignment) {
|
||||||
|
die('Link non valido o scaduto');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica scadenza
|
||||||
|
if (strtotime($assignment['link_expires_at']) < time()) {
|
||||||
|
$expired = true;
|
||||||
|
} else {
|
||||||
|
$expired = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate($date) {
|
||||||
|
if (empty($date)) return '-';
|
||||||
|
return date('d/m/Y', strtotime($date));
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Territorio <?php echo htmlspecialchars($assignment['numero']); ?> - <?php echo APP_NAME; ?></title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body class="public-page">
|
||||||
|
<div class="container">
|
||||||
|
<?php if ($expired): ?>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1>Link Scaduto</h1>
|
||||||
|
<p class="alert alert-error">
|
||||||
|
Questo link è scaduto il <?php echo formatDate($assignment['link_expires_at']); ?>.
|
||||||
|
Contatta l'amministratore per ottenere un nuovo link.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Territorio: <?php echo htmlspecialchars($assignment['numero']); ?></h1>
|
||||||
|
<p class="subtitle">Visualizzazione Territorio</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Informazioni Territorio</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="detail-grid">
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Numero:</strong> <?php echo htmlspecialchars($assignment['numero']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Zona:</strong> <?php echo htmlspecialchars($assignment['zona']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Tipologia:</strong> <?php echo htmlspecialchars($assignment['tipologia']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Assegnato a:</strong> <?php echo htmlspecialchars($assignment['assigned_to']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<strong>Data Assegnazione:</strong> <?php echo formatDate($assignment['assigned_date']); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($assignment['territory_note']): ?>
|
||||||
|
<div class="detail-item" style="margin-top: 20px;">
|
||||||
|
<strong>Note:</strong>
|
||||||
|
<p><?php echo nl2br(htmlspecialchars($assignment['territory_note'])); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($assignment['image_path']): ?>
|
||||||
|
<div class="detail-item" style="margin-top: 20px;">
|
||||||
|
<strong>Piantina:</strong><br>
|
||||||
|
<a href="<?php echo htmlspecialchars($assignment['image_path']); ?>" target="_blank">
|
||||||
|
<img src="<?php echo htmlspecialchars($assignment['image_path']); ?>"
|
||||||
|
alt="Piantina Territorio"
|
||||||
|
style="max-width: 100%; margin-top: 10px; border: 1px solid #ddd;">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="help-text">
|
||||||
|
<strong>Nota:</strong> Questo link è valido fino al
|
||||||
|
<?php echo date('d/m/Y H:i', strtotime($assignment['link_expires_at'])); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="footer" style="margin-top: 40px; text-align: center;">
|
||||||
|
<p>© <?php echo date('Y'); ?> <?php echo APP_NAME; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user