Files
2026-06-17 19:13:40 +02:00

31 KiB

🧠 MEMORY.md — Stato Progetto

Obiettivo

App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.

Funzionalità Implementate

Page-length selector

  • 4 pagine: Individui, Eventi, Email, Gruppi
  • Valori: 10, 20, 25, 50, 100 (default 20)
  • Dropdown nel card-footer con campi hidden per preservare query params
  • GruppoController: usa LengthAwarePaginator per paginare la collezione ordinata gerarchicamente

CSV Import Gruppi

  • Stesso pattern di IndividuoController (import GET, importStore POST, downloadTemplate GET)
  • Colonne: nome, descrizione, parent_id, diocesi_id, indirizzo_incontro, cap_incontro, città_incontro, sigla_provincia_incontro
  • Fault-tolerant: skip righe vuote, log errori, try/catch su ogni riga

ICS Import Eventi

  • Usa Sabre\VObject\Reader::read() già in vendor/
  • Mapping: SUMMARY→nome_evento, DESCRIPTION→descrizione, DTSTART→data_specifica+ora_inizio, DTEND→durata_minuti, LOCATION→luogo_indirizzo, UID→uid_esterno
  • Dedup via uid_esterno
  • RRULE: import come evento singolo con prima occorrenza

build-dist.sh (script di distribuzione)

  • Genera glastree-YYYYMMDD_HHMM.tar.gz
  • Include: sorgenti, vendor (production)
  • Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests, Docker
  • Istruzioni post-estrazione complete: mkdir, permessi, configurazione, cache clear

Tag System

Implementazione Completa (Opzione B)

  • Migration: 2026_06_08_194155_create_tags_tables.php — tabelle tags + taggables
  • Model Tag.php: morphToMany a 5 entity types, auto-slug su creating, color badge
  • Trait HasTagsLight.php: tags() morphToMany, scopes withAllTags/withAnyTags
  • 5 Models aggiornati: Individuo, Gruppo, Evento, Documento, MailingList — usano HasTagsLight
  • ImpostazioniController: CRUD completo per Tag (store, update, destroy, reorder) con tab nella pagina Impostazioni
  • Tag selector: _tag-selector.blade.php — search + pill badges con colori, incluso in create/edit di Individui, Gruppi, Eventi, MailingList
  • Tag filtering: ?tag[]=slug supportato in IndividuoController@index, GruppoController@index, EventoController@index, DocumentoController@index, MailingListController@index via withAnyTags scope
  • Tag column in index views: Colonna "Tag" con badge colorati cliccabili (collegamento a index filtrato) in individui, gruppi, eventi, documenti, mailing-liste
  • Tag filter widget: _tag-filter-bar.blade.php — barra cliccabile con badge colorati sopra ogni index table (individui, gruppi, eventi, documenti, mailing-liste). Attiva/disattiva filtro ?tag[]= con un click, evidenzia tag attivi, include "Cancella filtri" quando attivo
  • RicercaController: Controller per ricerca unificata per tag — mostra risultati aggregati da tutte 5 entity types con info-box counters
  • Route GET /ricerca: Registrata come ricerca.indexRicercaController@index
  • Sidebar link: "Ricerca per Tag" (icona fas fa-tags) sotto la sezione Report, visibile con permesso individui
  • Mass tag action for Documenti: POST /documenti/mass-tagDocumentoController@massTag — modal with tag selector + assign/remove radio, processes selected documents in chunks of 100 via syncWithoutDetaching() (assign) or detach() (remove)
  • Report tag columns: tags.nome disponibile come colonna per individui/gruppi/eventi/documenti nei report personalizzati
  • Report tag filters: Custom report form include tag multi-select (Select2) + modalità OR/AND; runCustomReport() applica filtri via withAllTags/withAnyTags

Bug Fix Recenti

2026-06-07 — Colonne mancanti in eventi e gruppo_individuo

  • descrizione_evento e is_incontro_gruppo mancanti in install.sql → creata migration + ALTER TABLE
  • ruolo_nel_gruppo mancante in install.sql per pivot gruppo_individuo
  • VistaReport.php: aggiunto is_default a $fillable

