# MEMORY.md - Glastree Project

## Project Context
Laravel 13 / PHP 8.4 project for parish management (Glastree). Full codebase exists and is operational.

---

## System Status

### ✅ FUNZIONANTE (Stable)
- Dashboard
- Individui (CRUD completo) - funziona da internet (con viste personalizzate per colonna)
- Gruppi (CRUD completo) - funziona da internet (con vista tabella/albero e colonne personalizzate)
- Documenti - funziona da internet
- Eventi (CRUD completo) - funziona da internet (con mass delete)
- Calendario eventi - funziona da internet
- Email (sistema completo)
- Impostazioni (App Settings, Logo, Email Settings)
- Mailing List - funziona da internet (con mass delete)
- Report (predefiniti e personalizzati, accesso tramite permesso 'report')

### ⚠️ IN COMPLETAMENTO
- Sistema Email: funziona al 95%, SMTP potrebbe necessitare credenziali corrette
- Nuova implementazione: email sync logging

---

## Progress Summary

### Eventi Enhancements (Completed)
- Migration `2026_05_12_142009_add_location_to_eventi_table` added `luogo_indirizzo` and `luogo_url_maps` columns
- Evento model updated with fillable fields
- EventoController updated: index() with search + sorting, store/update with location fields
- eventi/index.blade.php: search input, sortable columns (nome_evento, created_at), delete button
- eventi/create.blade.php: "Dove" card with address + Google Maps URL fields
- eventi/edit.blade.php: "Dove" card with address + Google Maps URL fields
- eventi/show.blade.php: "Luogo" section with address, link to Google Maps, embedded map preview

### Responsabile Multi-select (Completed)
- Migration `2026_05_12_000003_change_responsabile_to_multi.php` changed `responsabile_id` to `responsabile_ids` JSON
- Gruppo model updated with `getResponsabiliIds()` and `getResponsabili()` methods
- GruppoController updated: index(), store(), update(), show() for multi-select
- Views updated: show, edit, create blades with multi-select support

### 13 Maggio 2026 - Fixes
- **Ordinamento colonne**: Aggiunto ordinamento per Descrizione, Tipo, Gruppi, Responsabili
- **Form edit**: Aggiunti campi `tipo_evento` e `note` mancanti
- **Show view**: Corretta struttura HTML della sezione documenti

### 14 Maggio 2026 - Fixes
- **Form annidati**: La sezione Documenti spostata FUORI dal form principale
- **Tipologia enum**: Aggiunti valori 'programma' e 'locandina' alla colonna tipologia in documenti
- **Visibilita enum**: Aggiunto valore 'evento' alla colonna visibilita in documenti

### 15 Maggio 2026 - Eventi Show
- Rimossa sezione "Dove" duplicata dalla sidebar
- Aggiunta nuova sezione "Luogo" sotto "Data e Orario" nella colonna centrale

### 15 Maggio 2026 - Email Settings
- Aggiunto pulsante "Test Invio Email (SMTP)" nella pagina impostazioni email
- Aggiunto metodo `testSmtp()` nel controller per verificare la configurazione SMTP
- Route: `POST /impostazioni/email/test-smtp`
- Campo password opzionale (non più required)
- Rimossi attributi required dai campi del form impostazioni email

### 15 Maggio 2026 - Eventi Calendar
- Nuova vista calendario con FullCalendar 6.1.10
- Routes: `GET /eventi/calendar` e `GET /eventi/calendar/events`
- Metodo `calendar()` e `calendarEvents()` in EventoController
- Sidebar: menu Eventi con sottovoci Calendario, Elenco, Nuovo Evento
- Click su evento apre la pagina show dell'evento

### 15 Maggio 2026 - Email UI
- **Compose view**: Rimossa sidebar sinistra, solo form a schermo intero
- **Sidebar menu Email**:
  - Ordine: Nuova Email, In arrivo, Preferiti, Inviate, Bozze, Archivio, Cestino
  - Cliccando "Email" apre direttamente inbox
- **Bottone sincronizzazione**: "Ricevi/Invia" nella pagina inbox
- **Sincronizzazione**: Limitata a 100 messaggi per cartella

### 15 Maggio 2026 - Bug Fixes
- **RFC 2822**: Usare `Address::create()` di Symfony per gestire correttamente il display name
- **Route email.sync**: Sostituito `route('email.sync.quick')` con `url('email/sync/quick')` nella vista email/index.blade.php

### 16 Maggio 2026 - Email UI Fixes
- **Footer**: Aggiunto `min-height: calc(100vh - 120px)` al content-wrapper per garantire visibilità footer
- **Pagination**: Creato custom view `vendor.pagination.simple-bootstrap-4` con caratteri `&lsaquo;` e `&rsaquo;` invece di SVG (problema scalabilità risolto)

### 16 Maggio 2026 - Logo Upload System
- **Model**: `App\Models\AppSetting` con metodi `getLogoUrl()`, `getLogoSmallUrl()`, `getAppName()`
- **Migration**: `2026_05_16_000001_add_logo_path_to_app_settings` - aggiunge `logo_path` e `logo_small_path`
- **Controller**: `ImpostazioniController` - metodi `uploadLogo()` e `removeLogo()`
- **Routes**: `POST /impostazioni/logo`, `POST /impostazioni/logo/remove`
- **Views**: Sezione Logo in `/impostazioni` per caricare logo grande e piccolo
- **Login page**: Logo dinamico da `AppSetting::getLogoUrl()`
- **Sidebar**: Logo piccolo da `AppSetting::getLogoSmallUrl()` con fallback a icona

### 16 Maggio 2026 - Email Signature Feature
- **Migration**: `2026_05_16_000003_add_signature_to_email_settings` - aggiunge colonna `signature` (longText)
- **Migration**: `2026_05_16_000004_add_signature_enabled_to_email_settings` - aggiunge colonna `signature_enabled` (boolean, default true)
- **Model**: `EmailSetting` aggiornato con `signature` e `signature_enabled` in fillable, cast boolean, metodo `getSignature()`
- **View**: `admin/email-settings/index.blade.php` aggiornato con:
  - Titolo cambiato da "Email IMAP" a "Email Settings"
  - Nuova sezione "Firma" nel menu laterale
  - Toggle switch "Attiva firma automatica" per abilitare/disabilitare la firma
  - Editor WYSIWYG Quill.js per la composizione della firma
  - Caricamento lazy di Quill.js da CDN
