setup viste

This commit is contained in:
2026-06-10 14:03:43 +02:00
parent 0dd567cf7a
commit 43a2c81312
17 changed files with 1306 additions and 582 deletions
+88 -22
View File
@@ -291,26 +291,92 @@ if (!empty($contatto['individuo_id'])) { create }
``` ```
## Prossimi Passi ## Prossimi Passi
- [DONE] Aggiungere filtro Tag nella vista Documenti — già presente (tag filter bar + colonna Tag in lista/griglia) - [DONE] ... (existing items)
- [DONE] Gestire il caso `tag[]` vuoto — già gestito da `$request->filled('tag')` in tutti i 4 controller index
- [DONE] MailingList — tag support completato (controller, index/create/edit/show views, filter bar)
- [DONE] Report — tag columns in ReportColumnRegistry + tag filter in custom reports + UI multi-select
- [DONE] Ricerca per Tag — MailingList integrata nei risultati
- [DONE] `storeCustom()` ora salva correttamente `tag_filter` e `tag_filter_mode`
- [DONE] `MailingListController@store/update` — auto-fetch email da Individuo quando non fornita
- [DONE] Verificata presenza `@stack('scripts')` nel layout — presente a riga 305 (prima di `@yield('scripts')`)
- [DONE] Verificata mass tag action Documenti (`DocumentoController@massTag`) — funzionante
- [DONE] Verificato: tutti i 5 entity controllers passano `$allTags` alle viste index
- [DONE] La logica `$toRemove = array_diff($existingIds, $newIds)` in `update()` è corretta — rimuove solo contatti che l'utente ha esplicitamente deselezionato o rimosso dalla tabella. Non necessita modifiche.
- [DONE] SweetAlert2 conferma su salvataggio mailing list (edit.blade.php) quando ci sono contatti senza email
- [DONE] Normalizzata `invia()` da `flatMap`+`pluck` a `map`+`first()` (1 email per individuo, come `invioElabora()`)
- [DONE] Aggiunto logging contatti saltati in `invia()` e `invioElabora()` (quando senza email)
- [DONE] Fix: contattiCaricati initialization ora include codice_id/cognome/nome da righe DOM — contatti esistenti non più blank dopo Carica contatti
- [DONE] Fix: EmailController@destroy ora redirect a inbox invece che back() (non rimane su email cancellata)
- [DONE] Mass action Individui: Assegna Gruppo (route + controller + modal con select gruppi + Assign/Remove radio)
- [DONE] Mass action Individui: Assegna Tag (route + controller + modal con _tag-selector)
- [DONE] Mass action Gruppi: Assegna Tag (route + controller + modal con _tag-selector)
- [DONE] Mass action Gruppi: Elimina (route + controller + modal con conferma, detach membri + delete)
- [DONE] Fix: IndividuoController@massGruppo e @massTag ora convertono stringa ids in array (non funzionavano perché hidden input invia stringa, non array)
- [DONE] Ricerca per Tag multipli: controller accetta `?tag[]=slug1&tag[]=slug2`, usa `withAllTags` (AND logic) per 2+ tag. Vista aggiornata: checkbox cliccabili per multi-selezione, info boxes unificate, "Vedi tutti" passa tutti i tag
- Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag - 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`.
+35 -1
View File
@@ -92,11 +92,45 @@ class DocumentoController extends Controller
'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]), 'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
]); ]);
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
->where('tipo', 'documenti')
->where('is_default', true)
->first();
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
->where('tipo', 'documenti')
->orderBy('is_default', 'desc')
->orderBy('nome')
->get();
$allColumnDefs = [
['key' => 'nome_file', 'label' => 'Nome'],
['key' => 'tipologia', 'label' => 'Tipologia'],
['key' => 'dimensione', 'label' => 'Dimensione'],
['key' => 'visibilita', 'label' => 'Contesto'],
['key' => 'tag', 'label' => 'Tag'],
['key' => 'created_at', 'label' => 'Data'],
];
$defaultVisible = ['nome_file', 'tipologia', 'dimensione', 'visibilita', 'tag', 'created_at'];
$visibleColumns = $vista && !empty($vista->colonne_visibili)
? $vista->colonne_visibili
: $defaultVisible;
$tableColumns = array_map(fn($col) => [
'key' => $col['key'],
'label' => $col['label'],
'visible' => in_array($col['key'], $visibleColumns),
], $allColumnDefs);
$entityType = 'documenti';
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
return view('documenti.index', compact( return view('documenti.index', compact(
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle', 'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
'currentRepo', 'repositories', 'currentRepo', 'repositories',
'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
'cartelleMoveOptions', 'allTags' 'cartelleMoveOptions', 'allTags',
'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths',
'userVistas'
)); ));
} }
+36 -1
View File
@@ -55,7 +55,42 @@ class EventoController extends Controller
$gruppi = Gruppo::orderBy('nome')->get(); $gruppi = Gruppo::orderBy('nome')->get();
$allTags = Tag::orderBy('name')->get(); $allTags = Tag::orderBy('name')->get();
return view('eventi.index', compact('eventi', 'gruppi', 'allTags')); $vista = \App\Models\VistaReport::where('user_id', auth()->id())
->where('tipo', 'eventi')
->where('is_default', true)
->first();
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
->where('tipo', 'eventi')
->orderBy('is_default', 'desc')
->orderBy('nome')
->get();
$allColumnDefs = [
['key' => 'nome_evento', 'label' => 'Nome Evento'],
['key' => 'descrizione_evento', 'label' => 'Descrizione'],
['key' => 'tipo_evento', 'label' => 'Tipologia'],
['key' => 'tipo_recorrenza', 'label' => 'Ricorrenza'],
['key' => 'gruppi', 'label' => 'Gruppi'],
['key' => 'responsabili', 'label' => 'Responsabili'],
['key' => 'tag', 'label' => 'Tag'],
['key' => 'data_specifica', 'label' => 'Data'],
['key' => 'created_at', 'label' => 'Creato'],
];
$defaultVisible = ['nome_evento', 'descrizione_evento', 'tipo_evento', 'tipo_recorrenza', 'gruppi', 'responsabili', 'tag', 'created_at'];
$visibleColumns = $vista && !empty($vista->colonne_visibili)
? $vista->colonne_visibili
: $defaultVisible;
$tableColumns = array_map(fn($col) => [
'key' => $col['key'],
'label' => $col['label'],
'visible' => in_array($col['key'], $visibleColumns),
], $allColumnDefs);
$entityType = 'eventi';
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
return view('eventi.index', compact('eventi', 'gruppi', 'allTags', 'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths', 'userVistas'));
} }
public function create(Request $request) public function create(Request $request)
+34 -2
View File
@@ -28,7 +28,27 @@ class GruppoController extends Controller
->where('is_default', true) ->where('is_default', true)
->first(); ->first();
$allColumns = ['nome', 'descrizione', 'diocesi', 'livello', 'padre', 'membri', 'responsabili', 'indirizzo', 'citta', 'telefono', 'email', 'tag']; $userVistas = \App\Models\VistaReport::where('user_id', $user->id)
->where('tipo', 'gruppi')
->orderBy('is_default', 'desc')
->orderBy('nome')
->get();
$allColumnDefs = [
['key' => 'nome', 'label' => 'Nome'],
['key' => 'descrizione', 'label' => 'Descrizione'],
['key' => 'diocesi', 'label' => 'Diocesi'],
['key' => 'livello', 'label' => 'Livello'],
['key' => 'padre', 'label' => 'Gruppo Padre'],
['key' => 'membri', 'label' => 'Membri'],
['key' => 'responsabili', 'label' => 'Responsabili'],
['key' => 'indirizzo', 'label' => 'Indirizzo'],
['key' => 'citta', 'label' => 'Città'],
['key' => 'telefono', 'label' => 'Telefono'],
['key' => 'email', 'label' => 'Email'],
['key' => 'tag', 'label' => 'Tag'],
];
$allColumns = array_column($allColumnDefs, 'key');
$defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili', 'tag']; $defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili', 'tag'];
if ($vista && !empty($vista->colonne_visibili)) { if ($vista && !empty($vista->colonne_visibili)) {
@@ -37,6 +57,14 @@ class GruppoController extends Controller
$visibleColumns = $defaultVisible; $visibleColumns = $defaultVisible;
} }
$tableColumns = array_map(fn($col) => [
'key' => $col['key'],
'label' => $col['label'],
'visible' => in_array($col['key'], $visibleColumns),
], $allColumnDefs);
$entityType = 'gruppi';
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
$gruppiQuery = Gruppo::with(['diocesi', 'parent', 'children', 'individui', 'avatar', 'tags']); $gruppiQuery = Gruppo::with(['diocesi', 'parent', 'children', 'individui', 'avatar', 'tags']);
if (request()->filled('tag')) { if (request()->filled('tag')) {
@@ -79,7 +107,11 @@ class GruppoController extends Controller
'visibleColumns', 'visibleColumns',
'viewMode', 'viewMode',
'vista', 'vista',
'allTags' 'allTags',
'tableColumns',
'entityType',
'columnWidths',
'userVistas'
)); ));
} }
+35 -9
View File
@@ -5,14 +5,15 @@ namespace App\Http\Controllers;
use App\Models\Individuo; use App\Models\Individuo;
use App\Models\Contatto; use App\Models\Contatto;
use App\Models\Documento; use App\Models\Documento;
use App\Models\Gruppo; use App\Models\Gruppo;
use App\Models\Comune; use App\Models\Comune;
use App\Models\Diocesi; use App\Models\Diocesi;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Http\Request; use App\Models\VistaReport;
use Illuminate\Support\Facades\Storage; use Illuminate\Http\Request;
use Illuminate\Validation\Rule; use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse; use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;
class IndividuoController extends Controller class IndividuoController extends Controller
{ {
@@ -50,9 +51,20 @@ class IndividuoController extends Controller
['key' => 'codice', 'label' => 'Codice'], ['key' => 'codice', 'label' => 'Codice'],
['key' => 'cognome', 'label' => 'Cognome'], ['key' => 'cognome', 'label' => 'Cognome'],
['key' => 'nome', 'label' => 'Nome'], ['key' => 'nome', 'label' => 'Nome'],
['key' => 'data_nascita', 'label' => 'Data Nascita'],
['key' => 'genere', 'label' => 'Genere'],
['key' => 'indirizzo', 'label' => 'Indirizzo'],
['key' => 'cap', 'label' => 'CAP'],
['key' => 'citta', 'label' => 'Città'],
['key' => 'provincia', 'label' => 'Provincia'],
['key' => 'tipo_documento', 'label' => 'Tipo Documento'],
['key' => 'numero_documento', 'label' => 'Numero Documento'],
['key' => 'scadenza_documento', 'label' => 'Scadenza Documento'],
['key' => 'email', 'label' => 'Email'], ['key' => 'email', 'label' => 'Email'],
['key' => 'telefono', 'label' => 'Telefono'], ['key' => 'telefono', 'label' => 'Telefono'],
['key' => 'note', 'label' => 'Note'],
['key' => 'tag', 'label' => 'Tag'], ['key' => 'tag', 'label' => 'Tag'],
['key' => 'created_at', 'label' => 'Creato il'],
]; ];
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono', 'tag']; $defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono', 'tag'];
@@ -60,8 +72,22 @@ class IndividuoController extends Controller
? $vista->colonne_visibili ? $vista->colonne_visibili
: $defaultVisible; : $defaultVisible;
$entityType = 'individui';
$tableColumns = array_map(fn($col) => [
'key' => $col['key'],
'label' => $col['label'],
'visible' => in_array($col['key'], $visibleColumns),
], $allColumns);
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
$userVistas = VistaReport::where('user_id', auth()->id())
->where('tipo', 'individui')
->orderBy('is_default', 'desc')
->orderBy('nome')
->get();
$gruppi = Gruppo::orderBy('nome')->get(['id', 'nome']); $gruppi = Gruppo::orderBy('nome')->get(['id', 'nome']);
return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns', 'allTags', 'gruppi')); return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns', 'allTags', 'gruppi', 'entityType', 'tableColumns', 'columnWidths', 'userVistas'));
} }
public function exportCSV(Request $request) public function exportCSV(Request $request)
+34 -1
View File
@@ -20,7 +20,40 @@ class MailingListController extends Controller
$mailingLists = $query->orderBy('nome')->get(); $mailingLists = $query->orderBy('nome')->get();
$allTags = Tag::orderBy('name')->get(); $allTags = Tag::orderBy('name')->get();
return view('mailing-liste.index', compact('mailingLists', 'allTags'));
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
->where('tipo', 'mailing-liste')
->where('is_default', true)
->first();
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
->where('tipo', 'mailing-liste')
->orderBy('is_default', 'desc')
->orderBy('nome')
->get();
$allColumnDefs = [
['key' => 'nome', 'label' => 'Nome'],
['key' => 'descrizione', 'label' => 'Descrizione'],
['key' => 'contatti_count', 'label' => 'Contatti'],
['key' => 'tag', 'label' => 'Tag'],
['key' => 'attiva', 'label' => 'Stato'],
['key' => 'created_at', 'label' => 'Creata il'],
];
$defaultVisible = ['nome', 'descrizione', 'contatti_count', 'tag', 'attiva', 'created_at'];
$visibleColumns = $vista && !empty($vista->colonne_visibili)
? $vista->colonne_visibili
: $defaultVisible;
$tableColumns = array_map(fn($col) => [
'key' => $col['key'],
'label' => $col['label'],
'visible' => in_array($col['key'], $visibleColumns),
], $allColumnDefs);
$entityType = 'mailing-liste';
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
return view('mailing-liste.index', compact('mailingLists', 'allTags', 'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths', 'userVistas'));
} }
public function create() public function create()
+11 -1
View File
@@ -33,9 +33,11 @@ class VistaReportController extends Controller
$this->authorizeWrite('viste'); $this->authorizeWrite('viste');
$data = $request->validate([ $data = $request->validate([
'nome' => 'required|string|max:255', 'nome' => 'required|string|max:255',
'tipo' => 'required|in:individui,gruppi,documenti,eventi', 'tipo' => 'required|in:individui,gruppi,documenti,eventi,mailing-liste',
'colonne_visibili' => 'nullable|array', 'colonne_visibili' => 'nullable|array',
'colonne_ordinamento' => 'nullable|array', 'colonne_ordinamento' => 'nullable|array',
'colonne_larghezze' => 'nullable|array',
'colonne_ordine' => 'nullable|array',
'filtri' => 'nullable|array', 'filtri' => 'nullable|array',
'ricerca' => 'nullable|string', 'ricerca' => 'nullable|string',
'is_default' => 'nullable|boolean', 'is_default' => 'nullable|boolean',
@@ -55,6 +57,8 @@ class VistaReportController extends Controller
'tipo' => $data['tipo'], 'tipo' => $data['tipo'],
'colonne_visibili' => $data['colonne_visibili'] ?? null, 'colonne_visibili' => $data['colonne_visibili'] ?? null,
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null, 'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
'colonne_larghezze' => $data['colonne_larghezze'] ?? null,
'colonne_ordine' => $data['colonne_ordine'] ?? null,
'filtri' => $data['filtri'] ?? null, 'filtri' => $data['filtri'] ?? null,
'ricerca' => $data['ricerca'] ?? null, 'ricerca' => $data['ricerca'] ?? null,
'is_default' => $isDefault, 'is_default' => $isDefault,
@@ -78,6 +82,8 @@ class VistaReportController extends Controller
'nome' => 'required|string|max:255', 'nome' => 'required|string|max:255',
'colonne_visibili' => 'nullable|array', 'colonne_visibili' => 'nullable|array',
'colonne_ordinamento' => 'nullable|array', 'colonne_ordinamento' => 'nullable|array',
'colonne_larghezze' => 'nullable|array',
'colonne_ordine' => 'nullable|array',
'filtri' => 'nullable|array', 'filtri' => 'nullable|array',
'ricerca' => 'nullable|string', 'ricerca' => 'nullable|string',
'is_default' => 'nullable|boolean', 'is_default' => 'nullable|boolean',
@@ -92,6 +98,10 @@ class VistaReportController extends Controller
$vistaReport->update($data); $vistaReport->update($data);
if ($request->expectsJson()) {
return response()->json(['success' => true, 'message' => 'Vista aggiornata.']);
}
return back()->with('success', 'Vista aggiornata.'); return back()->with('success', 'Vista aggiornata.');
} }
+4 -1
View File
@@ -10,13 +10,16 @@ class VistaReport extends Model
protected $table = 'viste_report'; protected $table = 'viste_report';
protected $fillable = [ protected $fillable = [
'user_id', 'nome', 'tipo', 'colonne_visibili', 'user_id', 'nome', 'tipo', 'colonne_visibili',
'colonne_ordinamento', 'filtri', 'ricerca', 'is_default' 'colonne_ordinamento', 'filtri', 'ricerca', 'is_default',
'colonne_larghezze', 'colonne_ordine',
]; ];
protected $casts = [ protected $casts = [
'colonne_visibili' => 'array', 'colonne_visibili' => 'array',
'colonne_ordinamento' => 'array', 'colonne_ordinamento' => 'array',
'filtri' => 'array', 'filtri' => 'array',
'colonne_larghezze' => 'array',
'colonne_ordine' => 'array',
]; ];
public function user(): BelongsTo public function user(): BelongsTo
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('viste_report', function (Blueprint $table) {
if (!Schema::hasColumn('viste_report', 'colonne_larghezze')) {
$table->json('colonne_larghezze')->nullable()->after('colonne_visibili');
}
if (!Schema::hasColumn('viste_report', 'colonne_ordine')) {
$table->json('colonne_ordine')->nullable()->after('colonne_larghezze');
}
});
}
public function down(): void
{
Schema::table('viste_report', function (Blueprint $table) {
if (Schema::hasColumn('viste_report', 'colonne_larghezze')) {
$table->dropColumn('colonne_larghezze');
}
if (Schema::hasColumn('viste_report', 'colonne_ordine')) {
$table->dropColumn('colonne_ordine');
}
});
}
};
+384
View File
@@ -0,0 +1,384 @@
class ColumnManager {
constructor(tableId, options = {}) {
this.tableId = tableId;
this.table = document.getElementById(tableId);
if (!this.table) return;
this.options = Object.assign({
entityType: 'individui',
storageKey: null,
resizable: true,
reorderable: true,
persistWidths: true,
persistOrder: true,
}, options);
this.columns = [];
this.columnWidths = {};
this.columnOrder = [];
this.dragState = null;
this.resizeState = null;
this.init();
}
init() {
this.loadState();
this.initColumns();
if (this.options.resizable) this.initResize();
if (this.options.reorderable) this.initReorder();
this.applyState();
}
initColumns() {
const ths = this.table.querySelectorAll('thead th');
ths.forEach((th, idx) => {
const colKey = th.dataset.column || `col_${idx}`;
const col = {
index: idx,
key: colKey,
element: th,
width: th.style.width || '',
visible: th.style.display !== 'none',
label: th.textContent.trim(),
};
this.columns.push(col);
});
}
loadState() {
if (this.options.storageKey) {
try {
const saved = sessionStorage.getItem(this.options.storageKey);
if (saved) {
const data = JSON.parse(saved);
this.columnWidths = data.widths || {};
this.columnOrder = data.order || [];
return;
}
} catch (e) {}
}
const vistaEl = document.getElementById('vista-data');
if (vistaEl && vistaEl.textContent && vistaEl.textContent !== 'null') {
try {
const vista = JSON.parse(vistaEl.textContent);
this.columnWidths = vista.colonne_larghezze || {};
this.columnOrder = vista.colonne_ordine || [];
} catch (e) {}
}
}
persistState() {
if (!this.options.storageKey) return;
try {
sessionStorage.setItem(this.options.storageKey, JSON.stringify({
widths: this.columnWidths,
order: this.columnOrder,
}));
} catch (e) {}
}
applyState() {
if (this.columnOrder.length > 0) {
this.reorderColumns(this.columnOrder, false);
}
if (Object.keys(this.columnWidths).length > 0) {
this.applyWidths();
}
}
applyWidths() {
this.columns.forEach(col => {
const w = this.columnWidths[col.key];
if (w) {
col.element.style.width = w;
}
});
}
initResize() {
this.table.querySelectorAll('thead th').forEach(th => {
const handle = document.createElement('div');
handle.className = 'col-resize-handle';
handle.style.cssText = 'position:absolute;right:0;top:0;bottom:0;width:5px;cursor:col-resize;z-index:10;';
th.style.position = 'relative';
th.appendChild(handle);
handle.addEventListener('mousedown', (e) => {
e.preventDefault();
e.stopPropagation();
const thEl = th;
const startX = e.clientX;
const startWidth = thEl.offsetWidth;
const onMouseMove = (ev) => {
const diff = ev.clientX - startX;
const newWidth = Math.max(30, startWidth + diff);
thEl.style.width = newWidth + 'px';
const colKey = thEl.dataset.column;
if (colKey) {
this.columnWidths[colKey] = newWidth + 'px';
this.persistState();
}
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
});
});
}
initReorder() {
const thead = this.table.querySelector('thead');
if (!thead) return;
let dragCol = null;
let dragIndex = -1;
let placeholder = null;
thead.querySelectorAll('th').forEach(th => {
if (th.querySelector('.col-resize-handle')) return;
th.draggable = true;
th.addEventListener('dragstart', (e) => {
dragCol = th;
dragIndex = Array.from(thead.querySelectorAll('th')).indexOf(th);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', '');
th.classList.add('dragging');
});
th.addEventListener('dragend', () => {
th.classList.remove('dragging');
if (placeholder && placeholder.parentNode) {
placeholder.parentNode.removeChild(placeholder);
}
dragCol = null;
dragIndex = -1;
placeholder = null;
});
th.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
if (!dragCol || dragCol === th) return;
if (!placeholder) {
placeholder = document.createElement('th');
placeholder.style.cssText = 'border:2px dashed #007bff;background:rgba(0,123,255,0.1);width:0;padding:0;';
dragCol.parentNode.insertBefore(placeholder, dragCol);
}
const rect = th.getBoundingClientRect();
const midX = rect.left + rect.width / 2;
const parent = th.parentNode;
if (e.clientX < midX) {
parent.insertBefore(placeholder, th);
} else {
parent.insertBefore(placeholder, th.nextSibling);
}
});
th.addEventListener('drop', (e) => {
e.preventDefault();
if (!dragCol || dragCol === th) return;
const parent = th.parentNode;
const allTh = Array.from(parent.querySelectorAll('th')).filter(el => el !== placeholder);
const fromIdx = allTh.indexOf(dragCol);
let toIdx = allTh.indexOf(th);
if (placeholder) {
toIdx = Array.from(parent.children).indexOf(placeholder);
if (placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
placeholder = null;
}
if (fromIdx < 0) return;
const actualRows = this.table.querySelectorAll('tr');
actualRows.forEach(row => {
const cells = row.querySelectorAll('td, th');
if (cells[fromIdx] && cells[toIdx]) {
if (toIdx > fromIdx) {
row.insertBefore(cells[fromIdx], cells[toIdx].nextSibling);
} else {
row.insertBefore(cells[fromIdx], cells[toIdx]);
}
}
});
this.updateColumnOrder();
});
});
}
updateColumnOrder() {
const ths = this.table.querySelectorAll('thead th');
this.columnOrder = [];
const keysInOrder = [];
ths.forEach((th, idx) => {
const colKey = th.dataset.column || `col_${idx}`;
keysInOrder.push(colKey);
this.columnOrder.push(colKey);
});
this.columns.sort((a, b) => keysInOrder.indexOf(a.key) - keysInOrder.indexOf(b.key));
this.columns.forEach((col, idx) => col.index = idx);
this.persistState();
}
reorderColumns(keyOrder, persist = true) {
if (!keyOrder || keyOrder.length === 0) return;
const thead = this.table.querySelector('thead');
if (!thead) return;
const rows = this.table.querySelectorAll('tr');
const keyToThIndex = {};
const allTh = Array.from(thead.querySelectorAll('th'));
allTh.forEach((th, idx) => {
const key = th.dataset.column || `col_${idx}`;
keyToThIndex[key] = idx;
});
rows.forEach(row => {
const cells = row.querySelectorAll('td, th');
const reordered = [];
let lastRef = null;
keyOrder.forEach(key => {
const srcIdx = keyToThIndex[key];
if (srcIdx !== undefined && cells[srcIdx]) {
reordered.push(cells[srcIdx]);
}
});
reordered.forEach(cell => {
if (lastRef) {
row.insertBefore(cell, lastRef.nextSibling);
} else {
row.insertBefore(cell, row.firstChild);
}
lastRef = cell;
});
});
this.columnOrder = [...keyOrder];
this.columns.sort((a, b) => keyOrder.indexOf(a.key) - keyOrder.indexOf(b.key));
this.columns.forEach((col, idx) => col.index = idx);
if (persist) this.persistState();
}
setVisible(columnKey, visible) {
const th = this.table.querySelector(`th[data-column="${columnKey}"]`);
if (th) th.style.display = visible ? '' : 'none';
const colIdx = this.columns.findIndex(c => c.key === columnKey);
if (colIdx >= 0) {
this.columns[colIdx].visible = visible;
const rows = this.table.querySelectorAll('tbody tr, thead tr');
rows.forEach(row => {
const cells = row.querySelectorAll('td, th');
if (cells[colIdx]) {
cells[colIdx].style.display = visible ? '' : 'none';
}
});
}
}
getColumnWidths() {
return { ...this.columnWidths };
}
getColumnOrder() {
return [...this.columnOrder];
}
getVisibleColumns() {
return this.columns.filter(c => c.visible).map(c => c.key);
}
getColumnMetadata() {
return {
larghezze: this.getColumnWidths(),
ordine: this.getColumnOrder(),
visibili: this.getVisibleColumns(),
};
}
printWithSettings() {
const printWindow = window.open('', '_blank');
if (!printWindow) return;
const styles = Array.from(document.styleSheets)
.filter(sheet => {
try { return sheet.cssRules; } catch (e) { return false; }
})
.map(sheet => Array.from(sheet.cssRules).map(rule => rule.cssText).join('\n'))
.join('\n');
const printStyles = `
@media print {
body { font-family: 'DejaVu Sans', Arial, sans-serif; font-size: 10pt; }
.no-print { display: none !important; }
.card { border: none !important; box-shadow: none !important; }
.table { border-collapse: collapse; width: 100%; }
.table th { background: #f5f5f5 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.table th, .table td { border: 1px solid #ddd; padding: 4px 6px; }
a { text-decoration: none; color: #000; }
.badge { border: 1px solid #999; padding: 1px 4px; font-size: 8pt; }
.pagination, .card-header .card-tools { display: none !important; }
.page-break { page-break-before: always; }
}
`;
const title = document.title || 'Stampa';
const pageTitle = document.querySelector('.page_title, .card-title h1, h1')?.textContent?.trim() || title;
let content = '';
const printArea = this.table?.cloneNode(true);
if (printArea) {
printArea.querySelectorAll('.col-resize-handle').forEach(h => h.remove());
content = printArea.outerHTML;
} else {
content = document.getElementById(this.tableId)?.outerHTML || '';
}
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>${title}</title>
<style>${styles}${printStyles}</style>
</head>
<body>
<div class="no-print" style="text-align:right;margin-bottom:10px;">
<button onclick="window.print()" style="padding:6px 16px;font-size:12pt;">🖨️ Stampa</button>
<button onclick="window.close()" style="padding:6px 16px;font-size:12pt;">✖ Chiudi</button>
</div>
<h2 style="margin-bottom:16px;">${pageTitle}</h2>
${content}
<p style="text-align:center;margin-top:16px;color:#999;font-size:8pt;">
Stampato il ${new Date().toLocaleDateString('it-IT')}${document.location.href}
</p>
</body>
</html>
`);
printWindow.document.close();
}
}
window.ColumnManager = ColumnManager;
+36 -9
View File
@@ -5,6 +5,9 @@
@php @php
$canWriteDocumenti = Auth::user()->canManage('documenti'); $canWriteDocumenti = Auth::user()->canManage('documenti');
$canDeleteDocumenti = Auth::user()->canDelete('documenti'); $canDeleteDocumenti = Auth::user()->canDelete('documenti');
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
$visibleColumnsJson = json_encode($visibleColumns ?? []);
$tableColumnsJson = json_encode($tableColumns ?? []);
@endphp @endphp
@section('css') @section('css')
@@ -293,21 +296,21 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- View: Lista --}} {{-- View: Lista --}}
<div id="viewList" style="display:none;"> <div id="viewList" style="display:none;">
@if($sottoCartelle->count() > 0 || $documenti->count() > 0) @if($sottoCartelle->count() > 0 || $documenti->count() > 0)
<table class="table table-bordered table-hover table-striped mb-0"> <table class="table table-bordered table-hover table-striped mb-0" id="documenti-table">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
@if($canDeleteDocumenti) @if($canDeleteDocumenti)
<th style="width: 40px;"> <th style="width: 40px;" data-column="select">
<input type="checkbox" id="selectAll" onchange="toggleAll(this)"> <input type="checkbox" id="selectAll" onchange="toggleAll(this)">
</th> </th>
@endif @endif
<th>Nome</th> <th data-column="nome">Nome</th>
<th style="width: 100px;">Tipologia</th> <th style="width: 100px;" data-column="tipologia">Tipologia</th>
<th style="width: 90px;">Dimensione</th> <th style="width: 90px;" data-column="dimensione">Dimensione</th>
<th style="width: 120px;">Contesto</th> <th style="width: 120px;" data-column="visibilita">Contesto</th>
<th style="width: 100px;">Tag</th> <th style="width: 100px;" data-column="tag">Tag</th>
<th style="width: 110px;">Data</th> <th style="width: 110px;" data-column="created_at">Data</th>
<th style="width: 130px;">Azioni</th> <th style="width: 130px;" data-column="azioni">Azioni</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -515,6 +518,10 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
</div> </div>
</form>{{-- /massForm --}} </form>{{-- /massForm --}}
@include('partials.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
{{-- MODAL: Nuova Cartella --}} {{-- MODAL: Nuova Cartella --}}
<div class="modal fade" id="newFolderModal" tabindex="-1" role="dialog"> <div class="modal fade" id="newFolderModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-scrollable" role="document"> <div class="modal-dialog modal-dialog-scrollable" role="document">
@@ -938,6 +945,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
@endsection @endsection
@section('scripts') @section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script> <script>
const individui = @json($individui->map(fn($i) => ['id' => $i->id, 'label' => $i->cognome . ' ' . $i->nome])); const individui = @json($individui->map(fn($i) => ['id' => $i->id, 'label' => $i->cognome . ' ' . $i->nome]));
const gruppi = @json($gruppi->map(fn($g) => ['id' => $g->id, 'label' => $g->nome])); const gruppi = @json($gruppi->map(fn($g) => ['id' => $g->id, 'label' => $g->nome]));
@@ -1941,5 +1949,24 @@ function deleteFolder(id, nome) {
} }
}); });
} }
document.addEventListener('DOMContentLoaded', function () {
const table = document.getElementById('documenti-table');
if (table) {
window.columnManager = new ColumnManager('documenti-table', {
entityType: 'documenti',
storageKey: 'cm_documenti',
resizable: true,
reorderable: false,
});
initTableSettings();
}
});
window.printWithCurrentSettings = function () {
if (window.columnManager) {
window.columnManager.printWithSettings();
}
};
</script> </script>
@endsection @endsection
+74 -11
View File
@@ -5,6 +5,10 @@
@php @php
$canWriteEventi = Auth::user()->canManage('eventi'); $canWriteEventi = Auth::user()->canManage('eventi');
$canDeleteEventi = Auth::user()->canDelete('eventi'); $canDeleteEventi = Auth::user()->canDelete('eventi');
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
$visibleColumnsJson = json_encode($visibleColumns ?? []);
$tableColumnsJson = json_encode($tableColumns ?? []);
$entityType = $entityType ?? 'eventi';
@endphp @endphp
@section('content') @section('content')
@@ -20,6 +24,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Tutti gli Eventi</h3> <h3 class="card-title">Tutti gli Eventi</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
<i class="fas fa-cog"></i>
</button>
<a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2"> <a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2">
<i class="fas fa-calendar-alt mr-1"></i> Calendario <i class="fas fa-calendar-alt mr-1"></i> Calendario
</a> </a>
@@ -75,10 +82,10 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div> </div>
</form> </form>
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover" id="eventi-table">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
<th style="width: 40px;"> <th style="width: 40px;" data-column="select">
@if($canDeleteEventi) @if($canDeleteEventi)
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)"> <input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
@@ -86,7 +93,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div> </div>
@endif @endif
</th> </th>
<th> @if(in_array('nome_evento', $visibleColumns))
<th data-column="nome_evento" data-sortable="true">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'nome_evento', 'direction' => request('sort') === 'nome_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'nome_evento', 'direction' => request('sort') === 'nome_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Nome Evento Nome Evento
@if(request('sort') === 'nome_evento') @if(request('sort') === 'nome_evento')
@@ -96,7 +104,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
<th> @endif
@if(in_array('descrizione_evento', $visibleColumns))
<th data-column="descrizione_evento" data-sortable="true">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'descrizione_evento', 'direction' => request('sort') === 'descrizione_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'descrizione_evento', 'direction' => request('sort') === 'descrizione_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Descrizione Descrizione
@if(request('sort') === 'descrizione_evento') @if(request('sort') === 'descrizione_evento')
@@ -106,7 +116,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
<th> @endif
@if(in_array('tipo_evento', $visibleColumns))
<th data-column="tipo_evento" data-sortable="true">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_evento', 'direction' => request('sort') === 'tipo_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_evento', 'direction' => request('sort') === 'tipo_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Tipologia Tipologia
@if(request('sort') === 'tipo_evento') @if(request('sort') === 'tipo_evento')
@@ -116,7 +128,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
<th> @endif
@if(in_array('tipo_recorrenza', $visibleColumns))
<th data-column="tipo_recorrenza" data-sortable="true">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_recorrenza', 'direction' => request('sort') === 'tipo_recorrenza' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_recorrenza', 'direction' => request('sort') === 'tipo_recorrenza' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Ricorrenza Ricorrenza
@if(request('sort') === 'tipo_recorrenza') @if(request('sort') === 'tipo_recorrenza')
@@ -126,7 +140,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
<th> @endif
@if(in_array('gruppi', $visibleColumns))
<th data-column="gruppi">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'gruppi', 'direction' => request('sort') === 'gruppi' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'gruppi', 'direction' => request('sort') === 'gruppi' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Gruppi Gruppi
@if(request('sort') === 'gruppi') @if(request('sort') === 'gruppi')
@@ -136,7 +152,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
<th> @endif
@if(in_array('responsabili', $visibleColumns))
<th data-column="responsabili">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'responsabili', 'direction' => request('sort') === 'responsabili' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'responsabili', 'direction' => request('sort') === 'responsabili' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Responsabili Responsabili
@if(request('sort') === 'responsabili') @if(request('sort') === 'responsabili')
@@ -146,8 +164,12 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
<th>Tag</th> @endif
<th style="width: 120px;"> @if(in_array('tag', $visibleColumns))
<th data-column="tag">Tag</th>
@endif
@if(in_array('created_at', $visibleColumns))
<th style="width: 120px;" data-column="created_at">
<a href="{{ request()->fullUrlWithQuery(['sort' => 'created_at', 'direction' => request('sort') === 'created_at' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark"> <a href="{{ request()->fullUrlWithQuery(['sort' => 'created_at', 'direction' => request('sort') === 'created_at' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
Creato Creato
@if(request('sort') === 'created_at') @if(request('sort') === 'created_at')
@@ -157,6 +179,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif @endif
</a> </a>
</th> </th>
@endif
<th style="width: 120px;">Azioni</th> <th style="width: 120px;">Azioni</th>
</tr> </tr>
</thead> </thead>
@@ -171,6 +194,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div> </div>
@endif @endif
</td> </td>
@if(in_array('nome_evento', $visibleColumns))
<td> <td>
<a href="/eventi/{{ $evento->id }}"> <a href="/eventi/{{ $evento->id }}">
<i class="fas fa-calendar mr-1 text-primary"></i> <i class="fas fa-calendar mr-1 text-primary"></i>
@@ -180,7 +204,11 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<span class="badge badge-success ml-1">Incluso</span> <span class="badge badge-success ml-1">Incluso</span>
@endif @endif
</td> </td>
@endif
@if(in_array('descrizione_evento', $visibleColumns))
<td>{{ Str::limit($evento->descrizione_evento, 50) ?: '-' }}</td> <td>{{ Str::limit($evento->descrizione_evento, 50) ?: '-' }}</td>
@endif
@if(in_array('tipo_evento', $visibleColumns))
<td> <td>
@if($evento->tipo_evento) @if($evento->tipo_evento)
@php @php
@@ -195,6 +223,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<span class="text-muted">-</span> <span class="text-muted">-</span>
@endif @endif
</td> </td>
@endif
@if(in_array('tipo_recorrenza', $visibleColumns))
<td> <td>
@if($evento->tipo_recorrenza && $evento->tipo_recorrenza !== 'singolo') @if($evento->tipo_recorrenza && $evento->tipo_recorrenza !== 'singolo')
<span class="badge badge-info">{{ $evento->periodicita_label }}</span> <span class="badge badge-info">{{ $evento->periodicita_label }}</span>
@@ -202,6 +232,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<span class="badge badge-secondary">Singolo</span> <span class="badge badge-secondary">Singolo</span>
@endif @endif
</td> </td>
@endif
@if(in_array('gruppi', $visibleColumns))
<td> <td>
@forelse($evento->gruppi->take(2) as $gruppo) @forelse($evento->gruppi->take(2) as $gruppo)
<a href="/gruppi/{{ $gruppo->id }}"> <a href="/gruppi/{{ $gruppo->id }}">
@@ -214,6 +246,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<small class="text-muted">+{{ $evento->gruppi->count() - 2 }}</small> <small class="text-muted">+{{ $evento->gruppi->count() - 2 }}</small>
@endif @endif
</td> </td>
@endif
@if(in_array('responsabili', $visibleColumns))
<td> <td>
@forelse($evento->responsabili->take(2) as $resp) @forelse($evento->responsabili->take(2) as $resp)
<small>{{ $resp->nome_completo }}</small><br> <small>{{ $resp->nome_completo }}</small><br>
@@ -224,12 +258,17 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<small class="text-muted">+{{ $evento->responsabili->count() - 2 }} altri</small> <small class="text-muted">+{{ $evento->responsabili->count() - 2 }} altri</small>
@endif @endif
</td> </td>
@endif
@if(in_array('tag', $visibleColumns))
<td> <td>
@foreach($evento->tags as $tag) @foreach($evento->tags as $tag)
<a href="{{ route('eventi.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">{{ $tag->name }}</a> <a href="{{ route('eventi.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">{{ $tag->name }}</a>
@endforeach @endforeach
</td> </td>
@endif
@if(in_array('created_at', $visibleColumns))
<td><small class="text-muted">{{ $evento->created_at->format('d/m/Y') }}</small></td> <td><small class="text-muted">{{ $evento->created_at->format('d/m/Y') }}</small></td>
@endif
<td> <td>
<a href="{{ url('/eventi/' . $evento->id) }}" class="btn btn-xs btn-info" title="Visualizza"> <a href="{{ url('/eventi/' . $evento->id) }}" class="btn btn-xs btn-info" title="Visualizza">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
@@ -249,7 +288,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="10" class="text-center text-muted py-4"> <td colspan="20" class="text-center text-muted py-4">
<i class="fas fa-calendar fa-2x mb-2"></i> <i class="fas fa-calendar fa-2x mb-2"></i>
<p class="mb-0">Nessun evento trovato</p> <p class="mb-0">Nessun evento trovato</p>
</td> </td>
@@ -284,6 +323,10 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div> </div>
</div> </div>
@include('partials.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog"> <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
@if($canDeleteEventi) @if($canDeleteEventi)
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
@@ -309,6 +352,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endsection @endsection
@section('scripts') @section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script> <script>
function toggleSelectAll(source) { function toggleSelectAll(source) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked); document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
@@ -374,5 +418,24 @@ function exportSelectedIcs() {
document.body.appendChild(form); document.body.appendChild(form);
form.submit(); form.submit();
} }
document.addEventListener('DOMContentLoaded', function () {
const table = document.getElementById('eventi-table');
if (table) {
window.columnManager = new ColumnManager('eventi-table', {
entityType: 'eventi',
storageKey: 'cm_eventi',
resizable: true,
reorderable: true,
});
initTableSettings();
}
});
window.printWithCurrentSettings = function () {
if (window.columnManager) {
window.columnManager.printWithSettings();
}
};
</script> </script>
@endsection @endsection
+50 -149
View File
@@ -22,6 +22,7 @@ $columnLabels = [
$vistaDefaultJson = $vista ? $vista->toJson() : 'null'; $vistaDefaultJson = $vista ? $vista->toJson() : 'null';
$allColumnsJson = json_encode($allColumns); $allColumnsJson = json_encode($allColumns);
$visibleColumnsJson = json_encode($visibleColumns); $visibleColumnsJson = json_encode($visibleColumns);
$entityType = $entityType ?? 'gruppi';
@endphp @endphp
@section('content') @section('content')
@@ -44,6 +45,9 @@ $visibleColumnsJson = json_encode($visibleColumns);
<div class="card-header"> <div class="card-header">
<h3 class="card-title"><i class="fas fa-sitemap mr-2"></i>Elenco Gruppi</h3> <h3 class="card-title"><i class="fas fa-sitemap mr-2"></i>Elenco Gruppi</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
<i class="fas fa-cog"></i>
</button>
<div class="btn-group mr-2"> <div class="btn-group mr-2">
<button type="button" class="btn btn-sm btn-outline-primary {{ $viewMode === 'table' ? 'active' : '' }}" onclick="switchView('table')" id="btn-table-view"> <button type="button" class="btn btn-sm btn-outline-primary {{ $viewMode === 'table' ? 'active' : '' }}" onclick="switchView('table')" id="btn-table-view">
<i class="fas fa-table mr-1"></i> Tabella <i class="fas fa-table mr-1"></i> Tabella
@@ -68,9 +72,7 @@ $visibleColumnsJson = json_encode($visibleColumns);
@endif @endif
</div> </div>
</div> </div>
<button type="button" class="btn btn-primary btn-sm mr-1" onclick="showSaveVistaModal()">
<i class="fas fa-save mr-1"></i> Salva Vista
</button>
@if($canWriteGruppi) @if($canWriteGruppi)
<a href="{{ route('gruppi.import') }}" class="btn btn-warning btn-sm mr-1"> <a href="{{ route('gruppi.import') }}" class="btn btn-warning btn-sm mr-1">
<i class="fas fa-file-import mr-1"></i> Importa <i class="fas fa-file-import mr-1"></i> Importa
@@ -84,54 +86,58 @@ $visibleColumnsJson = json_encode($visibleColumns);
@include('partials._tag-filter-bar', ['filterTags' => $allTags]) @include('partials._tag-filter-bar', ['filterTags' => $allTags])
@include('partials.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
@if($viewMode === 'table') @if($viewMode === 'table')
<div class="card-body p-0"> <div class="card-body p-0">
<table class="table table-bordered table-hover table-striped mb-0" id="gruppi-table"> <table class="table table-bordered table-hover table-striped mb-0" id="gruppi-table">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
<th style="width: 40px;"> <th style="width: 40px;" data-column="select">
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)"> <input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
<label class="custom-control-label" for="select-all"></label> <label class="custom-control-label" for="select-all"></label>
</div> </div>
</th> </th>
@if(in_array('nome', $visibleColumns)) @if(in_array('nome', $visibleColumns))
<th data-sortable="true" onclick="sortTable('nome')" style="cursor:pointer;">Nome <i class="fas fa-sort float-right text-muted"></i></th> <th data-sortable="true" data-column="nome" onclick="sortTable('nome')" style="cursor:pointer;">Nome <i class="fas fa-sort float-right text-muted"></i></th>
@endif @endif
@if(in_array('descrizione', $visibleColumns)) @if(in_array('descrizione', $visibleColumns))
<th data-sortable="true" onclick="sortTable('descrizione')" style="cursor:pointer;">Descrizione <i class="fas fa-sort float-right text-muted"></i></th> <th data-sortable="true" data-column="descrizione" onclick="sortTable('descrizione')" style="cursor:pointer;">Descrizione <i class="fas fa-sort float-right text-muted"></i></th>
@endif @endif
@if(in_array('diocesi', $visibleColumns)) @if(in_array('diocesi', $visibleColumns))
<th>Diocesi</th> <th data-column="diocesi">Diocesi</th>
@endif @endif
@if(in_array('livello', $visibleColumns)) @if(in_array('livello', $visibleColumns))
<th style="width: 80px;">Livello</th> <th style="width: 80px;" data-column="livello">Livello</th>
@endif @endif
@if(in_array('padre', $visibleColumns)) @if(in_array('padre', $visibleColumns))
<th>Gruppo Padre</th> <th data-column="padre">Gruppo Padre</th>
@endif @endif
@if(in_array('membri', $visibleColumns)) @if(in_array('membri', $visibleColumns))
<th style="width: 100px;">Membri</th> <th style="width: 100px;" data-column="membri">Membri</th>
@endif @endif
@if(in_array('responsabili', $visibleColumns)) @if(in_array('responsabili', $visibleColumns))
<th>Responsabili</th> <th data-column="responsabili">Responsabili</th>
@endif @endif
@if(in_array('indirizzo', $visibleColumns)) @if(in_array('indirizzo', $visibleColumns))
<th>Indirizzo</th> <th data-column="indirizzo">Indirizzo</th>
@endif @endif
@if(in_array('citta', $visibleColumns)) @if(in_array('citta', $visibleColumns))
<th>Città</th> <th data-column="citta">Città</th>
@endif @endif
@if(in_array('telefono', $visibleColumns)) @if(in_array('telefono', $visibleColumns))
<th>Telefono</th> <th data-column="telefono">Telefono</th>
@endif @endif
@if(in_array('email', $visibleColumns)) @if(in_array('email', $visibleColumns))
<th>Email</th> <th data-column="email">Email</th>
@endif @endif
@if(in_array('tag', $visibleColumns)) @if(in_array('tag', $visibleColumns))
<th>Tag</th> <th data-column="tag">Tag</th>
@endif @endif
<th style="width: 120px;">Azioni</th> <th style="width: 120px;" data-column="azioni">Azioni</th>
</tr> </tr>
</thead> </thead>
<tbody id="table-body"> <tbody id="table-body">
@@ -263,93 +269,7 @@ $visibleColumnsJson = json_encode($visibleColumns);
@endif @endif
</div> </div>
<div class="modal fade" id="saveVistaModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-save mr-2"></i>Salva Vista</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Nome Vista *</label>
<input type="text" id="vista-nome" class="form-control" placeholder="Es. Elenco gruppi attivi">
</div>
<div class="form-group">
<label>Colonne visibili</label>
<div class="row" id="colonne-checkboxes">
@foreach($allColumns as $col)
<div class="col-md-4 mb-2">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input col-checkbox" id="col-{{ $col }}" value="{{ $col }}" {{ in_array($col, $visibleColumns) ? 'checked' : '' }}>
<label class="custom-control-label" for="col-{{ $col }}">{{ $columnLabels[$col] ?? $col }}</label>
</div>
</div>
@endforeach
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="vista-default">
<label class="custom-control-label" for="vista-default">Imposta come vista predefinita</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary" onclick="saveVista()">
<i class="fas fa-save mr-1"></i> Salva
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="vistaListModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-bookmark mr-2"></i>Viste Salvate</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
@php
$visteSalvate = \App\Models\VistaReport::where('user_id', Auth::id())
->where('tipo', 'gruppi')
->orderBy('is_default', 'desc')
->orderBy('nome')
->get();
@endphp
@if($visteSalvate->count() > 0)
<div class="list-group">
@foreach($visteSalvate as $v)
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<a href="?vista_id={{ $v->id }}">{{ $v->nome }}</a>
@if($v->is_default)
<span class="badge badge-success ml-1">Predefinita</span>
@endif
</div>
<form action="{{ route('gruppi.delete-vista', $v->id) }}" method="POST" class="d-inline">
@csrf @method('DELETE')
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa vista?')">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
@endforeach
</div>
@else
<p class="text-muted text-center">Nessuna vista salvata</p>
@endif
</div>
</div>
</div>
</div>
{{-- MODAL: Gestione Tag massiva --}} {{-- MODAL: Gestione Tag massiva --}}
<div class="modal fade" id="massTagModal" tabindex="-1" role="dialog"> <div class="modal fade" id="massTagModal" tabindex="-1" role="dialog">
@@ -415,6 +335,7 @@ $visibleColumnsJson = json_encode($visibleColumns);
@endsection @endsection
@section('scripts') @section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script> <script>
function switchView(mode) { function switchView(mode) {
window.location.href = '?view=' + mode; window.location.href = '?view=' + mode;
@@ -532,48 +453,7 @@ function getColumnIndex(column) {
return 1; return 1;
} }
function showSaveVistaModal() {
document.getElementById('vista-nome').value = '';
document.getElementById('vista-default').checked = false;
$('#saveVistaModal').modal('show');
}
function saveVista() {
const nome = document.getElementById('vista-nome').value.trim();
if (!nome) {
alert('Inserisci un nome per la vista');
return;
}
const colonneVisibili = Array.from(document.querySelectorAll('.col-checkbox:checked')).map(cb => cb.value);
const isDefault = document.getElementById('vista-default').checked;
fetch('/gruppi/save-vista', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
nome: nome,
colonne_visibili: colonneVisibili,
is_default: isDefault,
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
$('#saveVistaModal').modal('hide');
window.location.reload();
} else {
alert('Errore nel salvataggio della vista');
}
})
.catch(error => {
alert('Errore: ' + error.message);
});
}
</script> </script>
<style> <style>
.tree-item { margin-bottom: 5px; } .tree-item { margin-bottom: 5px; }
@@ -599,10 +479,31 @@ function saveVista() {
font-size: 0.7em; font-size: 0.7em;
padding: 2px 5px; padding: 2px 5px;
} }
.btn-group .btn.active { .btn-group .btn.active {
background-color: #007bff; background-color: #007bff;
color: white; color: white;
border-color: #007bff; border-color: #007bff;
} }
</style> </style>
<script>
document.addEventListener('DOMContentLoaded', function () {
const table = document.getElementById('gruppi-table');
if (table) {
window.columnManager = new ColumnManager('gruppi-table', {
entityType: 'gruppi',
storageKey: 'cm_gruppi',
resizable: true,
reorderable: true,
});
initTableSettings();
}
});
window.printWithCurrentSettings = function () {
if (window.columnManager) {
window.columnManager.printWithSettings();
}
};
</script>
@endsection @endsection
+108 -143
View File
@@ -16,6 +16,7 @@ $allColumnsJson = json_encode($allColumns);
$visibleColumnsJson = json_encode($visibleColumns); $visibleColumnsJson = json_encode($visibleColumns);
$canWriteIndividui = Auth::user()->canManage('individui'); $canWriteIndividui = Auth::user()->canManage('individui');
$canDeleteIndividui = Auth::user()->canDelete('individui'); $canDeleteIndividui = Auth::user()->canDelete('individui');
$entityType = $entityType ?? 'individui';
@endphp @endphp
@section('content') @section('content')
@@ -23,6 +24,9 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
<div class="card-header"> <div class="card-header">
<h3 class="card-title"><i class="fas fa-users mr-2"></i>Lista Individui</h3> <h3 class="card-title"><i class="fas fa-users mr-2"></i>Lista Individui</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-secondary btn-sm mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
<i class="fas fa-cog"></i>
</button>
<button type="button" class="btn btn-info btn-sm mr-1" onclick="toggleSearchPanel()"> <button type="button" class="btn btn-info btn-sm mr-1" onclick="toggleSearchPanel()">
<i class="fas fa-search mr-1"></i> Ricerca/Filtri <i class="fas fa-search mr-1"></i> Ricerca/Filtri
</button> </button>
@@ -61,9 +65,6 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endif @endif
</div> </div>
</div> </div>
<button type="button" class="btn btn-primary btn-sm" onclick="showSaveVistaModal()">
<i class="fas fa-save mr-1"></i> Salva Vista
</button>
@if($canWriteIndividui) @if($canWriteIndividui)
<a href="{{ route('individui.import') }}" class="btn btn-warning btn-sm ml-2"> <a href="{{ route('individui.import') }}" class="btn btn-warning btn-sm ml-2">
<i class="fas fa-file-import mr-1"></i> Importa <i class="fas fa-file-import mr-1"></i> Importa
@@ -133,12 +134,16 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@include('partials._tag-filter-bar', ['filterTags' => $allTags]) @include('partials._tag-filter-bar', ['filterTags' => $allTags])
@include('partials.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
<div class="card-body p-0"> <div class="card-body p-0">
@if($individui->count() > 0) @if($individui->count() > 0)
<table class="table table-bordered table-hover table-striped mb-0" id="individui-table"> <table class="table table-bordered table-hover table-striped mb-0" id="individui-table">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
<th style="width: 40px;"> <th style="width: 40px;" data-column="select">
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)"> <input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
<label class="custom-control-label" for="select-all"></label> <label class="custom-control-label" for="select-all"></label>
@@ -153,6 +158,39 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@if(in_array('nome', $visibleColumns)) @if(in_array('nome', $visibleColumns))
<th data-sortable="true" data-column="nome" onclick="sortTable('nome')">Nome <i class="fas fa-sort float-right"></i></th> <th data-sortable="true" data-column="nome" onclick="sortTable('nome')">Nome <i class="fas fa-sort float-right"></i></th>
@endif @endif
@if(in_array('data_nascita', $visibleColumns))
<th data-column="data_nascita">Data Nascita</th>
@endif
@if(in_array('genere', $visibleColumns))
<th data-column="genere">Genere</th>
@endif
@if(in_array('indirizzo', $visibleColumns))
<th data-column="indirizzo">Indirizzo</th>
@endif
@if(in_array('cap', $visibleColumns))
<th data-column="cap">CAP</th>
@endif
@if(in_array('citta', $visibleColumns))
<th data-column="citta">Città</th>
@endif
@if(in_array('provincia', $visibleColumns))
<th data-column="provincia">Prov.</th>
@endif
@if(in_array('tipo_documento', $visibleColumns))
<th data-column="tipo_documento">Tipo Doc.</th>
@endif
@if(in_array('numero_documento', $visibleColumns))
<th data-column="numero_documento">N. Documento</th>
@endif
@if(in_array('scadenza_documento', $visibleColumns))
<th data-column="scadenza_documento">Scad. Doc.</th>
@endif
@if(in_array('note', $visibleColumns))
<th data-column="note">Note</th>
@endif
@if(in_array('created_at', $visibleColumns))
<th data-column="created_at">Creato il</th>
@endif
@if(in_array('email', $visibleColumns)) @if(in_array('email', $visibleColumns))
<th data-sortable="true" data-column="email" onclick="sortTable('email')">Email <i class="fas fa-sort float-right"></i></th> <th data-sortable="true" data-column="email" onclick="sortTable('email')">Email <i class="fas fa-sort float-right"></i></th>
@endif @endif
@@ -160,9 +198,9 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
<th data-sortable="true" data-column="telefono" onclick="sortTable('telefono')">Telefono <i class="fas fa-sort float-right"></i></th> <th data-sortable="true" data-column="telefono" onclick="sortTable('telefono')">Telefono <i class="fas fa-sort float-right"></i></th>
@endif @endif
@if(in_array('tag', $visibleColumns)) @if(in_array('tag', $visibleColumns))
<th>Tag</th> <th data-column="tag">Tag</th>
@endif @endif
<th style="width: 120px;">Azioni</th> <th style="width: 120px;" data-column="azioni">Azioni</th>
</tr> </tr>
</thead> </thead>
<tbody id="table-body"> <tbody id="table-body">
@@ -187,6 +225,39 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
<a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->nome }}</a> <a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->nome }}</a>
</td> </td>
@endif @endif
@if(in_array('data_nascita', $visibleColumns))
<td>{{ $ind->data_nascita ? $ind->data_nascita->format('d/m/Y') : '-' }}</td>
@endif
@if(in_array('genere', $visibleColumns))
<td>{{ $ind->genere ?? '-' }}</td>
@endif
@if(in_array('indirizzo', $visibleColumns))
<td>{{ $ind->indirizzo ?? '-' }}</td>
@endif
@if(in_array('cap', $visibleColumns))
<td>{{ $ind->cap ?? '-' }}</td>
@endif
@if(in_array('citta', $visibleColumns))
<td>{{ $ind->città ?? '-' }}</td>
@endif
@if(in_array('provincia', $visibleColumns))
<td>{{ $ind->sigla_provincia ?? '-' }}</td>
@endif
@if(in_array('tipo_documento', $visibleColumns))
<td>{{ $ind->tipo_documento ? str_replace(['carta_identita', 'patente'], ['C.I.', 'Patente'], $ind->tipo_documento) : '-' }}</td>
@endif
@if(in_array('numero_documento', $visibleColumns))
<td>{{ $ind->numero_documento ?? '-' }}</td>
@endif
@if(in_array('scadenza_documento', $visibleColumns))
<td>{{ $ind->scadenza_documento ? $ind->scadenza_documento->format('d/m/Y') : '-' }}</td>
@endif
@if(in_array('note', $visibleColumns))
<td class="text-truncate" style="max-width: 200px;">{{ $ind->note ? Str::limit($ind->note, 50) : '-' }}</td>
@endif
@if(in_array('created_at', $visibleColumns))
<td>{{ $ind->created_at ? $ind->created_at->format('d/m/Y') : '-' }}</td>
@endif
@if(in_array('email', $visibleColumns)) @if(in_array('email', $visibleColumns))
<td>{{ $ind->getEmailPrimariaAttribute() ?: '-' }}</td> <td>{{ $ind->getEmailPrimariaAttribute() ?: '-' }}</td>
@endif @endif
@@ -266,75 +337,7 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endif @endif
</div> </div>
<div class="modal fade" id="saveVistaModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-save mr-2"></i>Salva Vista</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>Nome Vista *</label>
<input type="text" id="vista-nome" class="form-control" placeholder="Es. Elenco soci attivi">
</div>
<div class="form-group">
<label>Colonne visibili</label>
<div class="row" id="colonne-checkboxes">
@foreach($allColumns as $col)
<div class="col-md-6">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input col-visibile" id="col-vis-{{ $col['key'] }}" value="{{ $col['key'] }}" {{ in_array($col['key'], $visibleColumns) ? 'checked' : '' }}>
<label class="custom-control-label" for="col-vis-{{ $col['key'] }}">{{ $col['label'] }}</label>
</div>
</div>
@endforeach
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="vista-default" value="true">
<label class="custom-control-label" for="vista-default">Imposta come vista predefinita</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
<button type="button" class="btn btn-primary" onclick="saveVista()">Salva</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="colonneModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-columns mr-2"></i>Colonne Visibili</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
@foreach($allColumns as $col)
<div class="col-md-6">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input col-toggle" id="col-toggle-{{ $col['key'] }}" value="{{ $col['key'] }}" {{ in_array($col['key'], $visibleColumns) ? 'checked' : '' }} onchange="toggleColumn('{{ $col['key'] }}', this.checked)">
<label class="custom-control-label" for="col-toggle-{{ $col['key'] }}">{{ $col['label'] }}</label>
</div>
</div>
@endforeach
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog"> <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
@if($canDeleteIndividui) @if($canDeleteIndividui)
@@ -519,6 +522,7 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endsection @endsection
@section('scripts') @section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script> <script>
let currentSort = { column: null, direction: 'asc' }; let currentSort = { column: null, direction: 'asc' };
let columnVisibility = {!! json_encode(array_column($allColumns, 'key')) !!}; let columnVisibility = {!! json_encode(array_column($allColumns, 'key')) !!};
@@ -537,6 +541,16 @@ function applyGlobalSearch(value) {
}); });
} }
function getColumnIndex(colKey) {
const ths = document.querySelectorAll('#individui-table thead th');
let idx = 0;
for (const th of ths) {
if (th.dataset.column === colKey) return idx;
idx++;
}
return -1;
}
function applyColumnFilter() { function applyColumnFilter() {
const col = document.getElementById('filter-column').value; const col = document.getElementById('filter-column').value;
const op = document.getElementById('filter-operator').value; const op = document.getElementById('filter-operator').value;
@@ -545,7 +559,8 @@ function applyColumnFilter() {
if (!col || !val) return; if (!col || !val) return;
const rows = document.querySelectorAll('#table-body tr'); const rows = document.querySelectorAll('#table-body tr');
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, tag: 6, azioni: 7 }[col]; const colIndex = getColumnIndex(col);
if (colIndex < 0) return;
rows.forEach(row => { rows.forEach(row => {
const cells = row.querySelectorAll('td'); const cells = row.querySelectorAll('td');
@@ -577,7 +592,7 @@ function sortTable(column) {
currentSort = { column: column, direction: 'asc' }; currentSort = { column: column, direction: 'asc' };
} }
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, tag: 6, azioni: 7 }[column]; const colIndex = getColumnIndex(column);
const tbody = document.getElementById('table-body'); const tbody = document.getElementById('table-body');
const rows = Array.from(tbody.querySelectorAll('tr')); const rows = Array.from(tbody.querySelectorAll('tr'));
@@ -600,63 +615,7 @@ function sortTable(column) {
}); });
} }
function toggleColumn(column, visible) {
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, tag: 6, azioni: 7 }[column];
const ths = document.querySelectorAll('#individui-table thead th');
const rows = document.querySelectorAll('#table-body tr');
if (visible) {
ths[colIndex].style.display = '';
rows.forEach(row => {
row.querySelectorAll('td')[colIndex].style.display = '';
});
} else {
ths[colIndex].style.display = 'none';
rows.forEach(row => {
row.querySelectorAll('td')[colIndex].style.display = 'none';
});
}
}
async function saveVista() {
let nome = document.getElementById('vista-nome').value.trim();
if (!nome) {
const now = new Date();
nome = 'Vista ' + now.toLocaleDateString('it-IT') + ' ' + now.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' });
}
const colonneVisibili = Array.from(document.querySelectorAll('#colonne-checkboxes input:checked')).map(i => i.value);
const isDefault = document.getElementById('vista-default').checked;
const data = {
nome: nome,
tipo: 'individui',
colonne_visibili: colonneVisibili,
colonne_ordinamento: currentSort.column ? [[currentSort.column, currentSort.direction]] : [],
ricerca: document.getElementById('global-search').value,
is_default: isDefault
};
try {
const response = await fetch('/viste', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
},
body: JSON.stringify(data)
});
if (response.ok) {
alert('Vista salvata!');
$('#saveVistaModal').modal('hide');
} else {
alert('Errore salvataggio');
}
} catch(e) {
alert('Errore: ' + e.message);
}
}
function toggleSelectAll(source) { function toggleSelectAll(source) {
const checkboxes = document.querySelectorAll('.row-checkbox'); const checkboxes = document.querySelectorAll('.row-checkbox');
@@ -863,24 +822,30 @@ document.addEventListener('DOMContentLoaded', function() {
document.head.appendChild(meta); document.head.appendChild(meta);
} }
const visibleCols = {!! $visibleColumnsJson !!};
const allCols = {!! $allColumnsJson !!};
allCols.forEach(function(col) {
const visibile = visibleCols.includes(col.key);
const saveCheckbox = document.querySelector(`#col-vis-${col.key}`);
const toggleCheckbox = document.querySelector(`#col-toggle-${col.key}`);
if (saveCheckbox) saveCheckbox.checked = visibile;
if (toggleCheckbox) toggleCheckbox.checked = visibile;
});
const vistaDefault = {!! $vistaDefaultJson !!}; const vistaDefault = {!! $vistaDefaultJson !!};
if (vistaDefault && vistaDefault.ricerca) { if (vistaDefault && vistaDefault.ricerca) {
document.getElementById('global-search').value = vistaDefault.ricerca; document.getElementById('global-search').value = vistaDefault.ricerca;
applyGlobalSearch(vistaDefault.ricerca); applyGlobalSearch(vistaDefault.ricerca);
} }
const table = document.getElementById('individui-table');
if (table) {
window.columnManager = new ColumnManager('individui-table', {
entityType: 'individui',
storageKey: 'cm_individui',
resizable: true,
reorderable: true,
});
initTableSettings();
}
}); });
window.printWithCurrentSettings = function () {
if (window.columnManager) {
window.columnManager.printWithSettings();
}
};
function showCreateMailingListModal() { function showCreateMailingListModal() {
document.getElementById('ml-nome').value = ''; document.getElementById('ml-nome').value = '';
document.getElementById('ml-descrizione').value = ''; document.getElementById('ml-descrizione').value = '';
+62 -8
View File
@@ -5,6 +5,9 @@
@php @php
$canWriteMailing = Auth::user()->canManage('mailing'); $canWriteMailing = Auth::user()->canManage('mailing');
$canDeleteMailing = Auth::user()->canDelete('mailing'); $canDeleteMailing = Auth::user()->canDelete('mailing');
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
$visibleColumnsJson = json_encode($visibleColumns ?? []);
$tableColumnsJson = json_encode($tableColumns ?? []);
@endphp @endphp
@section('content') @section('content')
@@ -20,6 +23,9 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
<div class="card-header"> <div class="card-header">
<h3 class="card-title"><i class="fas fa-list mr-2"></i>Elenco Mailing Lists</h3> <h3 class="card-title"><i class="fas fa-list mr-2"></i>Elenco Mailing Lists</h3>
<div class="card-tools"> <div class="card-tools">
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
<i class="fas fa-cog"></i>
</button>
@if($canDeleteMailing) @if($canDeleteMailing)
<div class="btn-group mr-2"> <div class="btn-group mr-2">
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()"> <button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()">
@@ -37,10 +43,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
@include('partials._tag-filter-bar', ['filterTags' => $allTags ?? collect()]) @include('partials._tag-filter-bar', ['filterTags' => $allTags ?? collect()])
<div class="card-body p-0"> <div class="card-body p-0">
@if($mailingLists->count() > 0) @if($mailingLists->count() > 0)
<table class="table table-bordered table-hover table-striped mb-0"> <table class="table table-bordered table-hover table-striped mb-0" id="mailing-liste-table">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
<th style="width: 40px;"> <th style="width: 40px;" data-column="select">
@if($canDeleteMailing) @if($canDeleteMailing)
<div class="custom-control custom-checkbox"> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)"> <input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
@@ -48,12 +54,24 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
</div> </div>
@endif @endif
</th> </th>
<th>Nome</th> @if(in_array('nome', $visibleColumns))
<th>Descrizione</th> <th data-column="nome">Nome</th>
<th style="width: 100px;">Contatti</th> @endif
<th>Tag</th> @if(in_array('descrizione', $visibleColumns))
<th style="width: 100px;">Stato</th> <th data-column="descrizione">Descrizione</th>
<th style="width: 130px;">Creata il</th> @endif
@if(in_array('contatti_count', $visibleColumns))
<th style="width: 100px;" data-column="contatti_count">Contatti</th>
@endif
@if(in_array('tag', $visibleColumns))
<th data-column="tag">Tag</th>
@endif
@if(in_array('attiva', $visibleColumns))
<th style="width: 100px;" data-column="attiva">Stato</th>
@endif
@if(in_array('created_at', $visibleColumns))
<th style="width: 130px;" data-column="created_at">Creata il</th>
@endif
<th style="width: 100px;">Azioni</th> <th style="width: 100px;">Azioni</th>
</tr> </tr>
</thead> </thead>
@@ -68,13 +86,20 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
</div> </div>
@endif @endif
</td> </td>
@if(in_array('nome', $visibleColumns))
<td> <td>
<a href="{{ route('mailing-liste.show', $lista->id) }}">{{ $lista->nome }}</a> <a href="{{ route('mailing-liste.show', $lista->id) }}">{{ $lista->nome }}</a>
</td> </td>
@endif
@if(in_array('descrizione', $visibleColumns))
<td>{{ $lista->descrizione ?: '-' }}</td> <td>{{ $lista->descrizione ?: '-' }}</td>
@endif
@if(in_array('contatti_count', $visibleColumns))
<td> <td>
<span class="badge badge-info">{{ $lista->contatti->count() }}</span> <span class="badge badge-info">{{ $lista->contatti->count() }}</span>
</td> </td>
@endif
@if(in_array('tag', $visibleColumns))
<td> <td>
@foreach($lista->tags as $tag) @foreach($lista->tags as $tag)
<a href="{{ request()->url() }}?tag[]={{ $tag->slug }}" class="badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;font-size:0.75rem;text-decoration:none;"> <a href="{{ request()->url() }}?tag[]={{ $tag->slug }}" class="badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;font-size:0.75rem;text-decoration:none;">
@@ -85,6 +110,8 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
<span class="text-muted small">-</span> <span class="text-muted small">-</span>
@endif @endif
</td> </td>
@endif
@if(in_array('attiva', $visibleColumns))
<td> <td>
@if($lista->attiva) @if($lista->attiva)
<span class="badge badge-success">Attiva</span> <span class="badge badge-success">Attiva</span>
@@ -92,7 +119,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
<span class="badge badge-secondary">Disattiva</span> <span class="badge badge-secondary">Disattiva</span>
@endif @endif
</td> </td>
@endif
@if(in_array('created_at', $visibleColumns))
<td>{{ $lista->created_at->format('d/m/Y') }}</td> <td>{{ $lista->created_at->format('d/m/Y') }}</td>
@endif
<td> <td>
<a href="{{ route('mailing-liste.show', $lista->id) }}" class="btn btn-xs btn-info" title="Visualizza"> <a href="{{ route('mailing-liste.show', $lista->id) }}" class="btn btn-xs btn-info" title="Visualizza">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
@@ -125,6 +155,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
</div> </div>
</div> </div>
@include('partials.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog"> <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
@if($canDeleteMailing) @if($canDeleteMailing)
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
@@ -150,6 +184,7 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
@endsection @endsection
@section('scripts') @section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script> <script>
function toggleSelectAll(source) { function toggleSelectAll(source) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked); document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
@@ -187,5 +222,24 @@ function deleteSelected() {
$('#deleteModal').modal('show'); $('#deleteModal').modal('show');
} }
document.addEventListener('DOMContentLoaded', function () {
const table = document.getElementById('mailing-liste-table');
if (table) {
window.columnManager = new ColumnManager('mailing-liste-table', {
entityType: 'mailing-liste',
storageKey: 'cm_mailing',
resizable: true,
reorderable: true,
});
initTableSettings();
}
});
window.printWithCurrentSettings = function () {
if (window.columnManager) {
window.columnManager.printWithSettings();
}
};
</script> </script>
@endsection @endsection
+283 -110
View File
@@ -15,105 +15,159 @@
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="filters-tab" data-toggle="tab" href="#filters-panel" role="tab"> <a class="nav-link" id="vistas-tab" data-toggle="tab" href="#vistas-panel" role="tab">
<i class="fas fa-filter mr-1"></i> Filtri <i class="fas fa-save mr-1"></i> Viste
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="search-tab" data-toggle="tab" href="#search-panel" role="tab"> <a class="nav-link" id="print-tab" data-toggle="tab" href="#print-panel" role="tab">
<i class="fas fa-search mr-1"></i> Ricerca <i class="fas fa-print mr-1"></i> Stampa
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="export-tab" data-toggle="tab" href="#export-panel" role="tab">
<i class="fas fa-download mr-1"></i> Esporta
</a> </a>
</li> </li>
</ul> </ul>
<div class="tab-content p-3"> <div class="tab-content p-3">
<div class="tab-pane fade show active" id="columns-panel" role="tabpanel"> <div class="tab-pane fade show active" id="columns-panel" role="tabpanel">
<h6>Seleziona le colonne da visualizzare:</h6> <h6 class="mb-3">Seleziona colonne, regola larghezza e riordina:</h6>
<div class="row"> <p class="small text-muted mb-3">
<i class="fas fa-arrows-alt mr-1"></i> Trascina le intestazioni per riordinare &middot;
<i class="fas fa-arrows-alt-h mr-1"></i> Trascina il bordo destro di una cella per ridimensionare
</p>
<div id="column-list" class="mb-3">
@foreach($tableColumns as $col) @foreach($tableColumns as $col)
<div class="col-md-4"> <div class="form-row align-items-center mb-2 column-config-item" data-column="{{ $col['key'] }}">
<div class="custom-control custom-checkbox"> <div class="col-auto">
<input type="checkbox" class="custom-control-input column-toggle" <i class="fas fa-grip-vertical text-muted" style="cursor:grab;"></i>
id="col-{{ $col['key'] }}" data-column="{{ $col['key'] }}" </div>
{{ ($col['visible'] ?? true) ? 'checked' : '' }}> <div class="col-auto">
<label class="custom-control-label" for="col-{{ $col['key'] }}"> <div class="custom-control custom-checkbox">
{{ $col['label'] }} <input type="checkbox" class="custom-control-input column-toggle"
</label> id="col-{{ $col['key'] }}" data-column="{{ $col['key'] }}"
{{ ($col['visible'] ?? true) ? 'checked' : '' }}>
<label class="custom-control-label" for="col-{{ $col['key'] }}">
{{ $col['label'] }}
</label>
</div>
</div>
<div class="col">
<div class="input-group input-group-sm">
<input type="number" class="form-control form-control-sm column-width-input"
data-column="{{ $col['key'] }}"
value="{{ isset($columnWidths[$col['key']]) ? intval(str_replace('px', '', $columnWidths[$col['key']])) : '' }}"
placeholder="auto" min="30" style="max-width:80px;">
<div class="input-group-append">
<span class="input-group-text">px</span>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary ml-1 reset-col-width" data-column="{{ $col['key'] }}"
title="Reset larghezza">
<i class="fas fa-undo-alt"></i>
</button>
</div>
</div> </div>
</div> </div>
@endforeach @endforeach
</div> </div>
<button type="button" class="btn btn-sm btn-secondary" onclick="resetAllColumnWidths()">
<i class="fas fa-undo-alt mr-1"></i> Reset tutte larghezze
</button>
</div> </div>
<div class="tab-pane fade" id="filters-panel" role="tabpanel"> <div class="tab-pane fade" id="vistas-panel" role="tabpanel">
<h6>Aggiungi filtri:</h6> <h6 class="mb-2">Viste salvate</h6>
<div id="filters-container"> <div id="vistas-list" class="mb-3">
<div class="filter-row mb-2"> @php
<div class="row"> $entityType = $entityType ?? 'individui';
<div class="col-md-4"> $userVistas = $userVistas ?? collect();
<select class="form-control form-control-sm filter-column"> @endphp
<option value="">Seleziona colonna...</option> @if($userVistas->count() > 0)
@foreach($tableColumns as $col) <div class="list-group mb-3" id="vistas-list-group">
<option value="{{ $col['key'] }}">{{ $col['label'] }}</option> @foreach($userVistas as $v)
@endforeach <div class="list-group-item list-group-item-action d-flex align-items-center py-2 {{ $vista && $vista->id === $v->id ? 'active' : '' }}">
</select> <div class="mr-auto">
@if($v->is_default)
<i class="fas fa-star text-warning mr-1" title="Predefinita"></i>
@else
<i class="fas fa-file-alt text-muted mr-1"></i>
@endif
<span>{{ $v->nome }}</span>
@if($v->colonne_visibili)
<span class="badge badge-light ml-1">{{ count($v->colonne_visibili) }} col.</span>
@endif
</div> </div>
<div class="col-md-2"> <div class="ml-2">
<select class="form-control form-control-sm filter-operator"> <a href="?vista_id={{ $v->id }}" class="btn btn-xs btn-primary" title="Applica vista">
<option value="contains">Contiene</option> <i class="fas fa-play"></i>
<option value="equals">Uguale a</option> </a>
<option value="starts">Inizia con</option> @if(!$v->is_default)
<option value="ends">Finisce con</option> <form action="{{ url('/viste/' . $v->id . '/default') }}" method="POST" class="d-inline">
<option value="gt">Maggiore di</option> @csrf @method('PATCH')
<option value="lt">Minore di</option> <button type="submit" class="btn btn-xs btn-warning" title="Imposta predefinita">
</select> <i class="fas fa-star"></i>
</div> </button>
<div class="col-md-4"> </form>
<input type="text" class="form-control form-control-sm filter-value" placeholder="Valore"> @endif
<button type="button" class="btn btn-xs btn-info edit-vista-btn" title="Modifica vista"
data-vista-id="{{ $v->id }}"
data-vista-nome="{{ $v->nome }}"
data-vista-default="{{ $v->is_default ? 'true' : 'false' }}"
data-vista-colonne="{{ json_encode($v->colonne_visibili ?? []) }}">
<i class="fas fa-pencil-alt"></i>
</button>
<form action="{{ url('/viste/' . $v->id) }}" method="POST" class="d-inline">
@csrf @method('DELETE')
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa vista?')" title="Elimina">
<i class="fas fa-trash"></i>
</button>
</form>
</div> </div>
</div> </div>
@endforeach
</div>
@else
<p class="text-muted text-center py-2">Nessuna vista salvata</p>
@endif
</div>
<hr>
<h6 class="mb-2">Salva vista corrente</h6>
<div class="form-row align-items-end">
<div class="col">
<input type="text" id="vista-nome" class="form-control" placeholder="Nome vista...">
</div>
<div class="col-auto">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="vista-default" value="true">
<label class="custom-control-label" for="vista-default">Predefinita</label>
</div>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" id="save-vista-btn">
<i class="fas fa-save mr-1"></i> Salva
</button>
</div> </div>
</div> </div>
<button type="button" class="btn btn-xs btn-success mt-2" onclick="addFilterRow()">
<i class="fas fa-plus"></i> Aggiungi filtro
</button>
<button type="button" class="btn btn-xs btn-primary mt-2" id="apply-filters-btn">
<i class="fas fa-check"></i> Applica filtri
</button>
</div> </div>
<div class="tab-pane fade" id="search-panel" role="tabpanel"> <div class="tab-pane fade" id="print-panel" role="tabpanel">
<div class="form-group"> <h6 class="mb-3">Stampa con impostazioni correnti</h6>
<label>cerca in tutte le colonne:</label> <p>La stampa rispetterà le colonne visibili, la larghezza e l'ordine impostati.</p>
<input type="text" id="table-search" class="form-control" placeholder="Testo da cercare..."> <button type="button" class="btn btn-primary btn-lg" onclick="window.printWithCurrentSettings ? window.printWithCurrentSettings() : alert('ColumnManager non inizializzato'); return false;">
<i class="fas fa-print mr-2"></i> Stampa
</button>
<hr>
<h6 class="mb-2">Opzioni aggiuntive</h6>
<div class="custom-control custom-checkbox mb-2">
<input type="checkbox" class="custom-control-input" id="print-page-breaks" checked>
<label class="custom-control-label" for="print-page-breaks">Interruzione di pagina tra gli elementi</label>
</div> </div>
</div> <div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="print-background">
<div class="tab-pane fade" id="export-panel" role="tabpanel"> <label class="custom-control-label" for="print-background">Stampa sfondi e colori</label>
<h6>Esporta i dati visualizzati:</h6>
<div class="btn-group">
<button type="button" class="btn btn-success" id="export-csv-btn">
<i class="fas fa-file-csv mr-1"></i> Esporta CSV
</button>
<button type="button" class="btn btn-danger" id="export-pdf-btn">
<i class="fas fa-file-pdf mr-1"></i> Esporta PDF
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<div class="mr-auto">
<input type="text" id="vista-nome" class="form-control form-control-sm" placeholder="Nome vista..." style="display: inline-block; width: auto;">
<button type="button" class="btn btn-primary btn-sm" id="save-vista-btn">
<i class="fas fa-save mr-1"></i> Salva Vista
</button>
</div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
</div> </div>
</div> </div>
@@ -121,41 +175,160 @@
</div> </div>
<script> <script>
function addFilterRow() { function resetAllColumnWidths() {
const container = document.getElementById('filters-container'); document.querySelectorAll('.column-width-input').forEach(inp => { inp.value = ''; });
const columns = @json($tableColumns); document.querySelectorAll('.column-config-item').forEach(item => {
const options = columns.map(c => `<option value="${c.key}">${c.label}</option>`).join(''); const th = document.querySelector(`th[data-column="${item.dataset.column}"]`);
if (th) th.style.width = '';
const div = document.createElement('div'); });
div.className = 'filter-row mb-2'; if (window.columnManager) {
div.innerHTML = ` window.columnManager.columnWidths = {};
<div class="row"> window.columnManager.persistState();
<div class="col-md-4"> }
<select class="form-control form-control-sm filter-column">
<option value="">Seleziona colonna...</option>
${options}
</select>
</div>
<div class="col-md-2">
<select class="form-control form-control-sm filter-operator">
<option value="contains">Contiene</option>
<option value="equals">Uguale a</option>
<option value="starts">Inizia con</option>
<option value="ends">Finisce con</option>
<option value="gt">Maggiore di</option>
<option value="lt">Minore di</option>
</select>
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm filter-value" placeholder="Valore">
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-sm" onclick="this.closest('.filter-row').remove()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
`;
container.appendChild(div);
} }
</script>
function syncColumnListToTable() {
const items = document.querySelectorAll('#column-list .column-config-item');
const keys = Array.from(items).map(i => i.dataset.column);
if (window.columnManager && keys.length > 0) {
window.columnManager.reorderColumns(keys);
}
}
let editingVistaId = null;
function editVista(id, nome, isDefault, colonneVisibili) {
editingVistaId = id;
document.getElementById('vista-nome').value = nome;
document.getElementById('vista-default').checked = isDefault;
document.querySelectorAll('.column-toggle').forEach(cb => {
cb.checked = colonneVisibili.includes(cb.dataset.column);
if (window.columnManager) window.columnManager.setVisible(cb.dataset.column, cb.checked);
});
document.getElementById('save-vista-btn').innerHTML = '<i class="fas fa-save mr-1"></i> Aggiorna';
document.getElementById('vistas-tab').click();
}
function initTableSettings() {
const columnList = document.getElementById('column-list');
if (!columnList) return;
let dragItem = null;
columnList.querySelectorAll('.column-config-item').forEach(item => {
const grip = item.querySelector('.fa-grip-vertical');
if (!grip) return;
grip.addEventListener('mousedown', function () { item.draggable = true; });
item.addEventListener('dragstart', function (e) {
dragItem = this; e.dataTransfer.effectAllowed = 'move'; this.classList.add('dragging');
});
item.addEventListener('dragend', function () {
this.classList.remove('dragging'); this.draggable = false; dragItem = null;
});
item.addEventListener('dragover', function (e) {
e.preventDefault();
if (!dragItem || dragItem === this) return;
const rect = this.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
if (e.clientY < midY) columnList.insertBefore(dragItem, this);
else columnList.insertBefore(dragItem, this.nextSibling);
});
});
columnList.addEventListener('drop', function () {
const items = Array.from(columnList.querySelectorAll('.column-config-item'));
const newOrder = items.map(i => i.dataset.column);
if (window.columnManager && newOrder.length > 0) window.columnManager.reorderColumns(newOrder);
syncColumnListToTable();
});
columnList.querySelectorAll('.column-toggle').forEach(cb => {
cb.addEventListener('change', function () {
if (window.columnManager) window.columnManager.setVisible(this.dataset.column, this.checked);
});
});
columnList.querySelectorAll('.column-width-input').forEach(inp => {
inp.addEventListener('change', function () {
const key = this.dataset.column;
const val = parseInt(this.value);
const th = document.querySelector(`th[data-column="${key}"]`);
if (th) {
if (val && val >= 30) {
th.style.width = val + 'px';
if (window.columnManager) {
window.columnManager.columnWidths[key] = val + 'px';
window.columnManager.persistState();
}
} else {
th.style.width = '';
if (window.columnManager) {
delete window.columnManager.columnWidths[key];
window.columnManager.persistState();
}
}
}
});
});
columnList.querySelectorAll('.reset-col-width').forEach(btn => {
btn.addEventListener('click', function () {
const key = this.dataset.column;
const inp = document.querySelector(`.column-width-input[data-column="${key}"]`);
if (inp) inp.value = '';
const th = document.querySelector(`th[data-column="${key}"]`);
if (th) th.style.width = '';
if (window.columnManager) {
delete window.columnManager.columnWidths[key];
window.columnManager.persistState();
}
});
});
document.querySelectorAll('.edit-vista-btn').forEach(btn => {
btn.addEventListener('click', function () {
editVista(
this.dataset.vistaId,
this.dataset.vistaNome,
this.dataset.vistaDefault === 'true',
JSON.parse(this.dataset.vistaColonne)
);
});
});
document.getElementById('save-vista-btn')?.addEventListener('click', function() {
const nome = document.getElementById('vista-nome').value.trim();
if (!nome) { alert('Inserisci un nome per la vista'); return; }
const colonneVisibili = Array.from(document.querySelectorAll('.column-toggle:checked')).map(cb => cb.dataset.column);
const larghezze = {};
document.querySelectorAll('.column-width-input').forEach(inp => {
const val = parseInt(inp.value);
if (val && val >= 30) larghezze[inp.dataset.column] = val + 'px';
});
const ordine = Array.from(document.querySelectorAll('#column-list .column-config-item')).map(i => i.dataset.column);
const isDefault = document.getElementById('vista-default')?.checked || false;
const entityType = '{{ $entityType ?? 'individui' }}';
const isEditing = editingVistaId !== null;
const url = isEditing ? '/viste/' + editingVistaId : '/viste';
const method = isEditing ? 'PUT' : 'POST';
fetch(url, {
method: method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
},
body: JSON.stringify({
nome, tipo: entityType, colonne_visibili: colonneVisibili,
colonne_larghezze: larghezze, colonne_ordine: ordine,
colonne_ordinamento: [], is_default: isDefault,
})
})
.then(response => response.ok ? (alert(isEditing ? 'Vista aggiornata!' : 'Vista salvata!'), window.location.reload()) : response.text().then(t => { throw new Error(t); }))
.catch(err => alert('Errore salvataggio vista: ' + err.message));
});
}
</script>
-114
View File
@@ -1,114 +0,0 @@
Cognome,Nome,data_nascita,Indirizzo,CAP,Città,sigla_provincia,genere,tipo_documento,numero_documento,scadenza_documento,note,contatto_1_tipo,contatto_1_valore,contatto_1_etichetta,contatto_2_tipo,contatto_2_valore,contatto_2_etichetta
BENZI,MARIANO,,via per Telve 56/5,38051,Borgo Valsugana,TN,M,,,,,email,mariano.benzi@adim.it,,cellulare,393473028039,
TOMASI,don GIAMPAOLO,,,38061,ALA,TN,M,,,,,email,dongiampaolo.tomasi@gmail.com,,cellulare,3490839773,
CALDERA,FULVIA,,Fr. Larido,38071,BLEGGIO SUPERIORE,TN,F,,,,,email,fulviacaldera64@gmail.com,,cellulare,3421955876,
FURLAN,DANIELA,,"Via Larganzoni, 56",38050,RONCEGNO,TN,F,,,,,email,f-daniela@libero.it,,cellulare,3479394156,
HUEZ,ROBERTA,,"C. Moser, 21",38015,LAVIS,TN,F,,,,,email,robertaaam@gmail.com,,cellulare,3497257180,
LAZZERI,MARIO,,"Via Madonna Corona, 17",38123,TRENTO,TN,M,,,,,email,lazzeri.mario.43@gmail.com,,cellulare,3401048675,
MONTIBELLER,LINDA,,"Via Gedeone de Vettorazzi, 22",38056,Levico,TN,F,,,,,email,lindamontib@gmail.com,,cellulare,3427490695,
OSTI,STEFANO,,"Via Degasperi, 1",38010,SPORMAGGIORE,TN,M,,,,,email,stefosti@alice.it,,cellulare,336522848,
SEISER,LORENZA,,"Via al Pez, 3",38121,TAVERNARO - TRENTO,TN,F,,,,,email,lorenzaseiser@libero.it,,cellulare,3297881479,
AMATO,VINCENZINA (ENZA),,"Via Frescobaldi, 13",44042,CENTO,FE,F,,,,,email,vincenzinaamato08@gmail.com,,cellulare,3405716909,
GALLERANI,GILBERTO,,"Via Frescobaldi, 13",44042,CENTO,FE,M,,,,,email,ing.gallerani@libero.it,,cellulare,3356245621,
PARI,GIANNI,,"via Sabbadini, 11",46047,MANTOVA,MN,M,,,,,email,agiannipari@gmail.com,,cellulare,3338279649,
GABBANA,MARISA,,"via Ippolito de zorzi, 47",31029,VITTORIO VENETO,TV,F,,,,,email,gabbanadebiasimarisa@email.it,,cellulare,3202628190,
DALRI,DANILO,,"Via Ruatti, 45/a",38023,CLES,TN,M,,,,,email,danilodalri@gmail.com,,cellulare,3356250722,
NOCERA,MICHELA,,"Via Pola, 4",22100,COMO,CO,F,,,,,email,michela.nocera26@gmail.com,,cellulare,3519542968,
MOSCHETTA,ANNA MARIA,,"Via Fibonacci, 5",50131,FIRENZE,FI,F,,,,,email,moschettavannucci@gmail.com,,cellulare,3477106320,
BROCCIO,don FRANCESCO,,Contrada Ligoria,98028,Santa Teresa di Riva,ME,M,,,,,email,gesuconfidointe@icloud.com,,cellulare,3687410809,
NARDINI,GIACINTO,,"Via S.Vito, 129",41057,Spilamberto,MO,M,,,,,email,giacinto.nardini@gmail.com,,cellulare,3333792667,
SCAGLIONI,GIUSEPPE,,"via Rossini, 78",41028,Serramazzoni,MO,M,,,,,email,studioscaglioniquattrini@gmail.com,,cellulare,3335946940,
NOVI,DOMENICO,,"via Tiziano, 86",41100,Modena,MO,M,,,,,email,dnovi@outlook.it,,cellulare,3487314554,
CAMMARATA,LINDA,,,90123,PALERMO,MO,F,,,,,email,giuseppe.orecchio@libero.it,,cellulare,3299278652,
OGNIBENE,Sr. MARIA AUSILIA,,"via Maestro Pintorno,1",90015,CEFALU,PA,F,,,,,email,ludovicotesta@alice.it,,cellulare,3387058839,
CORTESI,LUCIA TERESA,,"via Marche, 4",48121,RAVENNA,RA,F,,,,,email,cortesiscuola@gmail.com,,cellulare,3402839926,
VIGNALI,ERMES,,"via Ungaretti, 8",42020,MONTECAVOLO,RE,M,,,,,email,vignaliermes@gmail.com,,cellulare,3405347341,
FIAMMETTA,MAURIZIO,,"Via Tre Santi, 80/2",91011,ALCAMO,TP,M,,,,,email,mauriziofiammetta@gmail.com,,cellulare,3391824865,
SCRASCIA,MARIA,,Via dellEremo 106/1,34139,TRIESTE,TS,F,,,,,email,paolopanjek@alice.it,,cellulare,3331613546,
BEVILACQUA,don BRUNO,,"via Riviera San Marco, 22",35012,CAMPOSANPIERO,PD,M,,,,,email,dbrunob@alice.it,,cellulare,3358121374,
BIASIOLO,FILIPPO,,"Piazza A. de Gasperi, 4",35010,CADONEGHE,PD,M,,,,,email,mariellacravotto@gmail.com,,cellulare,3485291162,
PANCELLA,IDA,,"strada Cetrullo,4",65129,PESCARA,PE,F,,,,,email,pancellaida@gmail.com,,cellulare,3282848768,
DI TOMA,DOMENICO,,"Andrea Cost, 19",20090,Trezzano sul Naviglio,MI,M,,,,,email,Dtomad49@gmail.com,,cellulare,3396639632,
TROIANO,LIBERA,,"via G. Pascoli, 8",20046,Cisliano,MI,F,,,,,email,troianolibera@gmail.com,,cellulare,3248062311,
MACCHIONI,don ERMES,,"Via Fontana, 31",42048,"Fontana, Rubiera",RE,M,,,,,email,ermesmac@gmail.com,,cellulare,3358156766,
CO,don BEPPINO,,PATIGNO,54029,ZERI,MS,M,,,,,email,padrebeppino@gmail.com,,cellulare,3338050556,
TALCO,ANGELA,,via Vatta,19121,La Spezia,SP,F,,,,,email,talcopaolo@libero.it,,cellulare,3487433253,
DI TOLVE,LUCA,,"VIA S. CARLO,17",23875,OSNAGO,LC,M,,,,,email,INFO@GRUPPOLOT.IT,,cellulare,3929182449,
ANGELINI,RENZO,,"Via Bolghera, 63",38122,TRENTO,TN,M,,,,,email,renzocandriai@gmail.com,,cellulare,3404621901,
BAZZANELLA,KATIA,,"Via del Bomport, 16",38123,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3470335033,
BEOZZO,LORENA,,"Via Pranzelores, 50",38121,TRENTO,TN,F,,,,,email,lorena.beozzo@gmail.com,,cellulare,3470484156,
BEOZZO,MARIASSUNTA,,"Via Al Maso Banal, 41",38123,Loc. Sopramonte - TRENTO,TN,F,,,,,email,mary.beozzo@alice.it,,cellulare,3494053464,
BEOZZO,ORNELLA,,"Via Menguzzato, 169",38123,TRENTO,TN,F,,,,,email,ornbe24@gmail.com,,cellulare,3206974758,
BERTOLDI,MARIA LINA,,"Via della Val, 35",38057,PERGINE VALSUGANA,TN,F,,,,,email,bertore44@gmail.com,,cellulare,3480826573,
BERTUZZI,MANUELA,,"Via G. Verdi, 9",38041,ALBIANO,TN,F,,,,,email,b.manuciauu@libero.it,,cellulare,3492320105,
BEVILACQUA,ANITA,,"Salita Dallafior, 15",38121,TRENTO,TN,F,,,,,email,anita.bevilacqua@apss.tn.it,,cellulare,3351707570,
BODECCHI,TIZIANA,,,,,,F,,,,,email,bodecchitiziana@gmail.com,,cellulare,3398618852,
BORTOLUZZI,UMBERTO,,"Via Enrico Conci, 64",38123,TRENTO,TN,M,,,,,email,umberto.bortoluzzi@fastwebnet.it,,cellulare,3497921533,
BUFFA,PIERINO,,"Via Valnigra, 14",38123,TRENTO,TN,M,,,,,email,frangioste@alice.it,,cellulare,3339561545,
CARLI,MARA,,"Via Lagorai, 28",38057,PERGINE VALSUGANA,TN,F,,,,,email,maracarli76@gmail.com,,cellulare,3427485121,
CATANZARO,NICOLINA,,"Via Dei Rivi, 21",38123,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3358275881,
CORRADINI,ROSSANA,,"Via Tonale, 104",38010,SAN MICHELE ALL'ADIGE,TN,F,,,,,email,rossana.rossy2401@gmail.com,,cellulare,3482736684,
DALLA FONTANA,RENZO,,"Via Prà dei Frati, 17",38122,TRENTO,TN,M,,,,,email,noemail@co.ot,,,,
DALLA TORRE,FLAVIO,,"Via della Val, 35",38057,PERGINE VALSUGANA,TN,M,,,,,email,bertore44@gmail.com,,cellulare,3808636746,
DEMATTIO,LUIGIA,,"Via Zambra, 26",38022,TRENTO,TN,F,,,,,email,noemail@co.ot,,,,
DE SANTIS,DOMENICO,,"Via Pozzata, 7",38123,TRENTO,TN,M,,,,,email,noemail@co.ot,,cellulare,3392292336,
DE VIGILI,CESARINA,,"Via Trento, 12",38017,MEZZOLOMBARDO,TN,F,,,,,email,dev.cesi@gmail.com,,cellulare,3333657802,
DE VIGILI,GIGLIOLA,,"Via Trento, 87/4",38017,MEZZOLOMBARDO,TN,F,,,,,email,g.devigili@gmail.com,,cellulare,3477996652,
FALETTI,MARIA LUISA,,"Via Bolghera, 63",38122,TRENTO,TN,F,,,,,email,mlfaletti@gmail.com,,cellulare,3403435105,
FILOSI,FERNANDA,,"Via Vittime Foibe, 36",38121,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3402381630,
FRONZA,LOREDANA,,"Via Dosso, 16",38045,CASTELLO - MOLINA DI FIEMME,TN,F,,,,,email,loredanafronza1@gmail.com,,cellulare,3475574930,
GOTTARDI,GABRIELLA,,"Via Madonna Corona, 17",38123,TRENTO,TN,F,,,,,email,lazzeri.mario.43@gmail.com,,,,
KASAWAKI,VERGARAI,,"Via Pascoli, 5/a",38122,TRENTO,TN,F,,,,,email,agonero@yahoo.it,,cellulare,3485279777,
LA MASTRA,ISABELLA,,"Via Marconi, 99",38057,PERGINE VALSUGANA,TN,F,,,,,email,isabellalamastra@live.it,,cellulare,3401918740,
LORANDINI,CESARINA,,"Via Stoppani, 10",38121,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3397935933,
MAROSO,ANGELO,,"Via Dosso, 16",38045,CASTELLO - MOLINA DI FIEMME,TN,M,,,,,email,loredanafronza1@gmail.com,,,,
MENGON,FLAVIANO,,"Vicolo Dallapiccola, 8",38122,TRENTO,TN,M,,,,,email,flavianomengon@gmail.com,,cellulare,3714694218,
MENGON,FEDERICO,,"Via al Pez, 3",38121,TAVERNARO - TRENTO,TN,M,,,,,email,federicomengon@gmail.com,,cellulare,3490064268,
MEZZANOTTE,MARIA LUISA,,"Via Valnigra, 14",38123,TRENTO,TN,F,,,,,email,frangioste@alice.it,,telefono,0461920038,
MOSCONI,RENATO,,"Via Pizzano, 20/b",38029,VERMIGLIO,TN,M,,,,,email,rmosconi550@gmail.com,,,,
MOSER,CHIARA,,"Via Alla Croce, 10",38057,PERGINE VALSUGANA,TN,F,,,,,email,chimos@hotmail.it,,cellulare,3481444247,
MUNARO,SYBILLE,,"Primo Vicolo Bristol, 50",38015,LAVIS,TN,F,,,,,email,sybillemunaro@gmail.com,,cellulare,3495502637,
NARDELLI,CARLO,,"Via Lungoparco, 20",38056,LEVICO TERME,TN,M,,,,,email,noemail@co.ot,,cellulare,3407286056,
OSELE,BRUNO,,"Via G. Morone, 1",38121,TRENTO,TN,M,,,,,email,noemail@co.ot,,cellulare,3481303356,
OSS ZATTEL,MARILENA,,"Via dei Pinteroti, 67",38057,PERGINE VALSUGANA,TN,F,,,,,email,marilenazattel@alice.it,,cellulare,3491048102,
PARISI,CHIARA,,"P.zza S. Giovanni, 15",38017,MEZZOLOMBARDO,TN,F,,,,,email,parisi.chiara@virgilio.it,,cellulare,3403056179,
PELLEGRIN,WALTER,,"Via della Pozza, 3",38123,TRENTO,TN,M,,,,,email,pellegrinwalter@gmail.com,,cellulare,3402658183,
ROSATTI,ROBERTA,,"Via della Pozza, 3",38123,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3287828116,
SITTON,GIUSEPPE,,"Via Ai Fovi, 1 -Canzolino",38057,PERGINE VALSUGANA,TN,M,,,,,email,bepisiton@gmail.com,,cellulare,3495481241,
TARASCHI,DONATELLA,,"Via Flli Fontana, 45",38123,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3358247491,
TISOT,don RENATO,,"Via Valnigra, 14",38123,TRENTO,TN,M,,,,,email,frangioste@alice.it,,cellulare,3492983794,
TRAMECERE,ENRICA,,"Via Pozzata, 7",38123,TRENTO,TN,F,,,,,email,noemail@co.ot,,cellulare,3392292336,
VICENTINI,LORENZO,,"Via Caduti, 10",38060,POMAROLO,TN,M,,,,,email,lobika@libero.it,,cellulare,3926130958,
POLETTA,GINO,,"via Ing. Noale Rinaldi, 16",31051,VALMARENGO DI FOLLINA,TV,M,,,,,email,ginopoletta@libero.it,,cellulare,3405389046,
RASI,GIAN MARCO (diacono),,"via G. Dina, 39",30115,CONEGLIANO,TV,M,,,,,email,gianmarcor@alice.it,,telefono,043823395,
PERUZZA,AMERIGA,,"via G. Dina, 39",30115,CONEGLIANO,TV,F,,,,,email,gianmarcor@alice.it,,telefono,043823395,
CHIAPPIN,MARA,,"Via del Boasco, 18",31029,VITTORIO VENETO,TV,F,,,,,email,chiappinmara@gmail.com,,cellulare,3925012356,
GALLON,ELENA,,"via Peccol, 9",31020,REVINE LAGO,TV,F,,,,,email,gallon.elena81@gmail.com,,cellulare,3486721873,
NEGRO,SANDRA,,"Strada dei laghi, 68",31020,REVINE LAGO,TV,F,,,,,email,sandra.negro.dm62@gmail.com,,cellulare,3396132973,
GABBANA,LUCIANO,,"via Ippolito de zorzi, 47",31029,VITTORIO VENETO,TV,M,,,,,email,gabbana.luciano@gmail.com,,cellulare,3474219879,
DI STEFANO,MARIA,,"Via Ronche, 26",31051,FOLLINA,TV,F,,,,,email,noemail@co.ot,,cellulare,3333418565,
CERUTTI,IVANA,,"piazza Madison, 21",46100,MANTOVA,MN,F,,,,,email,ivana@ivanacerutti.com,,cellulare,3357175872,
DAOLIO,MARIA TERESA,,"via XX settembre, 9",46030,VILLASTRADA,MN,F,,,,,email,daoliomteresa@gmail.com,,cellulare,3488018391,
DI PAOLA,MATILDE,,"Portella della Ginestra, 24",90123,PALERMO,PA,F,,,,,email,tilde.dipaola@gmail.com,,cellulare,3518958210,
PRIMA,RENATO,,"via S. Francesco dAssisi, 23",46047,MANTOVA,MN,M,,,,,email,renato.pr1@alice.it,,cellulare,3473105010,
ANSOVINI,MONICA,,"via Flaminia, 14",61032,FANO,PU,F,,,,,email,ansovinimonica@gmail.com,,cellulare,3296692555,
ZOPPO MARTELLINI,SIBILLA,,"Via De Gasperi, 86",63811,S. ELPIDIO A MARE,FM,F,,,,,email,sibillazoppo@libero.it,,cellulare,3383182811,
AMATO,CLAUDIO,,"Via Frescobaldi, 16",44042,CENTO,FE,M,,,,,email,claudio.amato100@gmail.com,,cellulare,3279826316,
CARUANA,VALERIA,,"Via Frescobaldi, 16",44042,CENTO,FE,F,,,,,email,valeriacaruana78@gmail.com,,cellulare,3496534350,
SAMBETTINI,MARIAGRAZIA,,"Via Alfredo Catalani, 7",44042,CENTO,FE,F,,,,,email,mariagrazia.sambettini@gmail.com,,cellulare,3404197334,
BERTELLI,LARA,,via M. Ruggeri 18/3,44042,CENTO,FE,F,,,,,email,larabertelli46@gmail.com,,cellulare,3711427518,
FIORINO,MARIA ANNA,,"via Giuseppe Garibaldi, 4",44047,TERRE DEL RENO,FE,F,,,,,email,noemail@co.ot,,cellulare,3270335275,
FACCHINI,AGNESE,,"via dei tigli, 32",40050,ARGELATO,BO,F,,,,,email,agnese1801@gmail.com,,cellulare,3480536779,
MONTEPOLI,SILVIA,,"via San Vito,1295",41057,SPILAMBERTO,MO,F,,,,,email,noemail@co.ot,,cellulare,3395670562,
PELLONI,TINA,,"via statale,116",41014,CASTELVETRO,MO,F,,,,,email,noemail@co.ot,,cellulare,3287444153,
ROSINI,CECILIA,,"via C. Bisi, 103",41125,MODENA,MO,F,,,,,email,noemail@co.ot,,cellulare,3457962032,
LOLITO,ANNA,,"via P. Nenni, 2/a Loc Bazzano",40053,VALSAMOGGIA,BO,F,,,,,email,alolito1959@gmail.com,,cellulare,3457086851,
BRAGALLI,GUERRINA,,"via Claudia, 2050/a",41056,SAVIGNANO SUL PANARO,MO,F,,,,,email,noemail@co.ot,,cellulare,3331225796,
FRANZELLI,ANNA MARIA,,"via Giacobazzi, 5",41049,SASSUOLO,MO,F,,,,,email,noemail@co.ot,,cellulare,3406176125,
GALASSINI,MARIAROSA,,"via Fornelli, 9",40050,MOTEVEGLIO,BO,F,,,,,email,noemail@co.ot,,cellulare,3407524133,
GUICCIARDI,NADIA,,"via Ponte Marianna, 36",41057,SPILAMBERTO,MO,F,,,,,email,noemail@co.ot,,cellulare,3358440652,
LATRONICO,ANTONELLA,,"via Monte San Giovanni,56",40050,MONTE SAN PIETRO,BO,F,,,,,email,anto21@hotmail.it,,cellulare,3451313680,
ABBATERUSSO,CLAUDIA,,"via villa doro, 42/1",41122,MODENA,MO,F,,,,,email,noemail@co.ot,,cellulare,3899042127,
FRACCHETTI,ROBERTA,,"Via del Santuario, 63",38061,MARANI DI ALA ALA,TN,F,,,,,email,roberta.fracchetti@gmail.com,,cellulare,3408998314,
PINTER,FRANCESCA,,"VIa Gubert, 1",38061,SANTA MARGHERITA DI ALA ALA,TN,F,,,,,email,francypinter@gmail.com,,cellulare,3456185174,
CAITI,VALDO,,"via Contarella,25",42019,SCANDIANO,RE,M,,,,,email,g.cremaschi@cremaschiengineering.it,,cellulare,3471431280,
1 Cognome Nome data_nascita Indirizzo CAP Città sigla_provincia genere tipo_documento numero_documento scadenza_documento note contatto_1_tipo contatto_1_valore contatto_1_etichetta contatto_2_tipo contatto_2_valore contatto_2_etichetta
2 BENZI MARIANO via per Telve 56/5 38051 Borgo Valsugana TN M email mariano.benzi@adim.it cellulare 393473028039
3 TOMASI don GIAMPAOLO 38061 ALA TN M email dongiampaolo.tomasi@gmail.com cellulare 3490839773
4 CALDERA FULVIA Fr. Larido 38071 BLEGGIO SUPERIORE TN F email fulviacaldera64@gmail.com cellulare 3421955876
5 FURLAN DANIELA Via Larganzoni, 56 38050 RONCEGNO TN F email f-daniela@libero.it cellulare 3479394156
6 HUEZ ROBERTA C. Moser, 21 38015 LAVIS TN F email robertaaam@gmail.com cellulare 3497257180
7 LAZZERI MARIO Via Madonna Corona, 17 38123 TRENTO TN M email lazzeri.mario.43@gmail.com cellulare 3401048675
8 MONTIBELLER LINDA Via Gedeone de Vettorazzi, 22 38056 Levico TN F email lindamontib@gmail.com cellulare 3427490695
9 OSTI STEFANO Via Degasperi, 1 38010 SPORMAGGIORE TN M email stefosti@alice.it cellulare 336522848
10 SEISER LORENZA Via al Pez, 3 38121 TAVERNARO - TRENTO TN F email lorenzaseiser@libero.it cellulare 3297881479
11 AMATO VINCENZINA (ENZA) Via Frescobaldi, 13 44042 CENTO FE F email vincenzinaamato08@gmail.com cellulare 3405716909
12 GALLERANI GILBERTO Via Frescobaldi, 13 44042 CENTO FE M email ing.gallerani@libero.it cellulare 3356245621
13 PARI GIANNI via Sabbadini, 11 46047 MANTOVA MN M email agiannipari@gmail.com cellulare 3338279649
14 GABBANA MARISA via Ippolito de zorzi, 47 31029 VITTORIO VENETO TV F email gabbanadebiasimarisa@email.it cellulare 3202628190
15 DALRI’ DANILO Via Ruatti, 45/a 38023 CLES TN M email danilodalri@gmail.com cellulare 3356250722
16 NOCERA MICHELA Via Pola, 4 22100 COMO CO F email michela.nocera26@gmail.com cellulare 3519542968
17 MOSCHETTA ANNA MARIA Via Fibonacci, 5 50131 FIRENZE FI F email moschettavannucci@gmail.com cellulare 3477106320
18 BROCCIO don FRANCESCO Contrada Ligoria 98028 Santa Teresa di Riva ME M email gesuconfidointe@icloud.com cellulare 3687410809
19 NARDINI GIACINTO Via S.Vito, 129 41057 Spilamberto MO M email giacinto.nardini@gmail.com cellulare 3333792667
20 SCAGLIONI GIUSEPPE via Rossini, 78 41028 Serramazzoni MO M email studioscaglioniquattrini@gmail.com cellulare 3335946940
21 NOVI DOMENICO via Tiziano, 86 41100 Modena MO M email dnovi@outlook.it cellulare 3487314554
22 CAMMARATA LINDA 90123 PALERMO MO F email giuseppe.orecchio@libero.it cellulare 3299278652
23 OGNIBENE Sr. MARIA AUSILIA via Maestro Pintorno,1 90015 CEFALU’ PA F email ludovicotesta@alice.it cellulare 3387058839
24 CORTESI LUCIA TERESA via Marche, 4 48121 RAVENNA RA F email cortesiscuola@gmail.com cellulare 3402839926
25 VIGNALI ERMES via Ungaretti, 8 42020 MONTECAVOLO RE M email vignaliermes@gmail.com cellulare 3405347341
26 FIAMMETTA MAURIZIO Via Tre Santi, 80/2 91011 ALCAMO TP M email mauriziofiammetta@gmail.com cellulare 3391824865
27 SCRASCIA MARIA Via dell’Eremo 106/1 34139 TRIESTE TS F email paolopanjek@alice.it cellulare 3331613546
28 BEVILACQUA don BRUNO via Riviera San Marco, 22 35012 CAMPOSANPIERO PD M email dbrunob@alice.it cellulare 3358121374
29 BIASIOLO FILIPPO Piazza A. de Gasperi, 4 35010 CADONEGHE PD M email mariellacravotto@gmail.com cellulare 3485291162
30 PANCELLA IDA strada Cetrullo,4 65129 PESCARA PE F email pancellaida@gmail.com cellulare 3282848768
31 DI TOMA DOMENICO Andrea Cost, 19 20090 Trezzano sul Naviglio MI M email Dtomad49@gmail.com cellulare 3396639632
32 TROIANO LIBERA via G. Pascoli, 8 20046 Cisliano MI F email troianolibera@gmail.com cellulare 3248062311
33 MACCHIONI don ERMES Via Fontana, 31 42048 Fontana, Rubiera RE M email ermesmac@gmail.com cellulare 3358156766
34 CO’ don BEPPINO PATIGNO 54029 ZERI MS M email padrebeppino@gmail.com cellulare 3338050556
35 TALCO ANGELA via Vatta 19121 La Spezia SP F email talcopaolo@libero.it cellulare 3487433253
36 DI TOLVE LUCA VIA S. CARLO,17 23875 OSNAGO LC M email INFO@GRUPPOLOT.IT cellulare 3929182449
37 ANGELINI RENZO Via Bolghera, 63 38122 TRENTO TN M email renzocandriai@gmail.com cellulare 3404621901
38 BAZZANELLA KATIA Via del Bomport, 16 38123 TRENTO TN F email noemail@co.ot cellulare 3470335033
39 BEOZZO LORENA Via Pranzelores, 50 38121 TRENTO TN F email lorena.beozzo@gmail.com cellulare 3470484156
40 BEOZZO MARIASSUNTA Via Al Maso Banal, 41 38123 Loc. Sopramonte - TRENTO TN F email mary.beozzo@alice.it cellulare 3494053464
41 BEOZZO ORNELLA Via Menguzzato, 169 38123 TRENTO TN F email ornbe24@gmail.com cellulare 3206974758
42 BERTOLDI MARIA LINA Via della Val, 35 38057 PERGINE VALSUGANA TN F email bertore44@gmail.com cellulare 3480826573
43 BERTUZZI MANUELA Via G. Verdi, 9 38041 ALBIANO TN F email b.manuciauu@libero.it cellulare 3492320105
44 BEVILACQUA ANITA Salita Dallafior, 15 38121 TRENTO TN F email anita.bevilacqua@apss.tn.it cellulare 3351707570
45 BODECCHI TIZIANA F email bodecchitiziana@gmail.com cellulare 3398618852
46 BORTOLUZZI UMBERTO Via Enrico Conci, 64 38123 TRENTO TN M email umberto.bortoluzzi@fastwebnet.it cellulare 3497921533
47 BUFFA PIERINO Via Valnigra, 14 38123 TRENTO TN M email frangioste@alice.it cellulare 3339561545
48 CARLI MARA Via Lagorai, 28 38057 PERGINE VALSUGANA TN F email maracarli76@gmail.com cellulare 3427485121
49 CATANZARO NICOLINA Via Dei Rivi, 21 38123 TRENTO TN F email noemail@co.ot cellulare 3358275881
50 CORRADINI ROSSANA Via Tonale, 104 38010 SAN MICHELE ALL'ADIGE TN F email rossana.rossy2401@gmail.com cellulare 3482736684
51 DALLA FONTANA RENZO Via Prà dei Frati, 17 38122 TRENTO TN M email noemail@co.ot
52 DALLA TORRE FLAVIO Via della Val, 35 38057 PERGINE VALSUGANA TN M email bertore44@gmail.com cellulare 3808636746
53 DEMATTIO LUIGIA Via Zambra, 26 38022 TRENTO TN F email noemail@co.ot
54 DE SANTIS DOMENICO Via Pozzata, 7 38123 TRENTO TN M email noemail@co.ot cellulare 3392292336
55 DE VIGILI CESARINA Via Trento, 12 38017 MEZZOLOMBARDO TN F email dev.cesi@gmail.com cellulare 3333657802
56 DE VIGILI GIGLIOLA Via Trento, 87/4 38017 MEZZOLOMBARDO TN F email g.devigili@gmail.com cellulare 3477996652
57 FALETTI MARIA LUISA Via Bolghera, 63 38122 TRENTO TN F email mlfaletti@gmail.com cellulare 3403435105
58 FILOSI FERNANDA Via Vittime Foibe, 36 38121 TRENTO TN F email noemail@co.ot cellulare 3402381630
59 FRONZA LOREDANA Via Dosso, 16 38045 CASTELLO - MOLINA DI FIEMME TN F email loredanafronza1@gmail.com cellulare 3475574930
60 GOTTARDI GABRIELLA Via Madonna Corona, 17 38123 TRENTO TN F email lazzeri.mario.43@gmail.com
61 KASAWAKI VERGARAI Via Pascoli, 5/a 38122 TRENTO TN F email agonero@yahoo.it cellulare 3485279777
62 LA MASTRA ISABELLA Via Marconi, 99 38057 PERGINE VALSUGANA TN F email isabellalamastra@live.it cellulare 3401918740
63 LORANDINI CESARINA Via Stoppani, 10 38121 TRENTO TN F email noemail@co.ot cellulare 3397935933
64 MAROSO ANGELO Via Dosso, 16 38045 CASTELLO - MOLINA DI FIEMME TN M email loredanafronza1@gmail.com
65 MENGON FLAVIANO Vicolo Dallapiccola, 8 38122 TRENTO TN M email flavianomengon@gmail.com cellulare 3714694218
66 MENGON FEDERICO Via al Pez, 3 38121 TAVERNARO - TRENTO TN M email federicomengon@gmail.com cellulare 3490064268
67 MEZZANOTTE MARIA LUISA Via Valnigra, 14 38123 TRENTO TN F email frangioste@alice.it telefono 0461920038
68 MOSCONI RENATO Via Pizzano, 20/b 38029 VERMIGLIO TN M email rmosconi550@gmail.com
69 MOSER CHIARA Via Alla Croce, 10 38057 PERGINE VALSUGANA TN F email chimos@hotmail.it cellulare 3481444247
70 MUNARO SYBILLE Primo Vicolo Bristol, 50 38015 LAVIS TN F email sybillemunaro@gmail.com cellulare 3495502637
71 NARDELLI CARLO Via Lungoparco, 20 38056 LEVICO TERME TN M email noemail@co.ot cellulare 3407286056
72 OSELE BRUNO Via G. Morone, 1 38121 TRENTO TN M email noemail@co.ot cellulare 3481303356
73 OSS ZATTEL MARILENA Via dei Pinteroti, 67 38057 PERGINE VALSUGANA TN F email marilenazattel@alice.it cellulare 3491048102
74 PARISI CHIARA P.zza S. Giovanni, 15 38017 MEZZOLOMBARDO TN F email parisi.chiara@virgilio.it cellulare 3403056179
75 PELLEGRIN WALTER Via della Pozza, 3 38123 TRENTO TN M email pellegrinwalter@gmail.com cellulare 3402658183
76 ROSATTI ROBERTA Via della Pozza, 3 38123 TRENTO TN F email noemail@co.ot cellulare 3287828116
77 SITTON GIUSEPPE Via Ai Fovi, 1 -Canzolino 38057 PERGINE VALSUGANA TN M email bepisiton@gmail.com cellulare 3495481241
78 TARASCHI DONATELLA Via Flli Fontana, 45 38123 TRENTO TN F email noemail@co.ot cellulare 3358247491
79 TISOT don RENATO Via Valnigra, 14 38123 TRENTO TN M email frangioste@alice.it cellulare 3492983794
80 TRAMECERE ENRICA Via Pozzata, 7 38123 TRENTO TN F email noemail@co.ot cellulare 3392292336
81 VICENTINI LORENZO Via Caduti, 10 38060 POMAROLO TN M email lobika@libero.it cellulare 3926130958
82 POLETTA GINO via Ing. Noale Rinaldi, 16 31051 VALMARENGO DI FOLLINA TV M email ginopoletta@libero.it cellulare 3405389046
83 RASI GIAN MARCO (diacono) via G. Dina, 39 30115 CONEGLIANO TV M email gianmarcor@alice.it telefono 043823395
84 PERUZZA AMERIGA via G. Dina, 39 30115 CONEGLIANO TV F email gianmarcor@alice.it telefono 043823395
85 CHIAPPIN MARA Via del Boasco, 18 31029 VITTORIO VENETO TV F email chiappinmara@gmail.com cellulare 3925012356
86 GALLON ELENA via Peccol, 9 31020 REVINE LAGO TV F email gallon.elena81@gmail.com cellulare 3486721873
87 NEGRO SANDRA Strada dei laghi, 68 31020 REVINE LAGO TV F email sandra.negro.dm62@gmail.com cellulare 3396132973
88 GABBANA LUCIANO via Ippolito de zorzi, 47 31029 VITTORIO VENETO TV M email gabbana.luciano@gmail.com cellulare 3474219879
89 DI STEFANO MARIA Via Ronche, 26 31051 FOLLINA TV F email noemail@co.ot cellulare 3333418565
90 CERUTTI IVANA piazza Madison, 21 46100 MANTOVA MN F email ivana@ivanacerutti.com cellulare 3357175872
91 DAOLIO MARIA TERESA via XX settembre, 9 46030 VILLASTRADA MN F email daoliomteresa@gmail.com cellulare 3488018391
92 DI PAOLA MATILDE Portella della Ginestra, 24 90123 PALERMO PA F email tilde.dipaola@gmail.com cellulare 3518958210
93 PRIMA RENATO via S. Francesco d’Assisi, 23 46047 MANTOVA MN M email renato.pr1@alice.it cellulare 3473105010
94 ANSOVINI MONICA via Flaminia, 14 61032 FANO PU F email ansovinimonica@gmail.com cellulare 3296692555
95 ZOPPO MARTELLINI SIBILLA Via De Gasperi, 86 63811 S. ELPIDIO A MARE FM F email sibillazoppo@libero.it cellulare 3383182811
96 AMATO CLAUDIO Via Frescobaldi, 16 44042 CENTO FE M email claudio.amato100@gmail.com cellulare 3279826316
97 CARUANA VALERIA Via Frescobaldi, 16 44042 CENTO FE F email valeriacaruana78@gmail.com cellulare 3496534350
98 SAMBETTINI MARIAGRAZIA Via Alfredo Catalani, 7 44042 CENTO FE F email mariagrazia.sambettini@gmail.com cellulare 3404197334
99 BERTELLI LARA via M. Ruggeri 18/3 44042 CENTO FE F email larabertelli46@gmail.com cellulare 3711427518
100 FIORINO MARIA ANNA via Giuseppe Garibaldi, 4 44047 TERRE DEL RENO FE F email noemail@co.ot cellulare 3270335275
101 FACCHINI AGNESE via dei tigli, 32 40050 ARGELATO BO F email agnese1801@gmail.com cellulare 3480536779
102 MONTEPOLI SILVIA via San Vito,1295 41057 SPILAMBERTO MO F email noemail@co.ot cellulare 3395670562
103 PELLONI TINA via statale,116 41014 CASTELVETRO MO F email noemail@co.ot cellulare 3287444153
104 ROSINI CECILIA via C. Bisi, 103 41125 MODENA MO F email noemail@co.ot cellulare 3457962032
105 LOLITO ANNA via P. Nenni, 2/a – Loc Bazzano 40053 VALSAMOGGIA BO F email alolito1959@gmail.com cellulare 3457086851
106 BRAGALLI GUERRINA via Claudia, 2050/a 41056 SAVIGNANO SUL PANARO MO F email noemail@co.ot cellulare 3331225796
107 FRANZELLI ANNA MARIA via Giacobazzi, 5 41049 SASSUOLO MO F email noemail@co.ot cellulare 3406176125
108 GALASSINI MARIAROSA via Fornelli, 9 40050 MOTEVEGLIO BO F email noemail@co.ot cellulare 3407524133
109 GUICCIARDI NADIA via Ponte Marianna, 36 41057 SPILAMBERTO MO F email noemail@co.ot cellulare 3358440652
110 LATRONICO ANTONELLA via Monte San Giovanni,56 40050 MONTE SAN PIETRO BO F email anto21@hotmail.it cellulare 3451313680
111 ABBATERUSSO CLAUDIA via villa d’oro, 42/1 41122 MODENA MO F email noemail@co.ot cellulare 3899042127
112 FRACCHETTI ROBERTA Via del Santuario, 63 38061 MARANI DI ALA – ALA TN F email roberta.fracchetti@gmail.com cellulare 3408998314
113 PINTER FRANCESCA VIa Gubert, 1 38061 SANTA MARGHERITA DI ALA – ALA TN F email francypinter@gmail.com cellulare 3456185174
114 CAITI VALDO via Contarella,25 42019 SCANDIANO RE M email g.cremaschi@cremaschiengineering.it cellulare 3471431280