2026-06-08 — build-dist.sh: "provide valid cache path" su server remoto

Problema: Le esclusioni in build-dist.sh usavano --exclude='storage/framework/cache' (e simili per sessions, views, logs), che escludevano l'intera directory dall'archivio. Dopo l'estrazione, le directory necessarie a Laravel non esistevano.

Fix:

  1. Cambiati exclude patterns per contenuti soltanto (non la directory):
    • --exclude='storage/framework/cache/*' (non /cache)
    • --exclude='storage/framework/sessions/*'
    • --exclude='storage/framework/views/*'
    • --exclude='storage/logs/*'
    • --exclude='storage/debugbar/*'
  2. Aggiunte esclusioni per upload utente:
    • --exclude='storage/app/public'
    • --exclude='storage/app/private'
  3. Creato .gitignore in storage/framework/views/ (mancante)
  4. Istruzioni post-estrazione riscritte con:
    • mkdir -p per tutte le directory necessarie (cache, sessions, views, logs, public, backups, documenti, bootstrap/cache)
    • chmod -R 775 e chown -R www-data:www-data per permessi
    • php artisan config:clear, route:clear, view:clear

Problema 419 (Page Expired) su Produzione

Il codice NON ha errori di CSRF — tutte le form hanno @csrf. Il problema è configurazione sessione del server:

  1. storage/framework/sessions/ non scrivibile dal web server
  2. APP_URL in .env non corrisponde all'URL reale
  3. SESSION_SECURE_COOKIE=true ma sito in HTTP

Soluzione:

chmod -R 775 storage/framework/sessions
php artisan config:clear

2026-06-08 — build-dist.sh: impossibilità salvare logo su server remoto

Problema:

  • public/storage era un symlink assoluto (/var/www/html/glastree/storage/app/public) incluso nell'archivio → all'estrazione su altro server puntava a path inesistente.
  • php artisan storage:link falliva perché il symlink rotto esisteva già.
  • Sottodirectory logos/, documenti/eventi/, avatars/gruppi/ non create dal mkdir -p.

Fix:

  1. Aggiunto --exclude='public/storage' al tar — il symlink va ricreato sul target.
  2. Aggiunto rm -f public/storage prima di php artisan storage:link per evitare conflitto.
  3. Usato ln -sf ../storage/app/public public/storage (invece di php artisan storage:link --relative che richiede symfony/filesystem non sempre presente).
  4. Aggiunte al mkdir -p le sottodirectory mancanti:
    • storage/app/public/logos (logo upload)
    • storage/app/public/documenti/eventi (event documenti)
    • storage/app/private/avatars/gruppi (avatar gruppi)
  5. Aggiunta creazione .gitignore in storage/app/public/ (placeholder Laravel).

Fix form annidato salvataggio email

2026-06-08 — Problema: Il form di DELETE era annidato dentro il form di SAVE in entrambe le pagine:

  • resources/views/impostazioni/index.blade.php
  • resources/views/admin/email-settings/index.blade.php
  • HTML invalido: il browser chiudeva implicitamente il form SAVE all'apertura del form DELETE, causando l'invio alla route sbagliata.

Fix: Spostato il form DELETE fuori dal form SAVE, separato in un div autonomo.

Fix campi mancanti in validazione EmailSettingsController@save

2026-06-08: signature, signature_enabled, smtp_username, smtp_password non erano nelle regole di validazione di save(). Laravel scarta i campi non validati, quindi non venivano mai scritti nel DB.

Fix: Aggiunte le regole mancanti a $request->validate().

DiagnoseEmail Command

  • php artisan diagnose:email — controlla APP_KEY, tabella email_settings, record, decrittazione password, sender_accounts, log errori, estensioni PHP.

2026-06-08 — Firma email ancora non salvata (secondo tentativo)

Problema: La firma (campo signature) non veniva salvata nonostante le regole di validazione fossero state aggiunte.