- **Controller**: `EmailController` aggiornato con metodo `appendSignature(body, signature, enabled)` che aggiunge la firma al body solo se abilitata
- La firma viene aggiunta sia alle email inviate via SMTP che a quelle salvate in "Invio"
- **Sidebar**: Voce di menu cambiata da "Email IMAP" a "Impostazione Email"

---

## Sistema Email - Dettagli Tecnici

### Routes Eventi
```
GET  /eventi              → eventi.index (elenco)
GET  /eventi/calendar     → eventi.calendar (calendario)
GET  /eventi/calendar/events → eventi.calendar.events (eventi per FullCalendar)
GET  /eventi/create       → eventi.create
POST /eventi              → eventi.store
GET  /eventi/{id}         → eventi.show
GET  /eventi/{id}/edit   → eventi.edit
PUT  /eventi/{id}         → eventi.update
DELETE /eventi/{id}       → eventi.destroy
```

### Routes Email
```
POST   /email/send              → email.send
POST   /email/sync/quick        → email.sync.quick
POST   /email/bulk              → email.bulk
GET    /email/{folder?}         → email.index
POST   /email/{id}/star         → email.star
DELETE /email/{id}              → email.destroy
```

### Folder Types
- inbox, sent, drafts, archive, starred, trash

### SMTP Configuration
- Credenziali salvate in `email_settings` con criptatura
- Password decriptata con `getDecryptedPassword()` o `getDecryptedSmtpPassword()`
- Display name validato con `filter_var($fromName, FILTER_VALIDATE_EMAIL)` - se è un'email usa "Glastree"

### Email Signature
- Campo `signature` in `email_settings` (longText, nullable)
- WYSIWYG editor Quill.js via CDN
- Metodo `appendSignature()` in EmailController aggiunge "---\n" + signature al body
- Applicato sia in `sendViaImap()` che in `storeSentMessage()`

---

## Key Decisions

### Database
- Location uses separate columns: `luogo_indirizzo` (varchar 500) and `luogo_url_maps` (text)
- Map preview via Google Maps embed iframe using the address
- Sortable columns: nome_evento, created_at, descrizione_evento, tipo_recorrenza, gruppi, responsabili
- Multi-select uses Ctrl+click pattern with array input names

### Email
- SMTP defaults to Gmail if not configured
- Email name sanitized to comply with RFC 2822
- Sincronizzazione IMAP limitata a 100 messaggi per cartella
- Signature appended with separator "---\n" before the signature text

### UI
- AdminLTE per tutte le viste
- Sidebar con menu navigazione
- Compose email senza sidebar sinistra

---

## Relevant Files

### Core Controllers
- `app/Http/Controllers/EventoController.php`: eventi with extended sorting
- `app/Http/Controllers/EmailController.php`: email system completo, signature auto-append
- `app/Http/Controllers/Admin/EmailSettingsController.php`: email settings + test SMTP

### Models
- `app/Models/EmailSetting.php`: getDecryptedPassword(), getDecryptedSmtpPassword(), getSignature()
- `app/Models/Evento.php`: fillable with location fields
- `app/Models/Gruppo.php`: getResponsabiliIds(), getResponsabili()

### Views
- `resources/views/admin/email-settings/index.blade.php`: Email settings with signature section (Quill.js WYSIWYG)
- `resources/views/eventi/index.blade.php`: sortable columns
- `resources/views/eventi/edit.blade.php`: fixed nested form, added tipo_evento and note
- `resources/views/eventi/show.blade.php`: removed duplicate "Dove", added "Luogo" section
- `resources/views/email/index.blade.php`: mailbox view con AdminLTE
- `resources/views/email/compose.blade.php`: compose senza sidebar sinistra
- `resources/views/layouts/adminlte.blade.php`: sidebar menu Email con ordine corretto

### Migrations
- `2026_05_16_000003_add_signature_to_email_settings.php`: Adds signature column to email_settings table

---

## Commands Utili
```bash
# Clear all caches
php artisan optimize:clear

# Recompile views
php artisan view:cache

# Check routes
php artisan route:list --name=email
```

---

## Known Issues / TODO
1. SMTP authentication con Infomaniak - verificare credenziali corrette
2. Invio email richiede password SMTP corretta nel form impostazioni

---

### 16 Maggio 2026 - URL Hardcoded Fix (Internet Access)
- **Problema**: Da internet (accesso remoto) molti form e link puntavano a `http://localhost` invece del server remoto
- **Causa**: Uso di `url('/path')` in tutte le view che generava URL basati su `APP_URL=http://localhost`
- **Soluzione**: Sostituzione di tutte le occorrenze di `url('/path')` con path relativi `/path`
- **Files modificati**:
  - `individui/edit.blade.php`: form action, link gruppi/documenti, fetch API calls
  - `individui/show.blade.php`: link gruppi, documenti, form, fetch API calls
  - `gruppi/edit.blade.php`: form action, link individui/documenti, fetch API calls
  - `gruppi/show.blade.php`: link individui, eventi
  - `eventi/edit.blade.php`: form action, link annulla, documenti
  - `eventi/show.blade.php`: breadcrumbs, link gruppi/responsabili, documenti
  - `eventi/create.blade.php`: form action, link annulla
  - `eventi/index.blade.php`: link create, reset, eventi, gruppi
  - `documenti/index.blade.php`: link targets, edit, delete, mass actions, fetch API
  - `documenti/edit.blade.php`: form action, download, back link
  - `mailing-liste/edit.blade.php`: fetch API calls
  - `eventi/calendar.blade.php`: eventSource URL
- **Layout fix**: Aggiunto meta tag `csrf-token` in `adminlte.blade.php` head

---

### 16 Maggio 2026 - Individui Edit Form Fix
- **Problema**: Form non salvava da accesso internet, il pulsante submit non funzionava
- **Causa**: Il button type="submit" non triggherava il form submission
- **Soluzione**: 
  - Cambiato button da `type="submit"` a `type="button"` con `onclick="document.getElementById('main-form').submit()"`
  - Path relativi per tutti i form action
  - CSRF token in meta tag del layout

---

### 17 Maggio 2026 - Bulk Action Error Fix
- **Problema**: Azioni massive sulle email (sposta nel cestino) mostravano popup "errore" generico
- **Causa**: `authorizeWrite()` lancia eccezione 403 HTML invece di JSON per richieste AJAX
- **Soluzione**: Aggiunto try-catch in `bulkAction()`, `markRead()` e `toggleStar()` per ritornare JSON con messaggio appropriato

