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