Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 005fa5b4a3 | |||
| 7302146a77 | |||
| 43a2c81312 | |||
| 0dd567cf7a | |||
| d9421c9159 | |||
| c959f72c66 | |||
| 7999c663fe | |||
| 708cadfac2 | |||
| a35b0056e1 | |||
| b7842bf2e0 | |||
| 06387568cb | |||
| d82b123bfd | |||
| 6f106e3130 | |||
| ce48481bc8 |
@@ -28,6 +28,25 @@ App gestionale Laravel 13 con AdminLTE 4 per gestione Persone e Gruppi.
|
|||||||
- Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests, Docker
|
- Esclude: .git, node_modules, tests, cache, storage content, vendor/docs/tests, Docker
|
||||||
- **Istruzioni post-estrazione complete**: mkdir, permessi, configurazione, cache clear
|
- **Istruzioni post-estrazione complete**: mkdir, permessi, configurazione, cache clear
|
||||||
|
|
||||||
|
## Tag System
|
||||||
|
|
||||||
|
### Implementazione Completa (Opzione B)
|
||||||
|
- **Migration**: `2026_06_08_194155_create_tags_tables.php` — tabelle `tags` + `taggables`
|
||||||
|
- **Model `Tag.php`**: morphToMany a 5 entity types, auto-slug su creating, color badge
|
||||||
|
- **Trait `HasTagsLight.php`**: `tags()` morphToMany, scopes `withAllTags`/`withAnyTags`
|
||||||
|
- **5 Models aggiornati**: Individuo, Gruppo, Evento, Documento, MailingList — usano `HasTagsLight`
|
||||||
|
- **ImpostazioniController**: CRUD completo per Tag (store, update, destroy, reorder) con tab nella pagina Impostazioni
|
||||||
|
- **Tag selector**: `_tag-selector.blade.php` — search + pill badges con colori, incluso in create/edit di Individui, Gruppi, Eventi, MailingList
|
||||||
|
- **Tag filtering**: `?tag[]=slug` supportato in `IndividuoController@index`, `GruppoController@index`, `EventoController@index`, `DocumentoController@index`, `MailingListController@index` via `withAnyTags` scope
|
||||||
|
- **Tag column in index views**: Colonna "Tag" con badge colorati cliccabili (collegamento a index filtrato) in individui, gruppi, eventi, documenti, mailing-liste
|
||||||
|
- **Tag filter widget**: `_tag-filter-bar.blade.php` — barra cliccabile con badge colorati sopra ogni index table (individui, gruppi, eventi, documenti, mailing-liste). Attiva/disattiva filtro `?tag[]=` con un click, evidenzia tag attivi, include "Cancella filtri" quando attivo
|
||||||
|
- **RicercaController**: Controller per ricerca unificata per tag — mostra risultati aggregati da tutte 5 entity types con info-box counters
|
||||||
|
- **Route `GET /ricerca`**: Registrata come `ricerca.index` → `RicercaController@index`
|
||||||
|
- **Sidebar link**: "Ricerca per Tag" (icona `fas fa-tags`) sotto la sezione Report, visibile con permesso `individui`
|
||||||
|
- **Mass tag action for Documenti**: `POST /documenti/mass-tag` → `DocumentoController@massTag` — modal with tag selector + assign/remove radio, processes selected documents in chunks of 100 via `syncWithoutDetaching()` (assign) or `detach()` (remove)
|
||||||
|
- **Report tag columns**: `tags.nome` disponibile come colonna per individui/gruppi/eventi/documenti nei report personalizzati
|
||||||
|
- **Report tag filters**: Custom report form include tag multi-select (Select2) + modalità OR/AND; `runCustomReport()` applica filtri via `withAllTags`/`withAnyTags`
|
||||||
|
|
||||||
## Bug Fix Recenti
|
## Bug Fix Recenti
|
||||||
|
|
||||||
### 2026-06-07 — Colonne mancanti in eventi e gruppo_individuo
|
### 2026-06-07 — Colonne mancanti in eventi e gruppo_individuo
|
||||||
@@ -158,5 +177,249 @@ php artisan config:clear
|
|||||||
1. **`app/Models/AppSetting.php`**: Sostituito `file_exists(public_path('storage/' . $path))` con `Storage::disk('public')->exists($path)` e `asset('storage/' . $path)` con `Storage::disk('public')->url($path)`. Il controllo ora avviene direttamente su `storage/app/public/` (disk root), bypassando il symlink.
|
1. **`app/Models/AppSetting.php`**: Sostituito `file_exists(public_path('storage/' . $path))` con `Storage::disk('public')->exists($path)` e `asset('storage/' . $path)` con `Storage::disk('public')->url($path)`. Il controllo ora avviene direttamente su `storage/app/public/` (disk root), bypassando il symlink.
|
||||||
2. **`app/Http/Controllers/ImpostazioniController.php`**: In `uploadLogo()`, aggiunta creazione automatica del symlink `public/storage → storage/app/public` se mancante, per garantire che il browser possa servire l'immagine.
|
2. **`app/Http/Controllers/ImpostazioniController.php`**: In `uploadLogo()`, aggiunta creazione automatica del symlink `public/storage → storage/app/public` se mancante, per garantire che il browser possa servire l'immagine.
|
||||||
|
|
||||||
|
## 2026-06-08 — Guardie Schema::hasColumn() aggiunte a 18 migration
|
||||||
|
|
||||||
|
**Problema**: 18 migration files aggiungevano colonne a tabelle esistenti senza `Schema::hasColumn()` guard. Se eseguite su DB dove le colonne già esistevano (es. da install.sql), causavano errore "Duplicate column".
|
||||||
|
|
||||||
|
**Fix**: Aggiunto `if (!Schema::hasColumn(...))` wrapper a ogni colonna aggiunta nelle seguenti migration:
|
||||||
|
1. `2026_06_02_000002_add_uid_esterno_to_eventi_table.php` — `uid_esterno`
|
||||||
|
2. `2026_05_12_000002_change_ruolo_to_multi.php` — `ruolo_ids` (up), `ruolo_id` (down)
|
||||||
|
3. `2026_05_28_000001_add_storage_disk_to_documenti.php` — `storage_disk`
|
||||||
|
4. `2026_05_27_113246_add_repository_id_to_documenti.php` — `repository_id`
|
||||||
|
5. `2026_05_26_000003_add_log_falliti_to_mailing_messaggi.php` — `log_falliti`, `mittente_nome`, `mittente_email`
|
||||||
|
6. `2026_05_27_000002_add_cartella_id_to_documenti.php` — `cartella_id`
|
||||||
|
7. `2026_05_16_000004_add_signature_enabled_to_email_settings.php` — `signature_enabled`
|
||||||
|
8. `2026_05_16_000003_add_signature_to_email_settings.php` — `signature`
|
||||||
|
9. `2024_01_01_000017_add_occorrenza_mese_to_eventi.php` — `occorrenza_mese`
|
||||||
|
10. `2024_01_01_000016_add_eventi_recurrence_fields.php` — `giorno_mese`, `mesi_recorrenza`, `mese_annuale`
|
||||||
|
11. `2026_05_10_000002_add_acl_tables.php` — `permissions` (users)
|
||||||
|
12. `2026_05_12_142009_add_location_to_eventi_table.php` — `luogo_indirizzo`, `luogo_url_maps`
|
||||||
|
13. `2026_05_12_000003_change_responsabile_to_multi.php` — `responsabile_ids` (up), `responsabile_id` (down)
|
||||||
|
14. `2024_01_01_000023_add_is_default_to_viste_report_table.php` — `is_default`
|
||||||
|
15. `2024_01_01_000024_add_user_id_to_mailing_lists_table.php` — `user_id`
|
||||||
|
16. `2026_05_12_000001_create_ruoli_table.php` — `ruolo_id` (gruppo_individuo) + data migration annessa
|
||||||
|
17. `2024_01_01_000021_add_user_id_to_documenti_table.php` — `user_id`
|
||||||
|
18. `2024_01_01_000013_add_numero_documento_to_individui_table.php` — `numero_documento`
|
||||||
|
|
||||||
|
Tutte verificate con `php -l` (nessun errore di sintassi).
|
||||||
|
|
||||||
|
### 2026-06-09 — `@stack('scripts')` mancante nel layout
|
||||||
|
|
||||||
|
**Problema**: Il partial `_tag-selector.blade.php` usa `@push('scripts')` per iniettare le funzioni JS `toggleTag`, `removeTag`, `filterTags`. Il layout `adminlte.blade.php` aveva solo `@yield('scripts')` (per `@section`), ma **non** `@stack('scripts')` (per `@push`). Di conseguenza il JavaScript interattivo del tag selector (clic sui badge, ricerca, rimozione) non veniva mai caricato su nessuna pagina create/edit.
|
||||||
|
|
||||||
|
**Fix**: `resources/views/layouts/adminlte.blade.php:305` — Aggiunto `@stack('scripts')` subito prima di `@yield('scripts')`.
|
||||||
|
|
||||||
|
### 2026-06-09 — Tag support completato per MailingList + Report + Ricerca
|
||||||
|
|
||||||
|
**MailingList — Controller & Views** (`app/Http/Controllers/MailingListController.php`):
|
||||||
|
- `index()`: eager-load `tags`, supporto `?tag[]=` filter via `withAnyTags`, pass `$allTags` per filter bar
|
||||||
|
- `create()`: pass `$tags` per tag selector
|
||||||
|
- `store()`: validazione `tags.* exists:tags,id`, sync dopo create. Auto-fetch email da `Individuo::contatti()` quando email non fornita
|
||||||
|
- `edit()`: eager-load `tags`, pass `$tags` + `$selectedTags`
|
||||||
|
- `update()`: validazione `tags.*`, sync se presente, detach se assente. Auto-fetch email da `Individuo::contatti()` quando email non fornita. Diff logica: `$toRemove = array_diff($existingIds, $newIds)` corretto — rimuove solo contatti deselezionati/esplicitamente rimossi
|
||||||
|
- `show()`: eager-load `tags`
|
||||||
|
|
||||||
|
**MailingList — Views**:
|
||||||
|
- `index.blade.php`: aggiunto `@include('partials._tag-filter-bar')`, colonna "Tag" con badge colorati cliccabili
|
||||||
|
- `create.blade.php`: incluso `_tag-selector` dopo descrizione
|
||||||
|
- `edit.blade.php`: incluso `_tag-selector` dopo descrizione (prima della sezione contatti). Submit handler include `#individui-select.selectedOptions` in `contatti_json` con `email: ''` (già presente da fix precedente)
|
||||||
|
- `show.blade.php`: aggiunta card Tag nel pannello sinistro con badge colorati (o "Nessun tag assegnato")
|
||||||
|
|
||||||
|
**ReportColumnRegistry** (`app/Helpers/ReportColumnRegistry.php`):
|
||||||
|
- Aggiunta colonna `'tags.nome'` con label 'Tag' a 4 entity types: individui, gruppi, eventi, documenti
|
||||||
|
|
||||||
|
**ReportController** (`app/Http/Controllers/ReportController.php`):
|
||||||
|
- `runCustomReport()`: supporta `config['tag_filter']` (array di tag IDs) + `config['tag_filter_mode']` ('any'/'all')
|
||||||
|
- `storeCustom()`: ora salva `tag_filter` e `tag_filter_mode` nella config del report salvato
|
||||||
|
- Carica `$allTags` nell'index e passa alla vista
|
||||||
|
- Converte IDs → slugs via `Tag::whereIn('id', ...)->pluck('slug')`, applica `withAllTags`/`withAnyTags`
|
||||||
|
- Eager-load `tags` automaticamente quando la colonna `tags.nome` è selezionata
|
||||||
|
|
||||||
|
**Report — View** (`resources/views/report/index.blade.php`):
|
||||||
|
- Aggiunto filtro tag multi-select nel form "Crea Report Personalizzato" (Select2 con badge colorati)
|
||||||
|
- Aggiunto select per modalità filtro (OR/AND)
|
||||||
|
- JS: `initTagFilterSelect2()` / `destroyTagFilterSelect2()` con template colorato
|
||||||
|
|
||||||
|
**RicercaController** (`app/Http/Controllers/RicercaController.php`):
|
||||||
|
- Aggiunta MailingList alle query per tag slug
|
||||||
|
- `results['mailingLists']` e `totals['mailingLists']` passati alla view
|
||||||
|
|
||||||
|
**Ricerca — View** (`resources/views/ricerca/index.blade.php`):
|
||||||
|
- Aggiunto info-box Mailing List (colore bg-purple) con link "Vedi tutti" → `mailing-liste.index?tag[]=`
|
||||||
|
- Aggiunta tabella dettaglio Mailing List nel risultato di ricerca (nome, contatti, stato, azione show)
|
||||||
|
- Fix: `route('documenti.edit')` → `url('/documenti/' . $doc->id . '/edit')` (route non esiste)
|
||||||
|
|
||||||
|
### 2026-06-09 — Fix: storeCustom() non salvava tag_filter
|
||||||
|
|
||||||
|
**Problema**: `ReportController@storeCustom()` non salvava `tag_filter` e `tag_filter_mode` nella config del report personalizzato. Quando un report salvato veniva eseguito, `runCustomReport()` non trovava i filtri tag nella config e non li applicava.
|
||||||
|
|
||||||
|
**Fix**: Aggiunte righe 143-148 in `ReportController.php`:
|
||||||
|
```php
|
||||||
|
if ($request->has('tag_filter')) {
|
||||||
|
$config['tag_filter'] = $request->input('tag_filter');
|
||||||
|
}
|
||||||
|
if ($request->filled('tag_filter_mode')) {
|
||||||
|
$config['tag_filter_mode'] = $request->input('tag_filter_mode');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2026-06-09 — Fix: MailingList edit — "Carica contatti" non inseriva i selezionati
|
||||||
|
|
||||||
|
**Problema**: Due bug nel JS `caricaSelezionati()` in `mailing-liste/edit.blade.php`:
|
||||||
|
1. **Typo**: `ind.cogname` invece di `ind.cognome` — il campo Cognome nella tabella era sempre vuoto/undefined (API restituisce `cognome`)
|
||||||
|
2. **Email gate**: `if (ind.emails && ind.emails.length > 0)` filtro bloccante — individui senza email salvata in `contatti` venivano SILENZIOSAMENTE scartati, mai aggiunti alla tabella
|
||||||
|
|
||||||
|
**Fix**:
|
||||||
|
1. `ind.cogname` → `ind.cognome` (righe 227 e 256)
|
||||||
|
2. Rimossa condizione `if (ind.emails...)` — ora anche individui senza email vengono aggiunti alla tabella
|
||||||
|
3. `email: ind.emails[0]` → `email: (ind.emails && ind.emails.length > 0) ? ind.emails[0] : ''` — email vuota se nessuna presente
|
||||||
|
4. Controller già fixato per auto-risolvere email da `Individuo::contatti()` al salvataggio
|
||||||
|
|
||||||
|
### 2026-06-09 — Fix: MailingListController email gate bloccava nuovi contatti senza email
|
||||||
|
|
||||||
|
**Problema**: `store()` e `update()` in `MailingListController` usavano `!empty($contatto['email'])` per decidere se creare un contatto. L'email era richiesta ma il MailingContact non memorizza l'email — la risolve dall'Individuo al momento del display/invio. Questo impediva di aggiungere individui senza email (o con email non nei contatti) alla mailing list.
|
||||||
|
|
||||||
|
**Fix**: Rimossa completamente la verifica email sia in `store()` che in `update()`. Ora ogni `individuo_id` valido viene aggiunto come MailingContact. L'email viene risolta al display dal model `$contact->individuo->contatti->where('tipo', 'email')->first()?->valore`.
|
||||||
|
|
||||||
|
Prima (bloccante):
|
||||||
|
```php
|
||||||
|
if (!empty($contatto['individuo_id']) && !empty($contatto['email'])) { create }
|
||||||
|
```
|
||||||
|
Dopo (semplice):
|
||||||
|
```php
|
||||||
|
if (!empty($contatto['individuo_id'])) { create }
|
||||||
|
```
|
||||||
|
|
||||||
## Prossimi Passi
|
## Prossimi Passi
|
||||||
- *(nessuno — in attesa di nuove richieste)*
|
- [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 `"` → `"` 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 `"` → `"`).
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ class ReportColumnRegistry
|
|||||||
'contatti.telefono' => ['label' => 'Telefono (da contatti)', 'type' => 'relation'],
|
'contatti.telefono' => ['label' => 'Telefono (da contatti)', 'type' => 'relation'],
|
||||||
'contatti.cellulare' => ['label' => 'Cellulare (da contatti)', 'type' => 'relation'],
|
'contatti.cellulare' => ['label' => 'Cellulare (da contatti)', 'type' => 'relation'],
|
||||||
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'gruppi' => [
|
'gruppi' => [
|
||||||
@@ -151,6 +152,7 @@ class ReportColumnRegistry
|
|||||||
'parent.nome' => ['label' => 'Gruppo Padre', 'type' => 'relation'],
|
'parent.nome' => ['label' => 'Gruppo Padre', 'type' => 'relation'],
|
||||||
'diocesi.nome' => ['label' => 'Diocesi', 'type' => 'relation'],
|
'diocesi.nome' => ['label' => 'Diocesi', 'type' => 'relation'],
|
||||||
'individui_count' => ['label' => 'Numero Membri', 'type' => 'relation'],
|
'individui_count' => ['label' => 'Numero Membri', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'eventi' => [
|
'eventi' => [
|
||||||
@@ -171,6 +173,7 @@ class ReportColumnRegistry
|
|||||||
'info_ricorrenza' => ['label' => 'Info Ricorrenza', 'type' => 'accessor'],
|
'info_ricorrenza' => ['label' => 'Info Ricorrenza', 'type' => 'accessor'],
|
||||||
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
'gruppi.nome' => ['label' => 'Gruppi', 'type' => 'relation'],
|
||||||
'responsabili.nome_completo' => ['label' => 'Responsabili', 'type' => 'relation'],
|
'responsabili.nome_completo' => ['label' => 'Responsabili', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'documenti' => [
|
'documenti' => [
|
||||||
@@ -183,6 +186,7 @@ class ReportColumnRegistry
|
|||||||
'note' => ['label' => 'Note', 'type' => 'direct'],
|
'note' => ['label' => 'Note', 'type' => 'direct'],
|
||||||
'created_at' => ['label' => 'Data Caricamento', 'type' => 'direct'],
|
'created_at' => ['label' => 'Data Caricamento', 'type' => 'direct'],
|
||||||
'user.name' => ['label' => 'Caricato Da', 'type' => 'relation'],
|
'user.name' => ['label' => 'Caricato Da', 'type' => 'relation'],
|
||||||
|
'tags.nome' => ['label' => 'Tag', 'type' => 'relation'],
|
||||||
],
|
],
|
||||||
|
|
||||||
'contatti' => [
|
'contatti' => [
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\Individuo;
|
|||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Evento;
|
use App\Models\Evento;
|
||||||
use App\Models\StorageRepository;
|
use App\Models\StorageRepository;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaDocumento;
|
use App\Models\TipologiaDocumento;
|
||||||
use App\Services\StorageRepositoryService;
|
use App\Services\StorageRepositoryService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@@ -42,9 +43,14 @@ class DocumentoController extends Controller
|
|||||||
$folderId = $request->get('folder_id');
|
$folderId = $request->get('folder_id');
|
||||||
$repoId = $request->get('repository_id');
|
$repoId = $request->get('repository_id');
|
||||||
|
|
||||||
$query = Documento::with(['user', 'target', 'cartella', 'repository'])
|
$query = Documento::with(['user', 'target', 'cartella', 'repository', 'tags'])
|
||||||
->orderByDesc('created_at');
|
->orderByDesc('created_at');
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
if ($repoId) {
|
if ($repoId) {
|
||||||
$query->where('repository_id', $repoId);
|
$query->where('repository_id', $repoId);
|
||||||
} elseif ($folderId) {
|
} elseif ($folderId) {
|
||||||
@@ -54,6 +60,7 @@ class DocumentoController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$documenti = $query->paginate(20);
|
$documenti = $query->paginate(20);
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
$cartelle = DocumentoCartella::with('children')
|
$cartelle = DocumentoCartella::with('children')
|
||||||
->whereNull('parent_id')
|
->whereNull('parent_id')
|
||||||
->orderBy('nome')
|
->orderBy('nome')
|
||||||
@@ -85,11 +92,45 @@ class DocumentoController extends Controller
|
|||||||
'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
|
'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'documenti')
|
||||||
|
->where('is_default', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'documenti')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome_file', 'label' => 'Nome'],
|
||||||
|
['key' => 'tipologia', 'label' => 'Tipologia'],
|
||||||
|
['key' => 'dimensione', 'label' => 'Dimensione'],
|
||||||
|
['key' => 'visibilita', 'label' => 'Contesto'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'created_at', 'label' => 'Data'],
|
||||||
|
];
|
||||||
|
$defaultVisible = ['nome_file', 'tipologia', 'dimensione', 'visibilita', 'tag', 'created_at'];
|
||||||
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
|
? $vista->colonne_visibili
|
||||||
|
: $defaultVisible;
|
||||||
|
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'documenti';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
return view('documenti.index', compact(
|
return view('documenti.index', compact(
|
||||||
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
|
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
|
||||||
'currentRepo', 'repositories',
|
'currentRepo', 'repositories',
|
||||||
'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
|
'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
|
||||||
'cartelleMoveOptions'
|
'cartelleMoveOptions', 'allTags',
|
||||||
|
'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths',
|
||||||
|
'userVistas'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +147,15 @@ class DocumentoController extends Controller
|
|||||||
public function edit($documento)
|
public function edit($documento)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('documenti');
|
$this->authorizeWrite('documenti');
|
||||||
$documento = Documento::with('target')->findOrFail($documento);
|
$documento = Documento::with(['target', 'tags'])->findOrFail($documento);
|
||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$gruppi = Gruppo::orderBy('nome')->get();
|
$gruppi = Gruppo::orderBy('nome')->get();
|
||||||
$eventi = Evento::orderBy('nome_evento')->get();
|
$eventi = Evento::orderBy('nome_evento')->get();
|
||||||
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
|
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
|
||||||
$tipologie = TipologiaDocumento::attive();
|
$tipologie = TipologiaDocumento::attive();
|
||||||
return view('documenti.edit', compact('documento', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $documento->tags->pluck('id')->toArray();
|
||||||
|
return view('documenti.edit', compact('documento', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function options(Request $request)
|
public function options(Request $request)
|
||||||
@@ -165,6 +208,8 @@ class DocumentoController extends Controller
|
|||||||
'visibilita_target_id' => 'nullable|integer',
|
'visibilita_target_id' => 'nullable|integer',
|
||||||
'visibilita_target_type' => 'nullable|string',
|
'visibilita_target_type' => 'nullable|string',
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data['visibilita'] = $request->contesto_tipo ?: 'pubblico';
|
$data['visibilita'] = $request->contesto_tipo ?: 'pubblico';
|
||||||
@@ -185,6 +230,12 @@ class DocumentoController extends Controller
|
|||||||
|
|
||||||
$documento->update($data);
|
$documento->update($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$documento->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$documento->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
return redirect('/documenti')->with('success', 'Documento aggiornato.');
|
return redirect('/documenti')->with('success', 'Documento aggiornato.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,6 +757,41 @@ class DocumentoController extends Controller
|
|||||||
return back()->with('success', count($ids) . ' documenti associati.');
|
return back()->with('success', count($ids) . ' documenti associati.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('documenti');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
return back()->with('error', 'Nessun documento selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Documento::whereIn('id', $ids)->chunk(100, function ($documenti) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($documenti as $documento) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$documento->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$documento->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return back()->with('success', "Tag $actionLabel per $count documenti.");
|
||||||
|
}
|
||||||
|
|
||||||
public function massMove(Request $request): RedirectResponse
|
public function massMove(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('documenti');
|
$this->authorizeWrite('documenti');
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ class EmailController extends Controller
|
|||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$senderAccounts = SenderAccount::active()->get();
|
$senderAccounts = SenderAccount::active()->get();
|
||||||
|
|
||||||
|
$documenti = Documento::orderBy('nome_file')->get();
|
||||||
|
|
||||||
$prefill = [];
|
$prefill = [];
|
||||||
if ($request->reply_to) {
|
if ($request->reply_to) {
|
||||||
$original = EmailMessage::find($request->reply_to);
|
$original = EmailMessage::find($request->reply_to);
|
||||||
@@ -111,7 +113,7 @@ class EmailController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'prefill', 'senderAccounts'));
|
return view('email.compose', compact('folders', 'mailingLists', 'gruppi', 'individui', 'documenti', 'prefill', 'senderAccounts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function send(Request $request)
|
public function send(Request $request)
|
||||||
@@ -341,7 +343,7 @@ class EmailController extends Controller
|
|||||||
$message->delete();
|
$message->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->with('success', 'Email spostata nel cestino.');
|
return redirect()->route('email.index', ['folder' => 'inbox'])->with('success', 'Email spostata nel cestino.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadAttachment($id)
|
public function downloadAttachment($id)
|
||||||
@@ -565,6 +567,7 @@ class EmailController extends Controller
|
|||||||
private function processAttachments(Request $request): array
|
private function processAttachments(Request $request): array
|
||||||
{
|
{
|
||||||
$paths = [];
|
$paths = [];
|
||||||
|
|
||||||
if ($request->hasFile('allegati')) {
|
if ($request->hasFile('allegati')) {
|
||||||
foreach ($request->file('allegati') as $file) {
|
foreach ($request->file('allegati') as $file) {
|
||||||
if ($file->isValid()) {
|
if ($file->isValid()) {
|
||||||
@@ -572,6 +575,18 @@ class EmailController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$documentIds = $request->input('documenti_selezionati');
|
||||||
|
if (!empty($documentIds)) {
|
||||||
|
$ids = is_array($documentIds) ? $documentIds : explode(',', $documentIds);
|
||||||
|
$docs = Documento::whereIn('id', $ids)->get();
|
||||||
|
foreach ($docs as $doc) {
|
||||||
|
if ($doc->file_path && Storage::exists($doc->file_path)) {
|
||||||
|
$paths[] = Storage::path($doc->file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $paths;
|
return $paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\Evento;
|
use App\Models\Evento;
|
||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaEvento;
|
use App\Models\TipologiaEvento;
|
||||||
use App\Services\IcsExportService;
|
use App\Services\IcsExportService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -16,7 +17,12 @@ class EventoController extends Controller
|
|||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('eventi');
|
$this->authorizeRead('eventi');
|
||||||
$query = Evento::with(['gruppi', 'responsabili']);
|
$query = Evento::with(['gruppi', 'responsabili', 'tags']);
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->filled('search')) {
|
if ($request->filled('search')) {
|
||||||
$query->where('nome_evento', 'like', '%' . $request->search . '%');
|
$query->where('nome_evento', 'like', '%' . $request->search . '%');
|
||||||
@@ -47,8 +53,44 @@ class EventoController extends Controller
|
|||||||
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
|
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
|
||||||
$eventi = $query->paginate($perPage);
|
$eventi = $query->paginate($perPage);
|
||||||
$gruppi = Gruppo::orderBy('nome')->get();
|
$gruppi = Gruppo::orderBy('nome')->get();
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('eventi.index', compact('eventi', 'gruppi'));
|
$vista = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'eventi')
|
||||||
|
->where('is_default', true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$userVistas = \App\Models\VistaReport::where('user_id', auth()->id())
|
||||||
|
->where('tipo', 'eventi')
|
||||||
|
->orderBy('is_default', 'desc')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome_evento', 'label' => 'Nome Evento'],
|
||||||
|
['key' => 'descrizione_evento', 'label' => 'Descrizione'],
|
||||||
|
['key' => 'tipo_evento', 'label' => 'Tipologia'],
|
||||||
|
['key' => 'tipo_recorrenza', 'label' => 'Ricorrenza'],
|
||||||
|
['key' => 'gruppi', 'label' => 'Gruppi'],
|
||||||
|
['key' => 'responsabili', 'label' => 'Responsabili'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'data_specifica', 'label' => 'Data'],
|
||||||
|
['key' => 'created_at', 'label' => 'Creato'],
|
||||||
|
];
|
||||||
|
$defaultVisible = ['nome_evento', 'descrizione_evento', 'tipo_evento', 'tipo_recorrenza', 'gruppi', 'responsabili', 'tag', 'created_at'];
|
||||||
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
|
? $vista->colonne_visibili
|
||||||
|
: $defaultVisible;
|
||||||
|
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'eventi';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
|
return view('eventi.index', compact('eventi', 'gruppi', 'allTags', 'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths', 'userVistas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(Request $request)
|
public function create(Request $request)
|
||||||
@@ -58,8 +100,9 @@ class EventoController extends Controller
|
|||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$selectedGruppo = $request->query('gruppo_id') ? Gruppo::find($request->query('gruppo_id')) : null;
|
$selectedGruppo = $request->query('gruppo_id') ? Gruppo::find($request->query('gruppo_id')) : null;
|
||||||
$tipologieEventi = TipologiaEvento::attive();
|
$tipologieEventi = TipologiaEvento::attive();
|
||||||
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('eventi.create', compact('gruppi', 'individui', 'selectedGruppo', 'tipologieEventi'));
|
return view('eventi.create', compact('gruppi', 'individui', 'selectedGruppo', 'tipologieEventi', 'tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -88,6 +131,8 @@ class EventoController extends Controller
|
|||||||
'responsabili.*' => 'exists:individui,id',
|
'responsabili.*' => 'exists:individui,id',
|
||||||
'luogo_indirizzo' => 'nullable|string|max:500',
|
'luogo_indirizzo' => 'nullable|string|max:500',
|
||||||
'luogo_url_maps' => 'nullable|string',
|
'luogo_url_maps' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$evento = Evento::create([
|
$evento = Evento::create([
|
||||||
@@ -110,6 +155,10 @@ class EventoController extends Controller
|
|||||||
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!empty($data['tags'])) {
|
||||||
|
$evento->tags()->attach($data['tags']);
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($data['gruppi'])) {
|
if (!empty($data['gruppi'])) {
|
||||||
$evento->gruppi()->attach($data['gruppi']);
|
$evento->gruppi()->attach($data['gruppi']);
|
||||||
}
|
}
|
||||||
@@ -124,19 +173,21 @@ class EventoController extends Controller
|
|||||||
public function show($evento)
|
public function show($evento)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('eventi');
|
$this->authorizeRead('eventi');
|
||||||
$evento = Evento::with(['gruppi', 'responsabili.contatti', 'documenti'])->findOrFail($evento);
|
$evento = Evento::with(['gruppi', 'responsabili.contatti', 'documenti', 'tags'])->findOrFail($evento);
|
||||||
return view('eventi.show', compact('evento'));
|
return view('eventi.show', compact('evento'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit($evento)
|
public function edit($evento)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('eventi');
|
$this->authorizeWrite('eventi');
|
||||||
$evento = Evento::with(['gruppi', 'responsabili', 'documenti'])->findOrFail($evento);
|
$evento = Evento::with(['gruppi', 'responsabili', 'documenti', 'tags'])->findOrFail($evento);
|
||||||
$gruppi = Gruppo::orderBy('nome')->get();
|
$gruppi = Gruppo::orderBy('nome')->get();
|
||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
$tipologieEventi = TipologiaEvento::attive();
|
$tipologieEventi = TipologiaEvento::attive();
|
||||||
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $evento->tags->pluck('id')->toArray();
|
||||||
|
|
||||||
return view('eventi.edit', compact('evento', 'gruppi', 'individui', 'tipologieEventi'));
|
return view('eventi.edit', compact('evento', 'gruppi', 'individui', 'tipologieEventi', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $evento)
|
public function update(Request $request, $evento)
|
||||||
@@ -167,6 +218,8 @@ class EventoController extends Controller
|
|||||||
'responsabili.*' => 'exists:individui,id',
|
'responsabili.*' => 'exists:individui,id',
|
||||||
'luogo_indirizzo' => 'nullable|string|max:500',
|
'luogo_indirizzo' => 'nullable|string|max:500',
|
||||||
'luogo_url_maps' => 'nullable|string',
|
'luogo_url_maps' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$evento->update([
|
$evento->update([
|
||||||
@@ -189,6 +242,7 @@ class EventoController extends Controller
|
|||||||
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
'luogo_url_maps' => $data['luogo_url_maps'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$evento->tags()->sync($data['tags'] ?? []);
|
||||||
$evento->gruppi()->sync($data['gruppi'] ?? []);
|
$evento->gruppi()->sync($data['gruppi'] ?? []);
|
||||||
$evento->responsabili()->sync($data['responsabili'] ?? []);
|
$evento->responsabili()->sync($data['responsabili'] ?? []);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\Gruppo;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
use App\Models\Diocesi;
|
use App\Models\Diocesi;
|
||||||
|
use App\Models\Tag;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Pagination\Paginator;
|
use Illuminate\Pagination\Paginator;
|
||||||
@@ -27,8 +28,28 @@ class GruppoController extends Controller
|
|||||||
->where('is_default', true)
|
->where('is_default', true)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
$allColumns = ['nome', 'descrizione', 'diocesi', 'livello', 'padre', 'membri', 'responsabili', 'indirizzo', 'citta', 'telefono', 'email'];
|
$userVistas = \App\Models\VistaReport::where('user_id', $user->id)
|
||||||
$defaultVisible = ['nome', 'livello', 'padre', 'membri', 'responsabili'];
|
->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)) {
|
if ($vista && !empty($vista->colonne_visibili)) {
|
||||||
$visibleColumns = $vista->colonne_visibili;
|
$visibleColumns = $vista->colonne_visibili;
|
||||||
@@ -36,9 +57,22 @@ class GruppoController extends Controller
|
|||||||
$visibleColumns = $defaultVisible;
|
$visibleColumns = $defaultVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
$allGruppi = Gruppo::with(['diocesi', 'parent', 'children', 'individui', 'avatar'])
|
$tableColumns = array_map(fn($col) => [
|
||||||
->orderBy('nome')
|
'key' => $col['key'],
|
||||||
->get()
|
'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')) {
|
||||||
|
$tagSlugs = (array) request()->input('tag');
|
||||||
|
$gruppiQuery->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allGruppi = $gruppiQuery->orderBy('nome')->get()
|
||||||
->map(function ($gruppo) {
|
->map(function ($gruppo) {
|
||||||
$depth = 0;
|
$depth = 0;
|
||||||
$parent = $gruppo->parent;
|
$parent = $gruppo->parent;
|
||||||
@@ -65,12 +99,19 @@ class GruppoController extends Controller
|
|||||||
$gruppi = $allGruppi;
|
$gruppi = $allGruppi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('gruppi.index', compact(
|
return view('gruppi.index', compact(
|
||||||
'gruppi',
|
'gruppi',
|
||||||
'allColumns',
|
'allColumns',
|
||||||
'visibleColumns',
|
'visibleColumns',
|
||||||
'viewMode',
|
'viewMode',
|
||||||
'vista'
|
'vista',
|
||||||
|
'allTags',
|
||||||
|
'tableColumns',
|
||||||
|
'entityType',
|
||||||
|
'columnWidths',
|
||||||
|
'userVistas'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +145,8 @@ class GruppoController extends Controller
|
|||||||
$allGruppi = Gruppo::orderBy('nome')->get();
|
$allGruppi = Gruppo::orderBy('nome')->get();
|
||||||
$selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null;
|
$selectedParent = $request->query('parent_id') ? Gruppo::find($request->query('parent_id')) : null;
|
||||||
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
|
||||||
return view('gruppi.create', compact('diocesi', 'allGruppi', 'selectedParent', 'individui'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
return view('gruppi.create', compact('diocesi', 'allGruppi', 'selectedParent', 'individui', 'tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -122,6 +164,8 @@ class GruppoController extends Controller
|
|||||||
'città_incontro' => 'nullable|string|max:255',
|
'città_incontro' => 'nullable|string|max:255',
|
||||||
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
||||||
'mappa_posizione' => 'nullable|string',
|
'mappa_posizione' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($data['responsabile_ids'])) {
|
if (!empty($data['responsabile_ids'])) {
|
||||||
@@ -130,6 +174,10 @@ class GruppoController extends Controller
|
|||||||
|
|
||||||
$gruppo = Gruppo::create($data);
|
$gruppo = Gruppo::create($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$gruppo->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('individui')) {
|
if ($request->has('individui')) {
|
||||||
foreach ($request->individui as $individuoData) {
|
foreach ($request->individui as $individuoData) {
|
||||||
if (!empty($individuoData['individuo_id'])) {
|
if (!empty($individuoData['individuo_id'])) {
|
||||||
@@ -149,7 +197,7 @@ class GruppoController extends Controller
|
|||||||
$this->authorizeRead('gruppi');
|
$this->authorizeRead('gruppi');
|
||||||
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
|
$gruppo = Gruppo::with(['parent', 'children', 'diocesi', 'individui.contatti', 'individui.documenti', 'individui' => function ($q) {
|
||||||
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
||||||
}, 'avatar', 'eventi' => function ($q) {
|
}, 'avatar', 'tags', 'eventi' => function ($q) {
|
||||||
$q->where('is_incontro_gruppo', true);
|
$q->where('is_incontro_gruppo', true);
|
||||||
}, 'eventi.responsabili.contatti'])->findOrFail($id);
|
}, 'eventi.responsabili.contatti'])->findOrFail($id);
|
||||||
return view('gruppi.show', compact('gruppo'));
|
return view('gruppi.show', compact('gruppo'));
|
||||||
@@ -158,11 +206,13 @@ class GruppoController extends Controller
|
|||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('gruppi');
|
$this->authorizeWrite('gruppi');
|
||||||
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'avatar'])->findOrFail($id);
|
$gruppo = Gruppo::with(['individui.contatti', 'individui.documenti', 'avatar', 'tags'])->findOrFail($id);
|
||||||
$diocesi = Diocesi::orderBy('nome')->get();
|
$diocesi = Diocesi::orderBy('nome')->get();
|
||||||
$gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get();
|
$gruppi = Gruppo::where('id', '!=', $gruppo->id)->orderBy('nome')->get();
|
||||||
$membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get();
|
$membri = $gruppo->individui()->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione')->orderBy('cognome')->orderBy('nome')->get();
|
||||||
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $gruppo->tags->pluck('id')->toArray();
|
||||||
|
return view('gruppi.edit', compact('gruppo', 'diocesi', 'gruppi', 'membri', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
@@ -181,6 +231,8 @@ class GruppoController extends Controller
|
|||||||
'città_incontro' => 'nullable|string|max:255',
|
'città_incontro' => 'nullable|string|max:255',
|
||||||
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
'sigla_provincia_incontro' => 'nullable|string|max:2',
|
||||||
'mappa_posizione' => 'nullable|string',
|
'mappa_posizione' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!empty($data['responsabile_ids'])) {
|
if (!empty($data['responsabile_ids'])) {
|
||||||
@@ -191,6 +243,12 @@ class GruppoController extends Controller
|
|||||||
|
|
||||||
$gruppo->update($data);
|
$gruppo->update($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$gruppo->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$gruppo->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('individui')) {
|
if ($request->has('individui')) {
|
||||||
$gruppo->individui()->detach();
|
$gruppo->individui()->detach();
|
||||||
foreach ($request->individui as $individuoData) {
|
foreach ($request->individui as $individuoData) {
|
||||||
@@ -410,4 +468,59 @@ class GruppoController extends Controller
|
|||||||
'Content-Disposition' => 'attachment; filename="template_gruppi.csv"',
|
'Content-Disposition' => 'attachment; filename="template_gruppi.csv"',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('gruppi');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
|
||||||
|
if (empty($ids)) {
|
||||||
|
return redirect()->route('gruppi.index')->with('error', 'Nessun gruppo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Gruppo::whereIn('id', $ids)->chunk(100, function ($gruppi) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($gruppi as $gruppo) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$gruppo->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$gruppo->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return redirect()->route('gruppi.index')->with('success', "Tag {$actionLabel} per {$count} gruppi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function massElimina(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeDelete('gruppi');
|
||||||
|
$ids = $request->input('ids', []);
|
||||||
|
if (!is_array($ids) || empty($ids)) {
|
||||||
|
return redirect()->route('gruppi.index')->with('error', 'Nessun gruppo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$gruppi = Gruppo::whereIn('id', $ids)->get();
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($gruppi as $gruppo) {
|
||||||
|
$gruppo->individui()->detach();
|
||||||
|
$gruppo->delete();
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('gruppi.index')->with('success', "{$count} gruppi eliminati con successo.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Models\EmailSetting;
|
|||||||
use App\Models\Ruolo;
|
use App\Models\Ruolo;
|
||||||
use App\Models\SenderAccount;
|
use App\Models\SenderAccount;
|
||||||
use App\Models\StorageRepository;
|
use App\Models\StorageRepository;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaDocumento;
|
use App\Models\TipologiaDocumento;
|
||||||
use App\Models\TipologiaEvento;
|
use App\Models\TipologiaEvento;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
@@ -26,6 +27,7 @@ class ImpostazioniController extends Controller
|
|||||||
$tipologie = TipologiaDocumento::orderBy('ordine')->get();
|
$tipologie = TipologiaDocumento::orderBy('ordine')->get();
|
||||||
$tipologieEventi = TipologiaEvento::orderBy('ordine')->get();
|
$tipologieEventi = TipologiaEvento::orderBy('ordine')->get();
|
||||||
$ruoli = Ruolo::orderBy('ordine')->get();
|
$ruoli = Ruolo::orderBy('ordine')->get();
|
||||||
|
$tags = Tag::orderBy('order_column')->get();
|
||||||
$appSettings = AppSetting::first();
|
$appSettings = AppSetting::first();
|
||||||
$emailSettings = EmailSetting::first() ?? new EmailSetting();
|
$emailSettings = EmailSetting::first() ?? new EmailSetting();
|
||||||
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
$senderAccounts = SenderAccount::orderBy('email_address')->get();
|
||||||
@@ -33,7 +35,7 @@ class ImpostazioniController extends Controller
|
|||||||
$googleDriveNewToken = session('google_drive_new_token');
|
$googleDriveNewToken = session('google_drive_new_token');
|
||||||
$calendarioConnessioni = CalendarioConnessione::orderBy('ordine')->get();
|
$calendarioConnessioni = CalendarioConnessione::orderBy('ordine')->get();
|
||||||
|
|
||||||
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories', 'googleDriveNewToken', 'calendarioConnessioni'));
|
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'tags', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories', 'googleDriveNewToken', 'calendarioConnessioni'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveAppSettings(Request $request)
|
public function saveAppSettings(Request $request)
|
||||||
@@ -371,6 +373,70 @@ class ImpostazioniController extends Controller
|
|||||||
return response()->json(['success' => true]);
|
return response()->json(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tagStore(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100|unique:tags,name',
|
||||||
|
'color' => 'nullable|string|max:7',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$maxOrdine = Tag::max('order_column') ?? 0;
|
||||||
|
|
||||||
|
Tag::create([
|
||||||
|
'name' => $validated['name'],
|
||||||
|
'slug' => str()->slug($validated['name']),
|
||||||
|
'color' => $validated['color'] ?? null,
|
||||||
|
'order_column' => $maxOrdine + 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect('/impostazioni#tag')->with('success', 'Tag aggiunto.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tagUpdate(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
|
||||||
|
$tag = Tag::findOrFail($id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100|unique:tags,name,' . $id,
|
||||||
|
'color' => 'nullable|string|max:7',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tag->update([
|
||||||
|
'name' => $validated['name'],
|
||||||
|
'slug' => str()->slug($validated['name']),
|
||||||
|
'color' => $validated['color'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect('/impostazioni#tag')->with('success', 'Tag aggiornato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tagDestroy($id)
|
||||||
|
{
|
||||||
|
$this->authorizeDelete('settings');
|
||||||
|
|
||||||
|
$tag = Tag::findOrFail($id);
|
||||||
|
$tag->delete();
|
||||||
|
|
||||||
|
return redirect('/impostazioni#tag')->with('success', 'Tag eliminato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tagReorder(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('settings');
|
||||||
|
|
||||||
|
$order = $request->input('order', []);
|
||||||
|
|
||||||
|
foreach ($order as $index => $id) {
|
||||||
|
Tag::where('id', $id)->update(['order_column' => $index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
public function uploadLogo(Request $request)
|
public function uploadLogo(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('settings');
|
$this->authorizeWrite('settings');
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\Individuo;
|
use App\Models\Individuo;
|
||||||
use App\Models\Contatto;
|
use App\Models\Contatto;
|
||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
use App\Models\Comune;
|
use App\Models\Gruppo;
|
||||||
use App\Models\Diocesi;
|
use App\Models\Comune;
|
||||||
use Illuminate\Http\Request;
|
use App\Models\Diocesi;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use App\Models\Tag;
|
||||||
use Illuminate\Validation\Rule;
|
use App\Models\VistaReport;
|
||||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
class IndividuoController extends Controller
|
class IndividuoController extends Controller
|
||||||
{
|
{
|
||||||
@@ -18,7 +21,16 @@ class IndividuoController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorizeRead('individui');
|
$this->authorizeRead('individui');
|
||||||
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
|
$perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20;
|
||||||
$individui = Individuo::with('contatti')->orderBy('cognome')->orderBy('nome')->paginate($perPage);
|
|
||||||
|
$query = Individuo::with('contatti', 'tags');
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$individui = $query->orderBy('cognome')->orderBy('nome')->paginate($perPage);
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
$vista = null;
|
$vista = null;
|
||||||
|
|
||||||
@@ -39,16 +51,43 @@ class IndividuoController extends Controller
|
|||||||
['key' => 'codice', 'label' => 'Codice'],
|
['key' => 'codice', 'label' => 'Codice'],
|
||||||
['key' => 'cognome', 'label' => 'Cognome'],
|
['key' => 'cognome', 'label' => 'Cognome'],
|
||||||
['key' => 'nome', 'label' => 'Nome'],
|
['key' => 'nome', 'label' => 'Nome'],
|
||||||
|
['key' => 'data_nascita', 'label' => 'Data Nascita'],
|
||||||
|
['key' => 'genere', 'label' => 'Genere'],
|
||||||
|
['key' => 'indirizzo', 'label' => 'Indirizzo'],
|
||||||
|
['key' => 'cap', 'label' => 'CAP'],
|
||||||
|
['key' => 'citta', 'label' => 'Città'],
|
||||||
|
['key' => 'provincia', 'label' => 'Provincia'],
|
||||||
|
['key' => 'tipo_documento', 'label' => 'Tipo Documento'],
|
||||||
|
['key' => 'numero_documento', 'label' => 'Numero Documento'],
|
||||||
|
['key' => 'scadenza_documento', 'label' => 'Scadenza Documento'],
|
||||||
['key' => 'email', 'label' => 'Email'],
|
['key' => 'email', 'label' => 'Email'],
|
||||||
['key' => 'telefono', 'label' => 'Telefono'],
|
['key' => 'telefono', 'label' => 'Telefono'],
|
||||||
|
['key' => 'note', 'label' => 'Note'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'created_at', 'label' => 'Creato il'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono'];
|
$defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono', 'tag'];
|
||||||
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
? $vista->colonne_visibili
|
? $vista->colonne_visibili
|
||||||
: $defaultVisible;
|
: $defaultVisible;
|
||||||
|
|
||||||
return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns'));
|
$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', 'entityType', 'tableColumns', 'columnWidths', 'userVistas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportCSV(Request $request)
|
public function exportCSV(Request $request)
|
||||||
@@ -110,7 +149,8 @@ public function create()
|
|||||||
$this->authorizeWrite('individui');
|
$this->authorizeWrite('individui');
|
||||||
$comuni = Comune::orderBy('nome')->get();
|
$comuni = Comune::orderBy('nome')->get();
|
||||||
$diocesi = Diocesi::orderBy('nome')->get();
|
$diocesi = Diocesi::orderBy('nome')->get();
|
||||||
return view('individui.create', compact('comuni', 'diocesi'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
return view('individui.create', compact('comuni', 'diocesi', 'tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -129,10 +169,16 @@ public function create()
|
|||||||
'numero_documento' => 'nullable|string|max:50',
|
'numero_documento' => 'nullable|string|max:50',
|
||||||
'scadenza_documento' => 'nullable|date',
|
'scadenza_documento' => 'nullable|date',
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$individuo = Individuo::create($data);
|
$individuo = Individuo::create($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$individuo->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
if ($request->has('contatti')) {
|
if ($request->has('contatti')) {
|
||||||
foreach ($request->contatti as $contatto) {
|
foreach ($request->contatti as $contatto) {
|
||||||
if (!empty($contatto['tipo']) && !empty($contatto['valore'])) {
|
if (!empty($contatto['tipo']) && !empty($contatto['valore'])) {
|
||||||
@@ -154,7 +200,7 @@ public function create()
|
|||||||
$this->authorizeRead('individui');
|
$this->authorizeRead('individui');
|
||||||
$individuo = Individuo::with(['contatti', 'gruppi' => function ($q) {
|
$individuo = Individuo::with(['contatti', 'gruppi' => function ($q) {
|
||||||
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
$q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione');
|
||||||
}, 'avatar'])->findOrFail($individuo);
|
}, 'avatar', 'tags'])->findOrFail($individuo);
|
||||||
return view('individui.show', compact('individuo'));
|
return view('individui.show', compact('individuo'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +226,10 @@ public function create()
|
|||||||
public function edit($individuo)
|
public function edit($individuo)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('individui');
|
$this->authorizeWrite('individui');
|
||||||
$individuo = Individuo::with(['contatti', 'gruppi', 'avatar'])->findOrFail($individuo);
|
$individuo = Individuo::with(['contatti', 'gruppi', 'avatar', 'tags'])->findOrFail($individuo);
|
||||||
return view('individui.edit', compact('individuo'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $individuo->tags->pluck('id')->toArray();
|
||||||
|
return view('individui.edit', compact('individuo', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $individuo)
|
public function update(Request $request, $individuo)
|
||||||
@@ -205,9 +253,17 @@ public function create()
|
|||||||
'numero_documento' => 'nullable|string|max:50',
|
'numero_documento' => 'nullable|string|max:50',
|
||||||
'scadenza_documento' => 'nullable|date',
|
'scadenza_documento' => 'nullable|date',
|
||||||
'note' => 'nullable|string',
|
'note' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$individuo->update($data);
|
$individuo->update($data);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$individuo->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$individuo->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
\Log::info('Individuo dati salvati', ['id' => $individuo->id]);
|
\Log::info('Individuo dati salvati', ['id' => $individuo->id]);
|
||||||
|
|
||||||
@@ -308,6 +364,75 @@ public function create()
|
|||||||
return redirect()->route('individui.index')->with('success', count($individui) . ' individui eliminati con successo.');
|
return redirect()->route('individui.index')->with('success', count($individui) . ' individui eliminati con successo.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function massGruppo(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('individui');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
if (empty($ids)) {
|
||||||
|
return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'gruppi' => 'required|array',
|
||||||
|
'gruppi.*' => 'exists:gruppi,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$gruppiIds = $data['gruppi'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
$individuo = Individuo::find($id);
|
||||||
|
if (!$individuo) continue;
|
||||||
|
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$individuo->gruppi()->syncWithoutDetaching($gruppiIds);
|
||||||
|
} else {
|
||||||
|
$individuo->gruppi()->detach($gruppiIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return redirect()->route('individui.index')->with('success', "Gruppi {$actionLabel} per {$count} individui.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function massTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeWrite('individui');
|
||||||
|
$idsInput = $request->input('ids', '');
|
||||||
|
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
|
||||||
|
if (empty($ids)) {
|
||||||
|
return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'tags' => 'required|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
|
'mode' => 'required|in:assign,remove',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tagIds = $data['tags'];
|
||||||
|
$mode = $data['mode'];
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
Individuo::whereIn('id', $ids)->chunk(100, function ($individui) use ($tagIds, $mode, &$count) {
|
||||||
|
foreach ($individui as $individuo) {
|
||||||
|
if ($mode === 'assign') {
|
||||||
|
$individuo->tags()->syncWithoutDetaching($tagIds);
|
||||||
|
} else {
|
||||||
|
$individuo->tags()->detach($tagIds);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi';
|
||||||
|
return redirect()->route('individui.index')->with('success', "Tag {$actionLabel} per {$count} individui.");
|
||||||
|
}
|
||||||
|
|
||||||
public function elementiCollegati($individuo)
|
public function elementiCollegati($individuo)
|
||||||
{
|
{
|
||||||
$individuo = Individuo::findOrFail($individuo);
|
$individuo = Individuo::findOrFail($individuo);
|
||||||
|
|||||||
@@ -95,9 +95,17 @@ class MailingController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$emails = $destinatari->flatMap(fn($ind) => $ind->contatti->where('tipo', 'email')->pluck('valore'))
|
$totaleDestinatari = $destinatari->count();
|
||||||
|
$emails = $destinatari->map(fn($ind) => $ind->contatti->where('tipo', 'email')->first()?->valore)
|
||||||
->filter()->unique()->values();
|
->filter()->unique()->values();
|
||||||
|
|
||||||
|
$skipped = $totaleDestinatari - $emails->count();
|
||||||
|
if ($skipped > 0) {
|
||||||
|
Log::info("Mailing invia: {$skipped} contatti saltati perché senza email", [
|
||||||
|
'lista_id' => $data['lista_id'] ?? null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($emails->isEmpty()) {
|
if ($emails->isEmpty()) {
|
||||||
return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput();
|
return back()->with('error', 'Nessun destinatario email valido trovato.')->withInput();
|
||||||
}
|
}
|
||||||
@@ -229,9 +237,17 @@ class MailingController extends Controller
|
|||||||
->with('individuo.contatti')
|
->with('individuo.contatti')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$totaleContatti = $contatti->count();
|
||||||
$emails = $contatti->map(fn($c) => $c->individuo?->contatti->where('tipo', 'email')->first()?->valore)
|
$emails = $contatti->map(fn($c) => $c->individuo?->contatti->where('tipo', 'email')->first()?->valore)
|
||||||
->filter()->unique()->values();
|
->filter()->unique()->values();
|
||||||
|
|
||||||
|
$skipped = $totaleContatti - $emails->count();
|
||||||
|
if ($skipped > 0) {
|
||||||
|
Log::info("Mailing invio elabora: {$skipped} contatti saltati perché senza email", [
|
||||||
|
'liste_ids' => $data['liste'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($emails->isEmpty()) {
|
if ($emails->isEmpty()) {
|
||||||
return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.');
|
return back()->with('error', 'Nessun destinatario email valido trovato nelle liste selezionate.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,64 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\MailingList;
|
use App\Models\MailingList;
|
||||||
|
use App\Models\Tag;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class MailingListController extends Controller
|
class MailingListController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('mailing');
|
$this->authorizeRead('mailing');
|
||||||
$mailingLists = MailingList::with(['user', 'contatti'])
|
$query = MailingList::with(['user', 'contatti', 'tags']);
|
||||||
|
|
||||||
|
if ($request->filled('tag')) {
|
||||||
|
$tagSlugs = (array) $request->input('tag');
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailingLists = $query->orderBy('nome')->get();
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
|
$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')
|
->orderBy('nome')
|
||||||
->get();
|
->get();
|
||||||
return view('mailing-liste.index', compact('mailingLists'));
|
|
||||||
|
$allColumnDefs = [
|
||||||
|
['key' => 'nome', 'label' => 'Nome'],
|
||||||
|
['key' => 'descrizione', 'label' => 'Descrizione'],
|
||||||
|
['key' => 'contatti_count', 'label' => 'Contatti'],
|
||||||
|
['key' => 'tag', 'label' => 'Tag'],
|
||||||
|
['key' => 'attiva', 'label' => 'Stato'],
|
||||||
|
['key' => 'created_at', 'label' => 'Creata il'],
|
||||||
|
];
|
||||||
|
$defaultVisible = ['nome', 'descrizione', 'contatti_count', 'tag', 'attiva', 'created_at'];
|
||||||
|
$visibleColumns = $vista && !empty($vista->colonne_visibili)
|
||||||
|
? $vista->colonne_visibili
|
||||||
|
: $defaultVisible;
|
||||||
|
|
||||||
|
$tableColumns = array_map(fn($col) => [
|
||||||
|
'key' => $col['key'],
|
||||||
|
'label' => $col['label'],
|
||||||
|
'visible' => in_array($col['key'], $visibleColumns),
|
||||||
|
], $allColumnDefs);
|
||||||
|
$entityType = 'mailing-liste';
|
||||||
|
$columnWidths = $vista && $vista->colonne_larghezze ? $vista->colonne_larghezze : [];
|
||||||
|
|
||||||
|
return view('mailing-liste.index', compact('mailingLists', 'allTags', 'vista', 'visibleColumns', 'tableColumns', 'entityType', 'columnWidths', 'userVistas'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('mailing');
|
$this->authorizeWrite('mailing');
|
||||||
return view('mailing-liste.create');
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
return view('mailing-liste.create', compact('tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -30,6 +71,8 @@ class MailingListController extends Controller
|
|||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'attiva' => 'boolean',
|
'attiva' => 'boolean',
|
||||||
'contatti_json' => 'nullable|string',
|
'contatti_json' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data['user_id'] = auth()->id();
|
$data['user_id'] = auth()->id();
|
||||||
@@ -42,11 +85,15 @@ class MailingListController extends Controller
|
|||||||
'user_id' => $data['user_id'],
|
'user_id' => $data['user_id'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$lista->tags()->sync($request->tags);
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($data['contatti_json'])) {
|
if (!empty($data['contatti_json'])) {
|
||||||
$contatti = json_decode($data['contatti_json'], true);
|
$contatti = json_decode($data['contatti_json'], true);
|
||||||
if (is_array($contatti)) {
|
if (is_array($contatti)) {
|
||||||
foreach ($contatti as $contatto) {
|
foreach ($contatti as $contatto) {
|
||||||
if (!empty($contatto['individuo_id']) && !empty($contatto['email'])) {
|
if (!empty($contatto['individuo_id'])) {
|
||||||
$lista->contatti()->create([
|
$lista->contatti()->create([
|
||||||
'individuo_id' => $contatto['individuo_id'],
|
'individuo_id' => $contatto['individuo_id'],
|
||||||
'opt_in' => true,
|
'opt_in' => true,
|
||||||
@@ -63,15 +110,17 @@ class MailingListController extends Controller
|
|||||||
public function show($mailing_liste)
|
public function show($mailing_liste)
|
||||||
{
|
{
|
||||||
$this->authorizeRead('mailing');
|
$this->authorizeRead('mailing');
|
||||||
$mailingList = MailingList::with(['contatti.individuo.contatti'])->findOrFail($mailing_liste);
|
$mailingList = MailingList::with(['contatti.individuo.contatti', 'tags'])->findOrFail($mailing_liste);
|
||||||
return view('mailing-liste.show', compact('mailingList'));
|
return view('mailing-liste.show', compact('mailingList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit($mailingList)
|
public function edit($mailingList)
|
||||||
{
|
{
|
||||||
$this->authorizeWrite('mailing');
|
$this->authorizeWrite('mailing');
|
||||||
$mailingList = MailingList::with('contatti.individuo.contatti')->findOrFail($mailingList);
|
$mailingList = MailingList::with(['contatti.individuo.contatti', 'tags'])->findOrFail($mailingList);
|
||||||
return view('mailing-liste.edit', compact('mailingList'));
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = $mailingList->tags->pluck('id')->toArray();
|
||||||
|
return view('mailing-liste.edit', compact('mailingList', 'tags', 'selectedTags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, $mailingList)
|
public function update(Request $request, $mailingList)
|
||||||
@@ -84,6 +133,8 @@ class MailingListController extends Controller
|
|||||||
'descrizione' => 'nullable|string',
|
'descrizione' => 'nullable|string',
|
||||||
'attiva' => 'boolean',
|
'attiva' => 'boolean',
|
||||||
'contatti_json' => 'nullable|string',
|
'contatti_json' => 'nullable|string',
|
||||||
|
'tags' => 'nullable|array',
|
||||||
|
'tags.*' => 'exists:tags,id',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$data['attiva'] = $data['attiva'] ?? false;
|
$data['attiva'] = $data['attiva'] ?? false;
|
||||||
@@ -94,6 +145,12 @@ class MailingListController extends Controller
|
|||||||
'attiva' => $data['attiva'],
|
'attiva' => $data['attiva'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($request->has('tags')) {
|
||||||
|
$mailingList->tags()->sync($request->tags);
|
||||||
|
} else {
|
||||||
|
$mailingList->tags()->detach();
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($data['contatti_json'])) {
|
if (!empty($data['contatti_json'])) {
|
||||||
$newContatti = json_decode($data['contatti_json'], true);
|
$newContatti = json_decode($data['contatti_json'], true);
|
||||||
if (is_array($newContatti)) {
|
if (is_array($newContatti)) {
|
||||||
@@ -107,7 +164,7 @@ class MailingListController extends Controller
|
|||||||
|
|
||||||
$toAdd = array_diff($newIds, $existingIds);
|
$toAdd = array_diff($newIds, $existingIds);
|
||||||
foreach ($newContatti as $contatto) {
|
foreach ($newContatti as $contatto) {
|
||||||
if (in_array($contatto['individuo_id'], $toAdd) && !empty($contatto['email'])) {
|
if (in_array($contatto['individuo_id'], $toAdd)) {
|
||||||
$mailingList->contatti()->create([
|
$mailingList->contatti()->create([
|
||||||
'individuo_id' => $contatto['individuo_id'],
|
'individuo_id' => $contatto['individuo_id'],
|
||||||
'opt_in' => true,
|
'opt_in' => true,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\Evento;
|
|||||||
use App\Models\Documento;
|
use App\Models\Documento;
|
||||||
use App\Models\MailingList;
|
use App\Models\MailingList;
|
||||||
use App\Models\Contatto;
|
use App\Models\Contatto;
|
||||||
|
use App\Models\Tag;
|
||||||
use App\Models\TipologiaDocumento;
|
use App\Models\TipologiaDocumento;
|
||||||
use App\Models\ReportCustom;
|
use App\Models\ReportCustom;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -47,12 +48,15 @@ class ReportController extends Controller
|
|||||||
$columnOptions[$type] = ReportColumnRegistry::getColumns($type);
|
$columnOptions[$type] = ReportColumnRegistry::getColumns($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
return view('report.index', compact(
|
return view('report.index', compact(
|
||||||
'stats',
|
'stats',
|
||||||
'prebuiltReports',
|
'prebuiltReports',
|
||||||
'customReports',
|
'customReports',
|
||||||
'tipologieDocumento',
|
'tipologieDocumento',
|
||||||
'columnOptions'
|
'columnOptions',
|
||||||
|
'allTags'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +140,14 @@ class ReportController extends Controller
|
|||||||
$config['search_columns'] = $request->input('search_columns');
|
$config['search_columns'] = $request->input('search_columns');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->has('tag_filter')) {
|
||||||
|
$config['tag_filter'] = $request->input('tag_filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('tag_filter_mode')) {
|
||||||
|
$config['tag_filter_mode'] = $request->input('tag_filter_mode');
|
||||||
|
}
|
||||||
|
|
||||||
ReportCustom::create([
|
ReportCustom::create([
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'nome' => $data['nome'],
|
'nome' => $data['nome'],
|
||||||
@@ -252,6 +264,9 @@ class ReportController extends Controller
|
|||||||
'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter),
|
'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter),
|
||||||
'contatti_completo' => $this->reportContattiCompleto(),
|
'contatti_completo' => $this->reportContattiCompleto(),
|
||||||
'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter),
|
'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter),
|
||||||
|
'tag_statistiche' => $this->reportTagStatistiche(),
|
||||||
|
'individui_per_tag' => $this->reportIndividuiPerTag(),
|
||||||
|
'documenti_per_tag' => $this->reportDocumentiPerTag(),
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -403,6 +418,30 @@ class ReportController extends Controller
|
|||||||
'colore' => 'teal',
|
'colore' => 'teal',
|
||||||
'has_tipologia_filter' => false,
|
'has_tipologia_filter' => false,
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'id' => 'tag_statistiche',
|
||||||
|
'nome' => 'Statistiche Tag',
|
||||||
|
'descrizione' => 'Distribuzione degli elementi per tag (individui, gruppi, eventi, documenti, mailing list)',
|
||||||
|
'icona' => 'fa-tags',
|
||||||
|
'colore' => 'indigo',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'individui_per_tag',
|
||||||
|
'nome' => 'Individui per Tag',
|
||||||
|
'descrizione' => 'Elenco completo degli individui raggruppati per tag assegnato',
|
||||||
|
'icona' => 'fa-user-tag',
|
||||||
|
'colore' => 'info',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => 'documenti_per_tag',
|
||||||
|
'nome' => 'Documenti per Tag',
|
||||||
|
'descrizione' => 'Elenco completo dei documenti raggruppati per tag assegnato',
|
||||||
|
'icona' => 'fa-tags',
|
||||||
|
'colore' => 'secondary',
|
||||||
|
'has_tipologia_filter' => false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -927,6 +966,102 @@ class ReportController extends Controller
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function reportTagStatistiche(): array
|
||||||
|
{
|
||||||
|
$tags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
|
$rows = $tags->map(function ($tag) {
|
||||||
|
return [
|
||||||
|
'name' => $tag->name,
|
||||||
|
'color' => $tag->color ?? '#6c757d',
|
||||||
|
'individui' => $tag->individui()->count(),
|
||||||
|
'gruppi' => $tag->gruppi()->count(),
|
||||||
|
'eventi' => $tag->eventi()->count(),
|
||||||
|
'documenti' => $tag->documenti()->count(),
|
||||||
|
'mailing' => $tag->mailingLists()->count(),
|
||||||
|
'totale' => $tag->count,
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$totalIndividui = array_sum(array_column($rows, 'individui'));
|
||||||
|
$totalGruppi = array_sum(array_column($rows, 'gruppi'));
|
||||||
|
$totalEventi = array_sum(array_column($rows, 'eventi'));
|
||||||
|
$totalDocumenti = array_sum(array_column($rows, 'documenti'));
|
||||||
|
$totalMailing = array_sum(array_column($rows, 'mailing'));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Statistiche Tag',
|
||||||
|
'headers' => ['Tag', 'Individui', 'Gruppi', 'Eventi', 'Documenti', 'Mailing List', 'Totale Elementi'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['name'], $r['individui'], $r['gruppi'], $r['eventi'], $r['documenti'], $r['mailing'], $r['totale']], $rows),
|
||||||
|
'summary' => 'Tag: ' . count($tags) . ' | Individui: ' . $totalIndividui . ' | Gruppi: ' . $totalGruppi . ' | Eventi: ' . $totalEventi . ' | Documenti: ' . $totalDocumenti . ' | Mailing List: ' . $totalMailing,
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportIndividuiPerTag(): array
|
||||||
|
{
|
||||||
|
$individui = Individuo::with('tags')
|
||||||
|
->whereHas('tags')
|
||||||
|
->orderBy('cognome')
|
||||||
|
->orderBy('nome')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $individui->flatMap(function ($ind) {
|
||||||
|
if ($ind->tags->isEmpty()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
return $ind->tags->map(function ($tag) use ($ind) {
|
||||||
|
return [
|
||||||
|
'tag' => $tag->name,
|
||||||
|
'tag_color' => $tag->color ?? '#6c757d',
|
||||||
|
'codice' => $ind->codice_id,
|
||||||
|
'cognome' => $ind->cognome,
|
||||||
|
'nome' => $ind->nome,
|
||||||
|
'email' => $ind->email_primaria ?? '-',
|
||||||
|
];
|
||||||
|
});
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Individui per Tag',
|
||||||
|
'headers' => ['Tag', 'Codice', 'Cognome', 'Nome', 'Email'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['tag'], $r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows),
|
||||||
|
'summary' => 'Totale righe (individuo × tag): ' . count($rows),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function reportDocumentiPerTag(): array
|
||||||
|
{
|
||||||
|
$documenti = Documento::with('tags')
|
||||||
|
->whereHas('tags')
|
||||||
|
->orderBy('nome_file')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$rows = $documenti->flatMap(function ($doc) {
|
||||||
|
if ($doc->tags->isEmpty()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
return $doc->tags->map(function ($tag) use ($doc) {
|
||||||
|
return [
|
||||||
|
'tag' => $tag->name,
|
||||||
|
'tag_color' => $tag->color ?? '#6c757d',
|
||||||
|
'nome_file' => $doc->nome_file,
|
||||||
|
'tipologia' => ucfirst((string) $doc->tipologia),
|
||||||
|
'data' => $doc->created_at->format('d/m/Y'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => 'Documenti per Tag',
|
||||||
|
'headers' => ['Tag', 'Nome File', 'Tipologia', 'Data Caricamento'],
|
||||||
|
'rows' => array_map(fn($r) => [$r['tag'], $r['nome_file'], $r['tipologia'], $r['data']], $rows),
|
||||||
|
'summary' => 'Totale righe (documento × tag): ' . count($rows),
|
||||||
|
'detail_rows' => $rows,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
protected function runCustomReport(ReportCustom $report): array
|
protected function runCustomReport(ReportCustom $report): array
|
||||||
{
|
{
|
||||||
$tipo = $report->tipo_report;
|
$tipo = $report->tipo_report;
|
||||||
@@ -954,11 +1089,32 @@ class ReportController extends Controller
|
|||||||
$columns = $config['columns'] ?? [];
|
$columns = $config['columns'] ?? [];
|
||||||
$hasRelations = false;
|
$hasRelations = false;
|
||||||
|
|
||||||
|
// Always load tags if tags column or tag filter is present
|
||||||
|
$needsTags = in_array('tags.nome', $columns) || !empty($config['tag_filter']);
|
||||||
|
if ($needsTags) {
|
||||||
|
$query->with('tags');
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($columns as $col) {
|
foreach ($columns as $col) {
|
||||||
if (str_contains($col, '.')) {
|
if (str_contains($col, '.')) {
|
||||||
$hasRelations = true;
|
$hasRelations = true;
|
||||||
$relation = explode('.', $col)[0];
|
$relation = explode('.', $col)[0];
|
||||||
$query->with($relation);
|
if ($relation !== 'tags') {
|
||||||
|
$query->with($relation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply tag filter
|
||||||
|
if (!empty($config['tag_filter'])) {
|
||||||
|
$tagSlugs = Tag::whereIn('id', (array) $config['tag_filter'])->pluck('slug')->toArray();
|
||||||
|
if (!empty($tagSlugs)) {
|
||||||
|
$mode = $config['tag_filter_mode'] ?? 'any';
|
||||||
|
if ($mode === 'all') {
|
||||||
|
$query->withAllTags($tagSlugs);
|
||||||
|
} else {
|
||||||
|
$query->withAnyTags($tagSlugs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use App\Models\Individuo;
|
||||||
|
use App\Models\Gruppo;
|
||||||
|
use App\Models\Evento;
|
||||||
|
use App\Models\Documento;
|
||||||
|
use App\Models\MailingList;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class RicercaController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeRead('individui');
|
||||||
|
|
||||||
|
$allTags = Tag::orderBy('name')->get();
|
||||||
|
|
||||||
|
$tagSlugs = (array) $request->input('tag', []);
|
||||||
|
|
||||||
|
if (empty($tagSlugs)) {
|
||||||
|
return view('ricerca.index', compact('allTags'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagSlugs = array_filter($tagSlugs);
|
||||||
|
$selectedTags = Tag::whereIn('slug', $tagSlugs)->get();
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
$totals = [];
|
||||||
|
|
||||||
|
if (count($tagSlugs) === 1) {
|
||||||
|
$tag = $selectedTags->first();
|
||||||
|
$results['individui'] = $tag->individui()->orderBy('cognome')->orderBy('nome')->get();
|
||||||
|
$results['gruppi'] = $tag->gruppi()->orderBy('nome')->get();
|
||||||
|
$results['eventi'] = $tag->eventi()->orderBy('data_specifica')->orderBy('nome_evento')->get();
|
||||||
|
$results['documenti'] = $tag->documenti()->orderBy('nome_file')->get();
|
||||||
|
$results['mailingLists'] = $tag->mailingLists()->orderBy('nome')->get();
|
||||||
|
} else {
|
||||||
|
$results['individui'] = Individuo::withAllTags($tagSlugs)->orderBy('cognome')->orderBy('nome')->get();
|
||||||
|
$results['gruppi'] = Gruppo::withAllTags($tagSlugs)->orderBy('nome')->get();
|
||||||
|
$results['eventi'] = Evento::withAllTags($tagSlugs)->orderBy('data_specifica')->orderBy('nome_evento')->get();
|
||||||
|
$results['documenti'] = Documento::withAllTags($tagSlugs)->orderBy('nome_file')->get();
|
||||||
|
$results['mailingLists'] = MailingList::withAllTags($tagSlugs)->orderBy('nome')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$totals = [
|
||||||
|
'individui' => $results['individui']->count(),
|
||||||
|
'gruppi' => $results['gruppi']->count(),
|
||||||
|
'eventi' => $results['eventi']->count(),
|
||||||
|
'documenti' => $results['documenti']->count(),
|
||||||
|
'mailingLists' => $results['mailingLists']->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('ricerca.index', compact('selectedTags', 'tagSlugs', 'results', 'totals', 'allTags'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,9 +33,11 @@ class VistaReportController extends Controller
|
|||||||
$this->authorizeWrite('viste');
|
$this->authorizeWrite('viste');
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
'tipo' => 'required|in:individui,gruppi,documenti,eventi',
|
'tipo' => 'required|in:individui,gruppi,documenti,eventi,mailing-liste',
|
||||||
'colonne_visibili' => 'nullable|array',
|
'colonne_visibili' => 'nullable|array',
|
||||||
'colonne_ordinamento' => 'nullable|array',
|
'colonne_ordinamento' => 'nullable|array',
|
||||||
|
'colonne_larghezze' => 'nullable|array',
|
||||||
|
'colonne_ordine' => 'nullable|array',
|
||||||
'filtri' => 'nullable|array',
|
'filtri' => 'nullable|array',
|
||||||
'ricerca' => 'nullable|string',
|
'ricerca' => 'nullable|string',
|
||||||
'is_default' => 'nullable|boolean',
|
'is_default' => 'nullable|boolean',
|
||||||
@@ -55,6 +57,8 @@ class VistaReportController extends Controller
|
|||||||
'tipo' => $data['tipo'],
|
'tipo' => $data['tipo'],
|
||||||
'colonne_visibili' => $data['colonne_visibili'] ?? null,
|
'colonne_visibili' => $data['colonne_visibili'] ?? null,
|
||||||
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
|
'colonne_ordinamento' => $data['colonne_ordinamento'] ?? null,
|
||||||
|
'colonne_larghezze' => $data['colonne_larghezze'] ?? null,
|
||||||
|
'colonne_ordine' => $data['colonne_ordine'] ?? null,
|
||||||
'filtri' => $data['filtri'] ?? null,
|
'filtri' => $data['filtri'] ?? null,
|
||||||
'ricerca' => $data['ricerca'] ?? null,
|
'ricerca' => $data['ricerca'] ?? null,
|
||||||
'is_default' => $isDefault,
|
'is_default' => $isDefault,
|
||||||
@@ -78,6 +82,8 @@ class VistaReportController extends Controller
|
|||||||
'nome' => 'required|string|max:255',
|
'nome' => 'required|string|max:255',
|
||||||
'colonne_visibili' => 'nullable|array',
|
'colonne_visibili' => 'nullable|array',
|
||||||
'colonne_ordinamento' => 'nullable|array',
|
'colonne_ordinamento' => 'nullable|array',
|
||||||
|
'colonne_larghezze' => 'nullable|array',
|
||||||
|
'colonne_ordine' => 'nullable|array',
|
||||||
'filtri' => 'nullable|array',
|
'filtri' => 'nullable|array',
|
||||||
'ricerca' => 'nullable|string',
|
'ricerca' => 'nullable|string',
|
||||||
'is_default' => 'nullable|boolean',
|
'is_default' => 'nullable|boolean',
|
||||||
@@ -92,6 +98,10 @@ class VistaReportController extends Controller
|
|||||||
|
|
||||||
$vistaReport->update($data);
|
$vistaReport->update($data);
|
||||||
|
|
||||||
|
if ($request->expectsJson()) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'Vista aggiornata.']);
|
||||||
|
}
|
||||||
|
|
||||||
return back()->with('success', 'Vista aggiornata.');
|
return back()->with('success', 'Vista aggiornata.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
@@ -9,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|||||||
|
|
||||||
class Documento extends Model
|
class Documento extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'documenti';
|
protected $table = 'documenti';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'user_id', 'cartella_id', 'repository_id', 'storage_disk',
|
'tenant_id', 'user_id', 'cartella_id', 'repository_id', 'storage_disk',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
@@ -9,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
|
|
||||||
class Evento extends Model
|
class Evento extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'eventi';
|
protected $table = 'eventi';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|||||||
|
|
||||||
class Gruppo extends Model
|
class Gruppo extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'gruppi';
|
protected $table = 'gruppi';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'parent_id', 'diocesi_id', 'responsabile_ids',
|
'tenant_id', 'parent_id', 'diocesi_id', 'responsabile_ids',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
|
|||||||
|
|
||||||
class Individuo extends Model
|
class Individuo extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
protected $table = 'individui';
|
protected $table = 'individui';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'tenant_id', 'codice_id', 'cognome', 'nome', 'data_nascita',
|
'tenant_id', 'codice_id', 'cognome', 'nome', 'data_nascita',
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use App\Traits\HasTagsLight;
|
||||||
|
|
||||||
class MailingList extends Model
|
class MailingList extends Model
|
||||||
{
|
{
|
||||||
|
use HasTagsLight;
|
||||||
|
|
||||||
protected $table = 'mailing_lists';
|
protected $table = 'mailing_lists';
|
||||||
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva'];
|
protected $fillable = ['tenant_id', 'user_id', 'nome', 'descrizione', 'attiva'];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
|
|
||||||
|
class Tag extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'tags';
|
||||||
|
|
||||||
|
protected $fillable = ['name', 'slug', 'color', 'order_column'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'order_column' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::creating(function (self $tag) {
|
||||||
|
if (empty($tag->slug)) {
|
||||||
|
$tag->slug = str()->slug($tag->name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function individui(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Individuo::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gruppi(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Gruppo::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eventi(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Evento::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documenti(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Documento::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mailingLists(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(MailingList::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCountAttribute(): int
|
||||||
|
{
|
||||||
|
return $this->individui()->count()
|
||||||
|
+ $this->gruppi()->count()
|
||||||
|
+ $this->eventi()->count()
|
||||||
|
+ $this->documenti()->count()
|
||||||
|
+ $this->mailingLists()->count();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,13 +10,16 @@ class VistaReport extends Model
|
|||||||
protected $table = 'viste_report';
|
protected $table = 'viste_report';
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'user_id', 'nome', 'tipo', 'colonne_visibili',
|
'user_id', 'nome', 'tipo', 'colonne_visibili',
|
||||||
'colonne_ordinamento', 'filtri', 'ricerca', 'is_default'
|
'colonne_ordinamento', 'filtri', 'ricerca', 'is_default',
|
||||||
|
'colonne_larghezze', 'colonne_ordine',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'colonne_visibili' => 'array',
|
'colonne_visibili' => 'array',
|
||||||
'colonne_ordinamento' => 'array',
|
'colonne_ordinamento' => 'array',
|
||||||
'filtri' => 'array',
|
'filtri' => 'array',
|
||||||
|
'colonne_larghezze' => 'array',
|
||||||
|
'colonne_ordine' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
|
|
||||||
|
trait HasTagsLight
|
||||||
|
{
|
||||||
|
public function tags(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeWithAllTags($query, array $tags)
|
||||||
|
{
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$query->whereHas('tags', fn ($q) => $q->where('slug', $tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeWithAnyTags($query, array $tags)
|
||||||
|
{
|
||||||
|
$query->whereHas('tags', fn ($q) => $q->whereIn('slug', $tags));
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,10 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
|
if (Schema::hasTable('tenants')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('tenants', function (Blueprint $table) {
|
Schema::create('tenants', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nome');
|
$table->string('nome');
|
||||||
|
|||||||
@@ -8,26 +8,30 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('diocesi', function (Blueprint $table) {
|
if (!Schema::hasTable('diocesi')) {
|
||||||
$table->id();
|
Schema::create('diocesi', function (Blueprint $table) {
|
||||||
$table->string('nome');
|
$table->id();
|
||||||
$table->string('regione')->nullable();
|
$table->string('nome');
|
||||||
$table->timestamps();
|
$table->string('regione')->nullable();
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('comuni', function (Blueprint $table) {
|
if (!Schema::hasTable('comuni')) {
|
||||||
$table->id();
|
Schema::create('comuni', function (Blueprint $table) {
|
||||||
$table->string('nome');
|
$table->id();
|
||||||
$table->string('codice_istat', 10)->nullable();
|
$table->string('nome');
|
||||||
$table->string('cap')->nullable();
|
$table->string('codice_istat', 10)->nullable();
|
||||||
$table->string('sigla_provincia', 2)->nullable();
|
$table->string('cap')->nullable();
|
||||||
$table->string('regione')->nullable();
|
$table->string('sigla_provincia', 2)->nullable();
|
||||||
$table->float('latitudine')->nullable();
|
$table->string('regione')->nullable();
|
||||||
$table->float('longitudine')->nullable();
|
$table->float('latitudine')->nullable();
|
||||||
$table->timestamps();
|
$table->float('longitudine')->nullable();
|
||||||
$table->index('nome');
|
$table->timestamps();
|
||||||
$table->index('sigla_provincia');
|
$table->index('nome');
|
||||||
});
|
$table->index('sigla_provincia');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,27 +8,29 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('individui', function (Blueprint $table) {
|
if (!Schema::hasTable('individui')) {
|
||||||
$table->id();
|
Schema::create('individui', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->string('codice_id', 5)->unique();
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->string('cognome');
|
$table->string('codice_id', 5)->unique();
|
||||||
$table->string('nome');
|
$table->string('cognome');
|
||||||
$table->date('data_nascita')->nullable();
|
$table->string('nome');
|
||||||
$table->string('indirizzo')->nullable();
|
$table->date('data_nascita')->nullable();
|
||||||
$table->string('cap', 10)->nullable();
|
$table->string('indirizzo')->nullable();
|
||||||
$table->string('città')->nullable();
|
$table->string('cap', 10)->nullable();
|
||||||
$table->string('sigla_provincia', 2)->nullable();
|
$table->string('città')->nullable();
|
||||||
$table->enum('genere', ['M', 'F'])->nullable();
|
$table->string('sigla_provincia', 2)->nullable();
|
||||||
$table->enum('tipo_documento', ['carta_identita', 'patente'])->nullable();
|
$table->enum('genere', ['M', 'F'])->nullable();
|
||||||
$table->date('scadenza_documento')->nullable();
|
$table->enum('tipo_documento', ['carta_identita', 'patente'])->nullable();
|
||||||
$table->text('note')->nullable();
|
$table->date('scadenza_documento')->nullable();
|
||||||
$table->string('avatar_path')->nullable();
|
$table->text('note')->nullable();
|
||||||
$table->timestamps();
|
$table->string('avatar_path')->nullable();
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
$table->index('codice_id');
|
$table->index('tenant_id');
|
||||||
$table->index('cognome');
|
$table->index('codice_id');
|
||||||
});
|
$table->index('cognome');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('contatti', function (Blueprint $table) {
|
if (!Schema::hasTable('contatti')) {
|
||||||
$table->id();
|
Schema::create('contatti', function (Blueprint $table) {
|
||||||
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
$table->id();
|
||||||
$table->enum('tipo', ['telefono', 'cellulare', 'email', 'fax', 'web', 'telegram', 'whatsapp', 'altro']);
|
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
||||||
$table->string('valore');
|
$table->enum('tipo', ['telefono', 'cellulare', 'email', 'fax', 'web', 'telegram', 'whatsapp', 'altro']);
|
||||||
$table->string('etichetta')->nullable();
|
$table->string('valore');
|
||||||
$table->boolean('is_primary')->default(false);
|
$table->string('etichetta')->nullable();
|
||||||
$table->timestamps();
|
$table->boolean('is_primary')->default(false);
|
||||||
$table->index('individuo_id');
|
$table->timestamps();
|
||||||
});
|
$table->index('individuo_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,24 +8,26 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('gruppi', function (Blueprint $table) {
|
if (!Schema::hasTable('gruppi')) {
|
||||||
$table->id();
|
Schema::create('gruppi', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->foreignId('parent_id')->nullable()->constrained('gruppi')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->foreignId('diocesi_id')->nullable()->constrained('diocesi')->onDelete('set null');
|
$table->foreignId('parent_id')->nullable()->constrained('gruppi')->onDelete('set null');
|
||||||
$table->foreignId('responsabile_id')->nullable()->constrained('individui')->onDelete('set null');
|
$table->foreignId('diocesi_id')->nullable()->constrained('diocesi')->onDelete('set null');
|
||||||
$table->string('nome');
|
$table->foreignId('responsabile_id')->nullable()->constrained('individui')->onDelete('set null');
|
||||||
$table->text('descrizione')->nullable();
|
$table->string('nome');
|
||||||
$table->string('indirizzo_incontro')->nullable();
|
$table->text('descrizione')->nullable();
|
||||||
$table->string('cap_incontro', 10)->nullable();
|
$table->string('indirizzo_incontro')->nullable();
|
||||||
$table->string('città_incontro')->nullable();
|
$table->string('cap_incontro', 10)->nullable();
|
||||||
$table->string('sigla_provincia_incontro', 2)->nullable();
|
$table->string('città_incontro')->nullable();
|
||||||
$table->string('mappa_posizione')->nullable();
|
$table->string('sigla_provincia_incontro', 2)->nullable();
|
||||||
$table->json('metadata')->nullable();
|
$table->string('mappa_posizione')->nullable();
|
||||||
$table->timestamps();
|
$table->json('metadata')->nullable();
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
$table->index('parent_id');
|
$table->index('tenant_id');
|
||||||
});
|
$table->index('parent_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('gruppo_individuo', function (Blueprint $table) {
|
if (!Schema::hasTable('gruppo_individuo')) {
|
||||||
$table->id();
|
Schema::create('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
$table->id();
|
||||||
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
||||||
$table->string('ruolo_nel_gruppo')->nullable();
|
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
||||||
$table->date('data_adesione')->nullable();
|
$table->string('ruolo_nel_gruppo')->nullable();
|
||||||
$table->timestamps();
|
$table->date('data_adesione')->nullable();
|
||||||
$table->unique(['gruppo_id', 'individuo_id']);
|
$table->timestamps();
|
||||||
});
|
$table->unique(['gruppo_id', 'individuo_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,35 +8,41 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('eventi', function (Blueprint $table) {
|
if (!Schema::hasTable('eventi')) {
|
||||||
$table->id();
|
Schema::create('eventi', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->string('nome_evento');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->string('tipo_evento')->nullable();
|
$table->string('nome_evento');
|
||||||
$table->enum('tipo_recorrenza', ['singolo', 'ricorrente'])->default('singolo');
|
$table->string('tipo_evento')->nullable();
|
||||||
$table->unsignedTinyInteger('giorno_settimana')->nullable();
|
$table->enum('tipo_recorrenza', ['singolo', 'ricorrente'])->default('singolo');
|
||||||
$table->time('ora_inizio')->nullable();
|
$table->unsignedTinyInteger('giorno_settimana')->nullable();
|
||||||
$table->date('data_specifica')->nullable();
|
$table->time('ora_inizio')->nullable();
|
||||||
$table->unsignedInteger('durata_minuti')->nullable();
|
$table->date('data_specifica')->nullable();
|
||||||
$table->text('descrizione')->nullable();
|
$table->unsignedInteger('durata_minuti')->nullable();
|
||||||
$table->text('note')->nullable();
|
$table->text('descrizione')->nullable();
|
||||||
$table->timestamps();
|
$table->text('note')->nullable();
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
});
|
$table->index('tenant_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('eventi_gruppi', function (Blueprint $table) {
|
if (!Schema::hasTable('eventi_gruppi')) {
|
||||||
$table->id();
|
Schema::create('eventi_gruppi', function (Blueprint $table) {
|
||||||
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
$table->id();
|
||||||
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('eventi_responsabili', function (Blueprint $table) {
|
if (!Schema::hasTable('eventi_responsabili')) {
|
||||||
$table->id();
|
Schema::create('eventi_responsabili', function (Blueprint $table) {
|
||||||
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
$table->id();
|
||||||
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,22 +8,24 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('documenti', function (Blueprint $table) {
|
if (!Schema::hasTable('documenti')) {
|
||||||
$table->id();
|
Schema::create('documenti', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->string('nome_file');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->string('file_path');
|
$table->string('nome_file');
|
||||||
$table->string('tipo')->nullable();
|
$table->string('file_path');
|
||||||
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
|
$table->string('tipo')->nullable();
|
||||||
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
|
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
|
||||||
$table->unsignedBigInteger('visibilita_target_id')->nullable();
|
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
|
||||||
$table->string('visibilita_target_type')->nullable();
|
$table->unsignedBigInteger('visibilita_target_id')->nullable();
|
||||||
$table->string('mime_type')->nullable();
|
$table->string('visibilita_target_type')->nullable();
|
||||||
$table->unsignedBigInteger('dimensione')->nullable();
|
$table->string('mime_type')->nullable();
|
||||||
$table->text('note')->nullable();
|
$table->unsignedBigInteger('dimensione')->nullable();
|
||||||
$table->timestamps();
|
$table->text('note')->nullable();
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
});
|
$table->index('tenant_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,43 +8,49 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('mailing_lists', function (Blueprint $table) {
|
if (!Schema::hasTable('mailing_lists')) {
|
||||||
$table->id();
|
Schema::create('mailing_lists', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->string('nome');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->text('descrizione')->nullable();
|
$table->string('nome');
|
||||||
$table->boolean('attiva')->default(true);
|
$table->text('descrizione')->nullable();
|
||||||
$table->timestamps();
|
$table->boolean('attiva')->default(true);
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
});
|
$table->index('tenant_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('mailing_contacts', function (Blueprint $table) {
|
if (!Schema::hasTable('mailing_contacts')) {
|
||||||
$table->id();
|
Schema::create('mailing_contacts', function (Blueprint $table) {
|
||||||
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
|
$table->id();
|
||||||
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
|
||||||
$table->boolean('opt_in')->default(true);
|
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
|
||||||
$table->dateTime('opt_in_data')->nullable();
|
$table->boolean('opt_in')->default(true);
|
||||||
$table->dateTime('opt_out_data')->nullable();
|
$table->dateTime('opt_in_data')->nullable();
|
||||||
$table->timestamps();
|
$table->dateTime('opt_out_data')->nullable();
|
||||||
$table->unique(['mailing_list_id', 'individuo_id']);
|
$table->timestamps();
|
||||||
});
|
$table->unique(['mailing_list_id', 'individuo_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('mailing_messaggi', function (Blueprint $table) {
|
if (!Schema::hasTable('mailing_messaggi')) {
|
||||||
$table->id();
|
Schema::create('mailing_messaggi', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->foreignId('mailing_list_id')->nullable()->constrained('mailing_lists')->onDelete('set null');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->unsignedBigInteger('user_id')->nullable();
|
$table->foreignId('mailing_list_id')->nullable()->constrained('mailing_lists')->onDelete('set null');
|
||||||
$table->string('oggetto');
|
$table->unsignedBigInteger('user_id')->nullable();
|
||||||
$table->text('corpo')->nullable();
|
$table->string('oggetto');
|
||||||
$table->enum('canale', ['email', 'telegram'])->default('email');
|
$table->text('corpo')->nullable();
|
||||||
$table->enum('stato', ['bozza', 'in_coda', 'inviato', 'fallito'])->default('bozza');
|
$table->enum('canale', ['email', 'telegram'])->default('email');
|
||||||
$table->timestamp('inviato_al')->nullable();
|
$table->enum('stato', ['bozza', 'in_coda', 'inviato', 'fallito'])->default('bozza');
|
||||||
$table->integer('totale_destinatari')->default(0);
|
$table->timestamp('inviato_al')->nullable();
|
||||||
$table->integer('invii_ok')->default(0);
|
$table->integer('totale_destinatari')->default(0);
|
||||||
$table->integer('invii_falliti')->default(0);
|
$table->integer('invii_ok')->default(0);
|
||||||
$table->timestamps();
|
$table->integer('invii_falliti')->default(0);
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
});
|
$table->index('tenant_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,18 +8,20 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('notifiche', function (Blueprint $table) {
|
if (!Schema::hasTable('notifiche')) {
|
||||||
$table->id();
|
Schema::create('notifiche', function (Blueprint $table) {
|
||||||
$table->unsignedBigInteger('user_id')->nullable();
|
$table->id();
|
||||||
$table->string('tipo');
|
$table->unsignedBigInteger('user_id')->nullable();
|
||||||
$table->string('titolo');
|
$table->string('tipo');
|
||||||
$table->text('messaggio');
|
$table->string('titolo');
|
||||||
$table->string('link')->nullable();
|
$table->text('messaggio');
|
||||||
$table->boolean('is_read')->default(false);
|
$table->string('link')->nullable();
|
||||||
$table->timestamp('read_at')->nullable();
|
$table->boolean('is_read')->default(false);
|
||||||
$table->timestamps();
|
$table->timestamp('read_at')->nullable();
|
||||||
$table->index('user_id');
|
$table->timestamps();
|
||||||
});
|
$table->index('user_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,34 +8,40 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('users', function (Blueprint $table) {
|
if (!Schema::hasTable('users')) {
|
||||||
$table->id();
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
$table->id();
|
||||||
$table->string('name');
|
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
|
||||||
$table->string('email')->unique();
|
$table->string('name');
|
||||||
$table->timestamp('email_verified_at')->nullable();
|
$table->string('email')->unique();
|
||||||
$table->string('password');
|
$table->timestamp('email_verified_at')->nullable();
|
||||||
$table->boolean('is_admin')->default(false);
|
$table->string('password');
|
||||||
$table->rememberToken();
|
$table->boolean('is_admin')->default(false);
|
||||||
$table->timestamps();
|
$table->rememberToken();
|
||||||
$table->index('tenant_id');
|
$table->timestamps();
|
||||||
$table->index('email');
|
$table->index('tenant_id');
|
||||||
});
|
$table->index('email');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
if (!Schema::hasTable('password_reset_tokens')) {
|
||||||
$table->string('email')->primary();
|
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||||
$table->string('token');
|
$table->string('email')->primary();
|
||||||
$table->timestamp('created_at')->nullable();
|
$table->string('token');
|
||||||
});
|
$table->timestamp('created_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('sessions', function (Blueprint $table) {
|
if (!Schema::hasTable('sessions')) {
|
||||||
$table->string('id')->primary();
|
Schema::create('sessions', function (Blueprint $table) {
|
||||||
$table->foreignId('user_id')->nullable()->index();
|
$table->string('id')->primary();
|
||||||
$table->string('ip_address', 45)->nullable();
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
$table->text('user_agent')->nullable();
|
$table->string('ip_address', 45)->nullable();
|
||||||
$table->longText('payload');
|
$table->text('user_agent')->nullable();
|
||||||
$table->integer('last_activity')->index();
|
$table->longText('payload');
|
||||||
});
|
$table->integer('last_activity')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,50 +8,60 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('cache', function (Blueprint $table) {
|
if (!Schema::hasTable('cache')) {
|
||||||
$table->string('key')->primary();
|
Schema::create('cache', function (Blueprint $table) {
|
||||||
$table->mediumText('value');
|
$table->string('key')->primary();
|
||||||
$table->integer('expiration');
|
$table->mediumText('value');
|
||||||
});
|
$table->integer('expiration');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('cache_locks', function (Blueprint $table) {
|
if (!Schema::hasTable('cache_locks')) {
|
||||||
$table->string('key')->primary();
|
Schema::create('cache_locks', function (Blueprint $table) {
|
||||||
$table->string('owner');
|
$table->string('key')->primary();
|
||||||
$table->integer('expiration');
|
$table->string('owner');
|
||||||
});
|
$table->integer('expiration');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('jobs', function (Blueprint $table) {
|
if (!Schema::hasTable('jobs')) {
|
||||||
$table->id();
|
Schema::create('jobs', function (Blueprint $table) {
|
||||||
$table->string('queue')->index();
|
$table->id();
|
||||||
$table->longText('payload');
|
$table->string('queue')->index();
|
||||||
$table->unsignedTinyInteger('attempts');
|
$table->longText('payload');
|
||||||
$table->unsignedInteger('reserved_at')->nullable();
|
$table->unsignedTinyInteger('attempts');
|
||||||
$table->unsignedInteger('available_at');
|
$table->unsignedInteger('reserved_at')->nullable();
|
||||||
$table->unsignedInteger('created_at');
|
$table->unsignedInteger('available_at');
|
||||||
});
|
$table->unsignedInteger('created_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('job_batches', function (Blueprint $table) {
|
if (!Schema::hasTable('job_batches')) {
|
||||||
$table->string('id')->primary();
|
Schema::create('job_batches', function (Blueprint $table) {
|
||||||
$table->string('name');
|
$table->string('id')->primary();
|
||||||
$table->integer('total_jobs');
|
$table->string('name');
|
||||||
$table->integer('pending_jobs');
|
$table->integer('total_jobs');
|
||||||
$table->integer('failed_jobs');
|
$table->integer('pending_jobs');
|
||||||
$table->longText('failed_job_ids');
|
$table->integer('failed_jobs');
|
||||||
$table->mediumText('options')->nullable();
|
$table->longText('failed_job_ids');
|
||||||
$table->integer('cancelled_at')->nullable();
|
$table->mediumText('options')->nullable();
|
||||||
$table->integer('created_at');
|
$table->integer('cancelled_at')->nullable();
|
||||||
$table->integer('finished_at')->nullable();
|
$table->integer('created_at');
|
||||||
});
|
$table->integer('finished_at')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
if (!Schema::hasTable('failed_jobs')) {
|
||||||
$table->id();
|
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||||
$table->string('uuid')->unique();
|
$table->id();
|
||||||
$table->text('connection');
|
$table->string('uuid')->unique();
|
||||||
$table->text('queue');
|
$table->text('connection');
|
||||||
$table->longText('payload');
|
$table->text('queue');
|
||||||
$table->longText('exception');
|
$table->longText('payload');
|
||||||
$table->timestamp('failed_at');
|
$table->longText('exception');
|
||||||
});
|
$table->timestamp('failed_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('individui', function (Blueprint $table) {
|
if (!Schema::hasColumn('individui', 'numero_documento')) {
|
||||||
$table->string('numero_documento', 50)->nullable()->after('tipo_documento');
|
Schema::table('individui', function (Blueprint $table) {
|
||||||
});
|
$table->string('numero_documento', 50)->nullable()->after('tipo_documento');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
if (!Schema::hasColumn('eventi', 'giorno_mese')) {
|
||||||
$table->string('giorno_mese')->nullable()->after('giorno_settimana');
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->string('mesi_recorrenza')->nullable()->after('giorno_mese');
|
$table->string('giorno_mese')->nullable()->after('giorno_settimana');
|
||||||
$table->string('mese_annuale')->nullable()->after('mesi_recorrenza');
|
$table->string('mesi_recorrenza')->nullable()->after('giorno_mese');
|
||||||
});
|
$table->string('mese_annuale')->nullable()->after('mesi_recorrenza');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
if (!Schema::hasColumn('eventi', 'occorrenza_mese')) {
|
||||||
$table->unsignedTinyInteger('occorrenza_mese')->nullable()->after('giorno_mese');
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
});
|
$table->unsignedTinyInteger('occorrenza_mese')->nullable()->after('giorno_mese');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('eventi_documenti', function (Blueprint $table) {
|
if (!Schema::hasTable('eventi_documenti')) {
|
||||||
$table->id();
|
Schema::create('eventi_documenti', function (Blueprint $table) {
|
||||||
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
$table->id();
|
||||||
$table->foreignId('documento_id')->constrained('documenti')->onDelete('cascade');
|
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->foreignId('documento_id')->constrained('documenti')->onDelete('cascade');
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
if (!Schema::hasColumn('documenti', 'user_id')) {
|
||||||
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
});
|
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,19 +8,21 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('viste_report', function (Blueprint $table) {
|
if (!Schema::hasTable('viste_report')) {
|
||||||
$table->id();
|
Schema::create('viste_report', function (Blueprint $table) {
|
||||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
$table->id();
|
||||||
$table->string('nome');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
$table->string('tipo'); // individui, gruppi, documenti, eventi
|
$table->string('nome');
|
||||||
$table->json('colonne_visibili')->nullable();
|
$table->string('tipo'); // individui, gruppi, documenti, eventi
|
||||||
$table->json('colonne_ordinamento')->nullable(); // [['colonna', 'direzione']]
|
$table->json('colonne_visibili')->nullable();
|
||||||
$table->json('filtri')->nullable(); // [['colonna', 'operatore', 'valore']]
|
$table->json('colonne_ordinamento')->nullable(); // [['colonna', 'direzione']]
|
||||||
$table->string('ricerca')->nullable();
|
$table->json('filtri')->nullable(); // [['colonna', 'operatore', 'valore']]
|
||||||
$table->timestamps();
|
$table->string('ricerca')->nullable();
|
||||||
$table->index('tipo');
|
$table->timestamps();
|
||||||
$table->index('user_id');
|
$table->index('tipo');
|
||||||
});
|
$table->index('user_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('viste_report', function (Blueprint $table) {
|
if (!Schema::hasColumn('viste_report', 'is_default')) {
|
||||||
$table->boolean('is_default')->default(false)->after('nome');
|
Schema::table('viste_report', function (Blueprint $table) {
|
||||||
$table->index('is_default');
|
$table->boolean('is_default')->default(false)->after('nome');
|
||||||
});
|
$table->index('is_default');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('mailing_lists', function (Blueprint $table) {
|
if (!Schema::hasColumn('mailing_lists', 'user_id')) {
|
||||||
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
Schema::table('mailing_lists', function (Blueprint $table) {
|
||||||
$table->index('user_id');
|
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null')->after('tenant_id');
|
||||||
});
|
$table->index('user_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('tipologie_documenti', function (Blueprint $table) {
|
if (!Schema::hasTable('tipologie_documenti')) {
|
||||||
$table->id();
|
Schema::create('tipologie_documenti', function (Blueprint $table) {
|
||||||
$table->string('nome', 50)->unique();
|
$table->id();
|
||||||
$table->string('descrizione', 255)->nullable();
|
$table->string('nome', 50)->unique();
|
||||||
$table->integer('ordine')->default(0);
|
$table->string('descrizione', 255)->nullable();
|
||||||
$table->boolean('attiva')->default(true);
|
$table->integer('ordine')->default(0);
|
||||||
$table->timestamps();
|
$table->boolean('attiva')->default(true);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$defaultTipologie = ['avatar', 'galleria', 'documento', 'statuto', 'altro'];
|
$defaultTipologie = ['avatar', 'galleria', 'documento', 'statuto', 'altro'];
|
||||||
foreach ($defaultTipologie as $i => $nome) {
|
foreach ($defaultTipologie as $i => $nome) {
|
||||||
|
|||||||
@@ -8,32 +8,38 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
if (!Schema::hasColumn('users', 'permissions')) {
|
||||||
$table->json('permissions')->nullable()->after('is_admin');
|
Schema::table('users', function (Blueprint $table) {
|
||||||
});
|
$table->json('permissions')->nullable()->after('is_admin');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create('activity_logs', function (Blueprint $table) {
|
if (!Schema::hasTable('activity_logs')) {
|
||||||
$table->id();
|
Schema::create('activity_logs', function (Blueprint $table) {
|
||||||
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
$table->id();
|
||||||
$table->string('action');
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||||
$table->string('module');
|
$table->string('action');
|
||||||
$table->string('description')->nullable();
|
$table->string('module');
|
||||||
$table->json('details')->nullable();
|
$table->string('description')->nullable();
|
||||||
$table->string('ip_address', 45)->nullable();
|
$table->json('details')->nullable();
|
||||||
$table->timestamp('created_at')->useCurrent();
|
$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']);
|
if (!Schema::hasTable('role_presets')) {
|
||||||
$table->index(['created_at']);
|
Schema::create('role_presets', function (Blueprint $table) {
|
||||||
$table->index(['action']);
|
$table->id();
|
||||||
});
|
$table->string('name', 50)->unique();
|
||||||
|
$table->string('description')->nullable();
|
||||||
Schema::create('role_presets', function (Blueprint $table) {
|
$table->json('permissions');
|
||||||
$table->id();
|
$table->timestamps();
|
||||||
$table->string('name', 50)->unique();
|
});
|
||||||
$table->string('description')->nullable();
|
}
|
||||||
$table->json('permissions');
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
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.
|
* 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) {
|
if (!Schema::hasTable($tableNames['permissions'])) {
|
||||||
$table->id(); // permission id
|
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||||
$table->string('name');
|
$table->id(); // permission id
|
||||||
$table->string('guard_name');
|
$table->string('name');
|
||||||
$table->timestamps();
|
$table->string('guard_name');
|
||||||
|
$table->timestamps();
|
||||||
$table->unique(['name', 'guard_name']);
|
|
||||||
});
|
$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.
|
* 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) {
|
if (!Schema::hasTable($tableNames['roles'])) {
|
||||||
$table->id(); // role id
|
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||||
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
$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->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||||
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||||
}
|
}
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->string('guard_name');
|
$table->string('guard_name');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
if ($teams || config('permission.testing')) {
|
if ($teams || config('permission.testing')) {
|
||||||
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||||
} else {
|
} else {
|
||||||
$table->unique(['name', 'guard_name']);
|
$table->unique(['name', 'guard_name']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
if (!Schema::hasTable($tableNames['model_has_permissions'])) {
|
||||||
$table->unsignedBigInteger($pivotPermission);
|
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->string('model_type');
|
||||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
$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)
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
->references('id') // permission id
|
->references('id') // permission id
|
||||||
->on($tableNames['permissions'])
|
->on($tableNames['permissions'])
|
||||||
->cascadeOnDelete();
|
->cascadeOnDelete();
|
||||||
if ($teams) {
|
if ($teams) {
|
||||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
$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'],
|
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
'model_has_permissions_permission_model_type_primary');
|
'model_has_permissions_permission_model_type_primary');
|
||||||
} else {
|
} else {
|
||||||
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
'model_has_permissions_permission_model_type_primary');
|
'model_has_permissions_permission_model_type_primary');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
if (!Schema::hasTable($tableNames['model_has_roles'])) {
|
||||||
$table->unsignedBigInteger($pivotRole);
|
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->string('model_type');
|
||||||
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
$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)
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
->references('id') // role id
|
->references('id') // role id
|
||||||
->on($tableNames['roles'])
|
->on($tableNames['roles'])
|
||||||
->cascadeOnDelete();
|
->cascadeOnDelete();
|
||||||
if ($teams) {
|
if ($teams) {
|
||||||
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
$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'],
|
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
'model_has_roles_role_model_type_primary');
|
'model_has_roles_role_model_type_primary');
|
||||||
} else {
|
} else {
|
||||||
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
'model_has_roles_role_model_type_primary');
|
'model_has_roles_role_model_type_primary');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
if (!Schema::hasTable($tableNames['role_has_permissions'])) {
|
||||||
$table->unsignedBigInteger($pivotPermission);
|
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||||
$table->unsignedBigInteger($pivotRole);
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
$table->foreign($pivotPermission)
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
->references('id') // permission id
|
->references('id') // permission id
|
||||||
->on($tableNames['permissions'])
|
->on($tableNames['permissions'])
|
||||||
->cascadeOnDelete();
|
->cascadeOnDelete();
|
||||||
|
|
||||||
$table->foreign($pivotRole)
|
$table->foreign($pivotRole)
|
||||||
->references('id') // role id
|
->references('id') // role id
|
||||||
->on($tableNames['roles'])
|
->on($tableNames['roles'])
|
||||||
->cascadeOnDelete();
|
->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')
|
app('cache')
|
||||||
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||||
|
|||||||
@@ -8,26 +8,28 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('email_settings', function (Blueprint $table) {
|
if (!Schema::hasTable('email_settings')) {
|
||||||
$table->id();
|
Schema::create('email_settings', function (Blueprint $table) {
|
||||||
$table->string('imap_host')->nullable();
|
$table->id();
|
||||||
$table->integer('imap_port')->nullable()->default(993);
|
$table->string('imap_host')->nullable();
|
||||||
$table->string('imap_encryption')->nullable()->default('ssl');
|
$table->integer('imap_port')->nullable()->default(993);
|
||||||
$table->string('imap_username')->nullable();
|
$table->string('imap_encryption')->nullable()->default('ssl');
|
||||||
$table->string('imap_password')->nullable();
|
$table->string('imap_username')->nullable();
|
||||||
$table->string('smtp_host')->nullable();
|
$table->string('imap_password')->nullable();
|
||||||
$table->integer('smtp_port')->nullable()->default(587);
|
$table->string('smtp_host')->nullable();
|
||||||
$table->string('smtp_encryption')->nullable()->default('tls');
|
$table->integer('smtp_port')->nullable()->default(587);
|
||||||
$table->string('smtp_username')->nullable();
|
$table->string('smtp_encryption')->nullable()->default('tls');
|
||||||
$table->string('smtp_password')->nullable();
|
$table->string('smtp_username')->nullable();
|
||||||
$table->string('email_address')->nullable();
|
$table->string('smtp_password')->nullable();
|
||||||
$table->string('email_name')->nullable();
|
$table->string('email_address')->nullable();
|
||||||
$table->string('reply_to')->nullable();
|
$table->string('email_name')->nullable();
|
||||||
$table->integer('sync_interval_minutes')->default(5);
|
$table->string('reply_to')->nullable();
|
||||||
$table->timestamp('last_sync_at')->nullable();
|
$table->integer('sync_interval_minutes')->default(5);
|
||||||
$table->boolean('is_active')->default(false);
|
$table->timestamp('last_sync_at')->nullable();
|
||||||
$table->timestamps();
|
$table->boolean('is_active')->default(false);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,17 +8,19 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('email_folders', function (Blueprint $table) {
|
if (!Schema::hasTable('email_folders')) {
|
||||||
$table->id();
|
Schema::create('email_folders', function (Blueprint $table) {
|
||||||
$table->string('name');
|
$table->id();
|
||||||
$table->string('imap_folder_name')->nullable();
|
$table->string('name');
|
||||||
$table->string('type')->default('custom');
|
$table->string('imap_folder_name')->nullable();
|
||||||
$table->string('icon')->default('fa-folder');
|
$table->string('type')->default('custom');
|
||||||
$table->string('color')->nullable();
|
$table->string('icon')->default('fa-folder');
|
||||||
$table->integer('sort_order')->default(0);
|
$table->string('color')->nullable();
|
||||||
$table->boolean('is_system')->default(false);
|
$table->integer('sort_order')->default(0);
|
||||||
$table->timestamps();
|
$table->boolean('is_system')->default(false);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,35 +8,37 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('email_messages', function (Blueprint $table) {
|
if (!Schema::hasTable('email_messages')) {
|
||||||
$table->id();
|
Schema::create('email_messages', function (Blueprint $table) {
|
||||||
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
|
$table->id();
|
||||||
$table->string('message_id')->nullable()->index();
|
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
|
||||||
$table->string('subject')->nullable();
|
$table->string('message_id')->nullable()->index();
|
||||||
$table->string('from_name')->nullable();
|
$table->string('subject')->nullable();
|
||||||
$table->string('from_email')->nullable();
|
$table->string('from_name')->nullable();
|
||||||
$table->string('to_name')->nullable();
|
$table->string('from_email')->nullable();
|
||||||
$table->string('to_email')->nullable();
|
$table->string('to_name')->nullable();
|
||||||
$table->text('cc')->nullable();
|
$table->string('to_email')->nullable();
|
||||||
$table->text('bcc')->nullable();
|
$table->text('cc')->nullable();
|
||||||
$table->longText('body_text')->nullable();
|
$table->text('bcc')->nullable();
|
||||||
$table->longText('body_html')->nullable();
|
$table->longText('body_text')->nullable();
|
||||||
$table->boolean('is_read')->default(false);
|
$table->longText('body_html')->nullable();
|
||||||
$table->boolean('is_starred')->default(false);
|
$table->boolean('is_read')->default(false);
|
||||||
$table->boolean('is_important')->default(false);
|
$table->boolean('is_starred')->default(false);
|
||||||
$table->boolean('is_sent')->default(false);
|
$table->boolean('is_important')->default(false);
|
||||||
$table->boolean('is_draft')->default(false);
|
$table->boolean('is_sent')->default(false);
|
||||||
$table->boolean('is_trash')->default(false);
|
$table->boolean('is_draft')->default(false);
|
||||||
$table->timestamp('received_at')->nullable();
|
$table->boolean('is_trash')->default(false);
|
||||||
$table->timestamp('sent_at')->nullable();
|
$table->timestamp('received_at')->nullable();
|
||||||
$table->unsignedBigInteger('imap_uid')->nullable();
|
$table->timestamp('sent_at')->nullable();
|
||||||
$table->timestamps();
|
$table->unsignedBigInteger('imap_uid')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
$table->index('is_read');
|
|
||||||
$table->index('is_starred');
|
$table->index('is_read');
|
||||||
$table->index('is_sent');
|
$table->index('is_starred');
|
||||||
$table->index('received_at');
|
$table->index('is_sent');
|
||||||
});
|
$table->index('received_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,17 +8,19 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('email_attachments', function (Blueprint $table) {
|
if (!Schema::hasTable('email_attachments')) {
|
||||||
$table->id();
|
Schema::create('email_attachments', function (Blueprint $table) {
|
||||||
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
|
$table->id();
|
||||||
$table->string('filename');
|
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
|
||||||
$table->string('mime_type')->nullable();
|
$table->string('filename');
|
||||||
$table->bigInteger('size')->default(0);
|
$table->string('mime_type')->nullable();
|
||||||
$table->string('file_path');
|
$table->bigInteger('size')->default(0);
|
||||||
$table->boolean('is_saved_to_documenti')->default(false);
|
$table->string('file_path');
|
||||||
$table->foreignId('documenti_id')->nullable()->constrained('documenti')->onDelete('set null');
|
$table->boolean('is_saved_to_documenti')->default(false);
|
||||||
$table->timestamps();
|
$table->foreignId('documenti_id')->nullable()->constrained('documenti')->onDelete('set null');
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('ruoli', function (Blueprint $table) {
|
if (!Schema::hasTable('ruoli')) {
|
||||||
$table->id();
|
Schema::create('ruoli', function (Blueprint $table) {
|
||||||
$table->string('nome', 50)->unique();
|
$table->id();
|
||||||
$table->string('descrizione', 255)->nullable();
|
$table->string('nome', 50)->unique();
|
||||||
$table->integer('ordine')->default(0);
|
$table->string('descrizione', 255)->nullable();
|
||||||
$table->boolean('attiva')->default(true);
|
$table->integer('ordine')->default(0);
|
||||||
$table->timestamps();
|
$table->boolean('attiva')->default(true);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$defaultRuoli = [
|
$defaultRuoli = [
|
||||||
['nome' => 'Membro', 'descrizione' => 'Membro del gruppo'],
|
['nome' => 'Membro', 'descrizione' => 'Membro del gruppo'],
|
||||||
@@ -38,25 +40,27 @@ return new class extends Migration
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_id')) {
|
||||||
$table->unsignedBigInteger('ruolo_id')->nullable()->after('ruolo_nel_gruppo');
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
$table->unsignedBigInteger('ruolo_id')->nullable()->after('ruolo_nel_gruppo');
|
||||||
$table->index('ruolo_id');
|
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
||||||
});
|
$table->index('ruolo_id');
|
||||||
|
});
|
||||||
|
|
||||||
$ruoliMap = DB::table('ruoli')->pluck('id', 'nome')->toArray();
|
$ruoliMap = DB::table('ruoli')->pluck('id', 'nome')->toArray();
|
||||||
|
|
||||||
$pivotRecords = DB::table('gruppo_individuo')
|
$pivotRecords = DB::table('gruppo_individuo')
|
||||||
->whereNotNull('ruolo_nel_gruppo')
|
->whereNotNull('ruolo_nel_gruppo')
|
||||||
->where('ruolo_nel_gruppo', '!=', '')
|
->where('ruolo_nel_gruppo', '!=', '')
|
||||||
->distinct()
|
->distinct()
|
||||||
->pluck('ruolo_nel_gruppo');
|
->pluck('ruolo_nel_gruppo');
|
||||||
|
|
||||||
foreach ($pivotRecords as $ruoloNome) {
|
foreach ($pivotRecords as $ruoloNome) {
|
||||||
if (isset($ruoliMap[$ruoloNome])) {
|
if (isset($ruoliMap[$ruoloNome])) {
|
||||||
DB::table('gruppo_individuo')
|
DB::table('gruppo_individuo')
|
||||||
->where('ruolo_nel_gruppo', $ruoloNome)
|
->where('ruolo_nel_gruppo', $ruoloNome)
|
||||||
->update(['ruolo_id' => $ruoliMap[$ruoloNome]]);
|
->update(['ruolo_id' => $ruoliMap[$ruoloNome]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ return new class extends Migration
|
|||||||
$table->dropColumn('ruolo_id');
|
$table->dropColumn('ruolo_id');
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_ids')) {
|
||||||
$table->json('ruolo_ids')->nullable()->after('individuo_id');
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
});
|
$table->json('ruolo_ids')->nullable()->after('individuo_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
@@ -24,9 +26,11 @@ return new class extends Migration
|
|||||||
$table->dropColumn('ruolo_ids');
|
$table->dropColumn('ruolo_ids');
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_id')) {
|
||||||
$table->unsignedBigInteger('ruolo_id')->nullable()->after('individuo_id');
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
$table->unsignedBigInteger('ruolo_id')->nullable()->after('individuo_id');
|
||||||
});
|
$table->foreign('ruolo_id')->references('id')->on('ruoli')->onDelete('set null');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -13,9 +13,11 @@ return new class extends Migration
|
|||||||
$table->dropColumn('responsabile_id');
|
$table->dropColumn('responsabile_id');
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('gruppi', function (Blueprint $table) {
|
if (!Schema::hasColumn('gruppi', 'responsabile_ids')) {
|
||||||
$table->json('responsabile_ids')->nullable()->after('diocesi_id');
|
Schema::table('gruppi', function (Blueprint $table) {
|
||||||
});
|
$table->json('responsabile_ids')->nullable()->after('diocesi_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
@@ -24,9 +26,11 @@ return new class extends Migration
|
|||||||
$table->dropColumn('responsabile_ids');
|
$table->dropColumn('responsabile_ids');
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('gruppi', function (Blueprint $table) {
|
if (!Schema::hasColumn('gruppi', 'responsabile_id')) {
|
||||||
$table->unsignedBigInteger('responsabile_id')->nullable()->after('diocesi_id');
|
Schema::table('gruppi', function (Blueprint $table) {
|
||||||
$table->foreign('responsabile_id')->references('id')->on('individui')->onDelete('set null');
|
$table->unsignedBigInteger('responsabile_id')->nullable()->after('diocesi_id');
|
||||||
});
|
$table->foreign('responsabile_id')->references('id')->on('individui')->onDelete('set null');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -8,10 +8,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
if (!Schema::hasColumn('eventi', 'luogo_indirizzo')) {
|
||||||
$table->string('luogo_indirizzo', 500)->nullable()->after('note');
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->text('luogo_url_maps')->nullable()->after('luogo_indirizzo');
|
$table->string('luogo_indirizzo', 500)->nullable()->after('note');
|
||||||
});
|
$table->text('luogo_url_maps')->nullable()->after('luogo_indirizzo');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('email_settings', function (Blueprint $table) {
|
if (!Schema::hasColumn('email_settings', 'signature')) {
|
||||||
$table->longText('signature')->nullable()->after('reply_to');
|
Schema::table('email_settings', function (Blueprint $table) {
|
||||||
});
|
$table->longText('signature')->nullable()->after('reply_to');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('email_settings', function (Blueprint $table) {
|
if (!Schema::hasColumn('email_settings', 'signature_enabled')) {
|
||||||
$table->boolean('signature_enabled')->default(true)->after('signature');
|
Schema::table('email_settings', function (Blueprint $table) {
|
||||||
});
|
$table->boolean('signature_enabled')->default(true)->after('signature');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('report_custom', function (Blueprint $table) {
|
if (!Schema::hasTable('report_custom')) {
|
||||||
$table->id();
|
Schema::create('report_custom', function (Blueprint $table) {
|
||||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
$table->id();
|
||||||
$table->string('nome');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
$table->text('descrizione')->nullable();
|
$table->string('nome');
|
||||||
$table->string('tipo_report');
|
$table->text('descrizione')->nullable();
|
||||||
$table->json('config')->nullable();
|
$table->string('tipo_report');
|
||||||
$table->timestamps();
|
$table->json('config')->nullable();
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -10,14 +10,16 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('tipologie_eventi', function (Blueprint $table) {
|
if (!Schema::hasTable('tipologie_eventi')) {
|
||||||
$table->id();
|
Schema::create('tipologie_eventi', function (Blueprint $table) {
|
||||||
$table->string('nome', 50)->unique();
|
$table->id();
|
||||||
$table->string('descrizione', 255)->nullable();
|
$table->string('nome', 50)->unique();
|
||||||
$table->integer('ordine')->default(0);
|
$table->string('descrizione', 255)->nullable();
|
||||||
$table->boolean('attiva')->default(true);
|
$table->integer('ordine')->default(0);
|
||||||
$table->timestamps();
|
$table->boolean('attiva')->default(true);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
DB::table('tipologie_eventi')->insert([
|
DB::table('tipologie_eventi')->insert([
|
||||||
['nome' => 'catechesi', 'descrizione' => 'Catechesi', 'ordine' => 0, 'attiva' => true],
|
['nome' => 'catechesi', 'descrizione' => 'Catechesi', 'ordine' => 0, 'attiva' => true],
|
||||||
|
|||||||
@@ -10,21 +10,23 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('sender_accounts', function (Blueprint $table) {
|
if (!Schema::hasTable('sender_accounts')) {
|
||||||
$table->id();
|
Schema::create('sender_accounts', function (Blueprint $table) {
|
||||||
$table->string('email_address')->unique();
|
$table->id();
|
||||||
$table->string('email_name')->nullable();
|
$table->string('email_address')->unique();
|
||||||
$table->string('smtp_host')->nullable();
|
$table->string('email_name')->nullable();
|
||||||
$table->integer('smtp_port')->default(587);
|
$table->string('smtp_host')->nullable();
|
||||||
$table->string('smtp_encryption')->default('tls');
|
$table->integer('smtp_port')->default(587);
|
||||||
$table->string('smtp_username')->nullable();
|
$table->string('smtp_encryption')->default('tls');
|
||||||
$table->text('smtp_password')->nullable();
|
$table->string('smtp_username')->nullable();
|
||||||
$table->string('reply_to')->nullable();
|
$table->text('smtp_password')->nullable();
|
||||||
$table->string('verify_email')->nullable();
|
$table->string('reply_to')->nullable();
|
||||||
$table->text('note')->nullable();
|
$table->string('verify_email')->nullable();
|
||||||
$table->boolean('is_active')->default(true);
|
$table->text('note')->nullable();
|
||||||
$table->timestamps();
|
$table->boolean('is_active')->default(true);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('mailing_messaggi', function (Blueprint $table) {
|
if (!Schema::hasColumn('mailing_messaggi', 'log_falliti')) {
|
||||||
$table->json('log_falliti')->nullable()->after('invii_falliti');
|
Schema::table('mailing_messaggi', function (Blueprint $table) {
|
||||||
$table->string('mittente_nome')->nullable()->after('log_falliti');
|
$table->json('log_falliti')->nullable()->after('invii_falliti');
|
||||||
$table->string('mittente_email')->nullable()->after('mittente_nome');
|
$table->string('mittente_nome')->nullable()->after('log_falliti');
|
||||||
});
|
$table->string('mittente_email')->nullable()->after('mittente_nome');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('documenti_cartelle', function (Blueprint $table) {
|
if (!Schema::hasTable('documenti_cartelle')) {
|
||||||
$table->id();
|
Schema::create('documenti_cartelle', function (Blueprint $table) {
|
||||||
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
|
$table->id();
|
||||||
$table->string('nome');
|
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
|
||||||
$table->timestamps();
|
$table->string('nome');
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
if (!Schema::hasColumn('documenti', 'cartella_id')) {
|
||||||
$table->foreignId('cartella_id')->nullable()->constrained('documenti_cartelle')->onDelete('set null');
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
});
|
$table->foreignId('cartella_id')->nullable()->constrained('documenti_cartelle')->onDelete('set null');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('storage_repositories', function (Blueprint $table) {
|
if (!Schema::hasTable('storage_repositories')) {
|
||||||
$table->id();
|
Schema::create('storage_repositories', function (Blueprint $table) {
|
||||||
$table->string('nome');
|
$table->id();
|
||||||
$table->string('tipo', 50);
|
$table->string('nome');
|
||||||
$table->text('config');
|
$table->string('tipo', 50);
|
||||||
$table->boolean('is_active')->default(true);
|
$table->text('config');
|
||||||
$table->integer('ordine')->default(0);
|
$table->boolean('is_active')->default(true);
|
||||||
$table->timestamps();
|
$table->integer('ordine')->default(0);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
if (!Schema::hasColumn('documenti', 'repository_id')) {
|
||||||
$table->foreignId('repository_id')
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
->nullable()
|
$table->foreignId('repository_id')
|
||||||
->constrained('storage_repositories')
|
->nullable()
|
||||||
->nullOnDelete()
|
->constrained('storage_repositories')
|
||||||
->after('cartella_id');
|
->nullOnDelete()
|
||||||
});
|
->after('cartella_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('documenti', function (Blueprint $table) {
|
if (!Schema::hasColumn('documenti', 'storage_disk')) {
|
||||||
$table->string('storage_disk', 50)->nullable()->after('repository_id');
|
Schema::table('documenti', function (Blueprint $table) {
|
||||||
});
|
$table->string('storage_disk', 50)->nullable()->after('repository_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -10,21 +10,23 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('calendario_connessioni', function (Blueprint $table) {
|
if (!Schema::hasTable('calendario_connessioni')) {
|
||||||
$table->id();
|
Schema::create('calendario_connessioni', function (Blueprint $table) {
|
||||||
$table->string('nome');
|
$table->id();
|
||||||
$table->string('tipo'); // google_calendar, caldav
|
$table->string('nome');
|
||||||
$table->text('config'); // JSON
|
$table->string('tipo'); // google_calendar, caldav
|
||||||
$table->string('stato')->default('disconnesso'); // connesso, disconnesso, errore
|
$table->text('config'); // JSON
|
||||||
$table->string('sync_direction')->default('bidirectional'); // import, export, bidirectional
|
$table->string('stato')->default('disconnesso'); // connesso, disconnesso, errore
|
||||||
$table->integer('sync_interval_minutes')->default(60);
|
$table->string('sync_direction')->default('bidirectional'); // import, export, bidirectional
|
||||||
$table->timestamp('last_sync_at')->nullable();
|
$table->integer('sync_interval_minutes')->default(60);
|
||||||
$table->timestamp('last_error_at')->nullable();
|
$table->timestamp('last_sync_at')->nullable();
|
||||||
$table->text('last_error_message')->nullable();
|
$table->timestamp('last_error_at')->nullable();
|
||||||
$table->boolean('is_active')->default(true);
|
$table->text('last_error_message')->nullable();
|
||||||
$table->integer('ordine')->default(0);
|
$table->boolean('is_active')->default(true);
|
||||||
$table->timestamps();
|
$table->integer('ordine')->default(0);
|
||||||
});
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('eventi', function (Blueprint $table) {
|
if (!Schema::hasColumn('eventi', 'uid_esterno')) {
|
||||||
$table->string('uid_esterno', 500)->nullable()->after('luogo_url_maps');
|
Schema::table('eventi', function (Blueprint $table) {
|
||||||
$table->index('uid_esterno');
|
$table->string('uid_esterno', 500)->nullable()->after('luogo_url_maps');
|
||||||
});
|
$table->index('uid_esterno');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!Schema::hasColumn('gruppo_individuo', 'ruolo_nel_gruppo')) {
|
||||||
|
Schema::table('gruppo_individuo', function (Blueprint $table) {
|
||||||
|
$table->string('ruolo_nel_gruppo')->nullable()->after('ruolo_ids');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (!Schema::hasTable('tags')) {
|
||||||
|
Schema::create('tags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name', 100);
|
||||||
|
$table->string('slug', 100)->unique();
|
||||||
|
$table->string('color', 7)->nullable();
|
||||||
|
$table->integer('order_column')->default(0);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasTable('taggables')) {
|
||||||
|
Schema::create('taggables', function (Blueprint $table) {
|
||||||
|
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->morphs('taggable');
|
||||||
|
$table->primary(['tag_id', 'taggable_id', 'taggable_type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('taggables');
|
||||||
|
Schema::dropIfExists('tags');
|
||||||
|
}
|
||||||
|
};
|
||||||
+32
@@ -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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -68,7 +68,7 @@ class ComuniSeeder extends Seeder
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($comuni as $c) {
|
foreach ($comuni as $c) {
|
||||||
Comune::create($c);
|
Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,23 +20,27 @@ class DatabaseSeeder extends Seeder
|
|||||||
DockerBaseDataSeeder::class,
|
DockerBaseDataSeeder::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$admin = User::create([
|
$admin = User::firstOrCreate(
|
||||||
'name' => 'Admin',
|
['email' => 'admin@glastree.local'],
|
||||||
'email' => 'admin@glastree.local',
|
[
|
||||||
'password' => bcrypt('password'),
|
'name' => 'Admin',
|
||||||
'is_admin' => true,
|
'password' => bcrypt('password'),
|
||||||
'status' => 'active',
|
'is_admin' => true,
|
||||||
'permissions' => [
|
'status' => 'active',
|
||||||
'individui' => 2,
|
'permissions' => [
|
||||||
'gruppi' => 2,
|
'individui' => 2,
|
||||||
'eventi' => 2,
|
'gruppi' => 2,
|
||||||
'documenti' => 2,
|
'eventi' => 2,
|
||||||
'mailing' => 2,
|
'documenti' => 2,
|
||||||
'viste' => 2,
|
'mailing' => 2,
|
||||||
],
|
'viste' => 2,
|
||||||
]);
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
$admin->role_preset_id = 1;
|
if (!$admin->role_preset_id) {
|
||||||
$admin->save();
|
$admin->role_preset_id = 1;
|
||||||
|
$admin->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ class DiocesiSeeder extends Seeder
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($diocesi as $d) {
|
foreach ($diocesi as $d) {
|
||||||
Diocesi::create($d);
|
Diocesi::firstOrCreate(['nome' => $d['nome']], $d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -103,6 +103,42 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => $selectedTags ?? [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-sm table-borderless">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 100px;"><strong>File:</strong></td>
|
||||||
|
<td>{{ $documento->file_path }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Tipo MIME:</strong></td>
|
||||||
|
<td>{{ $documento->mime_type ?? '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Dimensione:</strong></td>
|
||||||
|
<td>{{ $documento->dimensione ? number_format($documento->dimensione / 1024, 1) . ' KB' : '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Creato:</strong></td>
|
||||||
|
<td>{{ $documento->created_at->format('d/m/Y H:i') }}</td>
|
||||||
|
</tr>
|
||||||
|
@if($documento->user)
|
||||||
|
<tr>
|
||||||
|
<td><strong>Utente:</strong></td>
|
||||||
|
<td>{{ $documento->user->name }}</td>
|
||||||
|
</tr>
|
||||||
|
@endif
|
||||||
|
</table>
|
||||||
|
@if($documento->file_path)
|
||||||
|
<hr>
|
||||||
|
<a href="/documenti/{{ $documento->id }}/download" class="btn btn-primary btn-block">
|
||||||
|
<i class="fas fa-download mr-1"></i> Scarica
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
@php
|
@php
|
||||||
$canWriteDocumenti = Auth::user()->canManage('documenti');
|
$canWriteDocumenti = Auth::user()->canManage('documenti');
|
||||||
$canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
$canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
||||||
|
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
|
||||||
|
$visibleColumnsJson = json_encode($visibleColumns ?? []);
|
||||||
|
$tableColumnsJson = json_encode($tableColumns ?? []);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@section('css')
|
@section('css')
|
||||||
@@ -123,6 +126,8 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials._tag-filter-bar', ['filterTags' => $allTags])
|
||||||
|
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
{{-- Toolbar azioni massive --}}
|
{{-- Toolbar azioni massive --}}
|
||||||
<form id="massForm" method="POST">
|
<form id="massForm" method="POST">
|
||||||
@@ -144,6 +149,9 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
<button type="button" class="btn btn-sm btn-secondary" onclick="$('#massMoveModal').modal('show')">
|
<button type="button" class="btn btn-sm btn-secondary" onclick="$('#massMoveModal').modal('show')">
|
||||||
<i class="fas fa-folder-open mr-1"></i> Sposta selezionati in...
|
<i class="fas fa-folder-open mr-1"></i> Sposta selezionati in...
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-info" onclick="$('#massTagModal').modal('show')">
|
||||||
|
<i class="fas fa-tags mr-1"></i> Tag
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-sm btn-success" onclick="massDownload()">
|
<button type="button" class="btn btn-sm btn-success" onclick="massDownload()">
|
||||||
<i class="fas fa-download mr-1"></i> Scarica
|
<i class="fas fa-download mr-1"></i> Scarica
|
||||||
</button>
|
</button>
|
||||||
@@ -226,6 +234,11 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center mb-1" style="display:flex;flex-wrap:wrap;gap:2px;justify-content:center;">
|
||||||
|
@foreach($documento->tags as $tag)
|
||||||
|
<a href="{{ route('documenti.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff; font-size:0.7rem;">{{ $tag->name }}</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
<div class="small text-muted text-center">
|
<div class="small text-muted text-center">
|
||||||
@if($documento->user)
|
@if($documento->user)
|
||||||
{{ $documento->user->name }}
|
{{ $documento->user->name }}
|
||||||
@@ -283,20 +296,21 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
{{-- View: Lista --}}
|
{{-- View: Lista --}}
|
||||||
<div id="viewList" style="display:none;">
|
<div id="viewList" style="display:none;">
|
||||||
@if($sottoCartelle->count() > 0 || $documenti->count() > 0)
|
@if($sottoCartelle->count() > 0 || $documenti->count() > 0)
|
||||||
<table class="table table-bordered table-hover table-striped mb-0">
|
<table class="table table-bordered table-hover table-striped mb-0" id="documenti-table">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
@if($canDeleteDocumenti)
|
@if($canDeleteDocumenti)
|
||||||
<th style="width: 40px;">
|
<th style="width: 40px;" data-column="select">
|
||||||
<input type="checkbox" id="selectAll" onchange="toggleAll(this)">
|
<input type="checkbox" id="selectAll" onchange="toggleAll(this)">
|
||||||
</th>
|
</th>
|
||||||
@endif
|
@endif
|
||||||
<th>Nome</th>
|
<th data-column="nome">Nome</th>
|
||||||
<th style="width: 100px;">Tipologia</th>
|
<th style="width: 100px;" data-column="tipologia">Tipologia</th>
|
||||||
<th style="width: 90px;">Dimensione</th>
|
<th style="width: 90px;" data-column="dimensione">Dimensione</th>
|
||||||
<th style="width: 120px;">Contesto</th>
|
<th style="width: 120px;" data-column="visibilita">Contesto</th>
|
||||||
<th style="width: 110px;">Data</th>
|
<th style="width: 100px;" data-column="tag">Tag</th>
|
||||||
<th style="width: 130px;">Azioni</th>
|
<th style="width: 110px;" data-column="created_at">Data</th>
|
||||||
|
<th style="width: 130px;" data-column="azioni">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -314,6 +328,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
<td class="small text-muted">-</td>
|
<td class="small text-muted">-</td>
|
||||||
<td class="small text-muted">-</td>
|
<td class="small text-muted">-</td>
|
||||||
<td class="small text-muted">-</td>
|
<td class="small text-muted">-</td>
|
||||||
|
<td class="small text-muted">-</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/documenti?folder_id={{ $cartella->id }}" class="btn btn-xs btn-warning" title="Apri cartella">
|
<a href="/documenti?folder_id={{ $cartella->id }}" class="btn btn-xs btn-warning" title="Apri cartella">
|
||||||
<i class="fas fa-folder-open"></i>
|
<i class="fas fa-folder-open"></i>
|
||||||
@@ -423,6 +438,11 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
<span class="text-muted small">-</span>
|
<span class="text-muted small">-</span>
|
||||||
@endswitch
|
@endswitch
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
@foreach($documento->tags as $tag)
|
||||||
|
<a href="{{ route('documenti.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">{{ $tag->name }}</a>
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
<td class="small">{{ $documento->created_at->format('d/m/Y') }}</td>
|
<td class="small">{{ $documento->created_at->format('d/m/Y') }}</td>
|
||||||
<td>
|
<td>
|
||||||
@if($documento->file_path)
|
@if($documento->file_path)
|
||||||
@@ -498,6 +518,10 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
</div>
|
</div>
|
||||||
</form>{{-- /massForm --}}
|
</form>{{-- /massForm --}}
|
||||||
|
|
||||||
|
@include('partials.table-settings')
|
||||||
|
|
||||||
|
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
|
||||||
|
|
||||||
{{-- MODAL: Nuova Cartella --}}
|
{{-- MODAL: Nuova Cartella --}}
|
||||||
<div class="modal fade" id="newFolderModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="newFolderModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
@@ -834,6 +858,50 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- MODAL: Gestione Tag massiva --}}
|
||||||
|
<div class="modal fade" id="massTagModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-tags mr-2"></i>Gestione Tag</h5>
|
||||||
|
<button type="button" class="close" onclick="$('#massTagModal').modal('hide')">
|
||||||
|
<span>×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form id="massTagForm" method="POST" action="/documenti/mass-tag">
|
||||||
|
@csrf
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="mb-2">Operazione su <strong id="massTagCount">0</strong> documenti selezionati.</p>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="font-weight-bold">Azione</label>
|
||||||
|
<div class="d-flex" style="gap:1.5rem;">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mtModeAssign" value="assign" checked>
|
||||||
|
<label class="form-check-label" for="mtModeAssign">Assegna tag</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mtModeRemove" value="remove">
|
||||||
|
<label class="form-check-label" for="mtModeRemove">Rimuovi tag</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@include('partials._tag-selector', ['label' => 'Seleziona tag'])
|
||||||
|
|
||||||
|
<input type="hidden" name="ids" id="massTagIds" value="">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="$('#massTagModal').modal('hide')">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-tags mr-1"></i> Applica
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{-- MODAL: Sposta documento --}}
|
{{-- MODAL: Sposta documento --}}
|
||||||
<div class="modal fade" id="moveModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="moveModal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
@@ -877,6 +945,7 @@ $canDeleteDocumenti = Auth::user()->canDelete('documenti');
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script src="{{ asset('js/column-manager.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
const individui = @json($individui->map(fn($i) => ['id' => $i->id, 'label' => $i->cognome . ' ' . $i->nome]));
|
const individui = @json($individui->map(fn($i) => ['id' => $i->id, 'label' => $i->cognome . ' ' . $i->nome]));
|
||||||
const gruppi = @json($gruppi->map(fn($g) => ['id' => $g->id, 'label' => $g->nome]));
|
const gruppi = @json($gruppi->map(fn($g) => ['id' => $g->id, 'label' => $g->nome]));
|
||||||
@@ -1218,6 +1287,12 @@ document.getElementById('massMoveModal')?.addEventListener('show.bs.modal', func
|
|||||||
document.getElementById('massMoveCount').textContent = ids.length;
|
document.getElementById('massMoveCount').textContent = ids.length;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('massTagModal')?.addEventListener('show.bs.modal', function() {
|
||||||
|
const ids = getSelectedIds();
|
||||||
|
document.getElementById('massTagIds').value = ids.join(',');
|
||||||
|
document.getElementById('massTagCount').textContent = ids.length;
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('massMoveNewFolderBtn')?.addEventListener('click', function() {
|
document.getElementById('massMoveNewFolderBtn')?.addEventListener('click', function() {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: 'Nuova cartella',
|
title: 'Nuova cartella',
|
||||||
@@ -1874,5 +1949,24 @@ function deleteFolder(id, nome) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const table = document.getElementById('documenti-table');
|
||||||
|
if (table) {
|
||||||
|
window.columnManager = new ColumnManager('documenti-table', {
|
||||||
|
entityType: 'documenti',
|
||||||
|
storageKey: 'cm_documenti',
|
||||||
|
resizable: true,
|
||||||
|
reorderable: false,
|
||||||
|
});
|
||||||
|
initTableSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.printWithCurrentSettings = function () {
|
||||||
|
if (window.columnManager) {
|
||||||
|
window.columnManager.printWithSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -87,10 +87,17 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Allegati</label>
|
<label>Allegati</label>
|
||||||
<div class="border rounded p-3 bg-light">
|
<div class="border rounded p-3 bg-light">
|
||||||
<label class="btn btn-sm btn-success mb-0">
|
<div class="mb-3">
|
||||||
<i class="fas fa-upload mr-1"></i> Carica file
|
<button type="button" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#documentiModal">
|
||||||
<input type="file" name="allegati[]" multiple class="d-none">
|
<i class="fas fa-folder-open mr-1"></i> Seleziona da Documenti
|
||||||
</label>
|
</button>
|
||||||
|
<label class="btn btn-sm btn-success mb-0">
|
||||||
|
<i class="fas fa-upload mr-1"></i> Carica file
|
||||||
|
<input type="file" name="allegati[]" id="allegati" multiple class="d-none" onchange="handleFileUpload(this)">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="allegati-list"></div>
|
||||||
|
<input type="hidden" name="documenti_selezionati" id="documenti_selezionati" value="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -110,10 +117,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="documentiModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><i class="fas fa-folder-open mr-2"></i>Seleziona Documenti</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal">
|
||||||
|
<span>×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
@if($documenti->count() > 0)
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 40px;"></th>
|
||||||
|
<th>Nome File</th>
|
||||||
|
<th>Tipologia</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($documenti as $doc)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input doc-checkbox" id="doc-{{ $doc->id }}" value="{{ $doc->id }}" data-nome="{{ $doc->nome_file }}">
|
||||||
|
<label class="custom-control-label" for="doc-{{ $doc->id }}"></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{ $doc->nome_file }}</td>
|
||||||
|
<td><span class="badge badge-info">{{ $doc->tipologia ?: $doc->tipo }}</span></td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-muted">Nessun documento disponibile.</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="aggiungiDocumenti()">
|
||||||
|
<i class="fas fa-plus mr-1"></i> Aggiungi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script>
|
<script>
|
||||||
|
let allegatiSelezionati = [];
|
||||||
|
|
||||||
function toggleDestinatari() {
|
function toggleDestinatari() {
|
||||||
const tipo = document.getElementById('destinatario_tipo').value;
|
const tipo = document.getElementById('destinatario_tipo').value;
|
||||||
|
|
||||||
@@ -160,6 +219,53 @@ function loadGruppiContatti() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function aggiungiDocumenti() {
|
||||||
|
const checkboxes = document.querySelectorAll('.doc-checkbox:checked');
|
||||||
|
checkboxes.forEach(function(cb) {
|
||||||
|
const id = parseInt(cb.value);
|
||||||
|
const nome = cb.dataset.nome;
|
||||||
|
|
||||||
|
if (!allegatiSelezionati.includes(id)) {
|
||||||
|
allegatiSelezionati.push(id);
|
||||||
|
|
||||||
|
const container = document.getElementById('allegati-list');
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'd-flex align-items-center justify-content-between bg-white p-2 mb-2 rounded';
|
||||||
|
div.id = 'allegato-' + id;
|
||||||
|
div.innerHTML = '<span><i class="fas fa-file mr-2"></i>' + nome + '</span>' +
|
||||||
|
'<button type="button" class="btn btn-xs btn-danger" onclick="removeAllegato(' + id + ')">' +
|
||||||
|
'<i class="fas fa-times"></i></button>';
|
||||||
|
container.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('documenti_selezionati').value = allegatiSelezionati.join(',');
|
||||||
|
$('#documentiModal').modal('hide');
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAllegato(id) {
|
||||||
|
allegatiSelezionati = allegatiSelezionati.filter(function(a) { return a !== id; });
|
||||||
|
const el = document.getElementById('allegato-' + id);
|
||||||
|
if (el) el.remove();
|
||||||
|
document.getElementById('documenti_selezionati').value = allegatiSelezionati.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileUpload(input) {
|
||||||
|
if (!input.files) return;
|
||||||
|
const container = document.getElementById('allegati-list');
|
||||||
|
for (const file of input.files) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'd-flex align-items-center justify-content-between bg-white p-2 mb-2 rounded';
|
||||||
|
div.innerHTML = '<span><i class="fas fa-file mr-2"></i>' + file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)</span>' +
|
||||||
|
'<button type="button" class="btn btn-xs btn-danger" onclick="this.parentElement.remove()">' +
|
||||||
|
'<i class="fas fa-times"></i></button>';
|
||||||
|
container.appendChild(div);
|
||||||
|
}
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
function saveDraft() {
|
function saveDraft() {
|
||||||
const form = document.getElementById('emailForm');
|
const form = document.getElementById('emailForm');
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
|||||||
@@ -247,6 +247,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="fas fa-save mr-1"></i> Salva
|
<i class="fas fa-save mr-1"></i> Salva
|
||||||
|
|||||||
@@ -252,6 +252,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => $selectedTags ?? [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="fas fa-save mr-1"></i> Salva Modifiche
|
<i class="fas fa-save mr-1"></i> Salva Modifiche
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
@php
|
@php
|
||||||
$canWriteEventi = Auth::user()->canManage('eventi');
|
$canWriteEventi = Auth::user()->canManage('eventi');
|
||||||
$canDeleteEventi = Auth::user()->canDelete('eventi');
|
$canDeleteEventi = Auth::user()->canDelete('eventi');
|
||||||
|
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
|
||||||
|
$visibleColumnsJson = json_encode($visibleColumns ?? []);
|
||||||
|
$tableColumnsJson = json_encode($tableColumns ?? []);
|
||||||
|
$entityType = $entityType ?? 'eventi';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@@ -20,6 +24,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Tutti gli Eventi</h3>
|
<h3 class="card-title">Tutti gli Eventi</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
<a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2">
|
<a href="/eventi/calendar" class="btn btn-sm btn-outline-primary mr-2">
|
||||||
<i class="fas fa-calendar-alt mr-1"></i> Calendario
|
<i class="fas fa-calendar-alt mr-1"></i> Calendario
|
||||||
</a>
|
</a>
|
||||||
@@ -43,6 +50,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@include('partials._tag-filter-bar', ['filterTags' => $allTags])
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="GET" class="mb-3">
|
<form method="GET" class="mb-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -73,10 +82,10 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table class="table table-bordered table-hover">
|
<table class="table table-bordered table-hover" id="eventi-table">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px;">
|
<th style="width: 40px;" data-column="select">
|
||||||
@if($canDeleteEventi)
|
@if($canDeleteEventi)
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
||||||
@@ -84,7 +93,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</th>
|
</th>
|
||||||
<th>
|
@if(in_array('nome_evento', $visibleColumns))
|
||||||
|
<th data-column="nome_evento" data-sortable="true">
|
||||||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'nome_evento', 'direction' => request('sort') === 'nome_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'nome_evento', 'direction' => request('sort') === 'nome_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Nome Evento
|
Nome Evento
|
||||||
@if(request('sort') === 'nome_evento')
|
@if(request('sort') === 'nome_evento')
|
||||||
@@ -94,7 +104,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
@endif
|
||||||
|
@if(in_array('descrizione_evento', $visibleColumns))
|
||||||
|
<th data-column="descrizione_evento" data-sortable="true">
|
||||||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'descrizione_evento', 'direction' => request('sort') === 'descrizione_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'descrizione_evento', 'direction' => request('sort') === 'descrizione_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Descrizione
|
Descrizione
|
||||||
@if(request('sort') === 'descrizione_evento')
|
@if(request('sort') === 'descrizione_evento')
|
||||||
@@ -104,7 +116,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
@endif
|
||||||
|
@if(in_array('tipo_evento', $visibleColumns))
|
||||||
|
<th data-column="tipo_evento" data-sortable="true">
|
||||||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_evento', 'direction' => request('sort') === 'tipo_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_evento', 'direction' => request('sort') === 'tipo_evento' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Tipologia
|
Tipologia
|
||||||
@if(request('sort') === 'tipo_evento')
|
@if(request('sort') === 'tipo_evento')
|
||||||
@@ -114,7 +128,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
@endif
|
||||||
|
@if(in_array('tipo_recorrenza', $visibleColumns))
|
||||||
|
<th data-column="tipo_recorrenza" data-sortable="true">
|
||||||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_recorrenza', 'direction' => request('sort') === 'tipo_recorrenza' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'tipo_recorrenza', 'direction' => request('sort') === 'tipo_recorrenza' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Ricorrenza
|
Ricorrenza
|
||||||
@if(request('sort') === 'tipo_recorrenza')
|
@if(request('sort') === 'tipo_recorrenza')
|
||||||
@@ -124,7 +140,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
@endif
|
||||||
|
@if(in_array('gruppi', $visibleColumns))
|
||||||
|
<th data-column="gruppi">
|
||||||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'gruppi', 'direction' => request('sort') === 'gruppi' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'gruppi', 'direction' => request('sort') === 'gruppi' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Gruppi
|
Gruppi
|
||||||
@if(request('sort') === 'gruppi')
|
@if(request('sort') === 'gruppi')
|
||||||
@@ -134,7 +152,9 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
@endif
|
||||||
|
@if(in_array('responsabili', $visibleColumns))
|
||||||
|
<th data-column="responsabili">
|
||||||
<a href="{{ request()->fullUrlWithQuery(['sort' => 'responsabili', 'direction' => request('sort') === 'responsabili' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'responsabili', 'direction' => request('sort') === 'responsabili' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Responsabili
|
Responsabili
|
||||||
@if(request('sort') === 'responsabili')
|
@if(request('sort') === 'responsabili')
|
||||||
@@ -144,7 +164,12 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</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">
|
<a href="{{ request()->fullUrlWithQuery(['sort' => 'created_at', 'direction' => request('sort') === 'created_at' && request('direction') === 'asc' ? 'desc' : 'asc']) }}" class="text-dark">
|
||||||
Creato
|
Creato
|
||||||
@if(request('sort') === 'created_at')
|
@if(request('sort') === 'created_at')
|
||||||
@@ -154,6 +179,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
|
@endif
|
||||||
<th style="width: 120px;">Azioni</th>
|
<th style="width: 120px;">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -168,6 +194,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@if(in_array('nome_evento', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
<a href="/eventi/{{ $evento->id }}">
|
<a href="/eventi/{{ $evento->id }}">
|
||||||
<i class="fas fa-calendar mr-1 text-primary"></i>
|
<i class="fas fa-calendar mr-1 text-primary"></i>
|
||||||
@@ -177,7 +204,11 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
<span class="badge badge-success ml-1">Incluso</span>
|
<span class="badge badge-success ml-1">Incluso</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('descrizione_evento', $visibleColumns))
|
||||||
<td>{{ Str::limit($evento->descrizione_evento, 50) ?: '-' }}</td>
|
<td>{{ Str::limit($evento->descrizione_evento, 50) ?: '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('tipo_evento', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
@if($evento->tipo_evento)
|
@if($evento->tipo_evento)
|
||||||
@php
|
@php
|
||||||
@@ -192,6 +223,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
<span class="text-muted">-</span>
|
<span class="text-muted">-</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('tipo_recorrenza', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
@if($evento->tipo_recorrenza && $evento->tipo_recorrenza !== 'singolo')
|
@if($evento->tipo_recorrenza && $evento->tipo_recorrenza !== 'singolo')
|
||||||
<span class="badge badge-info">{{ $evento->periodicita_label }}</span>
|
<span class="badge badge-info">{{ $evento->periodicita_label }}</span>
|
||||||
@@ -199,6 +232,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
<span class="badge badge-secondary">Singolo</span>
|
<span class="badge badge-secondary">Singolo</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('gruppi', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
@forelse($evento->gruppi->take(2) as $gruppo)
|
@forelse($evento->gruppi->take(2) as $gruppo)
|
||||||
<a href="/gruppi/{{ $gruppo->id }}">
|
<a href="/gruppi/{{ $gruppo->id }}">
|
||||||
@@ -211,6 +246,8 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
<small class="text-muted">+{{ $evento->gruppi->count() - 2 }}</small>
|
<small class="text-muted">+{{ $evento->gruppi->count() - 2 }}</small>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('responsabili', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
@forelse($evento->responsabili->take(2) as $resp)
|
@forelse($evento->responsabili->take(2) as $resp)
|
||||||
<small>{{ $resp->nome_completo }}</small><br>
|
<small>{{ $resp->nome_completo }}</small><br>
|
||||||
@@ -221,7 +258,17 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
<small class="text-muted">+{{ $evento->responsabili->count() - 2 }} altri</small>
|
<small class="text-muted">+{{ $evento->responsabili->count() - 2 }} altri</small>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('tag', $visibleColumns))
|
||||||
|
<td>
|
||||||
|
@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>
|
<td><small class="text-muted">{{ $evento->created_at->format('d/m/Y') }}</small></td>
|
||||||
|
@endif
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ url('/eventi/' . $evento->id) }}" class="btn btn-xs btn-info" title="Visualizza">
|
<a href="{{ url('/eventi/' . $evento->id) }}" class="btn btn-xs btn-info" title="Visualizza">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
@@ -241,7 +288,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="9" class="text-center text-muted py-4">
|
<td colspan="20" class="text-center text-muted py-4">
|
||||||
<i class="fas fa-calendar fa-2x mb-2"></i>
|
<i class="fas fa-calendar fa-2x mb-2"></i>
|
||||||
<p class="mb-0">Nessun evento trovato</p>
|
<p class="mb-0">Nessun evento trovato</p>
|
||||||
</td>
|
</td>
|
||||||
@@ -276,6 +323,10 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials.table-settings')
|
||||||
|
|
||||||
|
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
|
||||||
|
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
||||||
@if($canDeleteEventi)
|
@if($canDeleteEventi)
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
@@ -301,6 +352,7 @@ $canDeleteEventi = Auth::user()->canDelete('eventi');
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script src="{{ asset('js/column-manager.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
function toggleSelectAll(source) {
|
function toggleSelectAll(source) {
|
||||||
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
|
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
|
||||||
@@ -366,5 +418,24 @@ function exportSelectedIcs() {
|
|||||||
document.body.appendChild(form);
|
document.body.appendChild(form);
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const table = document.getElementById('eventi-table');
|
||||||
|
if (table) {
|
||||||
|
window.columnManager = new ColumnManager('eventi-table', {
|
||||||
|
entityType: 'eventi',
|
||||||
|
storageKey: 'cm_eventi',
|
||||||
|
resizable: true,
|
||||||
|
reorderable: true,
|
||||||
|
});
|
||||||
|
initTableSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.printWithCurrentSettings = function () {
|
||||||
|
if (window.columnManager) {
|
||||||
|
window.columnManager.printWithSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -202,6 +202,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header"><h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
@forelse($evento->tags as $tag)
|
||||||
|
<span class="badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;padding:4px 10px;border-radius:12px;font-size:0.85rem;">{{ $tag->name }}</span>
|
||||||
|
@empty
|
||||||
|
<p class="text-muted mb-0"><i class="fas fa-info-circle mr-1"></i>Nessun tag assegnato.</p>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if($evento->note)
|
@if($evento->note)
|
||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header"><h3 class="card-title"><i class="fas fa-sticky-note mr-2"></i>Note</h3></div>
|
<div class="card-header"><h3 class="card-title"><i class="fas fa-sticky-note mr-2"></i>Note</h3></div>
|
||||||
|
|||||||
@@ -117,6 +117,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="fas fa-save mr-1"></i> Salva
|
<i class="fas fa-save mr-1"></i> Salva
|
||||||
|
|||||||
@@ -270,6 +270,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => $selectedTags ?? [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="btn btn-success">
|
||||||
<i class="fas fa-save mr-1"></i> Salva Modifiche
|
<i class="fas fa-save mr-1"></i> Salva Modifiche
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ $columnLabels = [
|
|||||||
'citta' => 'Città',
|
'citta' => 'Città',
|
||||||
'telefono' => 'Telefono',
|
'telefono' => 'Telefono',
|
||||||
'email' => 'Email',
|
'email' => 'Email',
|
||||||
|
'tag' => 'Tag',
|
||||||
];
|
];
|
||||||
$vistaDefaultJson = $vista ? $vista->toJson() : 'null';
|
$vistaDefaultJson = $vista ? $vista->toJson() : 'null';
|
||||||
$allColumnsJson = json_encode($allColumns);
|
$allColumnsJson = json_encode($allColumns);
|
||||||
$visibleColumnsJson = json_encode($visibleColumns);
|
$visibleColumnsJson = json_encode($visibleColumns);
|
||||||
|
$entityType = $entityType ?? 'gruppi';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@@ -43,6 +45,9 @@ $visibleColumnsJson = json_encode($visibleColumns);
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-sitemap mr-2"></i>Elenco Gruppi</h3>
|
<h3 class="card-title"><i class="fas fa-sitemap mr-2"></i>Elenco Gruppi</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
<div class="btn-group mr-2">
|
<div class="btn-group mr-2">
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary {{ $viewMode === 'table' ? 'active' : '' }}" onclick="switchView('table')" id="btn-table-view">
|
<button type="button" class="btn btn-sm btn-outline-primary {{ $viewMode === 'table' ? 'active' : '' }}" onclick="switchView('table')" id="btn-table-view">
|
||||||
<i class="fas fa-table mr-1"></i> Tabella
|
<i class="fas fa-table mr-1"></i> Tabella
|
||||||
@@ -51,9 +56,23 @@ $visibleColumnsJson = json_encode($visibleColumns);
|
|||||||
<i class="fas fa-sitemap mr-1"></i> Albero
|
<i class="fas fa-sitemap mr-1"></i> Albero
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary btn-sm mr-1" onclick="showSaveVistaModal()">
|
<div class="btn-group mr-1">
|
||||||
<i class="fas fa-save mr-1"></i> Salva Vista
|
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown">
|
||||||
</button>
|
<i class="fas fa-cog mr-1"></i> Azioni
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
@if($canWriteGruppi)
|
||||||
|
<a class="dropdown-item" href="#" onclick="showMassTagModal(); return false;">
|
||||||
|
<i class="fas fa-tags mr-2"></i>Assegna Tag
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item text-danger" href="#" onclick="deleteSelected(); return false;">
|
||||||
|
<i class="fas fa-trash mr-2"></i>Elimina Selezionati
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if($canWriteGruppi)
|
@if($canWriteGruppi)
|
||||||
<a href="{{ route('gruppi.import') }}" class="btn btn-warning btn-sm mr-1">
|
<a href="{{ route('gruppi.import') }}" class="btn btn-warning btn-sm mr-1">
|
||||||
<i class="fas fa-file-import mr-1"></i> Importa
|
<i class="fas fa-file-import mr-1"></i> Importa
|
||||||
@@ -65,50 +84,71 @@ $visibleColumnsJson = json_encode($visibleColumns);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials._tag-filter-bar', ['filterTags' => $allTags])
|
||||||
|
|
||||||
|
@include('partials.table-settings')
|
||||||
|
|
||||||
|
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
|
||||||
|
|
||||||
@if($viewMode === 'table')
|
@if($viewMode === 'table')
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<table class="table table-bordered table-hover table-striped mb-0" id="gruppi-table">
|
<table class="table table-bordered table-hover table-striped mb-0" id="gruppi-table">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th style="width: 40px;" 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))
|
@if(in_array('nome', $visibleColumns))
|
||||||
<th data-sortable="true" onclick="sortTable('nome')" style="cursor:pointer;">Nome <i class="fas fa-sort float-right text-muted"></i></th>
|
<th data-sortable="true" data-column="nome" onclick="sortTable('nome')" style="cursor:pointer;">Nome <i class="fas fa-sort float-right text-muted"></i></th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('descrizione', $visibleColumns))
|
@if(in_array('descrizione', $visibleColumns))
|
||||||
<th data-sortable="true" onclick="sortTable('descrizione')" style="cursor:pointer;">Descrizione <i class="fas fa-sort float-right text-muted"></i></th>
|
<th data-sortable="true" data-column="descrizione" onclick="sortTable('descrizione')" style="cursor:pointer;">Descrizione <i class="fas fa-sort float-right text-muted"></i></th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('diocesi', $visibleColumns))
|
@if(in_array('diocesi', $visibleColumns))
|
||||||
<th>Diocesi</th>
|
<th data-column="diocesi">Diocesi</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('livello', $visibleColumns))
|
@if(in_array('livello', $visibleColumns))
|
||||||
<th style="width: 80px;">Livello</th>
|
<th style="width: 80px;" data-column="livello">Livello</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('padre', $visibleColumns))
|
@if(in_array('padre', $visibleColumns))
|
||||||
<th>Gruppo Padre</th>
|
<th data-column="padre">Gruppo Padre</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('membri', $visibleColumns))
|
@if(in_array('membri', $visibleColumns))
|
||||||
<th style="width: 100px;">Membri</th>
|
<th style="width: 100px;" data-column="membri">Membri</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('responsabili', $visibleColumns))
|
@if(in_array('responsabili', $visibleColumns))
|
||||||
<th>Responsabili</th>
|
<th data-column="responsabili">Responsabili</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('indirizzo', $visibleColumns))
|
@if(in_array('indirizzo', $visibleColumns))
|
||||||
<th>Indirizzo</th>
|
<th data-column="indirizzo">Indirizzo</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('citta', $visibleColumns))
|
@if(in_array('citta', $visibleColumns))
|
||||||
<th>Città</th>
|
<th data-column="citta">Città</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('telefono', $visibleColumns))
|
@if(in_array('telefono', $visibleColumns))
|
||||||
<th>Telefono</th>
|
<th data-column="telefono">Telefono</th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('email', $visibleColumns))
|
@if(in_array('email', $visibleColumns))
|
||||||
<th>Email</th>
|
<th data-column="email">Email</th>
|
||||||
@endif
|
@endif
|
||||||
<th style="width: 120px;">Azioni</th>
|
@if(in_array('tag', $visibleColumns))
|
||||||
|
<th data-column="tag">Tag</th>
|
||||||
|
@endif
|
||||||
|
<th style="width: 120px;" data-column="azioni">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="table-body">
|
<tbody id="table-body">
|
||||||
@foreach($gruppi as $gruppo)
|
@foreach($gruppi as $gruppo)
|
||||||
<tr data-id="{{ $gruppo->id }}">
|
<tr data-id="{{ $gruppo->id }}">
|
||||||
|
<td>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input row-checkbox" id="select-{{ $gruppo->id }}" value="{{ $gruppo->id }}">
|
||||||
|
<label class="custom-control-label" for="select-{{ $gruppo->id }}"></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
@if(in_array('nome', $visibleColumns))
|
@if(in_array('nome', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
<span style="padding-left: {{ ($gruppo->depth ?? 0) * 20 }}px;">
|
<span style="padding-left: {{ ($gruppo->depth ?? 0) * 20 }}px;">
|
||||||
@@ -154,6 +194,13 @@ $visibleColumnsJson = json_encode($visibleColumns);
|
|||||||
@if(in_array('email', $visibleColumns))
|
@if(in_array('email', $visibleColumns))
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
@endif
|
@endif
|
||||||
|
@if(in_array('tag', $visibleColumns))
|
||||||
|
<td>
|
||||||
|
@foreach($gruppo->tags as $tag)
|
||||||
|
<a href="{{ route('gruppi.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">{{ $tag->name }}</a>
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ route('gruppi.show', $gruppo->id) }}" class="btn btn-xs btn-info" title="Visualizza">
|
<a href="{{ route('gruppi.show', $gruppo->id) }}" class="btn btn-xs btn-info" title="Visualizza">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
@@ -222,101 +269,122 @@ $visibleColumnsJson = json_encode($visibleColumns);
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="saveVistaModal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
|
{{-- MODAL: Gestione Tag massiva --}}
|
||||||
|
<div class="modal fade" id="massTagModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header bg-info">
|
||||||
<h5 class="modal-title"><i class="fas fa-save mr-2"></i>Salva Vista</h5>
|
<h5 class="modal-title text-white"><i class="fas fa-tags mr-2"></i>Gestione Tag</h5>
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
<button type="button" class="close text-white" data-dismiss="modal"><span>×</span></button>
|
||||||
<span>×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<form id="massTagForm" method="POST" action="/gruppi/mass-tag">
|
||||||
<div class="form-group">
|
@csrf
|
||||||
<label>Nome Vista *</label>
|
<div class="modal-body">
|
||||||
<input type="text" id="vista-nome" class="form-control" placeholder="Es. Elenco gruppi attivi">
|
<p class="mb-2">Operazione su <strong id="massTagCount">0</strong> gruppi selezionati.</p>
|
||||||
</div>
|
<div class="form-group mb-3">
|
||||||
<div class="form-group">
|
<label class="font-weight-bold">Azione</label>
|
||||||
<label>Colonne visibili</label>
|
<div class="d-flex" style="gap:1.5rem;">
|
||||||
<div class="row" id="colonne-checkboxes">
|
<div class="form-check">
|
||||||
@foreach($allColumns as $col)
|
<input class="form-check-input" type="radio" name="mode" id="grMtModeAssign" value="assign" checked>
|
||||||
<div class="col-md-4 mb-2">
|
<label class="form-check-label" for="grMtModeAssign">Assegna tag</label>
|
||||||
<div class="custom-control custom-checkbox">
|
</div>
|
||||||
<input type="checkbox" class="custom-control-input col-checkbox" id="col-{{ $col }}" value="{{ $col }}" {{ in_array($col, $visibleColumns) ? 'checked' : '' }}>
|
<div class="form-check">
|
||||||
<label class="custom-control-label" for="col-{{ $col }}">{{ $columnLabels[$col] ?? $col }}</label>
|
<input class="form-check-input" type="radio" name="mode" id="grMtModeRemove" value="remove">
|
||||||
|
<label class="form-check-label" for="grMtModeRemove">Rimuovi tag</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
|
||||||
</div>
|
</div>
|
||||||
|
@include('partials._tag-selector', ['label' => 'Seleziona tag'])
|
||||||
|
<input type="hidden" name="ids" id="massTagIds" value="">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="modal-footer">
|
||||||
<div class="custom-control custom-checkbox">
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||||
<input type="checkbox" class="custom-control-input" id="vista-default">
|
<button type="submit" class="btn btn-primary"><i class="fas fa-tags mr-1"></i> Applica</button>
|
||||||
<label class="custom-control-label" for="vista-default">Imposta come vista predefinita</label>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- MODAL: Conferma Eliminazione massiva --}}
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
||||||
|
@if($canDeleteGruppi)
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-danger">
|
||||||
|
<h5 class="modal-title text-white"><i class="fas fa-trash-alt mr-2"></i>Conferma Eliminazione</h5>
|
||||||
|
<button type="button" class="close text-white" data-dismiss="modal"><span>×</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Stai per eliminare <strong id="delete-count">0</strong> gruppi.</p>
|
||||||
|
<div class="alert alert-warning mt-3 mb-0">
|
||||||
|
<small><i class="fas fa-info-circle mr-1"></i>I membri verranno scollegati ma non eliminati.</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||||
<button type="button" class="btn btn-primary" onclick="saveVista()">
|
<button type="button" class="btn btn-danger" id="confirm-delete-btn"><i class="fas fa-trash mr-1"></i> Elimina</button>
|
||||||
<i class="fas fa-save mr-1"></i> Salva
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
</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>×</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>
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script src="{{ asset('js/column-manager.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
function switchView(mode) {
|
function switchView(mode) {
|
||||||
window.location.href = '?view=' + mode;
|
window.location.href = '?view=' + mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSelectAll(source) {
|
||||||
|
document.querySelectorAll('.row-checkbox').forEach(function(cb) {
|
||||||
|
cb.checked = source.checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedIds() {
|
||||||
|
return Array.from(document.querySelectorAll('.row-checkbox:checked')).map(cb => cb.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMassTagModal() {
|
||||||
|
const ids = getSelectedIds();
|
||||||
|
if (ids.length === 0) {
|
||||||
|
alert('Seleziona almeno un gruppo');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('massTagIds').value = ids.join(',');
|
||||||
|
document.getElementById('massTagCount').textContent = ids.length;
|
||||||
|
$('#massTagModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelected() {
|
||||||
|
const ids = getSelectedIds();
|
||||||
|
if (ids.length === 0) {
|
||||||
|
alert('Seleziona almeno un gruppo');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('delete-count').textContent = ids.length;
|
||||||
|
document.getElementById('confirm-delete-btn').onclick = function() {
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.method = 'POST';
|
||||||
|
form.action = '/gruppi/mass-elimina';
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '{{ csrf_token() }}';
|
||||||
|
let html = '<input type="hidden" name="_token" value="' + csrfToken + '">';
|
||||||
|
ids.forEach(function(id) {
|
||||||
|
html += '<input type="hidden" name="ids[]" value="' + id + '">';
|
||||||
|
});
|
||||||
|
form.innerHTML = html;
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
$('#deleteModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
function toggleItem(id) {
|
function toggleItem(id) {
|
||||||
const el = document.getElementById('group-' + id);
|
const el = document.getElementById('group-' + id);
|
||||||
const icon = document.getElementById('icon-' + id);
|
const icon = document.getElementById('icon-' + id);
|
||||||
@@ -385,48 +453,7 @@ function getColumnIndex(column) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSaveVistaModal() {
|
|
||||||
document.getElementById('vista-nome').value = '';
|
|
||||||
document.getElementById('vista-default').checked = false;
|
|
||||||
$('#saveVistaModal').modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveVista() {
|
|
||||||
const nome = document.getElementById('vista-nome').value.trim();
|
|
||||||
if (!nome) {
|
|
||||||
alert('Inserisci un nome per la vista');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const colonneVisibili = Array.from(document.querySelectorAll('.col-checkbox:checked')).map(cb => cb.value);
|
|
||||||
const isDefault = document.getElementById('vista-default').checked;
|
|
||||||
|
|
||||||
fetch('/gruppi/save-vista', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
nome: nome,
|
|
||||||
colonne_visibili: colonneVisibili,
|
|
||||||
is_default: isDefault,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
$('#saveVistaModal').modal('hide');
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Errore nel salvataggio della vista');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
alert('Errore: ' + error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.tree-item { margin-bottom: 5px; }
|
.tree-item { margin-bottom: 5px; }
|
||||||
@@ -452,10 +479,31 @@ function saveVista() {
|
|||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
}
|
}
|
||||||
.btn-group .btn.active {
|
.btn-group .btn.active {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const table = document.getElementById('gruppi-table');
|
||||||
|
if (table) {
|
||||||
|
window.columnManager = new ColumnManager('gruppi-table', {
|
||||||
|
entityType: 'gruppi',
|
||||||
|
storageKey: 'cm_gruppi',
|
||||||
|
resizable: true,
|
||||||
|
reorderable: true,
|
||||||
|
});
|
||||||
|
initTableSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.printWithCurrentSettings = function () {
|
||||||
|
if (window.columnManager) {
|
||||||
|
window.columnManager.printWithSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -247,6 +247,17 @@ $canDeleteGruppi = Auth::user()->canDelete('gruppi');
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header"><h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
@forelse($gruppo->tags as $tag)
|
||||||
|
<span class="badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;padding:4px 10px;border-radius:12px;font-size:0.85rem;">{{ $tag->name }}</span>
|
||||||
|
@empty
|
||||||
|
<p class="text-muted mb-0"><i class="fas fa-info-circle mr-1"></i>Nessun tag assegnato.</p>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">
|
<h3 class="card-title">
|
||||||
|
|||||||
@@ -32,6 +32,11 @@
|
|||||||
<i class="fas fa-calendar-alt mr-2"></i> Calendario
|
<i class="fas fa-calendar-alt mr-2"></i> Calendario
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#help-tag" data-toggle="tab">
|
||||||
|
<i class="fas fa-tags mr-2"></i> Tag e Ricerca
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="#help-tipologie" data-toggle="tab">
|
<a class="nav-link" href="#help-tipologie" data-toggle="tab">
|
||||||
<i class="fas fa-tags mr-2"></i> Tipologie e Dati
|
<i class="fas fa-tags mr-2"></i> Tipologie e Dati
|
||||||
@@ -268,6 +273,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Tag e Ricerca --}}
|
||||||
|
<div class="tab-pane" id="help-tag">
|
||||||
|
<div class="card card-outline card-info">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i> Tag e Ricerca per Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5>Cos'è il sistema di Tag</h5>
|
||||||
|
<p>I tag permettono di classificare e raggruppare trasversalmente elementi di tipo diverso: individui, gruppi, eventi, documenti e mailing list. A differenza delle tipologie (specifiche per ogni entità), i tag sono <strong>condivisi</strong> tra tutte le entità.</p>
|
||||||
|
|
||||||
|
<h5>Gestione Tag</h5>
|
||||||
|
<p>Vai in <strong>Impostazioni → Tag</strong> per creare, modificare, riordinare ed eliminare i tag. Ogni tag ha:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Nome</strong> — il nome visualizzato</li>
|
||||||
|
<li><strong>Colore</strong> — colore personalizzato per il badge (es. <code>#dc3545</code> per rosso)</li>
|
||||||
|
<li><strong>Slug</strong> — identificatore univoco generato automaticamente dal nome</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h5>Assegnare Tag</h5>
|
||||||
|
<p>I tag si assegnano nelle pagine di creazione e modifica di ciascuna entità — individui, gruppi, eventi, documenti, mailing list — tramite il selettore a pillole colorate. Clicca su un tag per selezionarlo (il badge diventa opaco), clicca di nuovo per deselezionarlo. Usa la barra di ricerca per filtrare i tag per nome.</p>
|
||||||
|
|
||||||
|
<h5>Assegnazione Massiva di Tag</h5>
|
||||||
|
<p>Puoi assegnare o rimuovere tag a più elementi contemporaneamente:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Individui:</strong> seleziona gli individui nella tabella, apri <strong>Azioni → Assegna Tag</strong>, scegli Assegna/Rimuovi e seleziona i tag</li>
|
||||||
|
<li><strong>Gruppi:</strong> stessa procedura — seleziona i gruppi, <strong>Azioni → Assegna Tag</strong></li>
|
||||||
|
<li><strong>Documenti:</strong> seleziona i documenti, pulsante <strong>Tag</strong> nella toolbar</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h5>Filtrare per Tag</h5>
|
||||||
|
<p>In ogni pagina elenco (Individui, Gruppi, Eventi, Documenti, Mailing List) trovi una barra con tutti i tag. Clicca su un tag per filtrare la tabella mostrando solo gli elementi con quel tag. Clicca <strong>Cancella filtri</strong> per rimuovere il filtro.</p>
|
||||||
|
|
||||||
|
<h5>Ricerca per Tag</h5>
|
||||||
|
<p>La sezione <strong>Ricerca per Tag</strong> (sidebar → Ricerca per Tag) permette di trovare elementi di qualsiasi tipo in base ai tag:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Tag singolo:</strong> seleziona un tag per vedere tutti gli elementi associati</li>
|
||||||
|
<li><strong>Tag multipli (AND):</strong> seleziona più tag (Ctrl+click) per trovare elementi che hanno <strong>tutti</strong> i tag selezionati — utile per ricerche precise (es. "animatore" + "giovane")</li>
|
||||||
|
</ul>
|
||||||
|
<p>I risultati sono raggruppati per tipo con info-box colorati e tabelle dettagliate.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{-- Tipologie e Dati --}}
|
{{-- Tipologie e Dati --}}
|
||||||
<div class="tab-pane" id="help-tipologie">
|
<div class="tab-pane" id="help-tipologie">
|
||||||
<div class="card card-outline card-success">
|
<div class="card card-outline card-success">
|
||||||
@@ -297,6 +345,10 @@
|
|||||||
<li><strong>Evento</strong> — visibile solo ai partecipanti di un evento</li>
|
<li><strong>Evento</strong> — visibile solo ai partecipanti di un evento</li>
|
||||||
<li><strong>Mailing</strong> — visibile solo ai membri di una mailing list</li>
|
<li><strong>Mailing</strong> — visibile solo ai membri di una mailing list</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h5>Tag</h5>
|
||||||
|
<p>I <strong>tag</strong> affiancano le tipologie con un sistema trasversale di classificazione applicabile a individui, gruppi, eventi, documenti e mailing list. A differenza delle tipologie (specifiche per ogni entità), i tag sono condivisi tra tutte le entità e permettono ricerche incrociate.</p>
|
||||||
|
<p>Vedi la sezione <strong>Tag e Ricerca per Tag</strong> (nel menu laterale) per la gestione e l'assegnazione.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
<li><a href="#installazione">Guida all'Installazione</a></li>
|
<li><a href="#installazione">Guida all'Installazione</a></li>
|
||||||
<li><a href="#backup">Backup e Migrazione Server</a></li>
|
<li><a href="#backup">Backup e Migrazione Server</a></li>
|
||||||
<li><a href="#docker">Docker</a></li>
|
<li><a href="#docker">Docker</a></li>
|
||||||
|
<li><a href="#tag">Tag e Ricerca per Tag</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -273,6 +274,10 @@
|
|||||||
<li><strong>Mailing</strong> — visibile solo ai membri di una mailing list</li>
|
<li><strong>Mailing</strong> — visibile solo ai membri di una mailing list</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h3>Tag</h3>
|
||||||
|
<p>I <strong>tag</strong> affiancano le tipologie con un sistema trasversale di classificazione applicabile a individui, gruppi, eventi, documenti e mailing list. A differenza delle tipologie (specifiche per ogni entità), i tag sono condivisi tra tutte le entità e permettono ricerche incrociate.</p>
|
||||||
|
<p>Vedi la sezione <a href="#tag">Tag e Ricerca per Tag</a> per la gestione e l'assegnazione.</p>
|
||||||
|
|
||||||
<div class="page-break"></div>
|
<div class="page-break"></div>
|
||||||
|
|
||||||
{{-- 5. Email / SMTP --}}
|
{{-- 5. Email / SMTP --}}
|
||||||
@@ -324,6 +329,11 @@
|
|||||||
<li>Invia email alla lista da <strong>Email → Nuova Email</strong>, selezionando la mailing list come destinatario</li>
|
<li>Invia email alla lista da <strong>Email → Nuova Email</strong>, selezionando la mailing list come destinatario</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="callout callout-warning">
|
||||||
|
<h4>Avviso contatti senza email</h4>
|
||||||
|
<p>Se nella mailing list sono presenti individui che non hanno un indirizzo email nei contatti, il sistema mostra un avviso con l'elenco dei nominativi esclusi dall'invio. Verifica sempre la presenza di email valide prima di procedere con l'invio.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Test connessione</h3>
|
<h3>Test connessione</h3>
|
||||||
<p>Dopo aver configurato i server, usa il pulsante <strong>Test Connessione</strong> per verificare che IMAP e SMTP funzionino correttamente.</p>
|
<p>Dopo aver configurato i server, usa il pulsante <strong>Test Connessione</strong> per verificare che IMAP e SMTP funzionino correttamente.</p>
|
||||||
|
|
||||||
@@ -929,5 +939,43 @@ echo 'Utente creato con successo!';
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="page-break"></div>
|
||||||
|
|
||||||
|
{{-- 9. Tag e Ricerca --}}
|
||||||
|
<h2 id="tag">9. Tag e Ricerca per Tag</h2>
|
||||||
|
|
||||||
|
<h3>Cos'è il sistema di Tag</h3>
|
||||||
|
<p>I tag permettono di classificare e raggruppare trasversalmente elementi di tipo diverso: individui, gruppi, eventi, documenti e mailing list. A differenza delle tipologie (specifiche per ogni entità), i tag sono <strong>condivisi</strong> tra tutte le entità.</p>
|
||||||
|
|
||||||
|
<h3>Gestione Tag</h3>
|
||||||
|
<p>Vai in <strong>Impostazioni → Tag</strong> per creare, modificare, riordinare ed eliminare i tag. Ogni tag ha:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Nome</strong> — il nome visualizzato</li>
|
||||||
|
<li><strong>Colore</strong> — colore personalizzato per il badge (es. <code>#dc3545</code> per rosso)</li>
|
||||||
|
<li><strong>Slug</strong> — identificatore univoco generato automaticamente dal nome</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Assegnare Tag</h3>
|
||||||
|
<p>I tag si assegnano nelle pagine di creazione e modifica di ciascuna entità — individui, gruppi, eventi, documenti, mailing list — tramite il selettore a pillole colorate. Clicca su un tag per selezionarlo (il badge diventa opaco), clicca di nuovo per deselezionarlo. Usa la barra di ricerca per filtrare i tag per nome.</p>
|
||||||
|
|
||||||
|
<h3>Assegnazione Massiva di Tag</h3>
|
||||||
|
<p>Puoi assegnare o rimuovere tag a più elementi contemporaneamente:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Individui:</strong> seleziona gli individui nella tabella, apri <strong>Azioni → Assegna Tag</strong>, scegli Assegna/Rimuovi e seleziona i tag</li>
|
||||||
|
<li><strong>Gruppi:</strong> stessa procedura — seleziona i gruppi, <strong>Azioni → Assegna Tag</strong></li>
|
||||||
|
<li><strong>Documenti:</strong> seleziona i documenti, pulsante <strong>Tag</strong> nella toolbar</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Filtrare per Tag</h3>
|
||||||
|
<p>In ogni pagina elenco (Individui, Gruppi, Eventi, Documenti, Mailing List) trovi una barra con tutti i tag. Clicca su un tag per filtrare la tabella mostrando solo gli elementi con quel tag. Clicca <strong>Cancella filtri</strong> per rimuovere il filtro.</p>
|
||||||
|
|
||||||
|
<h3>Ricerca per Tag</h3>
|
||||||
|
<p>La sezione <strong>Ricerca per Tag</strong> (sidebar → Ricerca per Tag) permette di trovare elementi di qualsiasi tipo in base ai tag:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Tag singolo:</strong> seleziona un tag per vedere tutti gli elementi associati</li>
|
||||||
|
<li><strong>Tag multipli (AND):</strong> seleziona più tag (Ctrl+click) per trovare elementi che hanno <strong>tutti</strong> i tag selezionati — utile per ricerche precise (es. "animatore" + "giovane")</li>
|
||||||
|
</ul>
|
||||||
|
<p>I risultati sono raggruppati per tipo con info-box colorati e tabelle dettagliate.</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -44,6 +44,9 @@
|
|||||||
<a href="#ruoli" class="list-group-item list-group-item-action" data-toggle="tab">
|
<a href="#ruoli" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||||
<i class="nav-icon fas fa-users-cog mr-2"></i> Tipi di Ruolo
|
<i class="nav-icon fas fa-users-cog mr-2"></i> Tipi di Ruolo
|
||||||
</a>
|
</a>
|
||||||
|
<a href="#tag" class="list-group-item list-group-item-action" data-toggle="tab">
|
||||||
|
<i class="nav-icon fas fa-tags mr-2"></i> Tag
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1068,6 +1071,136 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- === TAG === --}}
|
||||||
|
<div class="tab-pane" id="tag">
|
||||||
|
<div class="card card-primary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted">Gestisci i tag (etichette) per classificare individui, gruppi, eventi e documenti.</p>
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
@if(session('error'))
|
||||||
|
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<button type="button" class="btn btn-success btn-sm" data-toggle="modal" data-target="#addTagModal">
|
||||||
|
<i class="fas fa-plus"></i> Nuovo Tag
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-striped" id="tag-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 40px;">Ordine</th>
|
||||||
|
<th>Tag</th>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Slug</th>
|
||||||
|
<th style="width: 100px;">Colore</th>
|
||||||
|
<th style="width: 150px;">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tag-sortable">
|
||||||
|
@foreach($tags as $tag)
|
||||||
|
<tr data-id="{{ $tag->id }}">
|
||||||
|
<td class="text-center">
|
||||||
|
<i class="fas fa-arrows-alt handle" style="cursor: grab;"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">
|
||||||
|
{{ $tag->name }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td><span class="tag-nome">{{ $tag->name }}</span></td>
|
||||||
|
<td><code>{{ $tag->slug }}</code></td>
|
||||||
|
<td>
|
||||||
|
<span class="tag-color-preview" style="display:inline-block;width:24px;height:24px;border-radius:4px;background:{{ $tag->color ?? '#6c757d' }};border:1px solid #dee2e6;vertical-align:middle;"></span>
|
||||||
|
<span class="tag-color-value">{{ $tag->color ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-xs btn-warning" onclick="editTag({{ $tag->id }}, '{{ addslashes($tag->name) }}', '{{ $tag->color ?? '' }}')">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<form method="POST" action="{{ route('impostazioni.tag.destroy', $tag->id) }}" style="display: inline;">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questo tag?')">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ===== MODALI TAG ===== --}}
|
||||||
|
<div class="modal fade" id="addTagModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST" action="{{ route('impostazioni.tag.store') }}">
|
||||||
|
@csrf
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Nuovo Tag</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tagName">Nome *</label>
|
||||||
|
<input type="text" name="name" id="tagName" class="form-control" required maxlength="100" placeholder="es. giovani">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tagColor">Colore</label>
|
||||||
|
<input type="color" name="color" id="tagColor" class="form-control" style="height:38px;padding:4px;" value="#007bff">
|
||||||
|
<small class="text-muted">Scegli un colore per il badge del tag</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Salva</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="editTagModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST" action="" id="editTagForm">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Modifica Tag</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="editTagName">Nome *</label>
|
||||||
|
<input type="text" name="name" id="editTagName" class="form-control" required maxlength="100">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="editTagColor">Colore</label>
|
||||||
|
<input type="color" name="color" id="editTagColor" class="form-control" style="height:38px;padding:4px;">
|
||||||
|
<small class="text-muted">Scegli un colore per il badge del tag</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Salva</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1618,6 +1751,32 @@ var sortableTipologieEventi = Sortable.create(document.getElementById('tipologie
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function editTag(id, name, color) {
|
||||||
|
$('#editTagName').val(name);
|
||||||
|
$('#editTagColor').val(color || '#007bff');
|
||||||
|
$('#editTagForm').attr('action', '/impostazioni/tag/' + id);
|
||||||
|
$('#editTagModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortableTag = Sortable.create(document.getElementById('tag-sortable'), {
|
||||||
|
handle: '.handle',
|
||||||
|
animation: 150,
|
||||||
|
onEnd: function(evt) {
|
||||||
|
var order = [];
|
||||||
|
$('#tag-sortable tr').each(function() {
|
||||||
|
order.push($(this).data('id'));
|
||||||
|
});
|
||||||
|
fetch('{{ route('impostazioni.tag.reorder') }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ order: order })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||||
var target = $(e.target).attr('href');
|
var target = $(e.target).attr('href');
|
||||||
if (target === '#ruoli') {
|
if (target === '#ruoli') {
|
||||||
@@ -1626,6 +1785,9 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
|||||||
if (target === '#tipologie-eventi') {
|
if (target === '#tipologie-eventi') {
|
||||||
sortableTipologieEventi.refresh();
|
sortableTipologieEventi.refresh();
|
||||||
}
|
}
|
||||||
|
if (target === '#tag') {
|
||||||
|
sortableTag.refresh();
|
||||||
|
}
|
||||||
if (target === '#calendario') {
|
if (target === '#calendario') {
|
||||||
if (typeof sortableCalendario !== 'undefined') {
|
if (typeof sortableCalendario !== 'undefined') {
|
||||||
sortableCalendario.refresh();
|
sortableCalendario.refresh();
|
||||||
|
|||||||
@@ -108,6 +108,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
||||||
|
|||||||
@@ -139,6 +139,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@include('partials._tag-selector', ['selectedTags' => $selectedTags ?? [], 'label' => 'Etichette'])
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
<h3 class="card-title"><i class="fas fa-address-book mr-2"></i>Contatti</h3>
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ $columnLabels = [
|
|||||||
'nome' => 'Nome',
|
'nome' => 'Nome',
|
||||||
'email' => 'Email',
|
'email' => 'Email',
|
||||||
'telefono' => 'Telefono',
|
'telefono' => 'Telefono',
|
||||||
|
'tag' => 'Tag',
|
||||||
];
|
];
|
||||||
$vistaDefaultJson = $vista ? $vista->toJson() : 'null';
|
$vistaDefaultJson = $vista ? $vista->toJson() : 'null';
|
||||||
$allColumnsJson = json_encode($allColumns);
|
$allColumnsJson = json_encode($allColumns);
|
||||||
$visibleColumnsJson = json_encode($visibleColumns);
|
$visibleColumnsJson = json_encode($visibleColumns);
|
||||||
$canWriteIndividui = Auth::user()->canManage('individui');
|
$canWriteIndividui = Auth::user()->canManage('individui');
|
||||||
$canDeleteIndividui = Auth::user()->canDelete('individui');
|
$canDeleteIndividui = Auth::user()->canDelete('individui');
|
||||||
|
$entityType = $entityType ?? 'individui';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@@ -22,6 +24,9 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-users mr-2"></i>Lista Individui</h3>
|
<h3 class="card-title"><i class="fas fa-users mr-2"></i>Lista Individui</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-info btn-sm mr-1" onclick="toggleSearchPanel()">
|
<button type="button" class="btn btn-info btn-sm mr-1" onclick="toggleSearchPanel()">
|
||||||
<i class="fas fa-search mr-1"></i> Ricerca/Filtri
|
<i class="fas fa-search mr-1"></i> Ricerca/Filtri
|
||||||
</button>
|
</button>
|
||||||
@@ -47,6 +52,12 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
<a class="dropdown-item" href="#" onclick="showCreateMailingListModal(); return false;">
|
<a class="dropdown-item" href="#" onclick="showCreateMailingListModal(); return false;">
|
||||||
<i class="fas fa-list mr-2"></i>Crea Mailing List
|
<i class="fas fa-list mr-2"></i>Crea Mailing List
|
||||||
</a>
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" onclick="showMassGruppoModal(); return false;">
|
||||||
|
<i class="fas fa-users mr-2"></i>Assegna Gruppo
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" href="#" onclick="showMassTagModal(); return false;">
|
||||||
|
<i class="fas fa-tags mr-2"></i>Assegna Tag
|
||||||
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item text-danger" href="#" onclick="deleteSelected(); return false;">
|
<a class="dropdown-item text-danger" href="#" onclick="deleteSelected(); return false;">
|
||||||
<i class="fas fa-trash mr-2"></i>Elimina Selezionati
|
<i class="fas fa-trash mr-2"></i>Elimina Selezionati
|
||||||
@@ -54,9 +65,6 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary btn-sm" onclick="showSaveVistaModal()">
|
|
||||||
<i class="fas fa-save mr-1"></i> Salva Vista
|
|
||||||
</button>
|
|
||||||
@if($canWriteIndividui)
|
@if($canWriteIndividui)
|
||||||
<a href="{{ route('individui.import') }}" class="btn btn-warning btn-sm ml-2">
|
<a href="{{ route('individui.import') }}" class="btn btn-warning btn-sm ml-2">
|
||||||
<i class="fas fa-file-import mr-1"></i> Importa
|
<i class="fas fa-file-import mr-1"></i> Importa
|
||||||
@@ -124,12 +132,18 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials._tag-filter-bar', ['filterTags' => $allTags])
|
||||||
|
|
||||||
|
@include('partials.table-settings')
|
||||||
|
|
||||||
|
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
|
||||||
|
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
@if($individui->count() > 0)
|
@if($individui->count() > 0)
|
||||||
<table class="table table-bordered table-hover table-striped mb-0" id="individui-table">
|
<table class="table table-bordered table-hover table-striped mb-0" id="individui-table">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px;">
|
<th style="width: 40px;" data-column="select">
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
||||||
<label class="custom-control-label" for="select-all"></label>
|
<label class="custom-control-label" for="select-all"></label>
|
||||||
@@ -144,13 +158,49 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
@if(in_array('nome', $visibleColumns))
|
@if(in_array('nome', $visibleColumns))
|
||||||
<th data-sortable="true" data-column="nome" onclick="sortTable('nome')">Nome <i class="fas fa-sort float-right"></i></th>
|
<th data-sortable="true" data-column="nome" onclick="sortTable('nome')">Nome <i class="fas fa-sort float-right"></i></th>
|
||||||
@endif
|
@endif
|
||||||
|
@if(in_array('data_nascita', $visibleColumns))
|
||||||
|
<th data-column="data_nascita">Data Nascita</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('genere', $visibleColumns))
|
||||||
|
<th data-column="genere">Genere</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('indirizzo', $visibleColumns))
|
||||||
|
<th data-column="indirizzo">Indirizzo</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('cap', $visibleColumns))
|
||||||
|
<th data-column="cap">CAP</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('citta', $visibleColumns))
|
||||||
|
<th data-column="citta">Città</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('provincia', $visibleColumns))
|
||||||
|
<th data-column="provincia">Prov.</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('tipo_documento', $visibleColumns))
|
||||||
|
<th data-column="tipo_documento">Tipo Doc.</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('numero_documento', $visibleColumns))
|
||||||
|
<th data-column="numero_documento">N. Documento</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('scadenza_documento', $visibleColumns))
|
||||||
|
<th data-column="scadenza_documento">Scad. Doc.</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('note', $visibleColumns))
|
||||||
|
<th data-column="note">Note</th>
|
||||||
|
@endif
|
||||||
|
@if(in_array('created_at', $visibleColumns))
|
||||||
|
<th data-column="created_at">Creato il</th>
|
||||||
|
@endif
|
||||||
@if(in_array('email', $visibleColumns))
|
@if(in_array('email', $visibleColumns))
|
||||||
<th data-sortable="true" data-column="email" onclick="sortTable('email')">Email <i class="fas fa-sort float-right"></i></th>
|
<th data-sortable="true" data-column="email" onclick="sortTable('email')">Email <i class="fas fa-sort float-right"></i></th>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('telefono', $visibleColumns))
|
@if(in_array('telefono', $visibleColumns))
|
||||||
<th data-sortable="true" data-column="telefono" onclick="sortTable('telefono')">Telefono <i class="fas fa-sort float-right"></i></th>
|
<th data-sortable="true" data-column="telefono" onclick="sortTable('telefono')">Telefono <i class="fas fa-sort float-right"></i></th>
|
||||||
@endif
|
@endif
|
||||||
<th style="width: 120px;">Azioni</th>
|
@if(in_array('tag', $visibleColumns))
|
||||||
|
<th data-column="tag">Tag</th>
|
||||||
|
@endif
|
||||||
|
<th style="width: 120px;" data-column="azioni">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="table-body">
|
<tbody id="table-body">
|
||||||
@@ -175,12 +225,52 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
<a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->nome }}</a>
|
<a href="{{ url('/individui/' . $ind->id . '/edit') }}">{{ $ind->nome }}</a>
|
||||||
</td>
|
</td>
|
||||||
@endif
|
@endif
|
||||||
|
@if(in_array('data_nascita', $visibleColumns))
|
||||||
|
<td>{{ $ind->data_nascita ? $ind->data_nascita->format('d/m/Y') : '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('genere', $visibleColumns))
|
||||||
|
<td>{{ $ind->genere ?? '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('indirizzo', $visibleColumns))
|
||||||
|
<td>{{ $ind->indirizzo ?? '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('cap', $visibleColumns))
|
||||||
|
<td>{{ $ind->cap ?? '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('citta', $visibleColumns))
|
||||||
|
<td>{{ $ind->città ?? '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('provincia', $visibleColumns))
|
||||||
|
<td>{{ $ind->sigla_provincia ?? '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('tipo_documento', $visibleColumns))
|
||||||
|
<td>{{ $ind->tipo_documento ? str_replace(['carta_identita', 'patente'], ['C.I.', 'Patente'], $ind->tipo_documento) : '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('numero_documento', $visibleColumns))
|
||||||
|
<td>{{ $ind->numero_documento ?? '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('scadenza_documento', $visibleColumns))
|
||||||
|
<td>{{ $ind->scadenza_documento ? $ind->scadenza_documento->format('d/m/Y') : '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('note', $visibleColumns))
|
||||||
|
<td class="text-truncate" style="max-width: 200px;">{{ $ind->note ? Str::limit($ind->note, 50) : '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('created_at', $visibleColumns))
|
||||||
|
<td>{{ $ind->created_at ? $ind->created_at->format('d/m/Y') : '-' }}</td>
|
||||||
|
@endif
|
||||||
@if(in_array('email', $visibleColumns))
|
@if(in_array('email', $visibleColumns))
|
||||||
<td>{{ $ind->getEmailPrimariaAttribute() ?: '-' }}</td>
|
<td>{{ $ind->getEmailPrimariaAttribute() ?: '-' }}</td>
|
||||||
@endif
|
@endif
|
||||||
@if(in_array('telefono', $visibleColumns))
|
@if(in_array('telefono', $visibleColumns))
|
||||||
<td>{{ $ind->getTelefonoPrimarioAttribute() ?: '-' }}</td>
|
<td>{{ $ind->getTelefonoPrimarioAttribute() ?: '-' }}</td>
|
||||||
@endif
|
@endif
|
||||||
|
@if(in_array('tag', $visibleColumns))
|
||||||
|
<td>
|
||||||
|
@foreach($ind->tags as $tag)
|
||||||
|
<a href="{{ route('individui.index', ['tag[]' => $tag->slug]) }}" class="badge" style="background-color: {{ $tag->color ?? '#6c757d' }}; color: #fff;">{{ $tag->name }}</a>
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
<td>
|
<td>
|
||||||
@if($canWriteIndividui)
|
@if($canWriteIndividui)
|
||||||
<a href="{{ url('/individui/' . $ind->id . '/edit') }}" class="btn btn-xs btn-warning" title="Modifica">
|
<a href="{{ url('/individui/' . $ind->id . '/edit') }}" class="btn btn-xs btn-warning" title="Modifica">
|
||||||
@@ -247,75 +337,7 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="saveVistaModal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title"><i class="fas fa-save mr-2"></i>Salva Vista</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span>×</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>×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row">
|
|
||||||
@foreach($allColumns as $col)
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="custom-control custom-checkbox">
|
|
||||||
<input type="checkbox" class="custom-control-input col-toggle" id="col-toggle-{{ $col['key'] }}" value="{{ $col['key'] }}" {{ in_array($col['key'], $visibleColumns) ? 'checked' : '' }} onchange="toggleColumn('{{ $col['key'] }}', this.checked)">
|
|
||||||
<label class="custom-control-label" for="col-toggle-{{ $col['key'] }}">{{ $col['label'] }}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
||||||
@if($canDeleteIndividui)
|
@if($canDeleteIndividui)
|
||||||
@@ -415,9 +437,92 @@ $canDeleteIndividui = Auth::user()->canDelete('individui');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- MODAL: Assegna Gruppo massivo --}}
|
||||||
|
<div class="modal fade" id="massGruppoModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-info">
|
||||||
|
<h5 class="modal-title text-white"><i class="fas fa-users mr-2"></i>Assegna Gruppo</h5>
|
||||||
|
<button type="button" class="close text-white" data-dismiss="modal"><span>×</span></button>
|
||||||
|
</div>
|
||||||
|
<form id="massGruppoForm" method="POST" action="/individui/mass-gruppo">
|
||||||
|
@csrf
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="mb-2">Operazione su <strong id="massGruppoCount">0</strong> individui selezionati.</p>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="font-weight-bold">Azione</label>
|
||||||
|
<div class="d-flex" style="gap:1.5rem;">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mgModeAssign" value="assign" checked>
|
||||||
|
<label class="form-check-label" for="mgModeAssign">Assegna a gruppo</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="mgModeRemove" value="remove">
|
||||||
|
<label class="form-check-label" for="mgModeRemove">Rimuovi da gruppo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Seleziona Gruppi</label>
|
||||||
|
<select name="gruppi[]" class="form-control" multiple size="8">
|
||||||
|
@foreach($gruppi as $g)
|
||||||
|
<option value="{{ $g->id }}">{{ $g->nome }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Seleziona con Ctrl/Cmd per scegliere più gruppi</small>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="ids" id="massGruppoIds" value="">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fas fa-users mr-1"></i> Applica</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- MODAL: Gestione Tag massiva --}}
|
||||||
|
<div class="modal fade" id="massTagModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-info">
|
||||||
|
<h5 class="modal-title text-white"><i class="fas fa-tags mr-2"></i>Gestione Tag</h5>
|
||||||
|
<button type="button" class="close text-white" data-dismiss="modal"><span>×</span></button>
|
||||||
|
</div>
|
||||||
|
<form id="massTagForm" method="POST" action="/individui/mass-tag">
|
||||||
|
@csrf
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="mb-2">Operazione su <strong id="massTagCount">0</strong> individui selezionati.</p>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="font-weight-bold">Azione</label>
|
||||||
|
<div class="d-flex" style="gap:1.5rem;">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="indMtModeAssign" value="assign" checked>
|
||||||
|
<label class="form-check-label" for="indMtModeAssign">Assegna tag</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="radio" name="mode" id="indMtModeRemove" value="remove">
|
||||||
|
<label class="form-check-label" for="indMtModeRemove">Rimuovi tag</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@include('partials._tag-selector', ['label' => 'Seleziona tag'])
|
||||||
|
<input type="hidden" name="ids" id="massTagIds" value="">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fas fa-tags mr-1"></i> Applica</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script src="{{ asset('js/column-manager.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
let currentSort = { column: null, direction: 'asc' };
|
let currentSort = { column: null, direction: 'asc' };
|
||||||
let columnVisibility = {!! json_encode(array_column($allColumns, 'key')) !!};
|
let columnVisibility = {!! json_encode(array_column($allColumns, 'key')) !!};
|
||||||
@@ -436,6 +541,16 @@ function applyGlobalSearch(value) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getColumnIndex(colKey) {
|
||||||
|
const ths = document.querySelectorAll('#individui-table thead th');
|
||||||
|
let idx = 0;
|
||||||
|
for (const th of ths) {
|
||||||
|
if (th.dataset.column === colKey) return idx;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
function applyColumnFilter() {
|
function applyColumnFilter() {
|
||||||
const col = document.getElementById('filter-column').value;
|
const col = document.getElementById('filter-column').value;
|
||||||
const op = document.getElementById('filter-operator').value;
|
const op = document.getElementById('filter-operator').value;
|
||||||
@@ -444,7 +559,8 @@ function applyColumnFilter() {
|
|||||||
if (!col || !val) return;
|
if (!col || !val) return;
|
||||||
|
|
||||||
const rows = document.querySelectorAll('#table-body tr');
|
const rows = document.querySelectorAll('#table-body tr');
|
||||||
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, azioni: 6 }[col];
|
const colIndex = getColumnIndex(col);
|
||||||
|
if (colIndex < 0) return;
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const cells = row.querySelectorAll('td');
|
const cells = row.querySelectorAll('td');
|
||||||
@@ -476,7 +592,7 @@ function sortTable(column) {
|
|||||||
currentSort = { column: column, direction: 'asc' };
|
currentSort = { column: column, direction: 'asc' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const colIndex = { codice: 0, cognome: 1, nome: 2, email: 3, telefono: 4, azioni: 5 }[column];
|
const colIndex = getColumnIndex(column);
|
||||||
const tbody = document.getElementById('table-body');
|
const tbody = document.getElementById('table-body');
|
||||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||||
|
|
||||||
@@ -499,63 +615,7 @@ function sortTable(column) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleColumn(column, visible) {
|
|
||||||
const colIndex = { codice: 1, cognome: 2, nome: 3, email: 4, telefono: 5, azioni: 6 }[column];
|
|
||||||
const ths = document.querySelectorAll('#individui-table thead th');
|
|
||||||
const rows = document.querySelectorAll('#table-body tr');
|
|
||||||
|
|
||||||
if (visible) {
|
|
||||||
ths[colIndex].style.display = '';
|
|
||||||
rows.forEach(row => {
|
|
||||||
row.querySelectorAll('td')[colIndex].style.display = '';
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ths[colIndex].style.display = 'none';
|
|
||||||
rows.forEach(row => {
|
|
||||||
row.querySelectorAll('td')[colIndex].style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveVista() {
|
|
||||||
let nome = document.getElementById('vista-nome').value.trim();
|
|
||||||
if (!nome) {
|
|
||||||
const now = new Date();
|
|
||||||
nome = 'Vista ' + now.toLocaleDateString('it-IT') + ' ' + now.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const colonneVisibili = Array.from(document.querySelectorAll('#colonne-checkboxes input:checked')).map(i => i.value);
|
|
||||||
const isDefault = document.getElementById('vista-default').checked;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
nome: nome,
|
|
||||||
tipo: 'individui',
|
|
||||||
colonne_visibili: colonneVisibili,
|
|
||||||
colonne_ordinamento: currentSort.column ? [[currentSort.column, currentSort.direction]] : [],
|
|
||||||
ricerca: document.getElementById('global-search').value,
|
|
||||||
is_default: isDefault
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/viste', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
alert('Vista salvata!');
|
|
||||||
$('#saveVistaModal').modal('hide');
|
|
||||||
} else {
|
|
||||||
alert('Errore salvataggio');
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
alert('Errore: ' + e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSelectAll(source) {
|
function toggleSelectAll(source) {
|
||||||
const checkboxes = document.querySelectorAll('.row-checkbox');
|
const checkboxes = document.querySelectorAll('.row-checkbox');
|
||||||
@@ -762,24 +822,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
document.head.appendChild(meta);
|
document.head.appendChild(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleCols = {!! $visibleColumnsJson !!};
|
|
||||||
const allCols = {!! $allColumnsJson !!};
|
|
||||||
|
|
||||||
allCols.forEach(function(col) {
|
|
||||||
const visibile = visibleCols.includes(col.key);
|
|
||||||
const saveCheckbox = document.querySelector(`#col-vis-${col.key}`);
|
|
||||||
const toggleCheckbox = document.querySelector(`#col-toggle-${col.key}`);
|
|
||||||
if (saveCheckbox) saveCheckbox.checked = visibile;
|
|
||||||
if (toggleCheckbox) toggleCheckbox.checked = visibile;
|
|
||||||
});
|
|
||||||
|
|
||||||
const vistaDefault = {!! $vistaDefaultJson !!};
|
const vistaDefault = {!! $vistaDefaultJson !!};
|
||||||
if (vistaDefault && vistaDefault.ricerca) {
|
if (vistaDefault && vistaDefault.ricerca) {
|
||||||
document.getElementById('global-search').value = vistaDefault.ricerca;
|
document.getElementById('global-search').value = vistaDefault.ricerca;
|
||||||
applyGlobalSearch(vistaDefault.ricerca);
|
applyGlobalSearch(vistaDefault.ricerca);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const table = document.getElementById('individui-table');
|
||||||
|
if (table) {
|
||||||
|
window.columnManager = new ColumnManager('individui-table', {
|
||||||
|
entityType: 'individui',
|
||||||
|
storageKey: 'cm_individui',
|
||||||
|
resizable: true,
|
||||||
|
reorderable: true,
|
||||||
|
});
|
||||||
|
initTableSettings();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.printWithCurrentSettings = function () {
|
||||||
|
if (window.columnManager) {
|
||||||
|
window.columnManager.printWithSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function showCreateMailingListModal() {
|
function showCreateMailingListModal() {
|
||||||
document.getElementById('ml-nome').value = '';
|
document.getElementById('ml-nome').value = '';
|
||||||
document.getElementById('ml-descrizione').value = '';
|
document.getElementById('ml-descrizione').value = '';
|
||||||
@@ -890,6 +956,28 @@ function updateMlCheckboxEmail(id, email) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showMassGruppoModal() {
|
||||||
|
const ids = getSelectedIds();
|
||||||
|
if (ids.length === 0) {
|
||||||
|
alert('Seleziona almeno un individuo');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('massGruppoIds').value = ids.join(',');
|
||||||
|
document.getElementById('massGruppoCount').textContent = ids.length;
|
||||||
|
$('#massGruppoModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMassTagModal() {
|
||||||
|
const ids = getSelectedIds();
|
||||||
|
if (ids.length === 0) {
|
||||||
|
alert('Seleziona almeno un individuo');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('massTagIds').value = ids.join(',');
|
||||||
|
document.getElementById('massTagCount').textContent = ids.length;
|
||||||
|
$('#massTagModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
async function createMailingList() {
|
async function createMailingList() {
|
||||||
const nome = document.getElementById('ml-nome').value.trim();
|
const nome = document.getElementById('ml-nome').value.trim();
|
||||||
if (!nome) {
|
if (!nome) {
|
||||||
|
|||||||
@@ -88,6 +88,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
@forelse($individuo->tags as $tag)
|
||||||
|
<span class="badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;padding:4px 10px;border-radius:12px;font-size:0.85rem;">{{ $tag->name }}</span>
|
||||||
|
@empty
|
||||||
|
<p class="text-muted mb-0"><i class="fas fa-info-circle mr-1"></i>Nessun tag assegnato.</p>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if($individuo->note)
|
@if($individuo->note)
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
|||||||
@@ -199,6 +199,14 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
@if(Auth::user()->canAccess('individui'))
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{ route('ricerca.index') }}" class="nav-link {{ request()->routeIs('ricerca.*') ? 'active' : '' }}">
|
||||||
|
<i class="nav-icon fas fa-tags"></i>
|
||||||
|
<p>Ricerca per Tag</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
@if(Auth::user()->canAccess('report'))
|
@if(Auth::user()->canAccess('report'))
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ route('report.index') }}" class="nav-link {{ request()->routeIs('report.*') ? 'active' : '' }}">
|
<a href="{{ route('report.index') }}" class="nav-link {{ request()->routeIs('report.*') ? 'active' : '' }}">
|
||||||
@@ -294,6 +302,7 @@ $appName = \App\Models\AppSetting::getAppName() ?? 'Glastree';
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
@stack('scripts')
|
||||||
@yield('scripts')
|
@yield('scripts')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
<label>Descrizione</label>
|
<label>Descrizione</label>
|
||||||
<textarea name="descrizione" class="form-control" rows="2"></textarea>
|
<textarea name="descrizione" class="form-control" rows="2"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
@include('partials._tag-selector', ['allTags' => $tags ?? collect(), 'selectedTags' => old('tags', []), 'label' => 'Tag'])
|
||||||
|
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="fas fa-info-circle mr-1"></i>
|
<i class="fas fa-info-circle mr-1"></i>
|
||||||
<strong>Come aggiungere contatti:</strong> vai su <em>Individui</em>, seleziona le righe desired, e usa il menu <em>Azioni > Crea Mailing List</em> per creare una lista dai contatti selezionati.
|
<strong>Come aggiungere contatti:</strong> vai su <em>Individui</em>, seleziona le righe desired, e usa il menu <em>Azioni > Crea Mailing List</em> per creare una lista dai contatti selezionati.
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
<textarea name="descrizione" class="form-control" rows="2">{{ $mailingList->descrizione }}</textarea>
|
<textarea name="descrizione" class="form-control" rows="2">{{ $mailingList->descrizione }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials._tag-selector', ['allTags' => $tags ?? collect(), 'selectedTags' => $selectedTags ?? [], 'label' => 'Tag'])
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -64,6 +66,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="no-email-warning" class="alert alert-warning alert-dismissible" role="alert" style="display:none;">
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||||
|
<strong>Attenzione:</strong> I seguenti individui non hanno un indirizzo email nei contatti:
|
||||||
|
<ul id="no-email-list" class="mb-0 mt-1"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Contatti da includere (<span id="contatti-count">{{ $mailingList->contatti->count() }}</span>)</label>
|
<label>Contatti da includere (<span id="contatti-count">{{ $mailingList->contatti->count() }}</span>)</label>
|
||||||
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
||||||
@@ -185,11 +194,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.error('Errore individui:', err);
|
console.error('Errore individui:', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
contattiCaricati = Array.from(document.querySelectorAll('.contatto-checkbox')).map(cb => ({
|
contattiCaricati = Array.from(document.querySelectorAll('.contatto-checkbox')).map(cb => {
|
||||||
individuo_id: parseInt(cb.value),
|
const row = cb.closest('tr');
|
||||||
email: cb.dataset.email,
|
return {
|
||||||
existing: true
|
individuo_id: parseInt(cb.value),
|
||||||
}));
|
email: cb.dataset.email || '',
|
||||||
|
existing: true,
|
||||||
|
codice_id: row ? (row.querySelector('td:nth-child(2)')?.textContent?.trim() || '') : '',
|
||||||
|
cognome: row ? (row.querySelector('td:nth-child(3)')?.textContent?.trim() || '') : '',
|
||||||
|
nome: row ? (row.querySelector('td:nth-child(4)')?.textContent?.trim() || '') : ''
|
||||||
|
};
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';
|
||||||
@@ -217,17 +232,15 @@ function caricaSelezionati() {
|
|||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
data.forEach(function(ind) {
|
data.forEach(function(ind) {
|
||||||
if (ind.emails && ind.emails.length > 0) {
|
if (!contattiCaricati.find(c => c.individuo_id === ind.id)) {
|
||||||
if (!contattiCaricati.find(c => c.individuo_id === ind.id)) {
|
contattiCaricati.push({
|
||||||
contattiCaricati.push({
|
individuo_id: ind.id,
|
||||||
individuo_id: ind.id,
|
codice_id: ind.codice_id,
|
||||||
codice_id: ind.codice_id,
|
cognome: ind.cognome,
|
||||||
cognome: ind.cogname,
|
nome: ind.nome,
|
||||||
nome: ind.nome,
|
email: (ind.emails && ind.emails.length > 0) ? ind.emails[0] : '',
|
||||||
email: ind.emails[0],
|
origini: ['Gruppo']
|
||||||
origini: ['Gruppo']
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -246,17 +259,15 @@ function caricaSelezionati() {
|
|||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
data.forEach(function(ind) {
|
data.forEach(function(ind) {
|
||||||
if (ind.emails && ind.emails.length > 0) {
|
if (!contattiCaricati.find(c => c.individuo_id === ind.id)) {
|
||||||
if (!contattiCaricati.find(c => c.individuo_id === ind.id)) {
|
contattiCaricati.push({
|
||||||
contattiCaricati.push({
|
individuo_id: ind.id,
|
||||||
individuo_id: ind.id,
|
codice_id: ind.codice_id,
|
||||||
codice_id: ind.codice_id,
|
cognome: ind.cognome,
|
||||||
cognome: ind.cogname,
|
nome: ind.nome,
|
||||||
nome: ind.nome,
|
email: (ind.emails && ind.emails.length > 0) ? ind.emails[0] : '',
|
||||||
email: ind.emails[0],
|
origini: [' Individuo']
|
||||||
origini: [' Individuo']
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -327,25 +338,87 @@ function deselezionaTutti() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('ml-form').addEventListener('submit', function(e) {
|
document.getElementById('ml-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const form = this;
|
||||||
const selected = [];
|
const selected = [];
|
||||||
|
|
||||||
document.querySelectorAll('.contatto-checkbox:checked').forEach(function(cb) {
|
document.querySelectorAll('.contatto-checkbox:checked').forEach(function(cb) {
|
||||||
|
const row = cb.closest('tr');
|
||||||
selected.push({
|
selected.push({
|
||||||
individuo_id: parseInt(cb.value),
|
individuo_id: parseInt(cb.value),
|
||||||
email: cb.dataset.email
|
email: cb.dataset.email || '',
|
||||||
|
_label: row
|
||||||
|
? (row.querySelector('td:nth-child(2)')?.textContent?.trim() || '') + ' - ' +
|
||||||
|
(row.querySelector('td:nth-child(3)')?.textContent?.trim() || '') + ' ' +
|
||||||
|
(row.querySelector('td:nth-child(4)')?.textContent?.trim() || '')
|
||||||
|
: 'ID ' + cb.value
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const indSelect = document.getElementById('individui-select');
|
||||||
|
if (indSelect) {
|
||||||
|
Array.from(indSelect.selectedOptions).forEach(function(opt) {
|
||||||
|
const id = parseInt(opt.value);
|
||||||
|
if (!selected.find(function(s) { return s.individuo_id === id; })) {
|
||||||
|
selected.push({
|
||||||
|
individuo_id: id,
|
||||||
|
email: '',
|
||||||
|
_label: opt.textContent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (selected.length === 0) {
|
if (selected.length === 0) {
|
||||||
e.preventDefault();
|
const gruppiSelect = document.getElementById('gruppi-select');
|
||||||
alert('Seleziona almeno un contatto con email');
|
if (gruppiSelect && gruppiSelect.selectedOptions.length > 0) {
|
||||||
|
alert('Devi cliccare "Carica contatti" per caricare i membri dei gruppi prima di salvare.');
|
||||||
|
} else {
|
||||||
|
alert('Seleziona almeno un contatto.');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const input = document.createElement('input');
|
function completaInvio() {
|
||||||
input.type = 'hidden';
|
const input = document.createElement('input');
|
||||||
input.name = 'contatti_json';
|
input.type = 'hidden';
|
||||||
input.value = JSON.stringify(selected);
|
input.name = 'contatti_json';
|
||||||
this.appendChild(input);
|
input.value = JSON.stringify(selected.map(function(s) {
|
||||||
|
return { individuo_id: s.individuo_id, email: s.email };
|
||||||
|
}));
|
||||||
|
form.appendChild(input);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const senzaEmail = selected.filter(function(s) { return !s.email; });
|
||||||
|
|
||||||
|
if (senzaEmail.length > 0) {
|
||||||
|
const elencoHtml = '<ul class="text-left mb-0">' +
|
||||||
|
senzaEmail.map(function(s) {
|
||||||
|
return '<li>' + s._label + '</li>';
|
||||||
|
}).join('') +
|
||||||
|
'</ul>';
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Contatti senza email',
|
||||||
|
html: '<p class="mb-2">I seguenti ' + senzaEmail.length + ' contatti non hanno un indirizzo email:</p>' +
|
||||||
|
elencoHtml +
|
||||||
|
'<p class="mt-3 mb-0 text-muted">La email verrà risolta automaticamente quando l\'individuo verrà aggiornato. Salvare comunque?</p>',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Salva lo stesso',
|
||||||
|
cancelButtonText: 'Annulla',
|
||||||
|
confirmButtonColor: '#28a745',
|
||||||
|
cancelButtonColor: '#6c757d'
|
||||||
|
}).then(function(result) {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
completaInvio();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
completaInvio();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
@php
|
@php
|
||||||
$canWriteMailing = Auth::user()->canManage('mailing');
|
$canWriteMailing = Auth::user()->canManage('mailing');
|
||||||
$canDeleteMailing = Auth::user()->canDelete('mailing');
|
$canDeleteMailing = Auth::user()->canDelete('mailing');
|
||||||
|
$vistaDefaultJson = isset($vista) ? $vista->toJson() : 'null';
|
||||||
|
$visibleColumnsJson = json_encode($visibleColumns ?? []);
|
||||||
|
$tableColumnsJson = json_encode($tableColumns ?? []);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@@ -20,6 +23,9 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-list mr-2"></i>Elenco Mailing Lists</h3>
|
<h3 class="card-title"><i class="fas fa-list mr-2"></i>Elenco Mailing Lists</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary mr-1" onclick="$('#table-settings-modal').modal('show')" title="Impostazioni tabella">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
@if($canDeleteMailing)
|
@if($canDeleteMailing)
|
||||||
<div class="btn-group mr-2">
|
<div class="btn-group mr-2">
|
||||||
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()">
|
<button type="button" class="btn btn-sm btn-danger" onclick="deleteSelected()">
|
||||||
@@ -34,12 +40,13 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@include('partials._tag-filter-bar', ['filterTags' => $allTags ?? collect()])
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
@if($mailingLists->count() > 0)
|
@if($mailingLists->count() > 0)
|
||||||
<table class="table table-bordered table-hover table-striped mb-0">
|
<table class="table table-bordered table-hover table-striped mb-0" id="mailing-liste-table">
|
||||||
<thead class="thead-light">
|
<thead class="thead-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px;">
|
<th style="width: 40px;" data-column="select">
|
||||||
@if($canDeleteMailing)
|
@if($canDeleteMailing)
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
<input type="checkbox" class="custom-control-input" id="select-all" onchange="toggleSelectAll(this)">
|
||||||
@@ -47,11 +54,24 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</th>
|
</th>
|
||||||
<th>Nome</th>
|
@if(in_array('nome', $visibleColumns))
|
||||||
<th>Descrizione</th>
|
<th data-column="nome">Nome</th>
|
||||||
<th style="width: 100px;">Contatti</th>
|
@endif
|
||||||
<th style="width: 100px;">Stato</th>
|
@if(in_array('descrizione', $visibleColumns))
|
||||||
<th style="width: 130px;">Creata il</th>
|
<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>
|
<th style="width: 100px;">Azioni</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -66,13 +86,32 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@if(in_array('nome', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ route('mailing-liste.show', $lista->id) }}">{{ $lista->nome }}</a>
|
<a href="{{ route('mailing-liste.show', $lista->id) }}">{{ $lista->nome }}</a>
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('descrizione', $visibleColumns))
|
||||||
<td>{{ $lista->descrizione ?: '-' }}</td>
|
<td>{{ $lista->descrizione ?: '-' }}</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('contatti_count', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-info">{{ $lista->contatti->count() }}</span>
|
<span class="badge badge-info">{{ $lista->contatti->count() }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('tag', $visibleColumns))
|
||||||
|
<td>
|
||||||
|
@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;">
|
||||||
|
{{ $tag->name }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
@if($lista->tags->isEmpty())
|
||||||
|
<span class="text-muted small">-</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('attiva', $visibleColumns))
|
||||||
<td>
|
<td>
|
||||||
@if($lista->attiva)
|
@if($lista->attiva)
|
||||||
<span class="badge badge-success">Attiva</span>
|
<span class="badge badge-success">Attiva</span>
|
||||||
@@ -80,7 +119,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
<span class="badge badge-secondary">Disattiva</span>
|
<span class="badge badge-secondary">Disattiva</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
@endif
|
||||||
|
@if(in_array('created_at', $visibleColumns))
|
||||||
<td>{{ $lista->created_at->format('d/m/Y') }}</td>
|
<td>{{ $lista->created_at->format('d/m/Y') }}</td>
|
||||||
|
@endif
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ route('mailing-liste.show', $lista->id) }}" class="btn btn-xs btn-info" title="Visualizza">
|
<a href="{{ route('mailing-liste.show', $lista->id) }}" class="btn btn-xs btn-info" title="Visualizza">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
@@ -113,6 +155,10 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('partials.table-settings')
|
||||||
|
|
||||||
|
<div id="vista-data" style="display:none;">{{ $vistaDefaultJson }}</div>
|
||||||
|
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog">
|
||||||
@if($canDeleteMailing)
|
@if($canDeleteMailing)
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
@@ -138,6 +184,7 @@ $canDeleteMailing = Auth::user()->canDelete('mailing');
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
|
<script src="{{ asset('js/column-manager.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
function toggleSelectAll(source) {
|
function toggleSelectAll(source) {
|
||||||
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
|
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = source.checked);
|
||||||
@@ -175,5 +222,24 @@ function deleteSelected() {
|
|||||||
|
|
||||||
$('#deleteModal').modal('show');
|
$('#deleteModal').modal('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const table = document.getElementById('mailing-liste-table');
|
||||||
|
if (table) {
|
||||||
|
window.columnManager = new ColumnManager('mailing-liste-table', {
|
||||||
|
entityType: 'mailing-liste',
|
||||||
|
storageKey: 'cm_mailing',
|
||||||
|
resizable: true,
|
||||||
|
reorderable: true,
|
||||||
|
});
|
||||||
|
initTableSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.printWithCurrentSettings = function () {
|
||||||
|
if (window.columnManager) {
|
||||||
|
window.columnManager.printWithSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -49,6 +49,23 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>Tag</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if($mailingList->tags->isNotEmpty())
|
||||||
|
@foreach($mailingList->tags as $tag)
|
||||||
|
<span class="badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;font-size:0.85rem;padding:6px 12px;border-radius:12px;margin:2px;">
|
||||||
|
{{ $tag->name }}
|
||||||
|
</span>
|
||||||
|
@endforeach
|
||||||
|
@else
|
||||||
|
<p class="text-muted mb-0">Nessun tag assegnato</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@php
|
||||||
|
$filterTags = $filterTags ?? (\App\Models\Tag::orderBy('name')->get());
|
||||||
|
$activeTags = (array) request('tag', []);
|
||||||
|
@endphp
|
||||||
|
@if($filterTags->isNotEmpty())
|
||||||
|
<div class="tag-filter-bar px-3 py-2 bg-light border-bottom" style="display:flex;flex-wrap:wrap;gap:4px;align-items:center;">
|
||||||
|
<span class="small font-weight-bold text-muted mr-1"><i class="fas fa-tags mr-1"></i>Tag:</span>
|
||||||
|
@foreach($filterTags as $tag)
|
||||||
|
@php
|
||||||
|
$isActive = in_array($tag->slug, $activeTags);
|
||||||
|
$query = request()->query();
|
||||||
|
if ($isActive) {
|
||||||
|
$newTags = array_values(array_diff($activeTags, [$tag->slug]));
|
||||||
|
if (empty($newTags)) {
|
||||||
|
unset($query['tag']);
|
||||||
|
} else {
|
||||||
|
$query['tag'] = $newTags;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$query['tag'] = array_merge($activeTags, [$tag->slug]);
|
||||||
|
}
|
||||||
|
$url = request()->url() . '?' . http_build_query($query);
|
||||||
|
@endphp
|
||||||
|
<a href="{{ $url }}" class="badge tag-filter-badge" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;{{ $isActive ? '' : 'opacity:0.45;' }}font-size:0.8rem;padding:4px 10px;border-radius:12px;text-decoration:none;">
|
||||||
|
{{ $tag->name }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
@if(!empty($activeTags))
|
||||||
|
<a href="{{ request()->url() }}" class="badge badge-light" style="font-size:0.8rem;padding:4px 10px;border-radius:12px;text-decoration:none;">
|
||||||
|
<i class="fas fa-times mr-1"></i>Cancella filtri
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
@php
|
||||||
|
$allTags = $allTags ?? \App\Models\Tag::orderBy('name')->get();
|
||||||
|
$selectedTags = old('tags', $selectedTags ?? []);
|
||||||
|
$tagFieldName = $tagFieldName ?? 'tags';
|
||||||
|
$selectorId = 'tag-selector-' . md5($tagFieldName);
|
||||||
|
$selectedTagObjects = $allTags->whereIn('id', $selectedTags);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="form-group" id="{{ $selectorId }}">
|
||||||
|
<label>{{ $label ?? 'Tag' }}</label>
|
||||||
|
|
||||||
|
<div class="tag-selector-container" style="border:1px solid #ced4da;border-radius:.25rem;padding:8px;background:#fff;">
|
||||||
|
<div class="tag-selected d-flex flex-wrap mb-1" style="gap:4px;">
|
||||||
|
@foreach($selectedTagObjects as $tag)
|
||||||
|
<span class="tag-pill badge d-inline-flex align-items-center" data-tag-id="{{ $tag->id }}" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;padding:4px 10px;border-radius:12px;font-size:0.85rem;">
|
||||||
|
{{ $tag->name }}
|
||||||
|
<button type="button" class="tag-remove btn btn-xs p-0 ml-1 text-white" style="line-height:1;font-size:14px;background:none;border:none;" onclick="removeTag(event, '{{ $selectorId }}', {{ $tag->id }})">×</button>
|
||||||
|
</span>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tag-search-input">
|
||||||
|
<input type="text" class="form-control form-control-sm" placeholder="Cerca tag..." oninput="filterTags(event, '{{ $selectorId }}')" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tag-available mt-1" style="max-height:140px;overflow-y:auto;display:flex;flex-wrap:wrap;gap:4px;">
|
||||||
|
@foreach($allTags as $tag)
|
||||||
|
<button type="button" class="tag-btn btn btn-sm {{ in_array($tag->id, $selectedTags) ? 'tag-selected' : '' }}" data-tag-id="{{ $tag->id }}" data-tag-name="{{ $tag->name }}" data-tag-color="{{ $tag->color ?? '#6c757d' }}" style="background-color:{{ $tag->color ?? '#6c757d' }};color:#fff;border-radius:12px;cursor:pointer;{{ in_array($tag->id, $selectedTags) ? '' : 'opacity:0.5;' }}" onclick="toggleTag(event, '{{ $selectorId }}', {{ $tag->id }})">
|
||||||
|
{{ $tag->name }}
|
||||||
|
</button>
|
||||||
|
@endforeach
|
||||||
|
@if($allTags->isEmpty())
|
||||||
|
<p class="text-muted small mb-0 w-100">Nessun tag disponibile. <a href="{{ route('impostazioni.index') }}#tag">Crea i tag</a> nelle Impostazioni.</p>
|
||||||
|
@endif
|
||||||
|
<p class="tag-no-match text-muted small mb-0 w-100" style="display:none;">Nessun tag corrisponde alla ricerca.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tag-hidden-inputs">
|
||||||
|
@foreach($selectedTags as $selectedId)
|
||||||
|
<input type="hidden" name="{{ $tagFieldName }}[]" value="{{ $selectedId }}">
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@once
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
function toggleTag(e, selId, tagId) {
|
||||||
|
e.preventDefault();
|
||||||
|
var container = document.getElementById(selId);
|
||||||
|
var btn = container.querySelector('.tag-btn[data-tag-id="' + tagId + '"]');
|
||||||
|
if (!btn) return;
|
||||||
|
|
||||||
|
var isSelected = btn.classList.contains('tag-selected');
|
||||||
|
var selectedBox = container.querySelector('.tag-selected');
|
||||||
|
var hiddenContainer = container.querySelector('.tag-hidden-inputs');
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
btn.classList.remove('tag-selected');
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
var pill = container.querySelector('.tag-pill[data-tag-id="' + tagId + '"]');
|
||||||
|
if (pill) pill.remove();
|
||||||
|
var hidden = hiddenContainer.querySelector('input[value="' + tagId + '"]');
|
||||||
|
if (hidden) hidden.remove();
|
||||||
|
} else {
|
||||||
|
btn.classList.add('tag-selected');
|
||||||
|
btn.style.opacity = '1';
|
||||||
|
var name = btn.dataset.tagName;
|
||||||
|
var color = btn.dataset.tagColor || '#6c757d';
|
||||||
|
var pill = document.createElement('span');
|
||||||
|
pill.className = 'tag-pill badge d-inline-flex align-items-center';
|
||||||
|
pill.style.cssText = 'background-color:' + color + ';color:#fff;padding:4px 10px;border-radius:12px;font-size:0.85rem;';
|
||||||
|
pill.dataset.tagId = tagId;
|
||||||
|
pill.innerHTML = name + '<button type="button" class="tag-remove btn btn-xs p-0 ml-1 text-white" style="line-height:1;font-size:14px;background:none;border:none;" onclick="removeTag(event, \'' + selId + '\', ' + tagId + ')">×</button>';
|
||||||
|
selectedBox.appendChild(pill);
|
||||||
|
|
||||||
|
if (hiddenContainer.querySelector('input[value="' + tagId + '"]')) return;
|
||||||
|
var hidden = document.createElement('input');
|
||||||
|
hidden.type = 'hidden';
|
||||||
|
hidden.name = '{{ $tagFieldName }}[]';
|
||||||
|
hidden.value = tagId;
|
||||||
|
hiddenContainer.appendChild(hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTag(e, selId, tagId) {
|
||||||
|
e.preventDefault();
|
||||||
|
var container = document.getElementById(selId);
|
||||||
|
var btn = container.querySelector('.tag-btn[data-tag-id="' + tagId + '"]');
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.remove('tag-selected');
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
}
|
||||||
|
var pill = container.querySelector('.tag-pill[data-tag-id="' + tagId + '"]');
|
||||||
|
if (pill) pill.remove();
|
||||||
|
var hiddenContainer = container.querySelector('.tag-hidden-inputs');
|
||||||
|
var hidden = hiddenContainer.querySelector('input[value="' + tagId + '"]');
|
||||||
|
if (hidden) hidden.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterTags(e, selId) {
|
||||||
|
var container = document.getElementById(selId);
|
||||||
|
var q = e.target.value.toLowerCase();
|
||||||
|
var btns = container.querySelectorAll('.tag-btn');
|
||||||
|
var visibleCount = 0;
|
||||||
|
btns.forEach(function(btn) {
|
||||||
|
var name = btn.dataset.tagName.toLowerCase();
|
||||||
|
if (name.includes(q)) {
|
||||||
|
btn.style.display = '';
|
||||||
|
visibleCount++;
|
||||||
|
} else {
|
||||||
|
btn.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var noMatch = container.querySelector('.tag-no-match');
|
||||||
|
if (noMatch) {
|
||||||
|
noMatch.style.display = visibleCount === 0 ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
@endonce
|
||||||
@@ -15,105 +15,159 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="filters-tab" data-toggle="tab" href="#filters-panel" role="tab">
|
<a class="nav-link" id="vistas-tab" data-toggle="tab" href="#vistas-panel" role="tab">
|
||||||
<i class="fas fa-filter mr-1"></i> Filtri
|
<i class="fas fa-save mr-1"></i> Viste
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="search-tab" data-toggle="tab" href="#search-panel" role="tab">
|
<a class="nav-link" id="print-tab" data-toggle="tab" href="#print-panel" role="tab">
|
||||||
<i class="fas fa-search mr-1"></i> Ricerca
|
<i class="fas fa-print mr-1"></i> Stampa
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" id="export-tab" data-toggle="tab" href="#export-panel" role="tab">
|
|
||||||
<i class="fas fa-download mr-1"></i> Esporta
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content p-3">
|
<div class="tab-content p-3">
|
||||||
<div class="tab-pane fade show active" id="columns-panel" role="tabpanel">
|
<div class="tab-pane fade show active" id="columns-panel" role="tabpanel">
|
||||||
<h6>Seleziona le colonne da visualizzare:</h6>
|
<h6 class="mb-3">Seleziona colonne, regola larghezza e riordina:</h6>
|
||||||
<div class="row">
|
<p class="small text-muted mb-3">
|
||||||
|
<i class="fas fa-arrows-alt mr-1"></i> Trascina le intestazioni per riordinare ·
|
||||||
|
<i class="fas fa-arrows-alt-h mr-1"></i> Trascina il bordo destro di una cella per ridimensionare
|
||||||
|
</p>
|
||||||
|
<div id="column-list" class="mb-3">
|
||||||
@foreach($tableColumns as $col)
|
@foreach($tableColumns as $col)
|
||||||
<div class="col-md-4">
|
<div class="form-row align-items-center mb-2 column-config-item" data-column="{{ $col['key'] }}">
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="col-auto">
|
||||||
<input type="checkbox" class="custom-control-input column-toggle"
|
<i class="fas fa-grip-vertical text-muted" style="cursor:grab;"></i>
|
||||||
id="col-{{ $col['key'] }}" data-column="{{ $col['key'] }}"
|
</div>
|
||||||
{{ ($col['visible'] ?? true) ? 'checked' : '' }}>
|
<div class="col-auto">
|
||||||
<label class="custom-control-label" for="col-{{ $col['key'] }}">
|
<div class="custom-control custom-checkbox">
|
||||||
{{ $col['label'] }}
|
<input type="checkbox" class="custom-control-input column-toggle"
|
||||||
</label>
|
id="col-{{ $col['key'] }}" data-column="{{ $col['key'] }}"
|
||||||
|
{{ ($col['visible'] ?? true) ? 'checked' : '' }}>
|
||||||
|
<label class="custom-control-label" for="col-{{ $col['key'] }}">
|
||||||
|
{{ $col['label'] }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="number" class="form-control form-control-sm column-width-input"
|
||||||
|
data-column="{{ $col['key'] }}"
|
||||||
|
value="{{ isset($columnWidths[$col['key']]) ? intval(str_replace('px', '', $columnWidths[$col['key']])) : '' }}"
|
||||||
|
placeholder="auto" min="30" style="max-width:80px;">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">px</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary ml-1 reset-col-width" data-column="{{ $col['key'] }}"
|
||||||
|
title="Reset larghezza">
|
||||||
|
<i class="fas fa-undo-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" onclick="resetAllColumnWidths()">
|
||||||
|
<i class="fas fa-undo-alt mr-1"></i> Reset tutte larghezze
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="filters-panel" role="tabpanel">
|
<div class="tab-pane fade" id="vistas-panel" role="tabpanel">
|
||||||
<h6>Aggiungi filtri:</h6>
|
<h6 class="mb-2">Viste salvate</h6>
|
||||||
<div id="filters-container">
|
<div id="vistas-list" class="mb-3">
|
||||||
<div class="filter-row mb-2">
|
@php
|
||||||
<div class="row">
|
$entityType = $entityType ?? 'individui';
|
||||||
<div class="col-md-4">
|
$userVistas = $userVistas ?? collect();
|
||||||
<select class="form-control form-control-sm filter-column">
|
@endphp
|
||||||
<option value="">Seleziona colonna...</option>
|
@if($userVistas->count() > 0)
|
||||||
@foreach($tableColumns as $col)
|
<div class="list-group mb-3" id="vistas-list-group">
|
||||||
<option value="{{ $col['key'] }}">{{ $col['label'] }}</option>
|
@foreach($userVistas as $v)
|
||||||
@endforeach
|
<div class="list-group-item list-group-item-action d-flex align-items-center py-2 {{ $vista && $vista->id === $v->id ? 'active' : '' }}">
|
||||||
</select>
|
<div class="mr-auto">
|
||||||
|
@if($v->is_default)
|
||||||
|
<i class="fas fa-star text-warning mr-1" title="Predefinita"></i>
|
||||||
|
@else
|
||||||
|
<i class="fas fa-file-alt text-muted mr-1"></i>
|
||||||
|
@endif
|
||||||
|
<span>{{ $v->nome }}</span>
|
||||||
|
@if($v->colonne_visibili)
|
||||||
|
<span class="badge badge-light ml-1">{{ count($v->colonne_visibili) }} col.</span>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="ml-2">
|
||||||
<select class="form-control form-control-sm filter-operator">
|
<a href="?vista_id={{ $v->id }}" class="btn btn-xs btn-primary" title="Applica vista">
|
||||||
<option value="contains">Contiene</option>
|
<i class="fas fa-play"></i>
|
||||||
<option value="equals">Uguale a</option>
|
</a>
|
||||||
<option value="starts">Inizia con</option>
|
@if(!$v->is_default)
|
||||||
<option value="ends">Finisce con</option>
|
<form action="{{ url('/viste/' . $v->id . '/default') }}" method="POST" class="d-inline">
|
||||||
<option value="gt">Maggiore di</option>
|
@csrf @method('PATCH')
|
||||||
<option value="lt">Minore di</option>
|
<button type="submit" class="btn btn-xs btn-warning" title="Imposta predefinita">
|
||||||
</select>
|
<i class="fas fa-star"></i>
|
||||||
</div>
|
</button>
|
||||||
<div class="col-md-4">
|
</form>
|
||||||
<input type="text" class="form-control form-control-sm filter-value" placeholder="Valore">
|
@endif
|
||||||
|
<button type="button" class="btn btn-xs btn-info edit-vista-btn" title="Modifica vista"
|
||||||
|
data-vista-id="{{ $v->id }}"
|
||||||
|
data-vista-nome="{{ $v->nome }}"
|
||||||
|
data-vista-default="{{ $v->is_default ? 'true' : 'false' }}"
|
||||||
|
data-vista-colonne="{{ json_encode($v->colonne_visibili ?? []) }}">
|
||||||
|
<i class="fas fa-pencil-alt"></i>
|
||||||
|
</button>
|
||||||
|
<form action="{{ url('/viste/' . $v->id) }}" method="POST" class="d-inline">
|
||||||
|
@csrf @method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-xs btn-danger" onclick="return confirm('Eliminare questa vista?')" title="Elimina">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-muted text-center py-2">Nessuna vista salvata</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h6 class="mb-2">Salva vista corrente</h6>
|
||||||
|
<div class="form-row align-items-end">
|
||||||
|
<div class="col">
|
||||||
|
<input type="text" id="vista-nome" class="form-control" placeholder="Nome vista...">
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="vista-default" value="true">
|
||||||
|
<label class="custom-control-label" for="vista-default">Predefinita</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<button type="button" class="btn btn-primary" id="save-vista-btn">
|
||||||
|
<i class="fas fa-save mr-1"></i> Salva
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-xs btn-success mt-2" onclick="addFilterRow()">
|
|
||||||
<i class="fas fa-plus"></i> Aggiungi filtro
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-xs btn-primary mt-2" id="apply-filters-btn">
|
|
||||||
<i class="fas fa-check"></i> Applica filtri
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="search-panel" role="tabpanel">
|
<div class="tab-pane fade" id="print-panel" role="tabpanel">
|
||||||
<div class="form-group">
|
<h6 class="mb-3">Stampa con impostazioni correnti</h6>
|
||||||
<label>cerca in tutte le colonne:</label>
|
<p>La stampa rispetterà le colonne visibili, la larghezza e l'ordine impostati.</p>
|
||||||
<input type="text" id="table-search" class="form-control" placeholder="Testo da cercare...">
|
<button type="button" class="btn btn-primary btn-lg" onclick="window.printWithCurrentSettings ? window.printWithCurrentSettings() : alert('ColumnManager non inizializzato'); return false;">
|
||||||
|
<i class="fas fa-print mr-2"></i> Stampa
|
||||||
|
</button>
|
||||||
|
<hr>
|
||||||
|
<h6 class="mb-2">Opzioni aggiuntive</h6>
|
||||||
|
<div class="custom-control custom-checkbox mb-2">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="print-page-breaks" checked>
|
||||||
|
<label class="custom-control-label" for="print-page-breaks">Interruzione di pagina tra gli elementi</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="print-background">
|
||||||
<div class="tab-pane fade" id="export-panel" role="tabpanel">
|
<label class="custom-control-label" for="print-background">Stampa sfondi e colori</label>
|
||||||
<h6>Esporta i dati visualizzati:</h6>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button type="button" class="btn btn-success" id="export-csv-btn">
|
|
||||||
<i class="fas fa-file-csv mr-1"></i> Esporta CSV
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-danger" id="export-pdf-btn">
|
|
||||||
<i class="fas fa-file-pdf mr-1"></i> Esporta PDF
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="mr-auto">
|
|
||||||
<input type="text" id="vista-nome" class="form-control form-control-sm" placeholder="Nome vista..." style="display: inline-block; width: auto;">
|
|
||||||
<button type="button" class="btn btn-primary btn-sm" id="save-vista-btn">
|
|
||||||
<i class="fas fa-save mr-1"></i> Salva Vista
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Chiudi</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,41 +175,160 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function addFilterRow() {
|
function resetAllColumnWidths() {
|
||||||
const container = document.getElementById('filters-container');
|
document.querySelectorAll('.column-width-input').forEach(inp => { inp.value = ''; });
|
||||||
const columns = @json($tableColumns);
|
document.querySelectorAll('.column-config-item').forEach(item => {
|
||||||
const options = columns.map(c => `<option value="${c.key}">${c.label}</option>`).join('');
|
const th = document.querySelector(`th[data-column="${item.dataset.column}"]`);
|
||||||
|
if (th) th.style.width = '';
|
||||||
const div = document.createElement('div');
|
});
|
||||||
div.className = 'filter-row mb-2';
|
if (window.columnManager) {
|
||||||
div.innerHTML = `
|
window.columnManager.columnWidths = {};
|
||||||
<div class="row">
|
window.columnManager.persistState();
|
||||||
<div class="col-md-4">
|
}
|
||||||
<select class="form-control form-control-sm filter-column">
|
|
||||||
<option value="">Seleziona colonna...</option>
|
|
||||||
${options}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<select class="form-control form-control-sm filter-operator">
|
|
||||||
<option value="contains">Contiene</option>
|
|
||||||
<option value="equals">Uguale a</option>
|
|
||||||
<option value="starts">Inizia con</option>
|
|
||||||
<option value="ends">Finisce con</option>
|
|
||||||
<option value="gt">Maggiore di</option>
|
|
||||||
<option value="lt">Minore di</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<input type="text" class="form-control form-control-sm filter-value" placeholder="Valore">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<button type="button" class="btn btn-danger btn-sm" onclick="this.closest('.filter-row').remove()">
|
|
||||||
<i class="fas fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
container.appendChild(div);
|
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
function syncColumnListToTable() {
|
||||||
|
const items = document.querySelectorAll('#column-list .column-config-item');
|
||||||
|
const keys = Array.from(items).map(i => i.dataset.column);
|
||||||
|
if (window.columnManager && keys.length > 0) {
|
||||||
|
window.columnManager.reorderColumns(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let editingVistaId = null;
|
||||||
|
|
||||||
|
function editVista(id, nome, isDefault, colonneVisibili) {
|
||||||
|
editingVistaId = id;
|
||||||
|
document.getElementById('vista-nome').value = nome;
|
||||||
|
document.getElementById('vista-default').checked = isDefault;
|
||||||
|
document.querySelectorAll('.column-toggle').forEach(cb => {
|
||||||
|
cb.checked = colonneVisibili.includes(cb.dataset.column);
|
||||||
|
if (window.columnManager) window.columnManager.setVisible(cb.dataset.column, cb.checked);
|
||||||
|
});
|
||||||
|
document.getElementById('save-vista-btn').innerHTML = '<i class="fas fa-save mr-1"></i> Aggiorna';
|
||||||
|
document.getElementById('vistas-tab').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTableSettings() {
|
||||||
|
const columnList = document.getElementById('column-list');
|
||||||
|
if (!columnList) return;
|
||||||
|
|
||||||
|
let dragItem = null;
|
||||||
|
|
||||||
|
columnList.querySelectorAll('.column-config-item').forEach(item => {
|
||||||
|
const grip = item.querySelector('.fa-grip-vertical');
|
||||||
|
if (!grip) return;
|
||||||
|
grip.addEventListener('mousedown', function () { item.draggable = true; });
|
||||||
|
item.addEventListener('dragstart', function (e) {
|
||||||
|
dragItem = this; e.dataTransfer.effectAllowed = 'move'; this.classList.add('dragging');
|
||||||
|
});
|
||||||
|
item.addEventListener('dragend', function () {
|
||||||
|
this.classList.remove('dragging'); this.draggable = false; dragItem = null;
|
||||||
|
});
|
||||||
|
item.addEventListener('dragover', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!dragItem || dragItem === this) return;
|
||||||
|
const rect = this.getBoundingClientRect();
|
||||||
|
const midY = rect.top + rect.height / 2;
|
||||||
|
if (e.clientY < midY) columnList.insertBefore(dragItem, this);
|
||||||
|
else columnList.insertBefore(dragItem, this.nextSibling);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
columnList.addEventListener('drop', function () {
|
||||||
|
const items = Array.from(columnList.querySelectorAll('.column-config-item'));
|
||||||
|
const newOrder = items.map(i => i.dataset.column);
|
||||||
|
if (window.columnManager && newOrder.length > 0) window.columnManager.reorderColumns(newOrder);
|
||||||
|
syncColumnListToTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
columnList.querySelectorAll('.column-toggle').forEach(cb => {
|
||||||
|
cb.addEventListener('change', function () {
|
||||||
|
if (window.columnManager) window.columnManager.setVisible(this.dataset.column, this.checked);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
columnList.querySelectorAll('.column-width-input').forEach(inp => {
|
||||||
|
inp.addEventListener('change', function () {
|
||||||
|
const key = this.dataset.column;
|
||||||
|
const val = parseInt(this.value);
|
||||||
|
const th = document.querySelector(`th[data-column="${key}"]`);
|
||||||
|
if (th) {
|
||||||
|
if (val && val >= 30) {
|
||||||
|
th.style.width = val + 'px';
|
||||||
|
if (window.columnManager) {
|
||||||
|
window.columnManager.columnWidths[key] = val + 'px';
|
||||||
|
window.columnManager.persistState();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
th.style.width = '';
|
||||||
|
if (window.columnManager) {
|
||||||
|
delete window.columnManager.columnWidths[key];
|
||||||
|
window.columnManager.persistState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
columnList.querySelectorAll('.reset-col-width').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
const key = this.dataset.column;
|
||||||
|
const inp = document.querySelector(`.column-width-input[data-column="${key}"]`);
|
||||||
|
if (inp) inp.value = '';
|
||||||
|
const th = document.querySelector(`th[data-column="${key}"]`);
|
||||||
|
if (th) th.style.width = '';
|
||||||
|
if (window.columnManager) {
|
||||||
|
delete window.columnManager.columnWidths[key];
|
||||||
|
window.columnManager.persistState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.edit-vista-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
editVista(
|
||||||
|
this.dataset.vistaId,
|
||||||
|
this.dataset.vistaNome,
|
||||||
|
this.dataset.vistaDefault === 'true',
|
||||||
|
JSON.parse(this.dataset.vistaColonne)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('save-vista-btn')?.addEventListener('click', function() {
|
||||||
|
const nome = document.getElementById('vista-nome').value.trim();
|
||||||
|
if (!nome) { alert('Inserisci un nome per la vista'); return; }
|
||||||
|
|
||||||
|
const colonneVisibili = Array.from(document.querySelectorAll('.column-toggle:checked')).map(cb => cb.dataset.column);
|
||||||
|
const larghezze = {};
|
||||||
|
document.querySelectorAll('.column-width-input').forEach(inp => {
|
||||||
|
const val = parseInt(inp.value);
|
||||||
|
if (val && val >= 30) larghezze[inp.dataset.column] = val + 'px';
|
||||||
|
});
|
||||||
|
const ordine = Array.from(document.querySelectorAll('#column-list .column-config-item')).map(i => i.dataset.column);
|
||||||
|
const isDefault = document.getElementById('vista-default')?.checked || false;
|
||||||
|
const entityType = '{{ $entityType ?? 'individui' }}';
|
||||||
|
|
||||||
|
const isEditing = editingVistaId !== null;
|
||||||
|
const url = isEditing ? '/viste/' + editingVistaId : '/viste';
|
||||||
|
const method = isEditing ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
nome, tipo: entityType, colonne_visibili: colonneVisibili,
|
||||||
|
colonne_larghezze: larghezze, colonne_ordine: ordine,
|
||||||
|
colonne_ordinamento: [], is_default: isDefault,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.ok ? (alert(isEditing ? 'Vista aggiornata!' : 'Vista salvata!'), window.location.reload()) : response.text().then(t => { throw new Error(t); }))
|
||||||
|
.catch(err => alert('Errore salvataggio vista: ' + err.message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -295,6 +295,22 @@
|
|||||||
<label>Limite risultati</label>
|
<label>Limite risultati</label>
|
||||||
<input type="number" name="limit" class="form-control" placeholder="Es: 100" min="1">
|
<input type="number" name="limit" class="form-control" placeholder="Es: 100" min="1">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Filtra per Tag</label>
|
||||||
|
<select name="tag_filter[]" id="tag_filter" class="form-control select2-tags" multiple style="width: 100%;">
|
||||||
|
@foreach($allTags as $tag)
|
||||||
|
<option value="{{ $tag->id }}" style="background-color:{{ $tag->color ?? '#6c757d' }};">{{ $tag->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<small class="text-muted">Filtra i risultati per tag</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Modalità filtro tag</label>
|
||||||
|
<select name="tag_filter_mode" class="form-control">
|
||||||
|
<option value="any">Almeno un tag (OR)</option>
|
||||||
|
<option value="all">Tutti i tag selezionati (AND)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block">
|
<button type="submit" class="btn btn-primary btn-block">
|
||||||
<i class="fas fa-save mr-1"></i> Salva Report
|
<i class="fas fa-save mr-1"></i> Salva Report
|
||||||
</button>
|
</button>
|
||||||
@@ -346,6 +362,37 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
columnsSelectInitialized = false;
|
columnsSelectInitialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tagFilterSelect2Initialized = false;
|
||||||
|
|
||||||
|
function initTagFilterSelect2() {
|
||||||
|
if (tagFilterSelect2Initialized) return;
|
||||||
|
$('#tag_filter').select2({
|
||||||
|
theme: 'bootstrap4',
|
||||||
|
placeholder: 'Seleziona tag...',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
templateResult: function(opt) {
|
||||||
|
if (!opt.id) return opt.text;
|
||||||
|
var color = $(opt.element).css('background-color');
|
||||||
|
var $span = $('<span style="padding:2px 8px;border-radius:10px;color:#fff;background-color:' + color + '">' + opt.text + '</span>');
|
||||||
|
return $span;
|
||||||
|
},
|
||||||
|
templateSelection: function(opt) {
|
||||||
|
if (!opt.id) return opt.text;
|
||||||
|
var color = $(opt.element).css('background-color');
|
||||||
|
var $span = $('<span style="padding:2px 8px;border-radius:10px;color:#fff;background-color:' + color + '">' + opt.text + '</span>');
|
||||||
|
return $span;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tagFilterSelect2Initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyTagFilterSelect2() {
|
||||||
|
if (!tagFilterSelect2Initialized) return;
|
||||||
|
$('#tag_filter').select2('destroy');
|
||||||
|
tagFilterSelect2Initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
function getSortOptions(tipoReport) {
|
function getSortOptions(tipoReport) {
|
||||||
const cols = columnOptions[tipoReport] || {};
|
const cols = columnOptions[tipoReport] || {};
|
||||||
const options = [];
|
const options = [];
|
||||||
@@ -365,8 +412,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
$('#collapseThree').on('shown.bs.collapse', function () {
|
$('#collapseThree').on('shown.bs.collapse', function () {
|
||||||
initColumnsSelect2();
|
initColumnsSelect2();
|
||||||
|
initTagFilterSelect2();
|
||||||
}).on('hidden.bs.collapse', function () {
|
}).on('hidden.bs.collapse', function () {
|
||||||
destroyColumnsSelect2();
|
destroyColumnsSelect2();
|
||||||
|
destroyTagFilterSelect2();
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($('#collapseTwo').hasClass('show')) {
|
if ($('#collapseTwo').hasClass('show')) {
|
||||||
@@ -375,6 +424,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
if ($('#collapseThree').hasClass('show')) {
|
if ($('#collapseThree').hasClass('show')) {
|
||||||
initColumnsSelect2();
|
initColumnsSelect2();
|
||||||
|
initTagFilterSelect2();
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#tipo_report').on('change', function() {
|
$('#tipo_report').on('change', function() {
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
@extends('layouts.adminlte')
|
||||||
|
|
||||||
|
@section('title', 'Ricerca per Tag')
|
||||||
|
@section('page_title', 'Ricerca per Tag')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-tags mr-2"></i>
|
||||||
|
@isset($selectedTags)
|
||||||
|
Risultati per:
|
||||||
|
@foreach($selectedTags as $t)
|
||||||
|
<span class="badge" style="background-color: {{ $t->color ?? '#6c757d' }}; color: #fff; font-size: 0.9rem; margin: 0 2px;">{{ $t->name }}</span>
|
||||||
|
@endforeach
|
||||||
|
@if(count($tagSlugs) > 1)
|
||||||
|
<small class="text-muted ml-1">(intersezione — tutti i tag richiesti)</small>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
Seleziona uno o più tag
|
||||||
|
@endisset
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if(!isset($selectedTags) || $selectedTags->isEmpty())
|
||||||
|
<p class="text-muted">Clicca sui tag per selezionarli (Ctrl+click per più tag).</p>
|
||||||
|
<form method="GET" action="{{ route('ricerca.index') }}" id="tag-form">
|
||||||
|
<div style="display:flex;flex-wrap:wrap;gap:8px;">
|
||||||
|
@foreach($allTags as $t)
|
||||||
|
<label class="btn btn-sm" style="background-color: {{ $t->color ?? '#6c757d' }}; color: #fff; border-radius: 14px; cursor:pointer; opacity: 0.7;">
|
||||||
|
<input type="checkbox" name="tag[]" value="{{ $t->slug }}" class="tag-checkbox" style="display:none;" onchange="this.closest('label').style.opacity = this.checked ? '1' : '0.7'">
|
||||||
|
{{ $t->name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
@if($allTags->isEmpty())
|
||||||
|
<p class="text-muted">Nessun tag disponibile. <a href="{{ route('impostazioni.index') }}#tag">Crea i tag</a> nelle Impostazioni.</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@if($allTags->isNotEmpty())
|
||||||
|
<div class="mt-3">
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fas fa-search mr-1"></i> Cerca</button>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
<div class="row">
|
||||||
|
@php
|
||||||
|
$indexRoutes = [
|
||||||
|
'individui' => 'individui.index',
|
||||||
|
'gruppi' => 'gruppi.index',
|
||||||
|
'eventi' => 'eventi.index',
|
||||||
|
'documenti' => 'documenti.index',
|
||||||
|
'mailingLists' => 'mailing-liste.index',
|
||||||
|
];
|
||||||
|
$tagParams = collect($tagSlugs)->map(fn($s) => "tag[]={$s}")->implode('&');
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@foreach([
|
||||||
|
['key' => 'individui', 'color' => 'info', 'icon' => 'fa-user', 'label' => 'Individui'],
|
||||||
|
['key' => 'gruppi', 'color' => 'success', 'icon' => 'fa-users', 'label' => 'Gruppi'],
|
||||||
|
['key' => 'eventi', 'color' => 'warning', 'icon' => 'fa-calendar-alt', 'label' => 'Eventi'],
|
||||||
|
['key' => 'documenti', 'color' => 'secondary', 'icon' => 'fa-file', 'label' => 'Documenti'],
|
||||||
|
['key' => 'mailingLists', 'color' => 'purple', 'icon' => 'fa-list', 'label' => 'Mailing List'],
|
||||||
|
] as $box)
|
||||||
|
<div class="col-md-6 col-lg-3 mb-4">
|
||||||
|
<div class="info-box bg-{{ $box['color'] }}">
|
||||||
|
<span class="info-box-icon"><i class="fas {{ $box['icon'] }}"></i></span>
|
||||||
|
<div class="info-box-content">
|
||||||
|
<span class="info-box-text">{{ $box['label'] }}</span>
|
||||||
|
<span class="info-box-number">{{ $totals[$box['key']] }}</span>
|
||||||
|
@if($totals[$box['key']] > 0 && isset($indexRoutes[$box['key']]))
|
||||||
|
<a href="{{ route($indexRoutes[$box['key']]) . '?' . $tagParams }}" class="small" style="color:#fff;">Vedi tutti »</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@php
|
||||||
|
$sections = [
|
||||||
|
'individui' => ['color' => 'info', 'icon' => 'fa-user', 'title' => 'Individui', 'cols' => ['Cognome', 'Nome', 'Email'], 'fields' => ['cognome', 'nome', 'email_primaria']],
|
||||||
|
'gruppi' => ['color' => 'success', 'icon' => 'fa-users', 'title' => 'Gruppi', 'cols' => ['Nome', 'Descrizione'], 'fields' => ['nome', 'descrizione_limit']],
|
||||||
|
'eventi' => ['color' => 'warning', 'icon' => 'fa-calendar-alt', 'title' => 'Eventi', 'cols' => ['Evento', 'Data', 'Ora'], 'fields' => ['nome_evento', 'data', 'ora']],
|
||||||
|
'documenti' => ['color' => 'secondary', 'icon' => 'fa-file', 'title' => 'Documenti', 'cols' => ['Nome File', 'Tipo'], 'fields' => ['nome_file', 'tipo']],
|
||||||
|
'mailingLists' => ['color' => 'purple', 'icon' => 'fa-list', 'title' => 'Mailing List', 'cols' => ['Nome', 'Contatti', 'Stato'], 'fields' => ['nome', 'contatti_count', 'stato']],
|
||||||
|
];
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@foreach($sections as $key => $section)
|
||||||
|
@if($totals[$key] > 0)
|
||||||
|
<div class="card card-{{ $section['color'] }} card-outline">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title"><i class="fas {{ $section['icon'] }} mr-2"></i>{{ $section['title'] }} ({{ $totals[$key] }})</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<table class="table table-bordered table-hover mb-0">
|
||||||
|
<thead><tr>
|
||||||
|
@foreach($section['cols'] as $col)<th>{{ $col }}</th>@endforeach
|
||||||
|
<th></th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($results[$key] as $item)
|
||||||
|
<tr>
|
||||||
|
@if($key === 'individui')
|
||||||
|
<td>{{ $item->cognome }}</td>
|
||||||
|
<td>{{ $item->nome }}</td>
|
||||||
|
<td>{{ $item->email_primaria ?? '-' }}</td>
|
||||||
|
<td><a href="{{ route('individui.show', $item) }}" class="btn btn-xs btn-primary"><i class="fas fa-eye"></i></a></td>
|
||||||
|
@elseif($key === 'gruppi')
|
||||||
|
<td>{{ $item->nome }}</td>
|
||||||
|
<td>{{ Str::limit($item->descrizione, 80) ?? '-' }}</td>
|
||||||
|
<td><a href="{{ route('gruppi.show', $item) }}" class="btn btn-xs btn-primary"><i class="fas fa-eye"></i></a></td>
|
||||||
|
@elseif($key === 'eventi')
|
||||||
|
<td>{{ $item->nome_evento }}</td>
|
||||||
|
<td>{{ $item->data_specifica ? $item->data_specifica->format('d/m/Y') : ($item->tipo_recorrenza ?? '-') }}</td>
|
||||||
|
<td>{{ $item->ora_inizio ? $item->ora_inizio->format('H:i') : '-' }}</td>
|
||||||
|
<td><a href="{{ route('eventi.show', $item) }}" class="btn btn-xs btn-primary"><i class="fas fa-eye"></i></a></td>
|
||||||
|
@elseif($key === 'documenti')
|
||||||
|
<td>{{ $item->nome_file }}</td>
|
||||||
|
<td><span class="badge badge-info">{{ $item->tipo ?? '-' }}</span></td>
|
||||||
|
<td><a href="{{ url('/documenti/' . $item->id . '/edit') }}" class="btn btn-xs btn-primary"><i class="fas fa-eye"></i></a></td>
|
||||||
|
@elseif($key === 'mailingLists')
|
||||||
|
<td>{{ $item->nome }}</td>
|
||||||
|
<td><span class="badge badge-info">{{ $item->contatti->count() }}</span></td>
|
||||||
|
<td>
|
||||||
|
@if($item->attiva)
|
||||||
|
<span class="badge badge-success">Attiva</span>
|
||||||
|
@else
|
||||||
|
<span class="badge badge-secondary">Disattiva</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td><a href="{{ route('mailing-liste.show', $item) }}" class="btn btn-xs btn-primary"><i class="fas fa-eye"></i></a></td>
|
||||||
|
@endif
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if(array_sum($totals) === 0)
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle mr-2"></i> Nessun elemento trovato con tutti i tag selezionati.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<a href="{{ route('ricerca.index') }}" class="btn btn-secondary"><i class="fas fa-arrow-left mr-1"></i> Nuova ricerca</a>
|
||||||
|
</div>
|
||||||
|
@endisset
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user