### 17 Maggio 2026 - Nginx Proxy Manager Config
- **Problema**: Email non funziona da internet (dietro NPM)
- **bootstrap/app.php**: Aggiunto `$middleware->trustProxies(at: '*')`
- **Configurazione .env**:
  ```
  APP_URL=
  APP_PROTOCOL=https
  FORCE_HTTPS=true
  TRUSTED_PROXIES=*
  ```
- **Middleware rimosso**: ForceHttps non necessario - NPM gestisce terminazione HTTPS
- **Advanced Tab NPM**: Abilitare X-Forwarded-Proto e headers proxy

### 17 Maggio 2026 - Form Annidati Fix
- **Problema**: Contatti, gruppi e documenti in individui/edit non venivano salvati
- **Causa**: Sezioni fuori dal main-form (chiusura prematura)
- **Soluzione**: Spostata chiusura form dopo tutte le sezioni, aggiunto pulsante "Salva Tutte le Modifiche"
- **Contatti**: Aggiunto pulsante salva nella riga nuova (icona💾)

### 17 Maggio 2026 - Bulk Action Email Fix
- **Problema**: Azioni massive su email mostravano popup "errore"
- **Causa**: authorizeWrite() lancia eccezione 403 HTML invece di JSON
- **Soluzione**: try-catch in bulkAction(), markRead(), toggleStar()

---

(Last updated: 25 Maggio 2026 - Fix export CSV individui)

### 18 Maggio 2026 - AGENTS.md Update
- Aggiunta regola "Verifica Salvataggio" nelle Regole Operative AI
- Obbligo di verificare SEMPRE l'avvenuta riuscita dopo operazioni di modifica (store, update, delete, upload, sync)

### 25 Maggio 2026 - IndividuoController::elimina() Method
- **Problema**: Errore 500 "Call to undefined method App\Http\Controllers\IndividuoController::elimina()" su richiesta `POST /individui/{id}/elimina`
- **Causa**: Route definita in `routes/web.php:58` ma metodo `elimina()` non implementato nel controller
- **Soluzione**: Aggiunto metodo `elimina()` in `IndividuoController` che:
  - Verifica autorizzazione con `authorizeDelete('individui')`
  - Riceve parametri `elimina_contatti` e `elimina_documenti` dal form
  - Elimina opzionalmente i contatti collegati se richiesto
  - Elimina opzionalmente i documenti collegati (con rimozione file fisico) se richiesto
  - Elimina l'individuo e redirect alla lista con messaggio di successo
- **Files modificati**:
  - `app/Http/Controllers/IndividuoController.php`: aggiunto metodo `elimina()` dopo `destroy()`

### 25 Maggio 2026 - Fix Eliminazione Singola e Massiva
- **Problema**: L'eliminazione non veniva eseguita né per il singolo né per la mass action
- **Cause**:
  1. `$request->boolean()` interpretava male i valori `'true'`/`'false'` come stringhe
  2. `deleteSelected()` faceva submit multipli di form separati causando redirect concorrenti
- **Soluzione**:
  1. Sostituito `$request->boolean()` con `filter_var(..., FILTER_VALIDATE_BOOLEAN)` per parsing corretto
  2. Aggiunto metodo `massElimina()` in `IndividuoController` per gestire eliminazione multipla con singolo form
  3. Aggiunta route `POST /individui/mass-elimina` in `routes/web.php`
  4. Rifattorizzata funzione JS `deleteSelected()` per usare singolo form con array `ids[]`
- **Files modificati**:
  - `app/Http/Controllers/IndividuoController.php`: corretto `elimina()`, aggiunto `massElimina()`
  - `routes/web.php`: aggiunta route `individui/mass-elimina`
  - `resources/views/individui/index.blade.php`: rifattorizzata `deleteSelected()`

### 25 Maggio 2026 - Fix Export CSV Individui
- **Problema**: "Esporta Tutti CSV" e "Esporta Selezionati CSV" non funzionavano
- **Cause**:
  1. `exportCSV()` usava `:visible` (selettore jQuery) con `querySelectorAll` (JS nativo) che non lo supporta
  2. Export client-side esportava solo righe nel DOM (pagina corrente con pagination), non tutti i record
  3. Dati limitati a 5 colonne senza contatti email/telefono
- **Soluzione**:
  1. Creato metodo `exportCSV()` server-side in `IndividuoController` con `StreamedResponse`
  2. Export completo con: Codice, Cognome, Nome, Data Nascita, Genere, Email, Telefono, Cellulare, Indirizzo, CAP, Città, Provincia, Note
  3. Email/Telefono/Cellulare recuperati dai contatti con preferenza per quelli primari
  4. Supporto per export tutti o solo selezionati tramite parametro `ids[]`
  5. Funzioni JS `exportCSV()` e `exportSelectedCSV()` ora puntano a `/individui/export`
- **Files modificati**:
  - `app/Http/Controllers/IndividuoController.php`: aggiunto metodo `exportCSV()` con StreamedResponse
  - `routes/web.php`: aggiunta route `GET /individui/export`
  - `resources/views/individui/index.blade.php`: semplificate funzioni `exportCSV()` e `exportSelectedCSV()`

(Last updated: 25 Maggio 2026 - Report system, mass actions, gruppi deletion fix)

### 25 Maggio 2026 - Fix Eliminazione Gruppi Padre (Superadmin Only)
- **Problema**: Eliminando un gruppo padre si otteneva pagina 404. Inoltre l'eliminazione di gruppi con figli era disponibile per tutti
- **Cause**: 
  1. Mancato detach delle tabelle pivot (`gruppo_individuo`, `eventi_gruppi`) prima della cancellazione
  2. Nessun controllo per limitare l'eliminazione di gruppi con sottogruppi al superadmin
- **Soluzione**:
  1. Rifattorizzato `destroy()` in `GruppoController`: aggiunta verifica `isSuperAdmin()` per gruppi con figli
  2. Creato metodo `destroyGruppo()` helper che esegue detach pivot, cancella documenti fisici e poi cancella il gruppo
  3. Aggiornata vista `tree-item.blade.php`: pulsante elimina disabilitato per non-superadmin su gruppi con figli
- **Files modificati**:
  - `app/Http/Controllers/GruppoController.php`: rifattorizzato `destroy()`, aggiunto `destroyGruppo()`
  - `resources/views/gruppi/partials/tree-item.blade.php`: pulsante elimina condizionato a superadmin per gruppi padre