Root cause analysis:

  1. HTML checkbox signature_enabled senza hidden fallback: quando non spuntato, il campo non veniva inviato → non presente in $validatedupdateOrCreate non lo aggiornava mai a false.
  2. La vecchia pagina /impostazioni/ aveva l'handler shown.bs.tab per il tab Firma (#email-firma) in un solo punto su due, causando possibile mancata inizializzazione dell'editor Quill su alcuni flussi di navigazione.
  3. La verifica nel controller controllava solo imap_host e email_address — anche se signature/signature_enabled fallivano, l'utente vedeva "salvato con successo".

Fix:

  1. resources/views/admin/email-settings/index.blade.php:268 — Aggiunto <input type="hidden" name="signature_enabled" value="0"> prima della checkbox
  2. resources/views/impostazioni/index.blade.php:603 — Stesso hidden fallback
  3. resources/views/impostazioni/index.blade.php:1633 — Aggiunto #email-firma al primo handler shown.bs.tab (il secondo handler esisteva già ma il primo no)
  4. app/Http/Controllers/Admin/EmailSettingsController.php:70-82 — Aggiunto log di warning se signature o signature_enabled non corrispondono dopo il salvataggio

2026-06-08 — Sync email: "Nessuna configurazione email attiva" nonostante test IMAP funzioni

Problema: Cliccando "Sincronizza Ora" nelle impostazioni email o "Ricevi/Invia" nella pagina email, viene mostrato "Nessuna configurazione email attiva". Il test IMAP funziona correttamente.

Root cause: Stesso bug dei checkbox senza hidden fallback. Il campo is_active nel tab Sincronizzazione non veniva inviato quando non spuntato. Alla prima configurazione, se l'utente non visitava esplicitamente il tab Sync e attivava lo switch, is_active rimaneva false (default DB). Il test IMAP usa EmailSetting::first() (ignora is_active), mentre syncEmails() e syncNow() usano EmailSetting::getActive() che filtra per is_active = true.

Fix:

  1. resources/views/admin/email-settings/index.blade.php:251 — Aggiunto <input type="hidden" name="is_active" value="0"> prima della checkbox
  2. resources/views/impostazioni/index.blade.php:586 — Stesso hidden fallback
  3. EmailSetting::getActive() ora riceve sempre is_active dal form (0 o 1) grazie all'hidden fallback

2026-06-08 — Help page: aggiunto tab "Calendario" con documentazione OAuth

  • resources/views/help/index.blade.php: Aggiunto tab "Calendario" nel menu laterale (dopo Google Drive) e relativo tab-pane content con:
    • Requisiti (account Google, API Calendar, OAuth credentials)
    • Passo 1: Google Cloud Console — abilitare Google Calendar API, creare OAuth Client ID (Web Application), redirect URI https://developers.google.com/oauthplayground
    • Passo 2: Ottenere Refresh Token via Google OAuth Playground (Use your own OAuth credentials, scope calendar)
    • Passo 3: Configurazione nell'app (Impostazioni → Calendario, nuova connessione Google Calendar, inserire Client ID/Secret/Refresh Token)
    • Test connessione e sincronizzazione
    • Nota: refresh token permanente finché non revocato
  • resources/views/help/pdf.blade.php: Aggiunta sezione "3. Google Calendar" nel TOC e contenuto, rinumerazione da 4 a 8 per sezioni successive

Differenza chiave Google Calendar vs Google Drive

  • Drive: endpoint server /auth/google-drive/callback che gestisce automaticamente il callback OAuth e salva refresh token in storage_repositories
  • Calendar: NON ha endpoint server dedicato. Refresh token ottenuto manualmente via Google OAuth Playground e incollato nel campo "Refresh Token" del form

2026-06-08 — Pulsante Sync nella pagina calendario eventi

  • app/Http/Controllers/CalendarioConnessioneController.php: Aggiunto metodo syncAll() che itera tutte le connessioni attive (is_active = true), esegue syncEvents() per ognuna, e restituisce JSON con risultati aggregati.
  • routes/web.php: Aggiunta route POST /calendario-connessioni/sync-allsyncAll (name: calendario-connessioni.sync-all).
  • resources/views/eventi/calendar.blade.php: Aggiunto pulsante "Sync" nel card-tools che:
    • Invia POST AJAX a sync-all
    • Mostra spinner durante l'operazione
    • Al completamento mostra alert con riepilogo (connessioni, importati, esportati)
    • Richiama window.calendar.refetchEvents() per aggiornare la vista FullCalendar
  • Esposto window.calendar globalmente per accesso dall'handler sync

