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
LengthAwarePaginatorper 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— tabelletags+taggables - Model
Tag.php: morphToMany a 5 entity types, auto-slug su creating, color badge - Trait
HasTagsLight.php:tags()morphToMany, scopeswithAllTags/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[]=slugsupportato inIndividuoController@index,GruppoController@index,EventoController@index,DocumentoController@index,MailingListController@indexviawithAnyTagsscope - 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 comericerca.index→RicercaController@index - Sidebar link: "Ricerca per Tag" (icona
fas fa-tags) sotto la sezione Report, visibile con permessoindividui - Mass tag action for Documenti:
POST /documenti/mass-tag→DocumentoController@massTag— modal with tag selector + assign/remove radio, processes selected documents in chunks of 100 viasyncWithoutDetaching()(assign) ordetach()(remove) - Report tag columns:
tags.nomedisponibile 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 viawithAllTags/withAnyTags
Bug Fix Recenti
2026-06-07 — Colonne mancanti in eventi e gruppo_individuo
descrizione_eventoeis_incontro_gruppomancanti in install.sql → creata migration + ALTER TABLEruolo_nel_gruppomancante in install.sql per pivotgruppo_individuoVistaReport.php: aggiuntois_defaulta$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:
- 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/*'
- Aggiunte esclusioni per upload utente:
--exclude='storage/app/public'--exclude='storage/app/private'
- Creato
.gitignoreinstorage/framework/views/(mancante) - Istruzioni post-estrazione riscritte con:
mkdir -pper tutte le directory necessarie (cache, sessions, views, logs, public, backups, documenti, bootstrap/cache)chmod -R 775echown -R www-data:www-dataper permessiphp 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:
storage/framework/sessions/non scrivibile dal web serverAPP_URLin.envnon corrisponde all'URL realeSESSION_SECURE_COOKIE=truema 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/storageera 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:linkfalliva perché il symlink rotto esisteva già.- Sottodirectory
logos/,documenti/eventi/,avatars/gruppi/non create dalmkdir -p.
Fix:
- Aggiunto
--exclude='public/storage'al tar — il symlink va ricreato sul target. - Aggiunto
rm -f public/storageprima diphp artisan storage:linkper evitare conflitto. - Usato
ln -sf ../storage/app/public public/storage(invece diphp artisan storage:link --relativeche richiedesymfony/filesystemnon sempre presente). - Aggiunte al
mkdir -ple sottodirectory mancanti:storage/app/public/logos(logo upload)storage/app/public/documenti/eventi(event documenti)storage/app/private/avatars/gruppi(avatar gruppi)
- Aggiunta creazione
.gitignoreinstorage/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.phpresources/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:
- HTML checkbox
signature_enabledsenza hidden fallback: quando non spuntato, il campo non veniva inviato → non presente in$validated→updateOrCreatenon lo aggiornava mai afalse. - La vecchia pagina
/impostazioni/aveva l'handlershown.bs.tabper il tab Firma (#email-firma) in un solo punto su due, causando possibile mancata inizializzazione dell'editor Quill su alcuni flussi di navigazione. - La verifica nel controller controllava solo
imap_hosteemail_address— anche se signature/signature_enabled fallivano, l'utente vedeva "salvato con successo".
Fix:
resources/views/admin/email-settings/index.blade.php:268— Aggiunto<input type="hidden" name="signature_enabled" value="0">prima della checkboxresources/views/impostazioni/index.blade.php:603— Stesso hidden fallbackresources/views/impostazioni/index.blade.php:1633— Aggiunto#email-firmaal primo handlershown.bs.tab(il secondo handler esisteva già ma il primo no)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:
resources/views/admin/email-settings/index.blade.php:251— Aggiunto<input type="hidden" name="is_active" value="0">prima della checkboxresources/views/impostazioni/index.blade.php:586— Stesso hidden fallbackEmailSetting::getActive()ora riceve sempreis_activedal 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/callbackche gestisce automaticamente il callback OAuth e salva refresh token instorage_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 metodosyncAll()che itera tutte le connessioni attive (is_active = true), eseguesyncEvents()per ognuna, e restituisce JSON con risultati aggregati.routes/web.php: Aggiunta routePOST /calendario-connessioni/sync-all→syncAll(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
- Invia POST AJAX a
- Esposto
window.calendarglobalmente 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:
app/Models/AppSetting.php: Sostituitofile_exists(public_path('storage/' . $path))conStorage::disk('public')->exists($path)easset('storage/' . $path)conStorage::disk('public')->url($path). Il controllo ora avviene direttamente sustorage/app/public/(disk root), bypassando il symlink.app/Http/Controllers/ImpostazioniController.php: InuploadLogo(), aggiunta creazione automatica del symlinkpublic/storage → storage/app/publicse 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:
2026_06_02_000002_add_uid_esterno_to_eventi_table.php—uid_esterno2026_05_12_000002_change_ruolo_to_multi.php—ruolo_ids(up),ruolo_id(down)2026_05_28_000001_add_storage_disk_to_documenti.php—storage_disk2026_05_27_113246_add_repository_id_to_documenti.php—repository_id2026_05_26_000003_add_log_falliti_to_mailing_messaggi.php—log_falliti,mittente_nome,mittente_email2026_05_27_000002_add_cartella_id_to_documenti.php—cartella_id2026_05_16_000004_add_signature_enabled_to_email_settings.php—signature_enabled2026_05_16_000003_add_signature_to_email_settings.php—signature2024_01_01_000017_add_occorrenza_mese_to_eventi.php—occorrenza_mese2024_01_01_000016_add_eventi_recurrence_fields.php—giorno_mese,mesi_recorrenza,mese_annuale2026_05_10_000002_add_acl_tables.php—permissions(users)2026_05_12_142009_add_location_to_eventi_table.php—luogo_indirizzo,luogo_url_maps2026_05_12_000003_change_responsabile_to_multi.php—responsabile_ids(up),responsabile_id(down)2024_01_01_000023_add_is_default_to_viste_report_table.php—is_default2024_01_01_000024_add_user_id_to_mailing_lists_table.php—user_id2026_05_12_000001_create_ruoli_table.php—ruolo_id(gruppo_individuo) + data migration annessa2024_01_01_000021_add_user_id_to_documenti_table.php—user_id2024_01_01_000013_add_numero_documento_to_individui_table.php—numero_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-loadtags, supporto?tag[]=filter viawithAnyTags, pass$allTagsper filter barcreate(): pass$tagsper tag selectorstore(): validazionetags.* exists:tags,id, sync dopo create. Auto-fetch email daIndividuo::contatti()quando email non fornitaedit(): eager-loadtags, pass$tags+$selectedTagsupdate(): validazionetags.*, sync se presente, detach se assente. Auto-fetch email daIndividuo::contatti()quando email non fornita. Diff logica:$toRemove = array_diff($existingIds, $newIds)corretto — rimuove solo contatti deselezionati/esplicitamente rimossishow(): eager-loadtags
MailingList — Views:
index.blade.php: aggiunto@include('partials._tag-filter-bar'), colonna "Tag" con badge colorati cliccabilicreate.blade.php: incluso_tag-selectordopo descrizioneedit.blade.php: incluso_tag-selectordopo descrizione (prima della sezione contatti). Submit handler include#individui-select.selectedOptionsincontatti_jsonconemail: ''(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(): supportaconfig['tag_filter'](array di tag IDs) +config['tag_filter_mode']('any'/'all')storeCustom(): ora salvatag_filteretag_filter_modenella config del report salvato- Carica
$allTagsnell'index e passa alla vista - Converte IDs → slugs via
Tag::whereIn('id', ...)->pluck('slug'), applicawithAllTags/withAnyTags - Eager-load
tagsautomaticamente quando la colonnatags.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']etotals['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:
- Typo:
ind.cognameinvece diind.cognome— il campo Cognome nella tabella era sempre vuoto/undefined (API restituiscecognome) - Email gate:
if (ind.emails && ind.emails.length > 0)filtro bloccante — individui senza email salvata incontattivenivano SILENZIOSAMENTE scartati, mai aggiunti alla tabella
Fix:
ind.cogname→ind.cognome(righe 227 e 256)- Rimossa condizione
if (ind.emails...)— ora anche individui senza email vengono aggiunti alla tabella email: ind.emails[0]→email: (ind.emails && ind.emails.length > 0) ? ind.emails[0] : ''— email vuota se nessuna presente- 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@phpnel partialtable-settings.blade.phpa 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) daindividuiegruppi— duplicate rispetto al nuovo modal unificato - [DONE] Rimosse vecchie funzioni JS (
saveVista(),toggleColumn(),showSaveVistaModal()) daindividuiegruppi - [DONE] Individui:
$allColumnsespanse 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, ...}insortTable()eapplyColumnFilter()) - [DONE] I controller per eventi, documenti, mailing-liste hanno già
$tableColumnscon flagvisible— stesso meccanismo di gruppi. Nessuna modifica necessaria. - [DONE] Fix: pulsante edit vista usa
data-*attributi invece dionclickconaddslashes(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) ecolonne_ordine(json) aviste_report - Guardia
Schema::hasColumnper compatibilità cross-DB (MySQL ↔ SQLite) - Già eseguita in batch 38
Backend:
VistaReport.php:$fillable+$casts(array) per entrambi i nuovi campiVistaReportController@store/@update: validazione estesa concolonne_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:$allColumnsespanse con tutte le colonne DB disponibili (da 6 a 17)IndividuoController: aggiuntouse App\Models\VistaReportper la query$userVistas
Frontend JS (public/js/column-manager.js):
- Classe
ColumnManager: resize via mouse drag su handle, reorder via HTML5 DnD, persistenza larghezze insessionStorage, fallback davista-dataDOM 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@phpblock — ora usa$userVistaspassato dal controller - Non usa più
Auth::id()direttamente — riceve$userVistasgià popolato
Views — tutte e 5 aggiornate:
individui/index.blade.php:$allColumnsnella 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 sudata-column sortTable()eapplyColumnFilter()ora usanogetColumnIndex()invece di hardcoded mappa
gruppi/index.blade.php:- RIMOSSE modal legacy:
#saveVistaModal,#vistaListModal - RIMOSSO pulsante "Salva Vista" legacy
- RIMOSSE funzioni JS legacy:
showSaveVistaModal(),saveVista()
- RIMOSSE modal legacy:
eventi/index.blade.php: colonne dinamiche via$visibleColumns,@section('scripts')con ColumnManager +initTableSettings()mailing-liste/index.blade.php: stessa strutturadocumenti/index.blade.php: ColumnManager init (non reorderable), column-manager.js incluso- Tutti i
<th>hannodata-columnattributo per mapping JS - Tutte chiamano
initTableSettings()doponew ColumnManager()nelDOMContentLoaded
Performance fix:
- Rimossa query
VistaReport::where(...)->get()dal@phpblock intable-settings.blade.php(1 query extra per ogni pagina load su 5 entità) - Spostata nei 5 controller come
$userVistaspassata viacompact() - 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
$visibleColumnsquando$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(chiamavashowSaveVistaModal()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:
- Sostituito
onclickcondata-*attributi (data-vista-id,data-vista-nome,data-vista-default,data-vista-colonne) usando{{ }}di Blade (che applicahtmlspecialchars, codificando"→"sicuro in HTML). editVista()ora accetta un array direttamente (non più JSON string da parsare).- Click handler registrato in
initTableSettings()viadocument.querySelectorAll('.edit-vista-btn')+dataset(browser decodifica automaticamente"→").
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:
VistaReportController@update(linea 101-103): aggiuntoif ($request->expectsJson()) { return response()->json([...]); }— così il fetch riceve JSON 200, non un redirect 302.- Fetch headers in
table-settings.blade.php: aggiunto'Accept': 'application/json'— necessario perchéexpectsJson()controlla l'headerAccept, nonContent-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.php2024_01_01_000003_create_individui_table.php2024_01_01_000004_create_contatti_table.php2024_01_01_000005_create_gruppi_table.php2024_01_01_000006_create_gruppo_individuo_table.php2024_01_01_000007_create_eventi_table.php2024_01_01_000008_create_documenti_table.php2024_01_01_000009_create_mailing_tables.php2024_01_01_000010_create_notifiche_table.php2024_01_01_000011_create_users_table.php2024_01_01_000012_create_cache_and_jobs_tables.php2024_01_01_000019_create_eventi_documenti_table.php2024_01_01_000022_create_table_viste_report.php2026_05_10_000001_create_tipologie_documenti_table.php2026_05_10_000002_add_acl_tables.php2026_05_10_125210_create_permission_tables.php(usa$tableNames[...]dinamici)2026_05_11_000001_create_email_settings_table.php2026_05_11_000002_create_email_folders_table.php2026_05_11_000003_create_email_messages_table.php2026_05_11_000007_create_email_attachments_table.php2026_05_12_000001_create_ruoli_table.php2026_05_25_000001_create_report_custom_table.php2026_05_26_000001_create_tipologie_eventi_table.php2026_05_26_000002_create_sender_accounts_table.php2026_05_27_000001_create_documenti_cartelle_table.php2026_05_27_113245_create_storage_repositories_table.php2026_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 surole_preset_id
Risultato: php artisan migrate --seed è ora completamente idempotente. Zero errori su 62 migration + 6 seeder.