### 25 Maggio 2026 - Mass Actions Eventi e Mailing List
- **Problema**: Eventi e Mailing List non avevano funzionalità di eliminazione massiva
- **Soluzione**: 
  1. Aggiunti checkbox di selezione, pulsante "Elimina Selezionati" e modal di conferma in `eventi/index.blade.php`
  2. Aggiunti checkbox di selezione, pulsante "Elimina Selezionati" e modal di conferma in `mailing-liste/index.blade.php`
  3. Aggiunto metodo `massElimina()` in `EventoController` con detach di gruppi e responsabili
  4. Aggiunto metodo `massElimina()` in `MailingListController` con cancellazione contatti associati
  5. Aggiunte route `POST /eventi/mass-elimina` e `POST /mailing-liste/mass-elimina`
- **Files modificati**:
  - `app/Http/Controllers/EventoController.php`: aggiunto `massElimina()`
  - `app/Http/Controllers/MailingListController.php`: aggiunto `massElimina()`
  - `routes/web.php`: aggiunte route mass-elimina per eventi e mailing-liste
  - `resources/views/eventi/index.blade.php`: aggiunta selezione massiva, modal conferma, JS
  - `resources/views/mailing-liste/index.blade.php`: aggiunta selezione massiva, modal conferma, JS

### 25 Maggio 2026 - Rimozione Menu Utilità Viste
- **Modifica**: Rimosso dal sidebar menu la voce "Utilità Viste" (`viste.index`)
- **Nota**: Le route e i controller per le viste rimangono funzionanti, solo il link sidebar è stato rimosso temporaneamente

### 25 Maggio 2026 - Sistema Report Completo
- **Funzionalità**: Nuova pagina Report accessibile solo dal superadmin con:
  1. **Panoramica database**: statistiche rapide (individui, gruppi, eventi, documenti, mailing list, contatti)
  2. **12 Report predefiniti**:
     - Individui per Genere
     - Individui per Fascia d'Età
     - Gerarchia Gruppi
     - Gruppi e Membri
     - Eventi del Mese
     - Documenti per Tipologia
     - Contatti per Tipo
     - Dettaglio Mailing List
     - Individui senza Contatti
     - Individui senza Gruppo
     - Eventi per Gruppo
     - Documenti Orfani
  3. **Report personalizzati**: creazione di report custom con selezione tipo dato, colonne, ordinamento e limite
  4. **Export CSV**: ogni report può essere esportato in CSV
- **Files creati**:
  - `app/Http/Controllers/ReportController.php`: controller completo con 12 report + custom
  - `app/Models/ReportCustom.php`: modello per report personalizzati
  - `database/migrations/2026_05_25_000001_create_report_custom_table.php`: migration tabella report_custom
  - `resources/views/report/index.blade.php`: pagina principale report con card e form creazione
  - `resources/views/report/result.blade.php`: pagina visualizzazione risultati report
- **Routes aggiunte**:
  - `GET /report` → report.index
  - `GET /report/run` → report.run
  - `GET /report/export` → report.export
  - `GET /report/custom/{id}` → report.run-custom
  - `POST /report/custom` → report.store-custom
  - `DELETE /report/custom/{id}` → report.destroy-custom
- **Sidebar aggiornata**: link "Report" ora punta a `route('report.index')` con evidenziazione active

(Last updated: 25 Maggio 2026 - Fix sidebar @endif orfano, verifica completa sintassi)

### 25 Maggio 2026 - Fix Sidebar Errore Sintassi
- **Problema**: Errore "syntax error, unexpected token 'endif'" nella dashboard
- **Causa**: Dopo aver rimosso il blocco `@if(Auth::user()->isSuperAdmin())` dalla sidebar per sostituirlo con `@if(Auth::user()->canAccess('report'))`, erano rimasti un `@endif` orfano (linea 237) e un `</li>` extra (linea 239)
- **Soluzione**: 
  1. Rimosso `@endif` orfano
  2. Rimosso `</li>` extra
  3. Aggiunto nuovo `@if(Auth::user()->isSuperAdmin())` per proteggere i menu Impostazioni e Admin
- **Verifica completa**: Scansionati tutti i blade file del progetto - tutte le direttive `@if/@endif`, `@foreach/@endforeach`, `@php/@endphp` sono correttamente bilanciate. Compilazione viste OK.

### 25 Maggio 2026 - Accesso Report Basato su Permessi
- **Modifica**: Il modulo 'report' è stato aggiunto a `User::MODULES` come nuovo modulo permissionabile
- **Funzionalità**: Gli utenti con permesso `canAccess('report')` (livello lettura o superiore) possono accedere alla pagina Report, non più solo il superadmin
- **Files modificati**:
  - `app/Models/User.php`: aggiunto 'report' a `MODULES` = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste', 'report']
  - `app/Http/Controllers/ReportController.php`: sostituiti tutti i check `isSuperAdmin()` con `canAccess('report')`
  - `resources/views/layouts/adminlte.blade.php`: sidebar mostra "Report" se `canAccess('report')` invece di `isSuperAdmin()`
- **Come abilitare un utente**: Dal pannello Admin → Utenti, assegnare permesso di livello ≥ 1 (Lettura) per il modulo 'report'

### 25 Maggio 2026 - Viste Personalizzate per Colonne (Individui e Gruppi)
- **Funzionalità**: Ogni utente può personalizzare quali colonne visualizzare nelle liste di Individui e Gruppi
- **Individui**:
  - Colonne disponibili: Codice, Cognome, Nome, Email, Telefono
  - Il controller `IndividuoController@index()` ora passa `$allColumns` e `$visibleColumns` alla vista
  - Le colonne visibili sono determinate dalla `VistaReport` predefinita dell'utente
  - Modal "Colonne Visibili" mostra tutte le colonne con checkbox per toggle
  - Modal "Salva Vista" permette di salvare una vista con nome, colonne, e opzione predefinita
- **Gruppi**:
  - Nuova vista tabella affiancata alla vista albero esistente
  - Toggle "Tabella/Albero" nella card header
  - Colonne disponibili: Nome, Descrizione, Diocesi, Livello, Gruppo Padre, Membri, Responsabili, Indirizzo, Città
  - Le colonne visibili sono determinate dalla `VistaReport` predefinita per tipo 'gruppi'
  - Salvataggio vista dedicato con route `POST /gruppi/save-vista`
  - Eliminazione vista con route `DELETE /gruppi/vista/{id}`
