Compare commits

5 Commits

Author SHA1 Message Date
Mariano 005fa5b4a3 sistemazione migrate 2026-06-17 19:13:40 +02:00
inext 7302146a77 Merge branch 'dev'
merge dev
2026-06-10 14:04:40 +02:00
inext 43a2c81312 setup viste 2026-06-10 14:03:43 +02:00
inext 0dd567cf7a email documenti 2026-06-10 06:42:49 +02:00
inext d9421c9159 ver 2.0.1.1 2026-06-09 12:04:03 +02:00
53 changed files with 2075 additions and 973 deletions
+131 -22
View File
@@ -291,26 +291,135 @@ if (!empty($contatto['individuo_id'])) { create }
```
## Prossimi Passi
- [DONE] Aggiungere filtro Tag nella vista Documenti — già presente (tag filter bar + colonna Tag in lista/griglia)
- [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
- [DONE] ... (existing items)
- Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag
- [DONE] Fix performance: spostata query `VistaReport::where(...)->get()` da `@php` nel partial `table-settings.blade.php` a tutti i 5 controller (prima veniva eseguita 1 query per ogni pagina load per ogni entity, anche senza mai aprire la modale)
- [DONE] Rimosse vecchie modal legacy (`#saveVistaModal`, `#colonneModal`, `#vistaListModal`) da `individui` e `gruppi` — duplicate rispetto al nuovo modal unificato
- [DONE] Rimosse vecchie funzioni JS (`saveVista()`, `toggleColumn()`, `showSaveVistaModal()`) da `individui` e `gruppi`
- [DONE] Individui: `$allColumns` espanse da 6 a 17 colonne (aggiunte: data_nascita, genere, indirizzo, cap, citta, provincia, tipo_documento, numero_documento, scadenza_documento, note, created_at) — tutte con `<th>`/`<td>` condizionali nella vista
- [DONE] Individui: `getColumnIndex()` introdotta per lookup dinamico dell'indice colonna (sostituisce hardcoded `{codice:1, cognome:2, ...}` in `sortTable()` e `applyColumnFilter()`)
- [DONE] I controller per eventi, documenti, mailing-liste hanno già `$tableColumns` con flag `visible` — stesso meccanismo di gruppi. Nessuna modifica necessaria.
- [DONE] Fix: pulsante edit vista usa `data-*` attributi invece di `onclick` con `addslashes(json_encode(...))` — previene rottura HTML per JSON contenente `"`
- [DA FARE] Test browser: caricare ogni pagina entity, verificare default vista, toggle colonne, resize, drag-reorder, print
- [DA FARE] Test browser: flusso edit vista (✏️ → popola form → modifica → Aggiorna → reload)
- [DA FARE] Test browser: salvare nuova vista, switching tra viste, cancellazione vista
## 2026-06-10 — Per-User Column Views (colonne visibili, larghezze, ordine)
**Obiettivo**: Implementare viste colonne personalizzabili per utente su tutte le 5 entity list pages (individui, gruppi, eventi, documenti, mailing-liste), con persistenza tra login.
### Cosa è stato fatto
**Migration** (`2026_06_10_044946_add_column_widths_and_order_to_viste_report_table.php`):
- Aggiunge `colonne_larghezze` (json) e `colonne_ordine` (json) a `viste_report`
- Guardia `Schema::hasColumn` per compatibilità cross-DB (MySQL ↔ SQLite)
- Già eseguita in batch 38
**Backend**:
- `VistaReport.php`: `$fillable` + `$casts` (array) per entrambi i nuovi campi
- `VistaReportController@store`/`@update`: validazione estesa con `colonne_larghezze`, `colonne_ordine` (array)
- Tutti i 5 controller passano alla vista: `$tableColumns` (array `['key','label','visible']`), `$entityType`, `$columnWidths`, `$visibleColumns`, `$allColumns` (varia nome in base al controller)
- Tutti i 5 controller ora passano `$userVistas` (precaricate) invece di eseguire query nella view
- `IndividuoController`: `$allColumns` espanse con tutte le colonne DB disponibili (da 6 a 17)
- `IndividuoController`: aggiunto `use App\Models\VistaReport` per la query `$userVistas`
**Frontend JS** (`public/js/column-manager.js`):
- Classe `ColumnManager`: resize via mouse drag su handle, reorder via HTML5 DnD, persistenza larghezze in `sessionStorage`, fallback da `vista-data` DOM element, `printWithSettings()` che clona la tabella in finestra print-ready
**Modal** (`resources/views/partials/table-settings.blade.php`):
- Modal unificato con 3 tab: Colonne (checkbox visibilità + input larghezza + drag-reorder list), Viste (salva/carica/cancella), Stampa (pulsante print)
- Definisce `initTableSettings()` (NON auto-esegue) per evitare race condition con ColumnManager
- **RIMOSSA** query `VistaReport::where(...)->get()` dal `@php` block — ora usa `$userVistas` passato dal controller
- Non usa più `Auth::id()` direttamente — riceve `$userVistas` già popolato
**Views** — tutte e 5 aggiornate:
- `individui/index.blade.php`:
- `$allColumns` nella vista ora ha 17 colonne (vs 6 prima)
- `<th>` e `<td>` condizionali per tutte le nuove colonne (data_nascita, genere, indirizzo, cap, citta, provincia, tipo_documento, numero_documento, scadenza_documento, note, created_at)
- RIMOSSE modal legacy: `#saveVistaModal`, `#colonneModal`
- RIMOSSE funzioni JS legacy: `saveVista()`, `toggleColumn()`
- RIMOSSO sync checkboxes nel `DOMContentLoaded` (non più necessari)
- NUOVA: `getColumnIndex(colKey)` — lookup dinamico dell'indice colonna basato su `data-column`
- `sortTable()` e `applyColumnFilter()` ora usano `getColumnIndex()` invece di hardcoded mappa
- `gruppi/index.blade.php`:
- RIMOSSE modal legacy: `#saveVistaModal`, `#vistaListModal`
- RIMOSSO pulsante "Salva Vista" legacy
- RIMOSSE funzioni JS legacy: `showSaveVistaModal()`, `saveVista()`
- `eventi/index.blade.php`: colonne dinamiche via `$visibleColumns`, `@section('scripts')` con ColumnManager + `initTableSettings()`
- `mailing-liste/index.blade.php`: stessa struttura
- `documenti/index.blade.php`: ColumnManager init (non reorderable), column-manager.js incluso
- Tutti i `<th>` hanno `data-column` attributo per mapping JS
- Tutte chiamano `initTableSettings()` dopo `new ColumnManager()` nel `DOMContentLoaded`
**Performance fix**:
- Rimossa query `VistaReport::where(...)->get()` dal `@php` block in `table-settings.blade.php` (1 query extra per ogni pagina load su 5 entità)
- Spostata nei 5 controller come `$userVistas` passata via `compact()`
- Totale: -5 query per pagina load (una per ogni entity, anche su pagine non di index)
**Verifica**:
- JS brace balance: OK su tutti i 5 file (node check)
- PHP lint: OK su tutti i 5 controller
- Presenza `data-column`: individui=19, gruppi=14, eventi=9, mailing-liste=7, documenti=8
- Fallback `$visibleColumns` quando `$vista === null`: presente e corretto in tutti i 5 controller
- Nessun residuo di vecchie modal legacy in individui e gruppi
- Rimosso bottone "Salva Vista" orfano da `individui/index.blade.php` (chiamava `showSaveVistaModal()` non più esistente)
- Aggiunto pulsante modifica ✏️ nel tab Viste per ogni vista salvata: popola il form "Salva vista corrente" con nome, default flag e colonne visibili; cambia bottone in "Aggiorna" e fa PUT `/viste/{id}` invece di POST; zero query extra
### 2026-06-10 — Fix: editVista onclick → data-* attributes
**Problema**: Il pulsante modifica vista usava `onclick` con `addslashes(json_encode(...))`. In un attributo HTML delimitato da `"`, il backslash-escaping di `\"` non è gestito in modo standard dai browser — il JSON contenente `"` poteva rompere l'attributo HTML.
**Fix**:
1. Sostituito `onclick` con `data-*` attributi (`data-vista-id`, `data-vista-nome`, `data-vista-default`, `data-vista-colonne`) usando `{{ }}` di Blade (che applica `htmlspecialchars`, codificando `"``&quot;` sicuro in HTML).
2. `editVista()` ora accetta un array direttamente (non più JSON string da parsare).
3. Click handler registrato in `initTableSettings()` via `document.querySelectorAll('.edit-vista-btn')` + `dataset` (browser decodifica automaticamente `&quot;``"`).
### 2026-06-10 — Fix: 405 Method Not Allowed su update vista (via AJAX fetch)
**Problema**: Modificando una vista e cliccando "Aggiorna", `fetch()` inviava `PUT /viste/{id}` ma il controller `VistaReportController@update` restituiva `return back()` (302 redirect). `fetch()` seguiva il redirect — in alcuni browser il metodo PUT veniva preservato, ma la route `gruppi` accetta solo GET → 405 Method Not Allowed.
**Fix**:
1. `VistaReportController@update` (linea 101-103): aggiunto `if ($request->expectsJson()) { return response()->json([...]); }` — così il fetch riceve JSON 200, non un redirect 302.
2. Fetch headers in `table-settings.blade.php`: aggiunto `'Accept': 'application/json'` — necessario perché `expectsJson()` controlla l'header `Accept`, non `Content-Type`.
## 2026-06-10 — Fix: tutte le migration idempotenti + seeders idempotenti
**Problema**: `php artisan migrate` falliva su tabelle già esistenti. `php artisan db:seed` creava duplicati su re-run.
### Migration: hasTable guard su tutti i 27 file mancanti
Aggiunto `if (!Schema::hasTable('table_name')) { ... }` wrapper a tutte le 27 migration file (44 `Schema::create()` calls) che ne erano prive, usando script Node.js di trasformazione automatica:
- `2024_01_01_000002_create_comuni_diocesi_tables.php`
- `2024_01_01_000003_create_individui_table.php`
- `2024_01_01_000004_create_contatti_table.php`
- `2024_01_01_000005_create_gruppi_table.php`
- `2024_01_01_000006_create_gruppo_individuo_table.php`
- `2024_01_01_000007_create_eventi_table.php`
- `2024_01_01_000008_create_documenti_table.php`
- `2024_01_01_000009_create_mailing_tables.php`
- `2024_01_01_000010_create_notifiche_table.php`
- `2024_01_01_000011_create_users_table.php`
- `2024_01_01_000012_create_cache_and_jobs_tables.php`
- `2024_01_01_000019_create_eventi_documenti_table.php`
- `2024_01_01_000022_create_table_viste_report.php`
- `2026_05_10_000001_create_tipologie_documenti_table.php`
- `2026_05_10_000002_add_acl_tables.php`
- `2026_05_10_125210_create_permission_tables.php` (usa `$tableNames[...]` dinamici)
- `2026_05_11_000001_create_email_settings_table.php`
- `2026_05_11_000002_create_email_folders_table.php`
- `2026_05_11_000003_create_email_messages_table.php`
- `2026_05_11_000007_create_email_attachments_table.php`
- `2026_05_12_000001_create_ruoli_table.php`
- `2026_05_25_000001_create_report_custom_table.php`
- `2026_05_26_000001_create_tipologie_eventi_table.php`
- `2026_05_26_000002_create_sender_accounts_table.php`
- `2026_05_27_000001_create_documenti_cartelle_table.php`
- `2026_05_27_113245_create_storage_repositories_table.php`
- `2026_06_02_000001_create_calendario_connessioni_table.php`
### Seeder: firstOrCreate per idempotenza
- `DiocesiSeeder.php`: `Diocesi::create()``Diocesi::firstOrCreate(['nome' => $d['nome']], $d)`
- `ComuniSeeder.php`: `Comune::create()``Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c)`
- `DatabaseSeeder.php`: `User::create()``User::firstOrCreate(['email' => 'admin@glastree.local'], [...])`, con guardia su `role_preset_id`
**Risultato**: `php artisan migrate --seed` è ora completamente idempotente. Zero errori su 62 migration + 6 seeder.
+35 -1
View File
@@ -92,11 +92,45 @@ class DocumentoController extends Controller
'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(
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
'currentRepo', 'repositories',
'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();
$senderAccounts = SenderAccount::active()->get();
$documenti = Documento::orderBy('nome_file')->get();
$prefill = [];
if ($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)
@@ -565,6 +567,7 @@ class EmailController extends Controller
private function processAttachments(Request $request): array
{
$paths = [];
if ($request->hasFile('allegati')) {
foreach ($request->file('allegati') as $file) {
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;
}
+36 -1
View File
@@ -55,7 +55,42 @@ class EventoController extends Controller
$gruppi = Gruppo::orderBy('nome')->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)
+34 -2
View File
@@ -28,7 +28,27 @@ class GruppoController extends Controller
->where('is_default', true)
->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'];
if ($vista && !empty($vista->colonne_visibili)) {
@@ -37,6 +57,14 @@ class GruppoController extends Controller
$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']);
if (request()->filled('tag')) {
@@ -79,7 +107,11 @@ class GruppoController extends Controller
'visibleColumns',
'viewMode',
'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\Contatto;
use App\Models\Documento;
use App\Models\Gruppo;
use App\Models\Comune;
use App\Models\Diocesi;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;
use App\Models\Gruppo;
use App\Models\Comune;
use App\Models\Diocesi;
use App\Models\Tag;
use App\Models\VistaReport;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\StreamedResponse;
class IndividuoController extends Controller
{
@@ -50,9 +51,20 @@ class IndividuoController extends Controller
['key' => 'codice', 'label' => 'Codice'],
['key' => 'cognome', 'label' => 'Cognome'],
['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' => 'telefono', 'label' => 'Telefono'],
['key' => 'note', 'label' => 'Note'],
['key' => 'tag', 'label' => 'Tag'],
['key' => 'created_at', 'label' => 'Creato il'],
];
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono', 'tag'];
@@ -60,8 +72,22 @@ class IndividuoController extends Controller
? $vista->colonne_visibili
: $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']);
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)
+34 -1
View File
@@ -20,7 +20,40 @@ class MailingListController extends Controller
$mailingLists = $query->orderBy('nome')->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()
+11 -1
View File
@@ -33,9 +33,11 @@ class VistaReportController extends Controller
$this->authorizeWrite('viste');
$data = $request->validate([
'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_ordinamento' => 'nullable|array',
'colonne_larghezze' => 'nullable|array',
'colonne_ordine' => 'nullable|array',
'filtri' => 'nullable|array',
'ricerca' => 'nullable|string',
'is_default' => 'nullable|boolean',
@@ -55,6 +57,8 @@ class VistaReportController extends Controller
'tipo' => $data['tipo'],
'colonne_visibili' => $data['colonne_visibili'] ?? null,
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
'colonne_larghezze' => $data['colonne_larghezze'] ?? null,
'colonne_ordine' => $data['colonne_ordine'] ?? null,
'filtri' => $data['filtri'] ?? null,
'ricerca' => $data['ricerca'] ?? null,
'is_default' => $isDefault,
@@ -78,6 +82,8 @@ class VistaReportController extends Controller
'nome' => 'required|string|max:255',
'colonne_visibili' => 'nullable|array',
'colonne_ordinamento' => 'nullable|array',
'colonne_larghezze' => 'nullable|array',
'colonne_ordine' => 'nullable|array',
'filtri' => 'nullable|array',
'ricerca' => 'nullable|string',
'is_default' => 'nullable|boolean',
@@ -92,6 +98,10 @@ class VistaReportController extends Controller
$vistaReport->update($data);
if ($request->expectsJson()) {
return response()->json(['success' => true, 'message' => '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 $fillable = [
'user_id', 'nome', 'tipo', 'colonne_visibili',
'colonne_ordinamento', 'filtri', 'ricerca', 'is_default'
'colonne_ordinamento', 'filtri', 'ricerca', 'is_default',
'colonne_larghezze', 'colonne_ordine',
];
protected $casts = [
'colonne_visibili' => 'array',
'colonne_ordinamento' => 'array',
'filtri' => 'array',
'colonne_larghezze' => 'array',
'colonne_ordine' => 'array',
];
public function user(): BelongsTo
@@ -8,6 +8,10 @@ return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable('tenants')) {
return;
}
Schema::create('tenants', function (Blueprint $table) {
$table->id();
$table->string('nome');
@@ -8,26 +8,30 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('diocesi', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('regione')->nullable();
$table->timestamps();
});
if (!Schema::hasTable('diocesi')) {
Schema::create('diocesi', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('regione')->nullable();
$table->timestamps();
});
}
Schema::create('comuni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('codice_istat', 10)->nullable();
$table->string('cap')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->string('regione')->nullable();
$table->float('latitudine')->nullable();
$table->float('longitudine')->nullable();
$table->timestamps();
$table->index('nome');
$table->index('sigla_provincia');
});
if (!Schema::hasTable('comuni')) {
Schema::create('comuni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('codice_istat', 10)->nullable();
$table->string('cap')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->string('regione')->nullable();
$table->float('latitudine')->nullable();
$table->float('longitudine')->nullable();
$table->timestamps();
$table->index('nome');
$table->index('sigla_provincia');
});
}
}
public function down(): void
@@ -8,27 +8,29 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('individui', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('codice_id', 5)->unique();
$table->string('cognome');
$table->string('nome');
$table->date('data_nascita')->nullable();
$table->string('indirizzo')->nullable();
$table->string('cap', 10)->nullable();
$table->string('città')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->enum('genere', ['M', 'F'])->nullable();
$table->enum('tipo_documento', ['carta_identita', 'patente'])->nullable();
$table->date('scadenza_documento')->nullable();
$table->text('note')->nullable();
$table->string('avatar_path')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('codice_id');
$table->index('cognome');
});
if (!Schema::hasTable('individui')) {
Schema::create('individui', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('codice_id', 5)->unique();
$table->string('cognome');
$table->string('nome');
$table->date('data_nascita')->nullable();
$table->string('indirizzo')->nullable();
$table->string('cap', 10)->nullable();
$table->string('città')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->enum('genere', ['M', 'F'])->nullable();
$table->enum('tipo_documento', ['carta_identita', 'patente'])->nullable();
$table->date('scadenza_documento')->nullable();
$table->text('note')->nullable();
$table->string('avatar_path')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('codice_id');
$table->index('cognome');
});
}
}
public function down(): void
@@ -8,16 +8,18 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('contatti', function (Blueprint $table) {
$table->id();
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->enum('tipo', ['telefono', 'cellulare', 'email', 'fax', 'web', 'telegram', 'whatsapp', 'altro']);
$table->string('valore');
$table->string('etichetta')->nullable();
$table->boolean('is_primary')->default(false);
$table->timestamps();
$table->index('individuo_id');
});
if (!Schema::hasTable('contatti')) {
Schema::create('contatti', function (Blueprint $table) {
$table->id();
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->enum('tipo', ['telefono', 'cellulare', 'email', 'fax', 'web', 'telegram', 'whatsapp', 'altro']);
$table->string('valore');
$table->string('etichetta')->nullable();
$table->boolean('is_primary')->default(false);
$table->timestamps();
$table->index('individuo_id');
});
}
}
public function down(): void
@@ -8,24 +8,26 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('parent_id')->nullable()->constrained('gruppi')->onDelete('set null');
$table->foreignId('diocesi_id')->nullable()->constrained('diocesi')->onDelete('set null');
$table->foreignId('responsabile_id')->nullable()->constrained('individui')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('indirizzo_incontro')->nullable();
$table->string('cap_incontro', 10)->nullable();
$table->string('città_incontro')->nullable();
$table->string('sigla_provincia_incontro', 2)->nullable();
$table->string('mappa_posizione')->nullable();
$table->json('metadata')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('parent_id');
});
if (!Schema::hasTable('gruppi')) {
Schema::create('gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('parent_id')->nullable()->constrained('gruppi')->onDelete('set null');
$table->foreignId('diocesi_id')->nullable()->constrained('diocesi')->onDelete('set null');
$table->foreignId('responsabile_id')->nullable()->constrained('individui')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('indirizzo_incontro')->nullable();
$table->string('cap_incontro', 10)->nullable();
$table->string('città_incontro')->nullable();
$table->string('sigla_provincia_incontro', 2)->nullable();
$table->string('mappa_posizione')->nullable();
$table->json('metadata')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('parent_id');
});
}
}
public function down(): void
@@ -8,15 +8,17 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('gruppo_individuo', function (Blueprint $table) {
$table->id();
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->string('ruolo_nel_gruppo')->nullable();
$table->date('data_adesione')->nullable();
$table->timestamps();
$table->unique(['gruppo_id', 'individuo_id']);
});
if (!Schema::hasTable('gruppo_individuo')) {
Schema::create('gruppo_individuo', function (Blueprint $table) {
$table->id();
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->string('ruolo_nel_gruppo')->nullable();
$table->date('data_adesione')->nullable();
$table->timestamps();
$table->unique(['gruppo_id', 'individuo_id']);
});
}
}
public function down(): void
@@ -8,35 +8,41 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('eventi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_evento');
$table->string('tipo_evento')->nullable();
$table->enum('tipo_recorrenza', ['singolo', 'ricorrente'])->default('singolo');
$table->unsignedTinyInteger('giorno_settimana')->nullable();
$table->time('ora_inizio')->nullable();
$table->date('data_specifica')->nullable();
$table->unsignedInteger('durata_minuti')->nullable();
$table->text('descrizione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('eventi')) {
Schema::create('eventi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_evento');
$table->string('tipo_evento')->nullable();
$table->enum('tipo_recorrenza', ['singolo', 'ricorrente'])->default('singolo');
$table->unsignedTinyInteger('giorno_settimana')->nullable();
$table->time('ora_inizio')->nullable();
$table->date('data_specifica')->nullable();
$table->unsignedInteger('durata_minuti')->nullable();
$table->text('descrizione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
}
Schema::create('eventi_gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->timestamps();
});
if (!Schema::hasTable('eventi_gruppi')) {
Schema::create('eventi_gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->timestamps();
});
}
Schema::create('eventi_responsabili', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->timestamps();
});
if (!Schema::hasTable('eventi_responsabili')) {
Schema::create('eventi_responsabili', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,22 +8,24 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_file');
$table->string('file_path');
$table->string('tipo')->nullable();
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
$table->unsignedBigInteger('visibilita_target_id')->nullable();
$table->string('visibilita_target_type')->nullable();
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('dimensione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('documenti')) {
Schema::create('documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_file');
$table->string('file_path');
$table->string('tipo')->nullable();
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
$table->unsignedBigInteger('visibilita_target_id')->nullable();
$table->string('visibilita_target_type')->nullable();
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('dimensione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
}
}
public function down(): void
@@ -8,43 +8,49 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('mailing_lists', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->boolean('attiva')->default(true);
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('mailing_lists')) {
Schema::create('mailing_lists', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->boolean('attiva')->default(true);
$table->timestamps();
$table->index('tenant_id');
});
}
Schema::create('mailing_contacts', function (Blueprint $table) {
$table->id();
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->boolean('opt_in')->default(true);
$table->dateTime('opt_in_data')->nullable();
$table->dateTime('opt_out_data')->nullable();
$table->timestamps();
$table->unique(['mailing_list_id', 'individuo_id']);
});
if (!Schema::hasTable('mailing_contacts')) {
Schema::create('mailing_contacts', function (Blueprint $table) {
$table->id();
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->boolean('opt_in')->default(true);
$table->dateTime('opt_in_data')->nullable();
$table->dateTime('opt_out_data')->nullable();
$table->timestamps();
$table->unique(['mailing_list_id', 'individuo_id']);
});
}
Schema::create('mailing_messaggi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('mailing_list_id')->nullable()->constrained('mailing_lists')->onDelete('set null');
$table->unsignedBigInteger('user_id')->nullable();
$table->string('oggetto');
$table->text('corpo')->nullable();
$table->enum('canale', ['email', 'telegram'])->default('email');
$table->enum('stato', ['bozza', 'in_coda', 'inviato', 'fallito'])->default('bozza');
$table->timestamp('inviato_al')->nullable();
$table->integer('totale_destinatari')->default(0);
$table->integer('invii_ok')->default(0);
$table->integer('invii_falliti')->default(0);
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('mailing_messaggi')) {
Schema::create('mailing_messaggi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('mailing_list_id')->nullable()->constrained('mailing_lists')->onDelete('set null');
$table->unsignedBigInteger('user_id')->nullable();
$table->string('oggetto');
$table->text('corpo')->nullable();
$table->enum('canale', ['email', 'telegram'])->default('email');
$table->enum('stato', ['bozza', 'in_coda', 'inviato', 'fallito'])->default('bozza');
$table->timestamp('inviato_al')->nullable();
$table->integer('totale_destinatari')->default(0);
$table->integer('invii_ok')->default(0);
$table->integer('invii_falliti')->default(0);
$table->timestamps();
$table->index('tenant_id');
});
}
}
public function down(): void
@@ -8,18 +8,20 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('notifiche', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->nullable();
$table->string('tipo');
$table->string('titolo');
$table->text('messaggio');
$table->string('link')->nullable();
$table->boolean('is_read')->default(false);
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index('user_id');
});
if (!Schema::hasTable('notifiche')) {
Schema::create('notifiche', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->nullable();
$table->string('tipo');
$table->string('titolo');
$table->text('messaggio');
$table->string('link')->nullable();
$table->boolean('is_read')->default(false);
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index('user_id');
});
}
}
public function down(): void
@@ -8,34 +8,40 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->boolean('is_admin')->default(false);
$table->rememberToken();
$table->timestamps();
$table->index('tenant_id');
$table->index('email');
});
if (!Schema::hasTable('users')) {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->boolean('is_admin')->default(false);
$table->rememberToken();
$table->timestamps();
$table->index('tenant_id');
$table->index('email');
});
}
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
if (!Schema::hasTable('password_reset_tokens')) {
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
if (!Schema::hasTable('sessions')) {
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
}
public function down(): void
@@ -8,50 +8,60 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
if (!Schema::hasTable('cache')) {
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
}
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
if (!Schema::hasTable('cache_locks')) {
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
if (!Schema::hasTable('jobs')) {
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
if (!Schema::hasTable('job_batches')) {
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
}
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at');
});
if (!Schema::hasTable('failed_jobs')) {
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at');
});
}
}
public function down(): void
@@ -8,12 +8,14 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('eventi_documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('documento_id')->constrained('documenti')->onDelete('cascade');
$table->timestamps();
});
if (!Schema::hasTable('eventi_documenti')) {
Schema::create('eventi_documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('documento_id')->constrained('documenti')->onDelete('cascade');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,19 +8,21 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('viste_report', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->string('tipo'); // individui, gruppi, documenti, eventi
$table->json('colonne_visibili')->nullable();
$table->json('colonne_ordinamento')->nullable(); // [['colonna', 'direzione']]
$table->json('filtri')->nullable(); // [['colonna', 'operatore', 'valore']]
$table->string('ricerca')->nullable();
$table->timestamps();
$table->index('tipo');
$table->index('user_id');
});
if (!Schema::hasTable('viste_report')) {
Schema::create('viste_report', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->string('tipo'); // individui, gruppi, documenti, eventi
$table->json('colonne_visibili')->nullable();
$table->json('colonne_ordinamento')->nullable(); // [['colonna', 'direzione']]
$table->json('filtri')->nullable(); // [['colonna', 'operatore', 'valore']]
$table->string('ricerca')->nullable();
$table->timestamps();
$table->index('tipo');
$table->index('user_id');
});
}
}
public function down(): void
@@ -8,14 +8,16 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('tipologie_documenti', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('tipologie_documenti')) {
Schema::create('tipologie_documenti', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
}
$defaultTipologie = ['avatar', 'galleria', 'documento', 'statuto', 'altro'];
foreach ($defaultTipologie as $i => $nome) {
@@ -14,28 +14,32 @@ return new class extends Migration
});
}
Schema::create('activity_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('action');
$table->string('module');
$table->string('description')->nullable();
$table->json('details')->nullable();
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
if (!Schema::hasTable('activity_logs')) {
Schema::create('activity_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('action');
$table->string('module');
$table->string('description')->nullable();
$table->json('details')->nullable();
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index(['user_id', 'module']);
$table->index(['created_at']);
$table->index(['action']);
});
}
$table->index(['user_id', 'module']);
$table->index(['created_at']);
$table->index(['action']);
});
Schema::create('role_presets', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->unique();
$table->string('description')->nullable();
$table->json('permissions');
$table->timestamps();
});
if (!Schema::hasTable('role_presets')) {
Schema::create('role_presets', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->unique();
$table->string('description')->nullable();
$table->json('permissions');
$table->timestamps();
});
}
}
public function down(): void
@@ -23,96 +23,106 @@ return new class extends Migration
/**
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
*/
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
$table->id(); // permission id
$table->string('name');
$table->string('guard_name');
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
if (!Schema::hasTable($tableNames['permissions'])) {
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
$table->id(); // permission id
$table->string('name');
$table->string('guard_name');
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
}
/**
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
*/
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
$table->id(); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
if (!Schema::hasTable($tableNames['roles'])) {
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
$table->id(); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name');
$table->string('guard_name');
$table->timestamps();
if ($teams || config('permission.testing')) {
}
$table->string('name');
$table->string('guard_name');
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
} else {
$table->unique(['name', 'guard_name']);
}
});
}
});
}
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
if (!Schema::hasTable($tableNames['model_has_permissions'])) {
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->cascadeOnDelete();
if ($teams) {
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
}
});
}
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
if (!Schema::hasTable($tableNames['model_has_roles'])) {
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->cascadeOnDelete();
if ($teams) {
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
}
});
}
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
if (!Schema::hasTable($tableNames['role_has_permissions'])) {
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->cascadeOnDelete();
$table->foreign($pivotRole)
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->cascadeOnDelete();
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
}
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
@@ -8,26 +8,28 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_settings', function (Blueprint $table) {
$table->id();
$table->string('imap_host')->nullable();
$table->integer('imap_port')->nullable()->default(993);
$table->string('imap_encryption')->nullable()->default('ssl');
$table->string('imap_username')->nullable();
$table->string('imap_password')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->nullable()->default(587);
$table->string('smtp_encryption')->nullable()->default('tls');
$table->string('smtp_username')->nullable();
$table->string('smtp_password')->nullable();
$table->string('email_address')->nullable();
$table->string('email_name')->nullable();
$table->string('reply_to')->nullable();
$table->integer('sync_interval_minutes')->default(5);
$table->timestamp('last_sync_at')->nullable();
$table->boolean('is_active')->default(false);
$table->timestamps();
});
if (!Schema::hasTable('email_settings')) {
Schema::create('email_settings', function (Blueprint $table) {
$table->id();
$table->string('imap_host')->nullable();
$table->integer('imap_port')->nullable()->default(993);
$table->string('imap_encryption')->nullable()->default('ssl');
$table->string('imap_username')->nullable();
$table->string('imap_password')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->nullable()->default(587);
$table->string('smtp_encryption')->nullable()->default('tls');
$table->string('smtp_username')->nullable();
$table->string('smtp_password')->nullable();
$table->string('email_address')->nullable();
$table->string('email_name')->nullable();
$table->string('reply_to')->nullable();
$table->integer('sync_interval_minutes')->default(5);
$table->timestamp('last_sync_at')->nullable();
$table->boolean('is_active')->default(false);
$table->timestamps();
});
}
}
public function down(): void
@@ -8,17 +8,19 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_folders', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('imap_folder_name')->nullable();
$table->string('type')->default('custom');
$table->string('icon')->default('fa-folder');
$table->string('color')->nullable();
$table->integer('sort_order')->default(0);
$table->boolean('is_system')->default(false);
$table->timestamps();
});
if (!Schema::hasTable('email_folders')) {
Schema::create('email_folders', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('imap_folder_name')->nullable();
$table->string('type')->default('custom');
$table->string('icon')->default('fa-folder');
$table->string('color')->nullable();
$table->integer('sort_order')->default(0);
$table->boolean('is_system')->default(false);
$table->timestamps();
});
}
}
public function down(): void
@@ -8,35 +8,37 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
$table->string('message_id')->nullable()->index();
$table->string('subject')->nullable();
$table->string('from_name')->nullable();
$table->string('from_email')->nullable();
$table->string('to_name')->nullable();
$table->string('to_email')->nullable();
$table->text('cc')->nullable();
$table->text('bcc')->nullable();
$table->longText('body_text')->nullable();
$table->longText('body_html')->nullable();
$table->boolean('is_read')->default(false);
$table->boolean('is_starred')->default(false);
$table->boolean('is_important')->default(false);
$table->boolean('is_sent')->default(false);
$table->boolean('is_draft')->default(false);
$table->boolean('is_trash')->default(false);
$table->timestamp('received_at')->nullable();
$table->timestamp('sent_at')->nullable();
$table->unsignedBigInteger('imap_uid')->nullable();
$table->timestamps();
$table->index('is_read');
$table->index('is_starred');
$table->index('is_sent');
$table->index('received_at');
});
if (!Schema::hasTable('email_messages')) {
Schema::create('email_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
$table->string('message_id')->nullable()->index();
$table->string('subject')->nullable();
$table->string('from_name')->nullable();
$table->string('from_email')->nullable();
$table->string('to_name')->nullable();
$table->string('to_email')->nullable();
$table->text('cc')->nullable();
$table->text('bcc')->nullable();
$table->longText('body_text')->nullable();
$table->longText('body_html')->nullable();
$table->boolean('is_read')->default(false);
$table->boolean('is_starred')->default(false);
$table->boolean('is_important')->default(false);
$table->boolean('is_sent')->default(false);
$table->boolean('is_draft')->default(false);
$table->boolean('is_trash')->default(false);
$table->timestamp('received_at')->nullable();
$table->timestamp('sent_at')->nullable();
$table->unsignedBigInteger('imap_uid')->nullable();
$table->timestamps();
$table->index('is_read');
$table->index('is_starred');
$table->index('is_sent');
$table->index('received_at');
});
}
}
public function down(): void
@@ -8,17 +8,19 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
$table->string('filename');
$table->string('mime_type')->nullable();
$table->bigInteger('size')->default(0);
$table->string('file_path');
$table->boolean('is_saved_to_documenti')->default(false);
$table->foreignId('documenti_id')->nullable()->constrained('documenti')->onDelete('set null');
$table->timestamps();
});
if (!Schema::hasTable('email_attachments')) {
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
$table->string('filename');
$table->string('mime_type')->nullable();
$table->bigInteger('size')->default(0);
$table->string('file_path');
$table->boolean('is_saved_to_documenti')->default(false);
$table->foreignId('documenti_id')->nullable()->constrained('documenti')->onDelete('set null');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,14 +8,16 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('ruoli', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('ruoli')) {
Schema::create('ruoli', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
}
$defaultRuoli = [
['nome' => 'Membro', 'descrizione' => 'Membro del gruppo'],
@@ -8,15 +8,17 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('report_custom', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('tipo_report');
$table->json('config')->nullable();
$table->timestamps();
});
if (!Schema::hasTable('report_custom')) {
Schema::create('report_custom', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('tipo_report');
$table->json('config')->nullable();
$table->timestamps();
});
}
}
public function down(): void
@@ -10,14 +10,16 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('tipologie_eventi', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('tipologie_eventi')) {
Schema::create('tipologie_eventi', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
}
DB::table('tipologie_eventi')->insert([
['nome' => 'catechesi', 'descrizione' => 'Catechesi', 'ordine' => 0, 'attiva' => true],
@@ -10,21 +10,23 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('sender_accounts', function (Blueprint $table) {
$table->id();
$table->string('email_address')->unique();
$table->string('email_name')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->default(587);
$table->string('smtp_encryption')->default('tls');
$table->string('smtp_username')->nullable();
$table->text('smtp_password')->nullable();
$table->string('reply_to')->nullable();
$table->string('verify_email')->nullable();
$table->text('note')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('sender_accounts')) {
Schema::create('sender_accounts', function (Blueprint $table) {
$table->id();
$table->string('email_address')->unique();
$table->string('email_name')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->default(587);
$table->string('smtp_encryption')->default('tls');
$table->string('smtp_username')->nullable();
$table->text('smtp_password')->nullable();
$table->string('reply_to')->nullable();
$table->string('verify_email')->nullable();
$table->text('note')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
}
public function down(): void
@@ -8,12 +8,14 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('documenti_cartelle', function (Blueprint $table) {
$table->id();
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
$table->string('nome');
$table->timestamps();
});
if (!Schema::hasTable('documenti_cartelle')) {
Schema::create('documenti_cartelle', function (Blueprint $table) {
$table->id();
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
$table->string('nome');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,15 +8,17 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('storage_repositories', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo', 50);
$table->text('config');
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
if (!Schema::hasTable('storage_repositories')) {
Schema::create('storage_repositories', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo', 50);
$table->text('config');
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
}
}
public function down(): void
@@ -10,21 +10,23 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('calendario_connessioni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo'); // google_calendar, caldav
$table->text('config'); // JSON
$table->string('stato')->default('disconnesso'); // connesso, disconnesso, errore
$table->string('sync_direction')->default('bidirectional'); // import, export, bidirectional
$table->integer('sync_interval_minutes')->default(60);
$table->timestamp('last_sync_at')->nullable();
$table->timestamp('last_error_at')->nullable();
$table->text('last_error_message')->nullable();
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
if (!Schema::hasTable('calendario_connessioni')) {
Schema::create('calendario_connessioni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo'); // google_calendar, caldav
$table->text('config'); // JSON
$table->string('stato')->default('disconnesso'); // connesso, disconnesso, errore
$table->string('sync_direction')->default('bidirectional'); // import, export, bidirectional
$table->integer('sync_interval_minutes')->default(60);
$table->timestamp('last_sync_at')->nullable();
$table->timestamp('last_error_at')->nullable();
$table->text('last_error_message')->nullable();
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
}
}
public function down(): void
@@ -18,7 +18,7 @@ return new class extends Migration
$table->string('color', 7)->nullable();
$table->integer('order_column')->default(0);
$table->timestamps();
});
});
}
if (!Schema::hasTable('taggables')) {
@@ -26,7 +26,7 @@ return new class extends Migration
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable');
$table->primary(['tag_id', 'taggable_id', 'taggable_type']);
});
});
}
}
@@ -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');
}
});
}
};
+1 -1
View File
@@ -68,7 +68,7 @@ class ComuniSeeder extends Seeder
];
foreach ($comuni as $c) {
Comune::create($c);
Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c);
}
}
}
+21 -17
View File
@@ -20,23 +20,27 @@ class DatabaseSeeder extends Seeder
DockerBaseDataSeeder::class,
]);
$admin = User::create([
'name' => 'Admin',
'email' => 'admin@glastree.local',
'password' => bcrypt('password'),
'is_admin' => true,
'status' => 'active',
'permissions' => [
'individui' => 2,
'gruppi' => 2,
'eventi' => 2,
'documenti' => 2,
'mailing' => 2,
'viste' => 2,
],
]);
$admin = User::firstOrCreate(
['email' => 'admin@glastree.local'],
[
'name' => 'Admin',
'password' => bcrypt('password'),
'is_admin' => true,
'status' => 'active',
'permissions' => [
'individui' => 2,
'gruppi' => 2,
'eventi' => 2,
'documenti' => 2,
'mailing' => 2,
'viste' => 2,
],
]
);
$admin->role_preset_id = 1;
$admin->save();
if (!$admin->role_preset_id) {
$admin->role_preset_id = 1;
$admin->save();
}
}
}
+1 -1
View File
@@ -149,7 +149,7 @@ class DiocesiSeeder extends Seeder
];
foreach ($diocesi as $d) {
Diocesi::create($d);
Diocesi::firstOrCreate(['nome' => $d['nome']], $d);
}
}
}
+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
$canWriteDocumenti = Auth::user()->canManage('documenti');
$canDeleteDocumenti = Auth::user()->canDelete('documenti');
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
$visibleColumnsJson = json_encode($visibleColumns ?? []);
$tableColumnsJson = json_encode($tableColumns ?? []);
@endphp
@section('css')
@@ -293,21 +296,21 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
{{-- View: Lista --}}
<div id="viewList" style="display:none;">
@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">
<tr>
@if($canDeleteDocumenti)
<th style="width: 40px;">
<th style="width: 40px;" data-column="select">
<input type="checkbox" id="selectAll" onchange="toggleAll(this)">
</th>
@endif
<th>Nome</th>
<th style="width: 100px;">Tipologia</th>
<th style="width: 90px;">Dimensione</th>
<th style="width: 120px;">Contesto</th>
<th style="width: 100px;">Tag</th>
<th style="width: 110px;">Data</th>
<th style="width: 130px;">Azioni</th>
<th data-column="nome">Nome</th>
<th style="width: 100px;" data-column="tipologia">Tipologia</th>
<th style="width: 90px;" data-column="dimensione">Dimensione</th>
<th style="width: 120px;" data-column="visibilita">Contesto</th>
<th style="width: 100px;" data-column="tag">Tag</th>
<th style="width: 110px;" data-column="created_at">Data</th>
<th style="width: 130px;" data-column="azioni">Azioni</th>
</tr>
</thead>
<tbody>
@@ -515,6 +518,10 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
</div>
</form>{{-- /massForm --}}
@include('partials.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
{{-- MODAL: Nuova Cartella --}}
<div class="modal fade" id="newFolderModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-scrollable" role="document">
@@ -938,6 +945,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
@endsection
@section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script>
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]));
@@ -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>
@endsection
+110 -4
View File
@@ -87,10 +87,17 @@
<div class="form-group">
<label>Allegati</label>
<div class="border rounded p-3 bg-light">
<label class="btn btn-sm btn-success mb-0">
<i class="fas fa-upload mr-1"></i> Carica file
<input type="file" name="allegati[]" multiple class="d-none">
</label>
<div class="mb-3">
<button type="button" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#documentiModal">
<i class="fas fa-folder-open mr-1"></i> Seleziona da Documenti
</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>
@@ -110,10 +117,62 @@
</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
@section('scripts')
<script>
let allegatiSelezionati = [];
function toggleDestinatari() {
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() {
const form = document.getElementById('emailForm');
const formData = new FormData(form);
+74 -11
View File
@@ -5,6 +5,10 @@
@php
$canWriteEventi = Auth::user()->canManage('eventi');
$canDeleteEventi = Auth::user()->canDelete('eventi');
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
$visibleColumnsJson = json_encode($visibleColumns ?? []);
$tableColumnsJson = json_encode($tableColumns ?? []);
$entityType = $entityType ?? 'eventi';
@endphp
@section('content')
@@ -20,6 +24,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<div class="card-header">
<h3 class="card-title">Tutti gli Eventi</h3>
<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">
<i class="fas fa-calendar-alt mr-1"></i> Calendario
</a>
@@ -75,10 +82,10 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div>
</form>
<table class="table table-bordered table-hover">
<table class="table table-bordered table-hover" id="eventi-table">
<thead class="thead-light">
<tr>
<th style="width: 40px;">
<th style="width: 40px;" data-column="select">
@if($canDeleteEventi)
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
@@ -86,7 +93,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div>
@endif
</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">
Nome Evento
@if(request('sort') === 'nome_evento')
@@ -96,7 +104,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</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">
Descrizione
@if(request('sort') === 'descrizione_evento')
@@ -106,7 +116,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</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">
Tipologia
@if(request('sort') === 'tipo_evento')
@@ -116,7 +128,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</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">
Ricorrenza
@if(request('sort') === 'tipo_recorrenza')
@@ -126,7 +140,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</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">
Gruppi
@if(request('sort') === 'gruppi')
@@ -136,7 +152,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</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">
Responsabili
@if(request('sort') === 'responsabili')
@@ -146,8 +164,12 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</th>
<th>Tag</th>
<th style="width: 120px;">
@endif
@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">
Creato
@if(request('sort') === 'created_at')
@@ -157,6 +179,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endif
</a>
</th>
@endif
<th style="width: 120px;">Azioni</th>
</tr>
</thead>
@@ -171,6 +194,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</div>
@endif
</td>
@if(in_array('nome_evento', $visibleColumns))
<td>
<a href="/eventi/{{ $evento->id }}">
<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>
@endif
</td>
@endif
@if(in_array('descrizione_evento', $visibleColumns))
<td>{{ Str::limit($evento->descrizione_evento, 50) ?: '-' }}</td>
@endif
@if(in_array('tipo_evento', $visibleColumns))
<td>
@if($evento->tipo_evento)
@php
@@ -195,6 +223,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<span class="text-muted">-</span>
@endif
</td>
@endif
@if(in_array('tipo_recorrenza', $visibleColumns))
<td>
@if($evento->tipo_recorrenza && $evento->tipo_recorrenza !== 'singolo')
<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>
@endif
</td>
@endif
@if(in_array('gruppi', $visibleColumns))
<td>
@forelse($evento->gruppi->take(2) as $gruppo)
<a href="/gruppi/{{ $gruppo->id }}">
@@ -214,6 +246,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
<small class="text-muted">+{{ $evento->gruppi->count() - 2 }}</small>
@endif
</td>
@endif
@if(in_array('responsabili', $visibleColumns))
<td>
@forelse($evento->responsabili->take(2) as $resp)
<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>
@endif
</td>
@endif
@if(in_array('tag', $visibleColumns))
<td>
@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>
@endforeach
</td>
@endif
@if(in_array('created_at', $visibleColumns))
<td><small class="text-muted">{{ $evento->created_at->format('d/m/Y') }}</small></td>
@endif
<td>
<a href="{{ url('/eventi/' . $evento->id) }}" class="btn btn-xs btn-info" title="Visualizza">
<i class="fas fa-eye"></i>
@@ -249,7 +288,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</tr>
@empty
<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>
<p class="mb-0">Nessun evento trovato</p>
</td>
@@ -284,6 +323,10 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
</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">
@if($canDeleteEventi)
<div class="modal-dialog" role="document">
@@ -309,6 +352,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
@endsection
@section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script>
function toggleSelectAll(source) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
@@ -374,5 +418,24 @@ function exportSelectedIcs() {
document.body.appendChild(form);
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>
@endsection
+50 -149
View File
@@ -22,6 +22,7 @@ $columnLabels = [
$vistaDefaultJson = $vista ? $vista->toJson() : 'null';
$allColumnsJson = json_encode($allColumns);
$visibleColumnsJson = json_encode($visibleColumns);
$entityType = $entityType ?? 'gruppi';
@endphp
@section('content')
@@ -44,6 +45,9 @@ $visibleColumnsJson = json_encode($visibleColumns);
<div class="card-header">
<h3 class="card-title"><i class="fas fa-sitemap mr-2"></i>Elenco Gruppi</h3>
<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">
<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
@@ -68,9 +72,7 @@ $visibleColumnsJson = json_encode($visibleColumns);
@endif
</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)
<a href="{{ route('gruppi.import') }}" class="btn btn-warning btn-sm mr-1">
<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.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
@if($viewMode === 'table')
<div class="card-body p-0">
<table class="table table-bordered table-hover table-striped mb-0" id="gruppi-table">
<thead class="thead-light">
<tr>
<th style="width: 40px;">
<th style="width: 40px;" data-column="select">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
<label class="custom-control-label" for="select-all"></label>
</div>
</th>
@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
@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
@if(in_array('diocesi', $visibleColumns))
<th>Diocesi</th>
<th data-column="diocesi">Diocesi</th>
@endif
@if(in_array('livello', $visibleColumns))
<th style="width: 80px;">Livello</th>
<th style="width: 80px;" data-column="livello">Livello</th>
@endif
@if(in_array('padre', $visibleColumns))
<th>Gruppo Padre</th>
<th data-column="padre">Gruppo Padre</th>
@endif
@if(in_array('membri', $visibleColumns))
<th style="width: 100px;">Membri</th>
<th style="width: 100px;" data-column="membri">Membri</th>
@endif
@if(in_array('responsabili', $visibleColumns))
<th>Responsabili</th>
<th data-column="responsabili">Responsabili</th>
@endif
@if(in_array('indirizzo', $visibleColumns))
<th>Indirizzo</th>
<th data-column="indirizzo">Indirizzo</th>
@endif
@if(in_array('citta', $visibleColumns))
<th>Città</th>
<th data-column="citta">Città</th>
@endif
@if(in_array('telefono', $visibleColumns))
<th>Telefono</th>
<th data-column="telefono">Telefono</th>
@endif
@if(in_array('email', $visibleColumns))
<th>Email</th>
<th data-column="email">Email</th>
@endif
@if(in_array('tag', $visibleColumns))
<th>Tag</th>
<th data-column="tag">Tag</th>
@endif
<th style="width: 120px;">Azioni</th>
<th style="width: 120px;" data-column="azioni">Azioni</th>
</tr>
</thead>
<tbody id="table-body">
@@ -263,93 +269,7 @@ $visibleColumnsJson = json_encode($visibleColumns);
@endif
</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 --}}
<div class="modal fade" id="massTagModal" tabindex="-1" role="dialog">
@@ -415,6 +335,7 @@ $visibleColumnsJson = json_encode($visibleColumns);
@endsection
@section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script>
function switchView(mode) {
window.location.href = '?view=' + mode;
@@ -532,48 +453,7 @@ function getColumnIndex(column) {
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>
<style>
.tree-item { margin-bottom: 5px; }
@@ -599,10 +479,31 @@ function saveVista() {
font-size: 0.7em;
padding: 2px 5px;
}
.btn-group .btn.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.btn-group .btn.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
</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
+108 -143
View File
@@ -16,6 +16,7 @@ $allColumnsJson = json_encode($allColumns);
$visibleColumnsJson = json_encode($visibleColumns);
$canWriteIndividui = Auth::user()->canManage('individui');
$canDeleteIndividui = Auth::user()->canDelete('individui');
$entityType = $entityType ?? 'individui';
@endphp
@section('content')
@@ -23,6 +24,9 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
<div class="card-header">
<h3 class="card-title"><i class="fas fa-users mr-2"></i>Lista Individui</h3>
<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()">
<i class="fas fa-search mr-1"></i> Ricerca/Filtri
</button>
@@ -61,9 +65,6 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endif
</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)
<a href="{{ route('individui.import') }}" class="btn btn-warning btn-sm ml-2">
<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.table-settings')
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
<div class="card-body p-0">
@if($individui->count() > 0)
<table class="table table-bordered table-hover table-striped mb-0" id="individui-table">
<thead class="thead-light">
<tr>
<th style="width: 40px;">
<th style="width: 40px;" data-column="select">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
<label class="custom-control-label" for="select-all"></label>
@@ -153,6 +158,39 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@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>
@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))
<th data-sortable="true" data-column="email" onclick="sortTable('email')">Email <i class="fas fa-sort float-right"></i></th>
@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>
@endif
@if(in_array('tag', $visibleColumns))
<th>Tag</th>
<th data-column="tag">Tag</th>
@endif
<th style="width: 120px;">Azioni</th>
<th style="width: 120px;" data-column="azioni">Azioni</th>
</tr>
</thead>
<tbody id="table-body">
@@ -187,6 +225,39 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
<a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->nome }}</a>
</td>
@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))
<td>{{ $ind->getEmailPrimariaAttribute() ?: '-' }}</td>
@endif
@@ -266,75 +337,7 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endif
</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">
@if($canDeleteIndividui)
@@ -519,6 +522,7 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
@endsection
@section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script>
let currentSort = { column: null, direction: 'asc' };
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() {
const col = document.getElementById('filter-column').value;
const op = document.getElementById('filter-operator').value;
@@ -545,7 +559,8 @@ function applyColumnFilter() {
if (!col || !val) return;
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 => {
const cells = row.querySelectorAll('td');
@@ -577,7 +592,7 @@ function sortTable(column) {
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 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) {
const checkboxes = document.querySelectorAll('.row-checkbox');
@@ -863,24 +822,30 @@ document.addEventListener('DOMContentLoaded', function() {
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 !!};
if (vistaDefault && vistaDefault.ricerca) {
document.getElementById('global-search').value = 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() {
document.getElementById('ml-nome').value = '';
document.getElementById('ml-descrizione').value = '';
+62 -8
View File
@@ -5,6 +5,9 @@
@php
$canWriteMailing = Auth::user()->canManage('mailing');
$canDeleteMailing = Auth::user()->canDelete('mailing');
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
$visibleColumnsJson = json_encode($visibleColumns ?? []);
$tableColumnsJson = json_encode($tableColumns ?? []);
@endphp
@section('content')
@@ -20,6 +23,9 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
<div class="card-header">
<h3 class="card-title"><i class="fas fa-list mr-2"></i>Elenco Mailing Lists</h3>
<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)
<div class="btn-group mr-2">
<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()])
<div class="card-body p-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">
<tr>
<th style="width: 40px;">
<th style="width: 40px;" data-column="select">
@if($canDeleteMailing)
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
@@ -48,12 +54,24 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
</div>
@endif
</th>
<th>Nome</th>
<th>Descrizione</th>
<th style="width: 100px;">Contatti</th>
<th>Tag</th>
<th style="width: 100px;">Stato</th>
<th style="width: 130px;">Creata il</th>
@if(in_array('nome', $visibleColumns))
<th data-column="nome">Nome</th>
@endif
@if(in_array('descrizione', $visibleColumns))
<th data-column="descrizione">Descrizione</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>
</tr>
</thead>
@@ -68,13 +86,20 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
</div>
@endif
</td>
@if(in_array('nome', $visibleColumns))
<td>
<a href="{{ route('mailing-liste.show', $lista->id) }}">{{ $lista->nome }}</a>
</td>
@endif
@if(in_array('descrizione', $visibleColumns))
<td>{{ $lista->descrizione ?: '-' }}</td>
@endif
@if(in_array('contatti_count', $visibleColumns))
<td>
<span class="badge badge-info">{{ $lista->contatti->count() }}</span>
</td>
@endif
@if(in_array('tag', $visibleColumns))
<td>
@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;">
@@ -85,6 +110,8 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
<span class="text-muted small">-</span>
@endif
</td>
@endif
@if(in_array('attiva', $visibleColumns))
<td>
@if($lista->attiva)
<span class="badge badge-success">Attiva</span>
@@ -92,7 +119,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
<span class="badge badge-secondary">Disattiva</span>
@endif
</td>
@endif
@if(in_array('created_at', $visibleColumns))
<td>{{ $lista->created_at->format('d/m/Y') }}</td>
@endif
<td>
<a href="{{ route('mailing-liste.show', $lista->id) }}" class="btn btn-xs btn-info" title="Visualizza">
<i class="fas fa-eye"></i>
@@ -125,6 +155,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
</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">
@if($canDeleteMailing)
<div class="modal-dialog" role="document">
@@ -150,6 +184,7 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
@endsection
@section('scripts')
<script src="{{ asset('js/column-manager.js') }}"></script>
<script>
function toggleSelectAll(source) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
@@ -187,5 +222,24 @@ function deleteSelected() {
$('#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>
@endsection
+283 -110
View File
@@ -15,105 +15,159 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="filters-tab" data-toggle="tab" href="#filters-panel" role="tab">
<i class="fas fa-filter mr-1"></i> Filtri
<a class="nav-link" id="vistas-tab" data-toggle="tab" href="#vistas-panel" role="tab">
<i class="fas fa-save mr-1"></i> Viste
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="search-tab" data-toggle="tab" href="#search-panel" role="tab">
<i class="fas fa-search mr-1"></i> Ricerca
</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 class="nav-link" id="print-tab" data-toggle="tab" href="#print-panel" role="tab">
<i class="fas fa-print mr-1"></i> Stampa
</a>
</li>
</ul>
<div class="tab-content p-3">
<div class="tab-pane fade show active" id="columns-panel" role="tabpanel">
<h6>Seleziona le colonne da visualizzare:</h6>
<div class="row">
<h6 class="mb-3">Seleziona colonne, regola larghezza e riordina:</h6>
<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)
<div class="col-md-4">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input column-toggle"
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 class="form-row align-items-center mb-2 column-config-item" data-column="{{ $col['key'] }}">
<div class="col-auto">
<i class="fas fa-grip-vertical text-muted" style="cursor:grab;"></i>
</div>
<div class="col-auto">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input column-toggle"
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>
@endforeach
</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 class="tab-pane fade" id="filters-panel" role="tabpanel">
<h6>Aggiungi filtri:</h6>
<div id="filters-container">
<div class="filter-row mb-2">
<div class="row">
<div class="col-md-4">
<select class="form-control form-control-sm filter-column">
<option value="">Seleziona colonna...</option>
@foreach($tableColumns as $col)
<option value="{{ $col['key'] }}">{{ $col['label'] }}</option>
@endforeach
</select>
<div class="tab-pane fade" id="vistas-panel" role="tabpanel">
<h6 class="mb-2">Viste salvate</h6>
<div id="vistas-list" class="mb-3">
@php
$entityType = $entityType ?? 'individui';
$userVistas = $userVistas ?? collect();
@endphp
@if($userVistas->count() > 0)
<div class="list-group mb-3" id="vistas-list-group">
@foreach($userVistas as $v)
<div class="list-group-item list-group-item-action d-flex align-items-center py-2 {{ $vista && $vista->id === $v->id ? 'active' : '' }}">
<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 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 class="ml-2">
<a href="?vista_id={{ $v->id }}" class="btn btn-xs btn-primary" title="Applica vista">
<i class="fas fa-play"></i>
</a>
@if(!$v->is_default)
<form action="{{ url('/viste/' . $v->id . '/default') }}" method="POST" class="d-inline">
@csrf @method('PATCH')
<button type="submit" class="btn btn-xs btn-warning" title="Imposta predefinita">
<i class="fas fa-star"></i>
</button>
</form>
@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>
@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>
<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 class="tab-pane fade" id="search-panel" role="tabpanel">
<div class="form-group">
<label>cerca in tutte le colonne:</label>
<input type="text" id="table-search" class="form-control" placeholder="Testo da cercare...">
<div class="tab-pane fade" id="print-panel" role="tabpanel">
<h6 class="mb-3">Stampa con impostazioni correnti</h6>
<p>La stampa rispetterà le colonne visibili, la larghezza e l'ordine impostati.</p>
<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 class="tab-pane fade" id="export-panel" role="tabpanel">
<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 class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="print-background">
<label class="custom-control-label" for="print-background">Stampa sfondi e colori</label>
</div>
</div>
</div>
</div>
<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>
</div>
</div>
@@ -121,41 +175,160 @@
</div>
<script>
function addFilterRow() {
const container = document.getElementById('filters-container');
const columns = @json($tableColumns);
const options = columns.map(c => `<option value="${c.key}">${c.label}</option>`).join('');
const div = document.createElement('div');
div.className = 'filter-row mb-2';
div.innerHTML = `
<div class="row">
<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);
function resetAllColumnWidths() {
document.querySelectorAll('.column-width-input').forEach(inp => { inp.value = ''; });
document.querySelectorAll('.column-config-item').forEach(item => {
const th = document.querySelector(`th[data-column="${item.dataset.column}"]`);
if (th) th.style.width = '';
});
if (window.columnManager) {
window.columnManager.columnWidths = {};
window.columnManager.persistState();
}
}
</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\\MailingListController' => $baseDir . '/app/Http/Controllers/MailingListController.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\\VistaReportController' => $baseDir . '/app/Http/Controllers/VistaReportController.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\\SenderAccount' => $baseDir . '/app/Models/SenderAccount.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\\TipologiaDocumento' => $baseDir . '/app/Models/TipologiaDocumento.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\\IcsExportService' => $baseDir . '/app/Services/IcsExportService.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\\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',
+3
View File
@@ -650,6 +650,7 @@ class ComposerStaticInit74199c0a3889e8d1256a25f93805ef56
'App\\Http\\Controllers\\MailingController' => __DIR__ . '/../..' . '/app/Http/Controllers/MailingController.php',
'App\\Http\\Controllers\\MailingListController' => __DIR__ . '/../..' . '/app/Http/Controllers/MailingListController.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\\VistaReportController' => __DIR__ . '/../..' . '/app/Http/Controllers/VistaReportController.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\\SenderAccount' => __DIR__ . '/../..' . '/app/Models/SenderAccount.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\\TipologiaDocumento' => __DIR__ . '/../..' . '/app/Models/TipologiaDocumento.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\\IcsExportService' => __DIR__ . '/../..' . '/app/Services/IcsExportService.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\\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',
+2 -2
View File
@@ -3,7 +3,7 @@
'name' => 'laravel/laravel',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '7d7d384a0d719ca6fa83e01e50577b94c877e676',
'reference' => '7999c663fe184a13576026be3af0abc1d91d036d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -427,7 +427,7 @@
'laravel/laravel' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '7d7d384a0d719ca6fa83e01e50577b94c877e676',
'reference' => '7999c663fe184a13576026be3af0abc1d91d036d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),