2026-06-08 — Fix logo non visibile su installazione remota

Problema: Il logo uploadato non veniva visualizzato su installazioni remote, pur salvandosi correttamente su disco.

Root cause: getLogoPath() e getLogoSmallPath() in AppSetting.php usavano file_exists(public_path('storage/' . $path)) per verificare l'esistenza del file prima di restituire l'URL. Questo controllo dipendeva dal symlink public/storage → storage/app/public. Su installazioni remote dove il symlink era assente o mal configurato, file_exists restituiva false → metodo restituiva null → logo non mostrato.

Fix:

  1. app/Models/AppSetting.php: Sostituito file_exists(public_path('storage/' . $path)) con Storage::disk('public')->exists($path) e asset('storage/' . $path) con Storage::disk('public')->url($path). Il controllo ora avviene direttamente su storage/app/public/ (disk root), bypassando il symlink.
  2. app/Http/Controllers/ImpostazioniController.php: In uploadLogo(), aggiunta creazione automatica del symlink public/storage → storage/app/public se mancante, per garantire che il browser possa servire l'immagine.

2026-06-08 — Guardie Schema::hasColumn() aggiunte a 18 migration

Problema: 18 migration files aggiungevano colonne a tabelle esistenti senza Schema::hasColumn() guard. Se eseguite su DB dove le colonne già esistevano (es. da install.sql), causavano errore "Duplicate column".

Fix: Aggiunto if (!Schema::hasColumn(...)) wrapper a ogni colonna aggiunta nelle seguenti migration:

  1. 2026_06_02_000002_add_uid_esterno_to_eventi_table.phpuid_esterno
  2. 2026_05_12_000002_change_ruolo_to_multi.phpruolo_ids (up), ruolo_id (down)
  3. 2026_05_28_000001_add_storage_disk_to_documenti.phpstorage_disk
  4. 2026_05_27_113246_add_repository_id_to_documenti.phprepository_id
  5. 2026_05_26_000003_add_log_falliti_to_mailing_messaggi.phplog_falliti, mittente_nome, mittente_email
  6. 2026_05_27_000002_add_cartella_id_to_documenti.phpcartella_id
  7. 2026_05_16_000004_add_signature_enabled_to_email_settings.phpsignature_enabled
  8. 2026_05_16_000003_add_signature_to_email_settings.phpsignature
  9. 2024_01_01_000017_add_occorrenza_mese_to_eventi.phpoccorrenza_mese
  10. 2024_01_01_000016_add_eventi_recurrence_fields.phpgiorno_mese, mesi_recorrenza, mese_annuale
  11. 2026_05_10_000002_add_acl_tables.phppermissions (users)
  12. 2026_05_12_142009_add_location_to_eventi_table.phpluogo_indirizzo, luogo_url_maps
  13. 2026_05_12_000003_change_responsabile_to_multi.phpresponsabile_ids (up), responsabile_id (down)
  14. 2024_01_01_000023_add_is_default_to_viste_report_table.phpis_default
  15. 2024_01_01_000024_add_user_id_to_mailing_lists_table.phpuser_id
  16. 2026_05_12_000001_create_ruoli_table.phpruolo_id (gruppo_individuo) + data migration annessa
  17. 2024_01_01_000021_add_user_id_to_documenti_table.phpuser_id
  18. 2024_01_01_000013_add_numero_documento_to_individui_table.phpnumero_documento

Tutte verificate con php -l (nessun errore di sintassi).

2026-06-09 — @stack('scripts') mancante nel layout

Problema: Il partial _tag-selector.blade.php usa @push('scripts') per iniettare le funzioni JS toggleTag, removeTag, filterTags. Il layout adminlte.blade.php aveva solo @yield('scripts') (per @section), ma non @stack('scripts') (per @push). Di conseguenza il JavaScript interattivo del tag selector (clic sui badge, ricerca, rimozione) non veniva mai caricato su nessuna pagina create/edit.