- **Model utilizzato**: `VistaReport` (tabella `viste_report`) con campi: `user_id`, `nome`, `tipo`, `colonne_visibili` (JSON), `is_default`
- **Files modificati**:
  - `app/Http/Controllers/IndividuoController.php`: `index()` passa `$allColumns` e `$visibleColumns`
  - `app/Http/Controllers/GruppoController.php`: `index()` rifattorizzato per vista tabella/albero, aggiunti `saveVista()` e `deleteVista()`
  - `routes/web.php`: aggiunte route `gruppi/save-vista` e `gruppi/delete-vista`
  - `resources/views/individui/index.blade.php`: colonne condizionali con `@if(in_array(..., $visibleColumns))`
  - `resources/views/gruppi/index.blade.php`: riscritta completamente con doppio mode (tabella/albero), modal salvataggio vista, toggle colonne

### 25 Maggio 2026 - Fix Vari Pagine (Individui, Eventi, Mailing, Documenti Upload)
- **Individui/Gruppi**: Errore `$tableColumns` non definito risolto usando `$allColumns`
- **Eventi**: Checkbox righe senza `id` impedivano il corretto funzionamento del label; aggiunto `id="row-checkbox-{{ $evento->id }}"`
- **Mailing List**: Stesso problema checkbox, corretto con `id` univoci
- **Documenti upload**: Funzione `loadUploadTargets()` chiamata ma non definita; aggiunta per popolare il dropdown target

### 25 Maggio 2026 - Fix Documento Avatar e Morph Map
- **Problema**: Caricando un documento avatar associato a un individuo, errore "Class 'individuo' not found"
- **Causa**: Il campo `visibilita_target_type` nel database conteneva nomi brevi ('individuo', 'gruppo') invece del namespace completo
- **Soluzione**:
  1. Registrata `Relation::morphMap()` in `AppServiceProvider@boot()` per mappare nomi brevi a classi complete
  2. Aggiornato `DocumentoController@store()` per impostare correttamente `visibilita_target_type` in base alla visibilità
- **Files modificati**:
  - `app/Providers/AppServiceProvider.php`: aggiunta morphMap per individuo, gruppo, evento, mailing, associazione, federazione
  - `app/Http/Controllers/DocumentoController.php`: logica esplicita per mappare visibilità → target_type

### 25 Maggio 2026 - Fix Pagina Gruppi (Tabella Pivot Inesistente)
- **Problema**: Pagina gruppi falliva con "Table 'glastree.gruppo_responsabili' doesn't exist"
- **Causa**: Il modello `Gruppo` aveva una relazione `responsabili()` che puntava a tabella pivot inesistente (la migration `2026_05_12_000003` aveva migrato a JSON `responsabile_ids`)
- **Soluzione**:
  1. Rimossa relazione `responsabili()` da `Gruppo.php`
  2. Rimossa eager loading `responsabili` da `GruppoController@index()`
  3. Mantenuti metodi helper `getResponsabiliIds()` e `getResponsabili()` che leggono dal campo JSON
  4. Corretti riferimenti in `gruppi/show.blade.php` e `individui/show.blade.php` per usare `getResponsabili()`
- **Files modificati**:
  - `app/Models/Gruppo.php`: rimossa relazione `responsabili()`, mantenuti helper
  - `app/Http/Controllers/GruppoController.php`: rimossa eager loading `responsabili`
  - `resources/views/gruppi/show.blade.php`: corretto `$child->responsabile` → `$child->getResponsabili()->first()`
  - `resources/views/individui/show.blade.php`: corretto `$gruppo->responsabile` → `$gruppo->getResponsabili()->first()`

### 25 Maggio 2026 - Fix Caricamento Documenti in Pagina Gruppi
- **Funzionalità**: Aggiunto modale "Carica Documento" nella pagina `gruppi/show` per upload diretto
- **Logica Responsabili**: Solo i membri del gruppo possono essere selezionati come responsabili
  - In `create.blade.php`: il dropdown responsabili si aggiorna dinamicamente quando si aggiungono/rimuovono membri
  - Funzione JS `updateResponsabiliFromMembri()` sincronizza il select responsabili con i membri aggiunti
- **Files modificati**:
  - `resources/views/gruppi/show.blade.php`: aggiunto modale upload documento, alert success/error
  - `resources/views/gruppi/create.blade.php`: logica JS per vincolare responsabili ai soli membri

(Last updated: 25 Maggio 2026 - Fix report eventi_calendario, colonna data)

### 25 Maggio 2026 - Fix Report Eventi Calendario
- **Problema**: Report "Eventi del Mese" falliva con "Unknown column 'data_inizio' in 'WHERE'"
- **Causa**: Il report usava `data_inizio` che non esiste nella tabella `eventi`. La colonna corretta è `data_specifica`
- **Soluzione**: Sostituito `data_inizio` con `data_specifica` in `reportEventiCalendario()` e `reportEventiPerGruppo()`
- **Files modificati**:
  - `app/Http/Controllers/ReportController.php`: righe 389-397 e 532, corretto nome colonna

(Last updated: 25 Maggio 2026 - Fix report eventi_calendario, colonna data)

### 25 Maggio 2026 - Fix Gruppi Table Hierarchy
- **Problema**: La visualizzazione a tabella dei gruppi non mostrava correttamente la gerarchia padre-figlio
- **Soluzione**: 
  - Aggiunto metodo `sortGruppiHierarchically()` in `GruppoController` per ordinare i gruppi in modo gerarchico
  - La tabella ora mostra prima i gruppi root, poi i loro figli in ordine annidato
  - Aggiunta eager loading `avatar` per supportare il futuro avatar dei gruppi
- **Files modificati**:
  - `app/Http/Controllers/GruppoController.php`: aggiunto `sortGruppiHierarchically()`, `addChildrenRecursive()`, aggiunto 'avatar' a eager loading

### 25 Maggio 2026 - Group Avatar Support
- **Funzionalità**: I gruppi ora possono avere un avatar/logo come gli individui
- **Implementazione**:
  - Aggiunto metodo `avatar()` in `Gruppo.php` (relazione HasOne a Documento con tipologia 'avatar')
  - Aggiunto accessor `getAvatarUrlAttribute()` per ottenere l'URL dell'avatar
  - Eager loading `avatar` aggiunto in `GruppoController@index()`
  - View `gruppi/index.blade.php` aggiornata per mostrare l'avatar nella tabella
  - View `gruppi/show.blade.php` aggiornata per mostrare l'avatar del gruppo
  - View `gruppi/create.blade.php` e `edit.blade.php` aggiornate per permettere upload avatar
