Merge branch 'dev'

merge dev
This commit is contained in:
2026-06-10 14:04:40 +02:00
21 changed files with 1440 additions and 475 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'
)); ));
} }
+16 -1
View File
@@ -94,6 +94,8 @@ class EmailController extends Controller
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get(); $individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
$senderAccounts = SenderAccount::active()->get(); $senderAccounts = SenderAccount::active()->get();
$documenti = Documento::orderBy('nome_file')->get();
$prefill = []; $prefill = [];
if ($request->reply_to) { if ($request->reply_to) {
$original = EmailMessage::find($request->reply_to); $original = EmailMessage::find($request->reply_to);
@@ -111,7 +113,7 @@ class EmailController extends Controller
} }
} }
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill', 'senderAccounts')); return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'documenti', 'prefill', 'senderAccounts'));
} }
public function send(Request $request) public function send(Request $request)
@@ -565,6 +567,7 @@ class EmailController extends Controller
private function processAttachments(Request $request): array private function processAttachments(Request $request): array
{ {
$paths = []; $paths = [];
if ($request->hasFile('allegati')) { if ($request->hasFile('allegati')) {
foreach ($request->file('allegati') as $file) { foreach ($request->file('allegati') as $file) {
if ($file->isValid()) { if ($file->isValid()) {
@@ -572,6 +575,18 @@ class EmailController extends Controller
} }
} }
} }
$documentIds = $request->input('documenti_selezionati');
if (!empty($documentIds)) {
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
$docs = Documento::whereIn('id', $ids)->get();
foreach ($docs as $doc) {
if ($doc->file_path && Storage::exists($doc->file_path)) {
$paths[] = Storage::path($doc->file_path);
}
}
}
return $paths; return $paths;
} }
+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
+110 -4
View File
@@ -87,10 +87,17 @@
<div class="form-group"> <div class="form-group">
<label>Allegati</label> <label>Allegati</label>
<div class="border rounded p-3 bg-light"> <div class="border rounded p-3 bg-light">
<label class="btn btn-sm btn-success mb-0"> <div class="mb-3">
<i class="fas fa-upload mr-1"></i> Carica file <button type="button" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#documentiModal">
<input type="file" name="allegati[]" multiple class="d-none"> <i class="fas fa-folder-open mr-1"></i> Seleziona da Documenti
</label> </button>
<label class="btn btn-sm btn-success mb-0">
<i class="fas fa-upload mr-1"></i> Carica file
<input type="file" name="allegati[]" id="allegati" multiple class="d-none" onchange="handleFileUpload(this)">
</label>
</div>
<div id="allegati-list"></div>
<input type="hidden" name="documenti_selezionati" id="documenti_selezionati" value="">
</div> </div>
</div> </div>
@@ -110,10 +117,62 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="documentiModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-folder-open mr-2"></i>Seleziona Documenti</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
@if($documenti->count() > 0)
<div class="table-responsive">
<table class="table table-bordered table-hover table-sm">
<thead>
<tr>
<th style="width: 40px;"></th>
<th>Nome File</th>
<th>Tipologia</th>
</tr>
</thead>
<tbody>
@foreach($documenti as $doc)
<tr>
<td>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input doc-checkbox" id="doc-{{ $doc->id }}" value="{{ $doc->id }}" data-nome="{{ $doc->nome_file }}">
<label class="custom-control-label" for="doc-{{ $doc->id }}"></label>
</div>
</td>
<td>{{ $doc->nome_file }}</td>
<td><span class="badge badge-info">{{ $doc->tipologia ?: $doc->tipo }}</span></td>
</tr>
@endforeach
</tbody>
</table>
</div>
@else
<p class="text-muted">Nessun documento disponibile.</p>
@endif
</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="aggiungiDocumenti()">
<i class="fas fa-plus mr-1"></i> Aggiungi
</button>
</div>
</div>
</div>
</div>
@endsection @endsection
@section('scripts') @section('scripts')
<script> <script>
let allegatiSelezionati = [];
function toggleDestinatari() { function toggleDestinatari() {
const tipo = document.getElementById('destinatario_tipo').value; const tipo = document.getElementById('destinatario_tipo').value;
@@ -160,6 +219,53 @@ function loadGruppiContatti() {
}); });
} }
function aggiungiDocumenti() {
const checkboxes = document.querySelectorAll('.doc-checkbox:checked');
checkboxes.forEach(function(cb) {
const id = parseInt(cb.value);
const nome = cb.dataset.nome;
if (!allegatiSelezionati.includes(id)) {
allegatiSelezionati.push(id);
const container = document.getElementById('allegati-list');
const div = document.createElement('div');
div.className = 'd-flex align-items-center justify-content-between bg-white p-2 mb-2 rounded';
div.id = 'allegato-' + id;
div.innerHTML = '<span><i class="fas fa-file mr-2"></i>' + nome + '</span>' +
'<button type="button" class="btn btn-xs btn-danger" onclick="removeAllegato(' + id + ')">' +
'<i class="fas fa-times"></i></button>';
container.appendChild(div);
}
cb.checked = false;
});
document.getElementById('documenti_selezionati').value = allegatiSelezionati.join(',');
$('#documentiModal').modal('hide');
}
function removeAllegato(id) {
allegatiSelezionati = allegatiSelezionati.filter(function(a) { return a !== id; });
const el = document.getElementById('allegato-' + id);
if (el) el.remove();
document.getElementById('documenti_selezionati').value = allegatiSelezionati.join(',');
}
function handleFileUpload(input) {
if (!input.files) return;
const container = document.getElementById('allegati-list');
for (const file of input.files) {
const div = document.createElement('div');
div.className = 'd-flex align-items-center justify-content-between bg-white p-2 mb-2 rounded';
div.innerHTML = '<span><i class="fas fa-file mr-2"></i>' + file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)</span>' +
'<button type="button" class="btn btn-xs btn-danger" onclick="this.parentElement.remove()">' +
'<i class="fas fa-times"></i></button>';
container.appendChild(div);
}
input.value = '';
}
function saveDraft() { function saveDraft() {
const form = document.getElementById('emailForm'); const form = document.getElementById('emailForm');
const formData = new FormData(form); const formData = new FormData(form);
+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>
+3
View File
@@ -38,6 +38,7 @@ return array(
'App\\Http\\Controllers\\MailingController' => $baseDir . '/app/Http/Controllers/MailingController.php', 'App\\Http\\Controllers\\MailingController' => $baseDir . '/app/Http/Controllers/MailingController.php',
'App\\Http\\Controllers\\MailingListController' => $baseDir . '/app/Http/Controllers/MailingListController.php', 'App\\Http\\Controllers\\MailingListController' => $baseDir . '/app/Http/Controllers/MailingListController.php',
'App\\Http\\Controllers\\ReportController' => $baseDir . '/app/Http/Controllers/ReportController.php', 'App\\Http\\Controllers\\ReportController' => $baseDir . '/app/Http/Controllers/ReportController.php',
'App\\Http\\Controllers\\RicercaController' => $baseDir . '/app/Http/Controllers/RicercaController.php',
'App\\Http\\Controllers\\StorageRepositoryController' => $baseDir . '/app/Http/Controllers/StorageRepositoryController.php', 'App\\Http\\Controllers\\StorageRepositoryController' => $baseDir . '/app/Http/Controllers/StorageRepositoryController.php',
'App\\Http\\Controllers\\VistaReportController' => $baseDir . '/app/Http/Controllers/VistaReportController.php', 'App\\Http\\Controllers\\VistaReportController' => $baseDir . '/app/Http/Controllers/VistaReportController.php',
'App\\Http\\Middleware\\AdminOnly' => $baseDir . '/app/Http/Middleware/AdminOnly.php', 'App\\Http\\Middleware\\AdminOnly' => $baseDir . '/app/Http/Middleware/AdminOnly.php',
@@ -67,6 +68,7 @@ return array(
'App\\Models\\Ruolo' => $baseDir . '/app/Models/Ruolo.php', 'App\\Models\\Ruolo' => $baseDir . '/app/Models/Ruolo.php',
'App\\Models\\SenderAccount' => $baseDir . '/app/Models/SenderAccount.php', 'App\\Models\\SenderAccount' => $baseDir . '/app/Models/SenderAccount.php',
'App\\Models\\StorageRepository' => $baseDir . '/app/Models/StorageRepository.php', 'App\\Models\\StorageRepository' => $baseDir . '/app/Models/StorageRepository.php',
'App\\Models\\Tag' => $baseDir . '/app/Models/Tag.php',
'App\\Models\\Tenant' => $baseDir . '/app/Models/Tenant.php', 'App\\Models\\Tenant' => $baseDir . '/app/Models/Tenant.php',
'App\\Models\\TipologiaDocumento' => $baseDir . '/app/Models/TipologiaDocumento.php', 'App\\Models\\TipologiaDocumento' => $baseDir . '/app/Models/TipologiaDocumento.php',
'App\\Models\\TipologiaEvento' => $baseDir . '/app/Models/TipologiaEvento.php', 'App\\Models\\TipologiaEvento' => $baseDir . '/app/Models/TipologiaEvento.php',
@@ -79,6 +81,7 @@ return array(
'App\\Services\\GoogleCalendarSyncService' => $baseDir . '/app/Services/GoogleCalendarSyncService.php', 'App\\Services\\GoogleCalendarSyncService' => $baseDir . '/app/Services/GoogleCalendarSyncService.php',
'App\\Services\\IcsExportService' => $baseDir . '/app/Services/IcsExportService.php', 'App\\Services\\IcsExportService' => $baseDir . '/app/Services/IcsExportService.php',
'App\\Services\\StorageRepositoryService' => $baseDir . '/app/Services/StorageRepositoryService.php', 'App\\Services\\StorageRepositoryService' => $baseDir . '/app/Services/StorageRepositoryService.php',
'App\\Traits\\HasTagsLight' => $baseDir . '/app/Traits/HasTagsLight.php',
'As247\\CloudStorages\\Cache\\PathCache' => $vendorDir . '/as247/cloud-storages/src/Cache/PathCache.php', 'As247\\CloudStorages\\Cache\\PathCache' => $vendorDir . '/as247/cloud-storages/src/Cache/PathCache.php',
'As247\\CloudStorages\\Cache\\Stores\\ArrayStore' => $vendorDir . '/as247/cloud-storages/src/Cache/Stores/ArrayStore.php', 'As247\\CloudStorages\\Cache\\Stores\\ArrayStore' => $vendorDir . '/as247/cloud-storages/src/Cache/Stores/ArrayStore.php',
'As247\\CloudStorages\\Cache\\Stores\\GoogleDrivePersistentStore' => $vendorDir . '/as247/cloud-storages/src/Cache/Stores/GoogleDrivePersistentStore.php', 'As247\\CloudStorages\\Cache\\Stores\\GoogleDrivePersistentStore' => $vendorDir . '/as247/cloud-storages/src/Cache/Stores/GoogleDrivePersistentStore.php',
+3
View File
@@ -650,6 +650,7 @@ class ComposerStaticInit74199c0a3889e8d1256a25f93805ef56
'App\\Http\\Controllers\\MailingController' => __DIR__ . '/../..' . '/app/Http/Controllers/MailingController.php', 'App\\Http\\Controllers\\MailingController' => __DIR__ . '/../..' . '/app/Http/Controllers/MailingController.php',
'App\\Http\\Controllers\\MailingListController' => __DIR__ . '/../..' . '/app/Http/Controllers/MailingListController.php', 'App\\Http\\Controllers\\MailingListController' => __DIR__ . '/../..' . '/app/Http/Controllers/MailingListController.php',
'App\\Http\\Controllers\\ReportController' => __DIR__ . '/../..' . '/app/Http/Controllers/ReportController.php', 'App\\Http\\Controllers\\ReportController' => __DIR__ . '/../..' . '/app/Http/Controllers/ReportController.php',
'App\\Http\\Controllers\\RicercaController' => __DIR__ . '/../..' . '/app/Http/Controllers/RicercaController.php',
'App\\Http\\Controllers\\StorageRepositoryController' => __DIR__ . '/../..' . '/app/Http/Controllers/StorageRepositoryController.php', 'App\\Http\\Controllers\\StorageRepositoryController' => __DIR__ . '/../..' . '/app/Http/Controllers/StorageRepositoryController.php',
'App\\Http\\Controllers\\VistaReportController' => __DIR__ . '/../..' . '/app/Http/Controllers/VistaReportController.php', 'App\\Http\\Controllers\\VistaReportController' => __DIR__ . '/../..' . '/app/Http/Controllers/VistaReportController.php',
'App\\Http\\Middleware\\AdminOnly' => __DIR__ . '/../..' . '/app/Http/Middleware/AdminOnly.php', 'App\\Http\\Middleware\\AdminOnly' => __DIR__ . '/../..' . '/app/Http/Middleware/AdminOnly.php',
@@ -679,6 +680,7 @@ class ComposerStaticInit74199c0a3889e8d1256a25f93805ef56
'App\\Models\\Ruolo' => __DIR__ . '/../..' . '/app/Models/Ruolo.php', 'App\\Models\\Ruolo' => __DIR__ . '/../..' . '/app/Models/Ruolo.php',
'App\\Models\\SenderAccount' => __DIR__ . '/../..' . '/app/Models/SenderAccount.php', 'App\\Models\\SenderAccount' => __DIR__ . '/../..' . '/app/Models/SenderAccount.php',
'App\\Models\\StorageRepository' => __DIR__ . '/../..' . '/app/Models/StorageRepository.php', 'App\\Models\\StorageRepository' => __DIR__ . '/../..' . '/app/Models/StorageRepository.php',
'App\\Models\\Tag' => __DIR__ . '/../..' . '/app/Models/Tag.php',
'App\\Models\\Tenant' => __DIR__ . '/../..' . '/app/Models/Tenant.php', 'App\\Models\\Tenant' => __DIR__ . '/../..' . '/app/Models/Tenant.php',
'App\\Models\\TipologiaDocumento' => __DIR__ . '/../..' . '/app/Models/TipologiaDocumento.php', 'App\\Models\\TipologiaDocumento' => __DIR__ . '/../..' . '/app/Models/TipologiaDocumento.php',
'App\\Models\\TipologiaEvento' => __DIR__ . '/../..' . '/app/Models/TipologiaEvento.php', 'App\\Models\\TipologiaEvento' => __DIR__ . '/../..' . '/app/Models/TipologiaEvento.php',
@@ -691,6 +693,7 @@ class ComposerStaticInit74199c0a3889e8d1256a25f93805ef56
'App\\Services\\GoogleCalendarSyncService' => __DIR__ . '/../..' . '/app/Services/GoogleCalendarSyncService.php', 'App\\Services\\GoogleCalendarSyncService' => __DIR__ . '/../..' . '/app/Services/GoogleCalendarSyncService.php',
'App\\Services\\IcsExportService' => __DIR__ . '/../..' . '/app/Services/IcsExportService.php', 'App\\Services\\IcsExportService' => __DIR__ . '/../..' . '/app/Services/IcsExportService.php',
'App\\Services\\StorageRepositoryService' => __DIR__ . '/../..' . '/app/Services/StorageRepositoryService.php', 'App\\Services\\StorageRepositoryService' => __DIR__ . '/../..' . '/app/Services/StorageRepositoryService.php',
'App\\Traits\\HasTagsLight' => __DIR__ . '/../..' . '/app/Traits/HasTagsLight.php',
'As247\\CloudStorages\\Cache\\PathCache' => __DIR__ . '/..' . '/as247/cloud-storages/src/Cache/PathCache.php', 'As247\\CloudStorages\\Cache\\PathCache' => __DIR__ . '/..' . '/as247/cloud-storages/src/Cache/PathCache.php',
'As247\\CloudStorages\\Cache\\Stores\\ArrayStore' => __DIR__ . '/..' . '/as247/cloud-storages/src/Cache/Stores/ArrayStore.php', 'As247\\CloudStorages\\Cache\\Stores\\ArrayStore' => __DIR__ . '/..' . '/as247/cloud-storages/src/Cache/Stores/ArrayStore.php',
'As247\\CloudStorages\\Cache\\Stores\\GoogleDrivePersistentStore' => __DIR__ . '/..' . '/as247/cloud-storages/src/Cache/Stores/GoogleDrivePersistentStore.php', 'As247\\CloudStorages\\Cache\\Stores\\GoogleDrivePersistentStore' => __DIR__ . '/..' . '/as247/cloud-storages/src/Cache/Stores/GoogleDrivePersistentStore.php',
+2 -2
View File
@@ -3,7 +3,7 @@
'name' => 'laravel/laravel', 'name' => 'laravel/laravel',
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => '7d7d384a0d719ca6fa83e01e50577b94c877e676', 'reference' => '7999c663fe184a13576026be3af0abc1d91d036d',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -427,7 +427,7 @@
'laravel/laravel' => array( 'laravel/laravel' => array(
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => '7d7d384a0d719ca6fa83e01e50577b94c877e676', 'reference' => '7999c663fe184a13576026be3af0abc1d91d036d',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),