Fix: resources/views/layouts/adminlte.blade.php:305 — Aggiunto @stack('scripts') subito prima di @yield('scripts').

2026-06-09 — Tag support completato per MailingList + Report + Ricerca

MailingList — Controller & Views (app/Http/Controllers/MailingListController.php):

  • index(): eager-load tags, supporto ?tag[]= filter via withAnyTags, pass $allTags per filter bar
  • create(): pass $tags per tag selector
  • store(): validazione tags.* exists:tags,id, sync dopo create. Auto-fetch email da Individuo::contatti() quando email non fornita
  • edit(): eager-load tags, pass $tags + $selectedTags
  • update(): validazione tags.*, sync se presente, detach se assente. Auto-fetch email da Individuo::contatti() quando email non fornita. Diff logica: $toRemove = array_diff($existingIds, $newIds) corretto — rimuove solo contatti deselezionati/esplicitamente rimossi
  • show(): eager-load tags

MailingList — Views:

  • index.blade.php: aggiunto @include('partials._tag-filter-bar'), colonna "Tag" con badge colorati cliccabili
  • create.blade.php: incluso _tag-selector dopo descrizione
  • edit.blade.php: incluso _tag-selector dopo descrizione (prima della sezione contatti). Submit handler include #individui-select.selectedOptions in contatti_json con email: '' (già presente da fix precedente)
  • show.blade.php: aggiunta card Tag nel pannello sinistro con badge colorati (o "Nessun tag assegnato")

ReportColumnRegistry (app/Helpers/ReportColumnRegistry.php):

  • Aggiunta colonna 'tags.nome' con label 'Tag' a 4 entity types: individui, gruppi, eventi, documenti

ReportController (app/Http/Controllers/ReportController.php):

  • runCustomReport(): supporta config['tag_filter'] (array di tag IDs) + config['tag_filter_mode'] ('any'/'all')
  • storeCustom(): ora salva tag_filter e tag_filter_mode nella config del report salvato
  • Carica $allTags nell'index e passa alla vista
  • Converte IDs → slugs via Tag::whereIn('id', ...)->pluck('slug'), applica withAllTags/withAnyTags
  • Eager-load tags automaticamente quando la colonna tags.nome è selezionata

Report — View (resources/views/report/index.blade.php):

  • Aggiunto filtro tag multi-select nel form "Crea Report Personalizzato" (Select2 con badge colorati)
  • Aggiunto select per modalità filtro (OR/AND)
  • JS: initTagFilterSelect2() / destroyTagFilterSelect2() con template colorato

RicercaController (app/Http/Controllers/RicercaController.php):

  • Aggiunta MailingList alle query per tag slug
  • results['mailingLists'] e totals['mailingLists'] passati alla view

Ricerca — View (resources/views/ricerca/index.blade.php):

  • Aggiunto info-box Mailing List (colore bg-purple) con link "Vedi tutti" → mailing-liste.index?tag[]=
  • Aggiunta tabella dettaglio Mailing List nel risultato di ricerca (nome, contatti, stato, azione show)
  • Fix: route('documenti.edit')url('/documenti/' . $doc->id . '/edit') (route non esiste)

2026-06-09 — Fix: storeCustom() non salvava tag_filter

Problema: ReportController@storeCustom() non salvava tag_filter e tag_filter_mode nella config del report personalizzato. Quando un report salvato veniva eseguito, runCustomReport() non trovava i filtri tag nella config e non li applicava.

Fix: Aggiunte righe 143-148 in ReportController.php:

if ($request->has('tag_filter')) {
    $config['tag_filter'] = $request->input('tag_filter');
}
if ($request->filled('tag_filter_mode')) {
    $config['tag_filter_mode'] = $request->input('tag_filter_mode');
}

2026-06-09 — Fix: MailingList edit — "Carica contatti" non inseriva i selezionati