- **Files modificati**:
  - `app/Models/Gruppo.php`: aggiunto `avatar()`, `getAvatarUrlAttribute()`
  - `app/Http/Controllers/GruppoController.php`: aggiunto eager loading `avatar`
  - `resources/views/gruppi/index.blade.php`: colonna avatar nella tabella
  - `resources/views/gruppi/show.blade.php`: avatar nella header card
  - `resources/views/gruppi/create.blade.php` e `edit.blade.php`: upload avatar

### 26 Maggio 2026 - Event Types System (Tipologie Eventi)
- **Nuova tabella**: `tipologie_eventi` con migration + seed dati default (catechesi, liturgia, animazione, formazione, incontro, riunione, festa, altro)
- **Nuovo modello**: `App\Models\TipologiaEvento` — identico pattern a `TipologiaDocumento` e `Ruolo`:
  - `$fillable`: nome, descrizione, ordine, attiva
  - Metodi: `eventi()` (hasMany), `attive()`, `opzioni()`
  - FK via `nome` (string) → `eventi.tipo_evento` (stesso pattern Documento.tipologia → TipologiaDocumento.nome)
- **Admin CRUD**: 4 nuovi metodi in `ImpostazioniController`: `tipologieEventiStore()`, `tipologieEventiUpdate()`, `tipologieEventiDestroy()`, `tipologieEventiReorder()`
- **Routes** (5 nuove): `/impostazioni/tipologie-eventi/*`
- **Impostazioni view**: Nuovo tab `#tipologie-eventi` con tabella sortabile, modali add/edit, stessi pattern esistenti
- **Sidebar**: Nuovo link "Tipologie Eventi" sotto Impostazioni (solo superadmin)
- **Evento model**: Aggiunta relazione `tipologiaEvento()` (BelongsTo via 'nome')
- **EventoController**: Validazione cambiata da `nullable|string|max:100` a `nullable|exists:tipologie_eventi,nome` in store() e update(); create/edit passano `$tipologieEventi` alla view
- **Views eventi**:
  - `create.blade.php`: Dropdown "Tipo Evento" dopo Descrizione Breve
  - `edit.blade.php`: Text libero → dropdown; fallback per vecchi valori non presenti
  - `show.blade.php`: Badge "Tipologia" nella sezione Data e Orario; colonna rinominata "Ricorrenza"
  - `index.blade.php`: Nuova colonna sortabile "Tipologia" con badge; colonna "Tipo" → "Ricorrenza"; colspan 8→9
- **Files creati**:
  - `database/migrations/2026_05_26_000001_create_tipologie_eventi_table.php`
  - `app/Models/TipologiaEvento.php`
- **Files modificati**:
  - `app/Http/Controllers/ImpostazioniController.php`
  - `app/Http/Controllers/EventoController.php`
  - `app/Models/Evento.php`
  - `routes/web.php`
  - `resources/views/impostazioni/index.blade.php`
  - `resources/views/layouts/adminlte.blade.php`
  - `resources/views/eventi/create.blade.php`
  - `resources/views/eventi/edit.blade.php`
  - `resources/views/eventi/show.blade.php`
  - `resources/views/eventi/index.blade.php`

### 26 Maggio 2026 - ACL Fixes & Form Modules Sync
- **Problema**: Form admin utenti (create/edit) aveva lista moduli hardcoded e mancante di `'report'`; user 4 (fbarachino) non aveva permesso `report` né `is_admin`
- **Fix form edit/create**: Sostituito `@foreach(['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste'] as $module)` con `@foreach(\App\Models\User::MODULES as $module)` in entrambe le view (`edit.blade.php:76`, `create.blade.php:66`) — ora la lista è sempre allineata alla costante `User::MODULES`
- **Fix user 4**:
  - Aggiunto `'report' => 2` alle permissions (full access)
  - Settato `is_admin = true` → `isSuperAdmin()` = true, bypass totale ACL
  - `canAccess('report')` = true, tutti i menu sidebar ora visibili
- **Nuovo comando**: `app/Console/Commands/SyncUserPermissions.php` — `php artisan users:sync-permissions`
  - Itera tutti gli utenti (inclusi superadmin) e aggiunge con default 0 ogni modulo mancante in `User::MODULES`
  - Previene discrepanze future tra permissions salvate e MODULES costante
- **Files modificati**:
  - `resources/views/admin/utenti/edit.blade.php`: linea 76, hardcoded → `User::MODULES`
  - `resources/views/admin/utenti/create.blade.php`: linea 66, hardcoded → `User::MODULES`
- **File creato**:
  - `app/Console/Commands/SyncUserPermissions.php`: nuovo comando artisan
- **Stato sidebar user 4**: `isSuperAdmin()` = true → tutti i menu visibili, `canAccess('report')` = true

### 26 Maggio 2026 - ACL 'settings' Module (Impostazioni/Admin Permission)
- **Problema**: Non esisteva un permesso ACL specifico per accedere al menu Impostazioni e Admin. L'accesso era legato esclusivamente a `isSuperAdmin()` bypass totale. Inoltre l'`ImpostazioniController` usava `authorizeWrite('viste')` come piggyback scorretto.
- **Soluzione**: Nuovo modulo `settings` separato, slegato da `viste` e da `isSuperAdmin()`.
- **Modifiche**:
  - `User.php`: aggiunto `'settings'` a `MODULES` (costante linea 36)
  - `adminlte.blade.php` sidebar: `@if(Auth::user()->isSuperAdmin())` → `@if(Auth::user()->canManage('settings'))` per menu Impostazioni e Admin (linee 205-226)
  - `AdminOnly.php` middleware: `isSuperAdmin()` → `isSuperAdmin() || canManage('settings')` — permette accesso a `/admin/*` anche a non-superadmin con permesso settings:2
  - `ImpostazioniController.php`: rimpiazzate tutte le 16 occorrenze di `authorizeWrite('viste')` e `authorizeDelete('viste')` con `'settings'`
  - `EmailSettingsController.php`: aggiunto `$this->authorizeWrite('settings')` in tutti i 5 metodi pubblici (index, save, testConnection, testSmtp, syncNow) — prima non avevano alcuna autorizzazione esplicita
  - `SyncUserPermissions.php` aggiornato: ora include anche superadmin nella sync (per consistenza dati)
  - Eseguito `php artisan users:sync-permissions` → user 1 e 4 aggiornati con `settings: 0`
- **Comportamento risultante**:
  - `isSuperAdmin()` bypassa sempre tutto (base Controller.php)
  - Utente normale con `settings:2` (Completo) → vede menu Impostazioni+Admin, può operare
  - Utente normale con `settings:0` → menu nascosto, 403 su accesso diretto URL
  - `settings:1` (Lettura) non basta per operazioni di scrittura (sidebar non mostra il menu perché richiede `canManage`)
- **Files modificati**:
  - `app/Models/User.php`: MODULES + 'settings'
  - `resources/views/layouts/adminlte.blade.php`: isSuperAdmin → canManage('settings')
  - `app/Http/Middleware/AdminOnly.php`: isSuperAdmin || canManage('settings')
  - `app/Http/Controllers/ImpostazioniController.php`: viste → settings in 16 authorize calls
  - `app/Http/Controllers/Admin/EmailSettingsController.php`: aggiunte 5 authorizeWrite('settings')
  - `app/Console/Commands/SyncUserPermissions.php`: superadmin inclusi nella sync

### 26 Maggio 2026 - Fix Salvataggio is_admin (Checkbox Superadmin)
- **Problema**: Non era possibile togliere il flag "Superadmin (bypass ACL)" dall'interfaccia admin. Il flag rimaneva sempre true dopo il salvataggio.
- **Cause** (3 bug concatenati):
  1. **`url()` assoluto** nell'action del form edit (`url('/admin/utenti/' . $user->id)`) → da accesso remoto generava `http://localhost/admin/utenti/4`, il POST falliva silenziosamente
  2. **Nessun hidden field** per unchecked checkbox → quando la checkbox veniva deselezionata, il campo `is_admin` non veniva inviato nella richiesta. Anche se il controller aveva `?? false`, il combinato col bug #1 impediva il salvataggio
  3. **Mancanza `old()`** per lo stato checkbox → su validation error redirect, la checkbox mostrava il valore DB invece del valore inviato