Problema: Due bug nel JS caricaSelezionati() in mailing-liste/edit.blade.php:

  1. Typo: ind.cogname invece di ind.cognome — il campo Cognome nella tabella era sempre vuoto/undefined (API restituisce cognome)
  2. Email gate: if (ind.emails && ind.emails.length > 0) filtro bloccante — individui senza email salvata in contatti venivano SILENZIOSAMENTE scartati, mai aggiunti alla tabella

Fix:

  1. ind.cognameind.cognome (righe 227 e 256)
  2. Rimossa condizione if (ind.emails...) — ora anche individui senza email vengono aggiunti alla tabella
  3. email: ind.emails[0]email: (ind.emails && ind.emails.length > 0) ? ind.emails[0] : '' — email vuota se nessuna presente
  4. Controller già fixato per auto-risolvere email da Individuo::contatti() al salvataggio

2026-06-09 — Fix: MailingListController email gate bloccava nuovi contatti senza email

Problema: store() e update() in MailingListController usavano !empty($contatto['email']) per decidere se creare un contatto. L'email era richiesta ma il MailingContact non memorizza l'email — la risolve dall'Individuo al momento del display/invio. Questo impediva di aggiungere individui senza email (o con email non nei contatti) alla mailing list.

Fix: Rimossa completamente la verifica email sia in store() che in update(). Ora ogni individuo_id valido viene aggiunto come MailingContact. L'email viene risolta al display dal model $contact->individuo->contatti->where('tipo', 'email')->first()?->valore.

Prima (bloccante):

if (!empty($contatto['individuo_id']) && !empty($contatto['email'])) { create }

Dopo (semplice):

if (!empty($contatto['individuo_id'])) { create }