- **Fix applicati**:
  - `edit.blade.php`: `url(...)` → path relativo `/admin/utenti/...` (linea 20); aggiunto hidden `<input type="hidden" name="is_admin" value="0">` prima della checkbox; `old()` per stato checkbox e status/role_preset_id
  - `create.blade.php`: aggiunto hidden field come sopra; default per `settings` = `'0'` (Nessuno) invece di `'1'` (Lettura) per nuovi utenti
  - `UtenteController.php` (store + update): rimosso `?? false` da `$user->is_admin` — ora usa direttamente `$validated['is_admin']` (sempre presente grazie all'hidden field)
  - `AuthController.php`: aggiunti `'report' => 0` e `'settings' => 0` ai default permissions per nuove registrazioni (sia first-user che non)
- **Files modificati**:
  - `resources/views/admin/utenti/edit.blade.php`: form action, hidden field, old()
  - `resources/views/admin/utenti/create.blade.php`: hidden field, settings default 0
  - `app/Http/Controllers/Admin/UtenteController.php`: removed ?? false
  - `app/Http/Controllers/Auth/AuthController.php`: added report/settings defaults

(Last updated: 26 Maggio 2026 - Fix Salvataggio is_admin)

### 26 Maggio 2026 - Sender Accounts (Mittenti Aggiuntivi solo invio)

- **Nuova tabella**: `sender_accounts` con migration `2026_05_26_000002`
- **Colonne**: `id`, `email_address` (UNIQUE), `email_name`, `smtp_host`, `smtp_port`, `smtp_encryption`, `smtp_username`, `smtp_password` (Crypt), `reply_to`, `verify_email`, `note`, `is_active`, timestamps
- **Model `SenderAccount`**: `$fillable`, `$casts`, metodi:
  - `getDecryptedPassword()` — decripta password SMTP con fallback
  - `sanitizeName()` — pulisce il nome visualizzato
  - `getReplyTo()` — priorità: `reply_to` → `verify_email` → null
  - `buildDsn()` — costruisce DSN Symfony Mailer
  - `buildSymfonyEmail(to, subject, body)` — costruisce email con From, Reply-To
  - `sendEmail(to, subject, body, attachmentPaths)` — invia via Symfony Mailer
  - `sendReport(subject, body)` — invia report riepilogo a `verify_email`
- **Logica Reply-To**: se impostato `reply_to` (es. `no-reply@parrocchia.it`) → quello; fallback `verify_email`; nessun Reply-To se entrambi null
- **Report invio**: dopo ogni invio massivo via sender, una email di riepilogo viene inviata a `verify_email` con: mittente, oggetto, totale, ok, falliti, dettaglio errori
- **Migration `2026_05_26_000003`**: aggiunte colonne `log_falliti` (JSON), `mittente_nome`, `mittente_email` a `mailing_messaggi`
- **Model `MailingMessaggio`**: aggiornato `$fillable` e `$casts` per `log_falliti` (array)
- **CRUD in `EmailSettingsController`**: 4 nuovi metodi protetti da `authorizeWrite('settings')`:
  - `senderStore()` — crea nuovo sender con password criptata
  - `senderUpdate()` — modifica sender, password opzionale
  - `senderDestroy()` — elimina sender
  - `senderTestSmtp()` — test invio email via sender
- **Routes** (6 nuove):
  - `POST /impostazioni/sender` → `senderStore`
  - `PUT /impostazioni/sender/{id}` → `senderUpdate`
  - `DELETE /impostazioni/sender/{id}` → `senderDestroy`
  - `POST /impostazioni/sender/{id}/test` → `senderTestSmtp`
  - `GET /mailing/invio` → `MailingController@invio` (prima mancante)
  - `POST /mailing/invio/elabora` → `MailingController@invioElabora` (prima mancante)
- **`EmailController@compose`**: passa `$senderAccounts` alla view; nuova view mostra dropdown "Da (mittente)" prima dei destinatari
- **`EmailController@send`** modificato:
  - Se `mittente_id` > 0 → usa `SenderAccount::sendEmail()` con allegati; salta store sent message
  - Se `mittente_id` = 0 o assente → comportamento attuale con `EmailSetting`
  - Processa allegati `allegati[]` e `documenti_selezionati`
  - Nuovo helper: `sendWithSender()`, `processAttachments()`, `resolveAttachmentPaths()`
  - `sendViaImap()` ora accetta `$attachmentPaths` e li attacha all'email
- **`MailingController`** riscritto con invio SMTP reale:
  - `invia()` — invio 1:1 a ogni destinatario, log su `mailing_messaggi`
  - `invioElabora()` — invio massivo 1:1 per ogni email da liste multiple, log su `mailing_messaggi`
  - Tracking: stato `in_coda` → `inviato`/`parziale`/`fallito`, conteggi ok/fail, log_falliti JSON
  - Summary email a `verify_email` dopo invio massivo
  - `sendViaSystem()` — invio via EmailSetting (fallback quando nessun sender selezionato)
  - `resolveMailingAttachmentPaths()` — risolve path file da Documenti selezionati
- **View `email-settings/index.blade.php`**:
  - Nuovo tab "Mittenti Aggiuntivi" nella sidebar
  - Tabella con email, nome, SMTP, Reply-To, Verify, Stato, Azioni (Modifica/Test/Elimina)
  - Modal CRUD per creare/modificare sender con tutti i campi
  - JS: `resetSenderForm()`, `editSender(id)`, `testSenderSmtp(id)`
  - Sezione informativa "Come funziona"
- **View `email/compose.blade.php`**: dropdown "Da (mittente)" se ci sono sender attivi (default: Sistema)
- **View `mailing/nuovo.blade.php`**: dropdown "Mittente" se ci sono sender attivi
- **View `mailing/invio.blade.php`**: dropdown "Mittente" se ci sono sender attivi
- **Files creati**:
  - `database/migrations/2026_05_26_000002_create_sender_accounts_table.php`
  - `database/migrations/2026_05_26_000003_add_log_falliti_to_mailing_messaggi.php`
  - `app/Models/SenderAccount.php`
- **Files modificati**:
  - `app/Models/MailingMessaggio.php`
  - `app/Http/Controllers/Admin/EmailSettingsController.php`
  - `app/Http/Controllers/EmailController.php`
  - `app/Http/Controllers/MailingController.php`
  - `routes/web.php`
  - `resources/views/admin/email-settings/index.blade.php`
  - `resources/views/email/compose.blade.php`
  - `resources/views/mailing/nuovo.blade.php`
  - `resources/views/mailing/invio.blade.php`

### 26 Maggio 2026 - Export ICS/iCal Eventi
- **Nuovo Service**: `app/Services/IcsExportService.php` — generazione file ICS RFC 5545
  - `generateSingle(Evento)` — esporta un singolo evento
  - `generateCollection(iterable)` — esporta multipli eventi in un unico file ICS
  - Supporto **RRULE** completo per eventi ricorrenti:
    - `settimanale` → `FREQ=WEEKLY;BYDAY=MO`
    - `mensile` → `FREQ=MONTHLY;BYSETPOS=1;BYDAY=SU` (con opzionale `BYMONTH`)
    - `annuale` → `FREQ=YEARLY;BYMONTH=6;BYMONTHDAY=15`
    - `altro` → `FREQ=WEEKLY;BYDAY=MO`
  - Timezone `Europe/Rome` per eventi con ora, DATE (all-day) per eventi senza ora
  - LOCATION, DESCRIPTION, SUMMARY, UID univoci
  - Line folding a 75 caratteri RFC 5545
  - Escape di `;`, `,`, `\`, `\n`
- **Nuovi metodi in `EventoController`**:
  - `exportIcs(int $id, IcsExportService $ics)` — StreamedResponse download singolo evento
  - `exportSelectedIcs(Request $request, IcsExportService $ics)` — StreamedResponse con filtro per ID selezionati o ricerca corrente
- **Routes**:
  - `GET /eventi/{evento}/export-ics` → `eventi.export-ics`
  - `POST /eventi/export-ics` → `eventi.export-selected-ics`
- **View updates**:
  - `show.blade.php`: pulsante "Esporta ICS" nella toolbar azioni
  - `index.blade.php`: pulsante "Esporta ICS" nella card-tools; se ci sono righe selezionate esporta solo quelle, altrimenti esporta tutti i risultati della ricerca corrente
  - `calendar.blade.php`: pulsante "Esporta ICS" che esporta tutti gli eventi
- **Files creati**:
  - `app/Services/IcsExportService.php`
- **Files modificati**:
  - `app/Http/Controllers/EventoController.php`
  - `routes/web.php`
  - `resources/views/eventi/show.blade.php`
  - `resources/views/eventi/index.blade.php`
  - `resources/views/eventi/calendar.blade.php`

### 27 Maggio 2026 - Mittenti Aggiuntivi in Sezione Email di /impostazioni

- **Modifica**: Spostata gestione sender accounts (Mittenti Aggiuntivi) da pagina dedicata `/impostazioni/email` a **sottosezione della sezione Email** nella pagina principale `/impostazioni`
- **Sub-tab aggiunto**: pill "Mittenti Aggiuntivi" dopo Firma nei sottotab della sezione Email
- **Tab-pane `email-mittenti`**: tabella sender con colonne Email, Nome, SMTP, Reply-To, Verify, Stato, Azioni; stessi campi CRUD della vista dedicata
- **Form puliti**: pulsanti Elimina usano fetch AJAX (`deleteSender()`) invece di form annidati per evitare HTML invalido (nested forms dentro il form principale Email)
- **Controller**: `ImpostazioniController@index` ora passa `$senderAccounts` alla view
- **`senderDestroy()`** in `EmailSettingsController`: ora ritorna JSON per richieste AJAX (`expectsJson()`)
- **Sidebar**: rimossa voce "Impostazioni Email" aggiunta in precedenza sotto Email (non serve — le impostazioni email sono accessibili dalla sezione Email in `/impostazioni`)
- **Files modificati**:
  - `app/Http/Controllers/ImpostazioniController.php`: import + compact SenderAccount
  - `resources/views/impostazioni/index.blade.php`: pill Mittenti Aggiuntivi, tab-pane email-mittenti, modale sender modifica/crea, funzioni JS (resetSenderForm, editSender, deleteSender, testSenderSmtp)
  - `app/Http/Controllers/Admin/EmailSettingsController.php`: senderDestroy() con expectsJson()
  - `resources/views/layouts/adminlte.blade.php`: rimossa voce Impostazioni Email dal sidebar