Prossimi Passi

  • [DONE] ... (existing items)
  • Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag
  • [DONE] Fix performance: spostata query VistaReport::where(...)->get() da @php nel partial table-settings.blade.php a tutti i 5 controller (prima veniva eseguita 1 query per ogni pagina load per ogni entity, anche senza mai aprire la modale)
  • [DONE] Rimosse vecchie modal legacy (#saveVistaModal, #colonneModal, #vistaListModal) da individui e gruppi — duplicate rispetto al nuovo modal unificato
  • [DONE] Rimosse vecchie funzioni JS (saveVista(), toggleColumn(), showSaveVistaModal()) da individui e gruppi
  • [DONE] Individui: $allColumns espanse da 6 a 17 colonne (aggiunte: data_nascita, genere, indirizzo, cap, citta, provincia, tipo_documento, numero_documento, scadenza_documento, note, created_at) — tutte con <th>/<td> condizionali nella vista
  • [DONE] Individui: getColumnIndex() introdotta per lookup dinamico dell'indice colonna (sostituisce hardcoded {codice:1, cognome:2, ...} in sortTable() e applyColumnFilter())
  • [DONE] I controller per eventi, documenti, mailing-liste hanno già $tableColumns con flag visible — stesso meccanismo di gruppi. Nessuna modifica necessaria.
  • [DONE] Fix: pulsante edit vista usa data-* attributi invece di onclick con addslashes(json_encode(...)) — previene rottura HTML per JSON contenente "
  • [DA FARE] Test browser: caricare ogni pagina entity, verificare default vista, toggle colonne, resize, drag-reorder, print
  • [DA FARE] Test browser: flusso edit vista (✏️ → popola form → modifica → Aggiorna → reload)
  • [DA FARE] Test browser: salvare nuova vista, switching tra viste, cancellazione vista

2026-06-10 — Per-User Column Views (colonne visibili, larghezze, ordine)

Obiettivo: Implementare viste colonne personalizzabili per utente su tutte le 5 entity list pages (individui, gruppi, eventi, documenti, mailing-liste), con persistenza tra login.

Cosa è stato fatto

Migration (2026_06_10_044946_add_column_widths_and_order_to_viste_report_table.php):

  • Aggiunge colonne_larghezze (json) e colonne_ordine (json) a viste_report
  • Guardia Schema::hasColumn per compatibilità cross-DB (MySQL ↔ SQLite)
  • Già eseguita in batch 38

Backend:

  • VistaReport.php: $fillable + $casts (array) per entrambi i nuovi campi
  • VistaReportController@store/@update: validazione estesa con colonne_larghezze, colonne_ordine (array)
  • Tutti i 5 controller passano alla vista: $tableColumns (array ['key','label','visible']), $entityType, $columnWidths, $visibleColumns, $allColumns (varia nome in base al controller)
  • Tutti i 5 controller ora passano $userVistas (precaricate) invece di eseguire query nella view
  • IndividuoController: $allColumns espanse con tutte le colonne DB disponibili (da 6 a 17)
  • IndividuoController: aggiunto use App\Models\VistaReport per la query $userVistas

Frontend JS (public/js/column-manager.js):

  • Classe ColumnManager: resize via mouse drag su handle, reorder via HTML5 DnD, persistenza larghezze in sessionStorage, fallback da vista-data DOM element, printWithSettings() che clona la tabella in finestra print-ready

Modal (resources/views/partials/table-settings.blade.php):

  • Modal unificato con 3 tab: Colonne (checkbox visibilità + input larghezza + drag-reorder list), Viste (salva/carica/cancella), Stampa (pulsante print)
  • Definisce initTableSettings() (NON auto-esegue) per evitare race condition con ColumnManager
  • RIMOSSA query VistaReport::where(...)->get() dal @php block — ora usa $userVistas passato dal controller
  • Non usa più Auth::id() direttamente — riceve $userVistas già popolato

Views — tutte e 5 aggiornate:

  • individui/index.blade.php:
    • $allColumns nella vista ora ha 17 colonne (vs 6 prima)
    • <th> e <td> condizionali per tutte le nuove colonne (data_nascita, genere, indirizzo, cap, citta, provincia, tipo_documento, numero_documento, scadenza_documento, note, created_at)
    • RIMOSSE modal legacy: #saveVistaModal, #colonneModal
    • RIMOSSE funzioni JS legacy: saveVista(), toggleColumn()
    • RIMOSSO sync checkboxes nel DOMContentLoaded (non più necessari)
    • NUOVA: getColumnIndex(colKey) — lookup dinamico dell'indice colonna basato su data-column
    • sortTable() e applyColumnFilter() ora usano getColumnIndex() invece di hardcoded mappa
  • gruppi/index.blade.php:
    • RIMOSSE modal legacy: #saveVistaModal, #vistaListModal
    • RIMOSSO pulsante "Salva Vista" legacy
    • RIMOSSE funzioni JS legacy: showSaveVistaModal(), saveVista()
  • eventi/index.blade.php: colonne dinamiche via $visibleColumns, @section('scripts') con ColumnManager + initTableSettings()
  • mailing-liste/index.blade.php: stessa struttura
  • documenti/index.blade.php: ColumnManager init (non reorderable), column-manager.js incluso
  • Tutti i <th> hanno data-column attributo per mapping JS
  • Tutte chiamano initTableSettings() dopo new ColumnManager() nel DOMContentLoaded

Performance fix:

  • Rimossa query VistaReport::where(...)->get() dal @php block in table-settings.blade.php (1 query extra per ogni pagina load su 5 entità)
  • Spostata nei 5 controller come $userVistas passata via compact()
  • Totale: -5 query per pagina load (una per ogni entity, anche su pagine non di index)

Verifica:

  • JS brace balance: OK su tutti i 5 file (node check)
  • PHP lint: OK su tutti i 5 controller
  • Presenza data-column: individui=19, gruppi=14, eventi=9, mailing-liste=7, documenti=8
  • Fallback $visibleColumns quando $vista === null: presente e corretto in tutti i 5 controller
  • Nessun residuo di vecchie modal legacy in individui e gruppi
  • Rimosso bottone "Salva Vista" orfano da individui/index.blade.php (chiamava showSaveVistaModal() non più esistente)
  • Aggiunto pulsante modifica ✏️ nel tab Viste per ogni vista salvata: popola il form "Salva vista corrente" con nome, default flag e colonne visibili; cambia bottone in "Aggiorna" e fa PUT /viste/{id} invece di POST; zero query extra

2026-06-10 — Fix: editVista onclick → data-* attributes

Problema: Il pulsante modifica vista usava onclick con addslashes(json_encode(...)). In un attributo HTML delimitato da ", il backslash-escaping di \" non è gestito in modo standard dai browser — il JSON contenente " poteva rompere l'attributo HTML.

Fix:

  1. Sostituito onclick con data-* attributi (data-vista-id, data-vista-nome, data-vista-default, data-vista-colonne) usando {{ }} di Blade (che applica htmlspecialchars, codificando "&quot; sicuro in HTML).
  2. editVista() ora accetta un array direttamente (non più JSON string da parsare).
  3. Click handler registrato in initTableSettings() via document.querySelectorAll('.edit-vista-btn') + dataset (browser decodifica automaticamente &quot;").

2026-06-10 — Fix: 405 Method Not Allowed su update vista (via AJAX fetch)

Problema: Modificando una vista e cliccando "Aggiorna", fetch() inviava PUT /viste/{id} ma il controller VistaReportController@update restituiva return back() (302 redirect). fetch() seguiva il redirect — in alcuni browser il metodo PUT veniva preservato, ma la route gruppi accetta solo GET → 405 Method Not Allowed.

Fix:

  1. VistaReportController@update (linea 101-103): aggiunto if ($request->expectsJson()) { return response()->json([...]); } — così il fetch riceve JSON 200, non un redirect 302.
  2. Fetch headers in table-settings.blade.php: aggiunto 'Accept': 'application/json' — necessario perché expectsJson() controlla l'header Accept, non Content-Type.

2026-06-10 — Fix: tutte le migration idempotenti + seeders idempotenti

Problema: php artisan migrate falliva su tabelle già esistenti. php artisan db:seed creava duplicati su re-run.

Migration: hasTable guard su tutti i 27 file mancanti

Aggiunto if (!Schema::hasTable('table_name')) { ... } wrapper a tutte le 27 migration file (44 Schema::create() calls) che ne erano prive, usando script Node.js di trasformazione automatica:

  • 2024_01_01_000002_create_comuni_diocesi_tables.php
  • 2024_01_01_000003_create_individui_table.php
  • 2024_01_01_000004_create_contatti_table.php
  • 2024_01_01_000005_create_gruppi_table.php
  • 2024_01_01_000006_create_gruppo_individuo_table.php
  • 2024_01_01_000007_create_eventi_table.php
  • 2024_01_01_000008_create_documenti_table.php
  • 2024_01_01_000009_create_mailing_tables.php
  • 2024_01_01_000010_create_notifiche_table.php
  • 2024_01_01_000011_create_users_table.php
  • 2024_01_01_000012_create_cache_and_jobs_tables.php
  • 2024_01_01_000019_create_eventi_documenti_table.php
  • 2024_01_01_000022_create_table_viste_report.php
  • 2026_05_10_000001_create_tipologie_documenti_table.php
  • 2026_05_10_000002_add_acl_tables.php
  • 2026_05_10_125210_create_permission_tables.php (usa $tableNames[...] dinamici)
  • 2026_05_11_000001_create_email_settings_table.php
  • 2026_05_11_000002_create_email_folders_table.php
  • 2026_05_11_000003_create_email_messages_table.php
  • 2026_05_11_000007_create_email_attachments_table.php
  • 2026_05_12_000001_create_ruoli_table.php
  • 2026_05_25_000001_create_report_custom_table.php
  • 2026_05_26_000001_create_tipologie_eventi_table.php
  • 2026_05_26_000002_create_sender_accounts_table.php
  • 2026_05_27_000001_create_documenti_cartelle_table.php
  • 2026_05_27_113245_create_storage_repositories_table.php
  • 2026_06_02_000001_create_calendario_connessioni_table.php

Seeder: firstOrCreate per idempotenza

  • DiocesiSeeder.php: Diocesi::create()Diocesi::firstOrCreate(['nome' => $d['nome']], $d)
  • ComuniSeeder.php: Comune::create()Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c)
  • DatabaseSeeder.php: User::create()User::firstOrCreate(['email' => 'admin@glastree.local'], [...]), con guardia su role_preset_id

Risultato: php artisan migrate --seed è ora completamente idempotente. Zero errori su 62 migration + 6 seeder.