Roberto Santini 1 hafta önce
ebeveyn
işleme
9e2061a83e

+ 14
- 3
app/DataTables/ContrattoServizioDataTable.php Dosyayı Görüntüle

@@ -28,7 +28,7 @@ class ContrattoServizioDataTable extends DataTable
28 28
       ->addColumn('action', fn ($e) => view('contratto_servizio.menu', ['entity' => $e, 'mode' => 'embed']))
29 29
       ->addColumn('template_label', fn ($e) => $e->contrattoTemplate ? $e->contrattoTemplate->nome : '—')
30 30
       ->addColumn('costo_label', fn ($e) => number_format((float) $e->totale_righe, 2, ',', '.').' €')
31
-      ->addColumn('righe_count', fn ($e) => $e->contratto_servizio_rigas_count ?? $e->contrattoServizioRigas()->count())
31
+      ->addColumn('righe_count', fn ($e) => (int) ($e->righe_count ?? $e->righe->filter(fn ($r) => $r->isValidaOggi())->count()))
32 32
       ->addColumn('periodo_label', fn ($e) => Prodotto::PERIODI[$e->periodo]
33 33
         ?? (\App\Models\ContrattoTemplate::PERIODI[$e->periodo] ?? ($e->periodo ?: '—')))
34 34
       ->addColumn('date_label', function ($e) {
@@ -49,7 +49,16 @@ class ContrattoServizioDataTable extends DataTable
49 49
 
50 50
   public function query(ContrattoServizio $model): QueryBuilder
51 51
   {
52
-    $q = $model->newQuery()->with(['contrattoTemplate', 'righe.prodotto'])->withCount('contrattoServizioRigas');
52
+    $oggi = \Carbon\Carbon::today()->toDateString();
53
+    $q = $model->newQuery()
54
+      ->with(['contrattoTemplate', 'righe.prodotto'])
55
+      ->withCount(['righe as righe_count' => function ($query) use ($oggi) {
56
+        $query->where(function ($q) use ($oggi) {
57
+          $q->whereNull('data_inizio')->orWhereDate('data_inizio', '<=', $oggi);
58
+        })->where(function ($q) use ($oggi) {
59
+          $q->whereNull('data_fine')->orWhereDate('data_fine', '>=', $oggi);
60
+        });
61
+      }]);
53 62
     if ($this->user) {
54 63
       $q->where('user_id', $this->user->id);
55 64
     }
@@ -70,6 +79,8 @@ class ContrattoServizioDataTable extends DataTable
70 79
     $templates = ContrattoTemplate::where('attivo', true)->orderBy('nome')->pluck('id', 'nome');
71 80
     $periodi = array_merge(Prodotto::PERIODI, ContrattoTemplate::PERIODI);
72 81
 
82
+    $showUrl = json_encode(route('admin.contratto_servizio.show', ['id' => '__ID__']));
83
+
73 84
     return $this->builder()
74 85
       ->setTableId($this->dataTableVariable)
75 86
       ->columns($this->getColumns())
@@ -79,7 +90,7 @@ class ContrattoServizioDataTable extends DataTable
79 90
       ->responsive()
80 91
       ->minifiedAjax(route('admin.contratto_servizio.index', ['user_id' => $this->user->id]))
81 92
       ->orderBy(0, 'desc')
82
-      ->initComplete('function(){ $("div.dt-buttons button").removeClass("btn-secondary"); }')
93
+      ->initComplete('function(){ $("div.dt-buttons button").removeClass("btn-secondary"); $(this.api().table().node()).css("cursor","pointer").on("dblclick","tbody tr",function(e){ if($(this).hasClass("child")||$(e.target).closest("a,button,.dropdown").length) return; var id=this.id; if(id) window.location.href='.$showUrl.'.replace("__ID__",id); }); }')
83 94
       ->editor(
84 95
         Editor::make()
85 96
           ->ajax(route('admin.contratto_servizio.store'))

+ 25
- 5
app/DataTables/SegnalazioneDataTable.php Dosyayı Görüntüle

@@ -31,6 +31,17 @@ class SegnalazioneDataTable extends DataTable
31 31
     public function dataTable(QueryBuilder $query): EloquentDataTable
32 32
     {
33 33
         return (new EloquentDataTable($query))
34
+            ->addColumn('select', function ($entity) {
35
+                return '<div class="form-check mb-0 d-flex justify-content-center">'
36
+                    .'<input type="checkbox" class="form-check-input js-segnalazione-select" value="'.(int) $entity->id.'"'
37
+                    .' data-azienda-id="'.e((string) ($entity->azienda_id ?? '')).'"'
38
+                    .' data-stato="'.e((string) ($entity->stato ?? '')).'"'
39
+                    .' data-priorita="'.e((string) ($entity->priorita ?? '')).'"'
40
+                    .' data-tipo="'.e((string) ($entity->tipo ?? '')).'"'
41
+                    .' data-assegnata-a-id="'.e((string) ($entity->assegnata_a_id ?? '')).'"'
42
+                    .' aria-label="Seleziona segnalazione">'
43
+                    .'</div>';
44
+            })
34 45
             ->addColumn('action', function ($entity) {
35 46
                 return view('segnalazione.admin.menu', ['entity' => $entity]);
36 47
             })
@@ -151,7 +162,7 @@ class SegnalazioneDataTable extends DataTable
151 162
             ->filterColumn('updated_at', function ($query, $keyword) {
152 163
                 $this->filterDateTimeColumn($query, $keyword, 'segnalazione.updated_at');
153 164
             })
154
-            ->rawColumns(['stato_display', 'priorita_display', 'tipo_display'])
165
+            ->rawColumns(['select', 'stato_display', 'priorita_display', 'tipo_display'])
155 166
             ->setRowId('id');
156 167
     }
157 168
 
@@ -297,7 +308,7 @@ class SegnalazioneDataTable extends DataTable
297 308
             ->minifiedAjax('', null, [
298 309
                 'show_chiuse' => '$("#filtro_segnalazioni_chiuse").is(":checked") ? 1 : 0',
299 310
             ])
300
-            ->orderBy(8, 'desc')
311
+            ->orderBy(9, 'desc')
301 312
             ->buttons($buttons)
302 313
             ->editor(
303 314
                 Editor::make()
@@ -310,8 +321,17 @@ class SegnalazioneDataTable extends DataTable
310 321
     public function getColumns(): array
311 322
     {
312 323
         return [
313
-            Column::make('id')->title('Numero')->responsivePriority(0),
314
-            Column::make('titolo')->title('Titolo')->responsivePriority(1),
324
+            Column::computed('select')
325
+                ->title('')
326
+                ->exportable(false)
327
+                ->printable(false)
328
+                ->orderable(false)
329
+                ->searchable(false)
330
+                ->width(36)
331
+                ->addClass('text-center')
332
+                ->responsivePriority(0),
333
+            Column::make('id')->title('Numero')->responsivePriority(1),
334
+            Column::make('titolo')->title('Titolo')->responsivePriority(2),
315 335
             Column::make('azienda_id')->title('Azienda')->data('azienda_label'),
316 336
             Column::make('stato')->title('Stato')->data('stato_display'),
317 337
             Column::make('priorita')->title('Priorità')->data('priorita_display'),
@@ -326,7 +346,7 @@ class SegnalazioneDataTable extends DataTable
326 346
                 ->width('10%')
327 347
                 ->title(' ')
328 348
                 ->addClass('text-end')
329
-                ->responsivePriority(2),
349
+                ->responsivePriority(3),
330 350
         ];
331 351
     }
332 352
 

+ 2
- 2
app/Http/Controllers/ConfigNotificheController.php Dosyayı Görüntüle

@@ -34,7 +34,7 @@ class ConfigNotificheController extends Controller
34 34
             ]);
35 35
         }
36 36
 
37
-        $users = User::where('is_azienda', false)->orderBy('cognome')->orderBy('nome')->get();
37
+        $users = User::assegnatariSegnalazione()->get();
38 38
 
39 39
         return view('config_notifiche.index', [
40 40
             'configList' => $configList,
@@ -47,7 +47,7 @@ class ConfigNotificheController extends Controller
47 47
     {
48 48
         $configList = ConfigNotifiche::getConfigList();
49 49
         $input = $request->input('config', []);
50
-        $validUserIds = User::pluck('id')->all();
50
+        $validUserIds = User::assegnatariSegnalazione()->pluck('id')->all();
51 51
         $validUserMap = array_flip($validUserIds);
52 52
 
53 53
         foreach ($configList as $event => $meta) {

+ 154
- 1
app/Http/Controllers/SegnalazioneController.php Dosyayı Görüntüle

@@ -43,6 +43,7 @@ class SegnalazioneController extends Controller implements HasMiddleware
43 43
       new Middleware('permission:edit-segnalazione', only: [
44 44
         'updateStato',
45 45
         'admin_aggiorna',
46
+        'admin_batch_update',
46 47
         'admin_toggle_todo',
47 48
         'admin_update_todo_note',
48 49
         'admin_store_todo',
@@ -50,7 +51,7 @@ class SegnalazioneController extends Controller implements HasMiddleware
50 51
         'admin_destroy_todo',
51 52
         'admin_tempo_timer',
52 53
       ]),
53
-      new Middleware('permission:delete-segnalazione', only: ['destroy', 'admin_delete']),
54
+      new Middleware('permission:delete-segnalazione', only: ['destroy', 'admin_delete', 'admin_batch_delete']),
54 55
     ];
55 56
   }
56 57
   public function admin_index(SegnalazioneDataTable $dataTable, Request $request, ?string $status = null)
@@ -657,6 +658,158 @@ class SegnalazioneController extends Controller implements HasMiddleware
657 658
       ]);
658 659
     }
659 660
 
661
+    public function admin_batch_update(Request $request)
662
+    {
663
+        $validated = $request->validate([
664
+            'ids' => 'required|array|min:1|max:200',
665
+            'ids.*' => 'integer',
666
+            'azienda_id' => ['nullable', Rule::exists('users', 'id')->where('is_azienda', 1)],
667
+            'stato' => ['nullable', 'string', 'max:255'],
668
+            'priorita' => ['nullable', 'string', 'max:255'],
669
+            'tipo' => ['nullable', 'string', 'max:255'],
670
+            'assegnata_a_id' => ['nullable', 'exists:users,id'],
671
+            'skip_notifica_cliente' => ['sometimes', 'boolean'],
672
+        ]);
673
+
674
+        $auth = Auth::user();
675
+        $ids = array_values(array_unique(array_map('intval', $validated['ids'])));
676
+        $skipNotifica = $request->boolean('skip_notifica_cliente');
677
+
678
+        $updates = [];
679
+        if ($request->filled('azienda_id')) {
680
+            $updates['azienda_id'] = (int) $request->azienda_id;
681
+        }
682
+        if ($request->filled('stato')) {
683
+            $updates['stato'] = (string) $request->stato;
684
+        }
685
+        if ($request->filled('priorita')) {
686
+            $updates['priorita'] = (string) $request->priorita;
687
+        }
688
+        if ($request->has('tipo') && $request->input('tipo') !== null && $request->input('tipo') !== '') {
689
+            $updates['tipo'] = (string) $request->tipo;
690
+        }
691
+        if ($request->filled('assegnata_a_id')) {
692
+            $updates['assegnata_a_id'] = (int) $request->assegnata_a_id;
693
+        }
694
+
695
+        if ($updates === []) {
696
+            return response()->json([
697
+                'success' => false,
698
+                'message' => 'Seleziona almeno un campo da aggiornare.',
699
+            ], 422);
700
+        }
701
+
702
+        $segnalazioni = $this->segnalazioniVisibiliAdAuth($ids);
703
+        $updated = 0;
704
+        $skipped = 0;
705
+        $errors = [];
706
+
707
+        foreach ($segnalazioni as $segnalazione) {
708
+            if (array_key_exists('stato', $updates)) {
709
+                $blockClose = $segnalazione->closingBlockedMessage($updates['stato']);
710
+                if ($blockClose) {
711
+                    $skipped++;
712
+                    $errors[] = ($segnalazione->getNumeroLabel() ?: '#'.$segnalazione->id).': '.$blockClose;
713
+                    continue;
714
+                }
715
+            }
716
+
717
+            $statoPrima = $segnalazione->stato;
718
+            foreach ($updates as $field => $value) {
719
+                $segnalazione->{$field} = $value;
720
+            }
721
+            $segnalazione->skipNotificaCliente = $skipNotifica;
722
+            $segnalazione->save();
723
+            $updated++;
724
+
725
+            $statoCambiato = array_key_exists('stato', $updates) && $statoPrima !== $segnalazione->stato;
726
+            if ($statoCambiato && ! $skipNotifica) {
727
+                $statoLabel = ConfiguraSegnalazione::where('gruppo', 'stato')
728
+                    ->where('value', $segnalazione->stato)
729
+                    ->value('label') ?: $segnalazione->stato;
730
+                $annotazione = new Annotazione();
731
+                $annotazione->segnalazione_id = $segnalazione->id;
732
+                $annotazione->user_id = $auth->id;
733
+                $annotazione->riservata = false;
734
+                $annotazione->text = '<p>Lo stato è stato aggiornato a <strong>'.e((string) $statoLabel).'</strong>.</p>';
735
+                $annotazione->save();
736
+                $segnalazione->notifyApertaDaAnnotazionePubblica($annotazione);
737
+            }
738
+        }
739
+
740
+        $nonTrovate = count($ids) - $segnalazioni->count();
741
+        if ($nonTrovate > 0) {
742
+            $skipped += $nonTrovate;
743
+        }
744
+
745
+        $message = $updated === 1
746
+            ? '1 segnalazione aggiornata.'
747
+            : $updated.' segnalazioni aggiornate.';
748
+        if ($skipped > 0) {
749
+            $message .= ' '.$skipped.' non modificate.';
750
+        }
751
+
752
+        return response()->json([
753
+            'success' => true,
754
+            'updated' => $updated,
755
+            'skipped' => $skipped,
756
+            'errors' => $errors,
757
+            'message' => $message,
758
+        ]);
759
+    }
760
+
761
+    public function admin_batch_delete(Request $request)
762
+    {
763
+        $request->validate([
764
+            'ids' => 'required|array|min:1|max:200',
765
+            'ids.*' => 'integer',
766
+        ]);
767
+
768
+        $ids = array_values(array_unique(array_map('intval', $request->input('ids'))));
769
+        $segnalazioni = $this->segnalazioniVisibiliAdAuth($ids);
770
+        $deleted = 0;
771
+        foreach ($segnalazioni as $segnalazione) {
772
+            $segnalazione->delete();
773
+            $deleted++;
774
+        }
775
+
776
+        $message = $deleted === 1
777
+            ? '1 segnalazione eliminata.'
778
+            : $deleted.' segnalazioni eliminate.';
779
+
780
+        return response()->json([
781
+            'success' => true,
782
+            'deleted' => $deleted,
783
+            'message' => $message,
784
+        ]);
785
+    }
786
+
787
+    /**
788
+     * @param  list<int>  $ids
789
+     * @return \Illuminate\Support\Collection<int, Segnalazione>
790
+     */
791
+    protected function segnalazioniVisibiliAdAuth(array $ids)
792
+    {
793
+        $auth = Auth::user();
794
+        $query = Segnalazione::query()->whereIn('id', $ids);
795
+
796
+        if ($auth && ! $auth->can('view-segnalazione')) {
797
+            $query->where(function ($q) use ($auth) {
798
+                if ($auth->can('view-own-segnalazione')) {
799
+                    $q->orWhere('user_id', $auth->id);
800
+                }
801
+                if ($auth->can('view-segnalazione-assegnate')) {
802
+                    $q->orWhereIn(
803
+                        'assegnata_a_id',
804
+                        Segnalazione::assigneeIdsForViewSegnalazioneAssegnate($auth)
805
+                    );
806
+                }
807
+            });
808
+        }
809
+
810
+        return $query->get();
811
+    }
812
+
660 813
     public function admin_delete(Request $request, $id)
661 814
     {
662 815
       $segnalazione = Segnalazione::find($id);

+ 2
- 2
app/Models/Config.php Dosyayı Görüntüle

@@ -203,8 +203,8 @@ class Config extends \App\Models\AbstractModels\AbstractConfig
203 203
   public static function getStatiSegnalazione($type){
204 204
     switch($type){
205 205
       case 'aperte': return ['nuova'];
206
-      case 'in_lavorazione': return ['presa_in_carico', 'intervento_in_corso'];
207
-      case 'chiuse': return ['chiusa', 'risolta', 'rimandata'];
206
+      case 'in_lavorazione': return ['presa_in_carico', 'in_attesa_operatore', 'in_attesa_cliente', 'pianificata', 'da_pianificare'];
207
+      case 'chiuse': return ['chiusa', 'risolta'];
208 208
     }
209 209
   }
210 210
 

+ 4
- 1
app/Models/Segnalazione.php Dosyayı Görüntüle

@@ -14,6 +14,9 @@ class Segnalazione extends \App\Models\AbstractModels\AbstractSegnalazione imple
14 14
   /** Flag runtime: notifica l'utente "Aperta da" (non persistito). */
15 15
   public bool $notificaApertaDa = false;
16 16
 
17
+  /** Flag runtime: non notificare il cliente (Aperta da) su questo salvataggio. */
18
+  public bool $skipNotificaCliente = false;
19
+
17 20
   // public function getCasts()
18 21
   // {
19 22
   //   return array_merge(parent::getCasts(), [
@@ -406,7 +409,7 @@ class Segnalazione extends \App\Models\AbstractModels\AbstractSegnalazione imple
406 409
      */
407 410
     public function notifyApertaDaAnnotazionePubblica(Annotazione $annotazione): void
408 411
     {
409
-        if ($annotazione->riservata || ! $this->user_id) {
412
+        if ($this->skipNotificaCliente || $annotazione->riservata || ! $this->user_id) {
410 413
             return;
411 414
         }
412 415
 

+ 1
- 1
resources/views/config_notifiche/index.blade.php Dosyayı Görüntüle

@@ -81,7 +81,7 @@ $configData = Helper::appClasses();
81 81
                 </div>
82 82
                 @endforeach
83 83
               </div>
84
-              <div class="form-text">Seleziona uno o più utenti che riceveranno la notifica.</div>
84
+              <div class="form-text">Solo gli utenti con permesso di modifica segnalazioni.</div>
85 85
             </div>
86 86
             @else
87 87
             {{-- <div class="mt-3">

+ 454
- 31
resources/views/segnalazione/admin/index.blade.php Dosyayı Görüntüle

@@ -12,6 +12,9 @@ $aziendaOptions = Azienda::query()
12 12
     ->orderBy('nome')
13 13
     ->get()
14 14
     ->mapWithKeys(fn ($azienda) => [$azienda->id => $azienda->full_name]);
15
+$assegnatariOptions = \App\Models\User::assegnatariSegnalazione()->get();
16
+$canEditSegnalazione = Auth::user()?->can('edit-segnalazione');
17
+$canDeleteSegnalazione = Auth::user()?->can('delete-segnalazione');
15 18
 ?>
16 19
 
17 20
 @extends('layouts/layoutMaster')
@@ -75,21 +78,21 @@ $aziendaOptions = Azienda::query()
75 78
   <x-datatable-filter-field
76 79
     label="Numero"
77 80
     table-id="dataTable_segnalazione"
78
-    column="0"
81
+    column="1"
79 82
     type="text"
80 83
     placeholder="Es. numero o ID"
81 84
   />
82 85
   <x-datatable-filter-field
83 86
     label="Titolo"
84 87
     table-id="dataTable_segnalazione"
85
-    column="1"
88
+    column="2"
86 89
     type="text"
87 90
     placeholder="Cerca nel titolo"
88 91
   />
89 92
   <x-datatable-filter-field
90 93
     label="Azienda"
91 94
     table-id="dataTable_segnalazione"
92
-    column="2"
95
+    column="3"
93 96
     type="select"
94 97
     :options="$aziendaOptions"
95 98
     empty-label="Tutte"
@@ -97,7 +100,7 @@ $aziendaOptions = Azienda::query()
97 100
   <x-datatable-filter-field
98 101
     label="Stato"
99 102
     table-id="dataTable_segnalazione"
100
-    column="3"
103
+    column="4"
101 104
     type="select"
102 105
     :options="$statiOptions"
103 106
     empty-label="Tutti"
@@ -105,7 +108,7 @@ $aziendaOptions = Azienda::query()
105 108
   <x-datatable-filter-field
106 109
     label="Priorità"
107 110
     table-id="dataTable_segnalazione"
108
-    column="4"
111
+    column="5"
109 112
     type="select"
110 113
     :options="$prioritaOptions"
111 114
     empty-label="Tutte"
@@ -113,7 +116,7 @@ $aziendaOptions = Azienda::query()
113 116
   <x-datatable-filter-field
114 117
     label="Tipo"
115 118
     table-id="dataTable_segnalazione"
116
-    column="5"
119
+    column="6"
117 120
     type="select"
118 121
     :options="$tipoOptions"
119 122
     empty-label="Tutti"
@@ -121,21 +124,21 @@ $aziendaOptions = Azienda::query()
121 124
   <x-datatable-filter-field
122 125
     label="Assegnato a"
123 126
     table-id="dataTable_segnalazione"
124
-    column="7"
127
+    column="8"
125 128
     type="text"
126 129
     placeholder="Nome, cognome o email"
127 130
   />
128 131
   <x-datatable-filter-date-range
129 132
     label="Creato il"
130 133
     table-id="dataTable_segnalazione"
131
-    column="8"
134
+    column="9"
132 135
     start-label="Dal"
133 136
     end-label="Al"
134 137
   />
135 138
   <x-datatable-filter-date-range
136 139
     label="Ultimo aggiornamento"
137 140
     table-id="dataTable_segnalazione"
138
-    column="9"
141
+    column="10"
139 142
     start-label="Dal"
140 143
     end-label="Al"
141 144
     hint="Filtra per data di ultimo aggiornamento del record (non il testo “fa …” in tabella)."
@@ -177,6 +180,108 @@ $aziendaOptions = Azienda::query()
177 180
   </div>
178 181
 </div>
179 182
 
183
+@if($canEditSegnalazione || $canDeleteSegnalazione)
184
+<div id="segnalazione-batch-bar" class="segnalazione-batch-bar" hidden>
185
+  <div class="segnalazione-batch-bar-inner">
186
+    <div class="d-flex align-items-center gap-2 flex-wrap">
187
+      <span class="fw-semibold">
188
+        <span id="segnalazione-batch-count">0</span>
189
+        <span id="segnalazione-batch-count-label">selezionate</span>
190
+      </span>
191
+      @if($canEditSegnalazione)
192
+      <button type="button" class="btn btn-sm btn-primary" id="segnalazione-batch-edit">
193
+        <i class="bx bx-edit-alt me-1"></i>Modifica
194
+      </button>
195
+      @endif
196
+      @if($canDeleteSegnalazione)
197
+      <button type="button" class="btn btn-sm btn-danger" id="segnalazione-batch-delete">
198
+        <i class="bx bx-trash me-1"></i>Elimina
199
+      </button>
200
+      @endif
201
+      <button type="button" class="btn btn-sm btn-outline-secondary" id="segnalazione-batch-clear">
202
+        Annulla selezione
203
+      </button>
204
+    </div>
205
+  </div>
206
+</div>
207
+@endif
208
+
209
+@if($canEditSegnalazione)
210
+<div class="modal fade" id="modalBatchSegnalazione" tabindex="-1" aria-labelledby="modalBatchSegnalazioneLabel" aria-hidden="true">
211
+  <div class="modal-dialog modal-dialog-centered">
212
+    <div class="modal-content">
213
+      <form id="formBatchSegnalazione">
214
+        <div class="modal-header">
215
+          <h5 class="modal-title" id="modalBatchSegnalazioneLabel">Modifica segnalazioni</h5>
216
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
217
+        </div>
218
+        <div class="modal-body">
219
+          <p class="text-muted small mb-3">
220
+            I campi lasciati su «Non modificare» restano invariati.
221
+            Stai modificando <strong id="modalBatchCount">0</strong> segnalazioni.
222
+          </p>
223
+          <div class="mb-3">
224
+            <label for="batch_azienda_id" class="form-label">Azienda</label>
225
+            <select class="form-select" id="batch_azienda_id" name="azienda_id">
226
+              <option value="">Non modificare</option>
227
+              @foreach($aziendaOptions as $id => $label)
228
+              <option value="{{ $id }}">{{ $label }}</option>
229
+              @endforeach
230
+            </select>
231
+          </div>
232
+          <div class="mb-3">
233
+            <label for="batch_stato" class="form-label">Stato</label>
234
+            <select class="form-select" id="batch_stato" name="stato">
235
+              <option value="">Non modificare</option>
236
+              @foreach($statiOptions as $value => $label)
237
+              <option value="{{ $value }}">{{ $label }}</option>
238
+              @endforeach
239
+            </select>
240
+          </div>
241
+          <div class="mb-3">
242
+            <label for="batch_priorita" class="form-label">Priorità</label>
243
+            <select class="form-select" id="batch_priorita" name="priorita">
244
+              <option value="">Non modificare</option>
245
+              @foreach($prioritaOptions as $value => $label)
246
+              <option value="{{ $value }}">{{ $label }}</option>
247
+              @endforeach
248
+            </select>
249
+          </div>
250
+          <div class="mb-3">
251
+            <label for="batch_tipo" class="form-label">Tipo</label>
252
+            <select class="form-select" id="batch_tipo" name="tipo">
253
+              <option value="">Non modificare</option>
254
+              @foreach($tipoOptions as $value => $label)
255
+              <option value="{{ $value }}">{{ $label }}</option>
256
+              @endforeach
257
+            </select>
258
+          </div>
259
+          <div class="mb-3">
260
+            <label for="batch_assegnata_a_id" class="form-label">Assegnata a</label>
261
+            <select class="form-select" id="batch_assegnata_a_id" name="assegnata_a_id">
262
+              <option value="">Non modificare</option>
263
+              @foreach($assegnatariOptions as $user)
264
+              <option value="{{ $user->id }}">{{ $user->full_name }}</option>
265
+              @endforeach
266
+            </select>
267
+          </div>
268
+          <div class="form-check mt-3">
269
+            <input class="form-check-input" type="checkbox" value="1" id="batch_skip_notifica_cliente" name="skip_notifica_cliente">
270
+            <label class="form-check-label" for="batch_skip_notifica_cliente">
271
+              Non notificare il cliente
272
+            </label>
273
+          </div>
274
+        </div>
275
+        <div class="modal-footer">
276
+          <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Annulla</button>
277
+          <button type="submit" class="btn btn-primary" id="formBatchSegnalazioneSubmit">Salva</button>
278
+        </div>
279
+      </form>
280
+    </div>
281
+  </div>
282
+</div>
283
+@endif
284
+
180 285
 @endsection
181 286
 
182 287
 @section('page-style')
@@ -210,45 +315,363 @@ $aziendaOptions = Azienda::query()
210 315
     flex-shrink: 0 !important;
211 316
     display: inline-block !important;
212 317
   }
318
+
319
+  #dataTable_segnalazione tbody tr {
320
+    cursor: pointer;
321
+  }
322
+  #dataTable_segnalazione tbody tr.segnalazione-row-selected {
323
+    background-color: rgba(var(--bs-primary-rgb), .08);
324
+  }
325
+
326
+  .segnalazione-batch-bar {
327
+    position: fixed;
328
+    z-index: 1085;
329
+    inset-inline: 0;
330
+    inset-block-end: 0;
331
+    padding: 0.75rem 1.25rem;
332
+    background: var(--bs-paper-bg, var(--bs-body-bg));
333
+    box-shadow: 0 -0.5rem 1.25rem rgba(34, 48, 62, .12);
334
+    border-block-start: 1px solid var(--bs-border-color);
335
+  }
336
+  .segnalazione-batch-bar-inner {
337
+    max-width: 1400px;
338
+    margin-inline: auto;
339
+  }
340
+  body.segnalazione-batch-bar-open {
341
+    padding-bottom: 5rem;
342
+  }
213 343
 </style>
214 344
 @endsection
215 345
 
216 346
 @section('page-script')
217 347
 {{ $dataTable_segnalazione->scripts(attributes: ['type' => 'module']) }}
218 348
 
349
+<script>
350
+  window.SegnalazioneBatch = (function () {
351
+    const selected = new Map();
352
+    let clickTimer = null;
353
+    let bound = false;
354
+    const canEdit = @json((bool) $canEditSegnalazione);
355
+    const canDelete = @json((bool) $canDeleteSegnalazione);
356
+    const urls = {
357
+      update: @json(route('admin.segnalazione.batch_update')),
358
+      destroy: @json(route('admin.segnalazione.batch_delete')),
359
+      show: "{{ route('admin.segnalazione.show', ['id' => '__ID__']) }}",
360
+    };
219 361
 
220
-<script type="module">
362
+    function csrfToken() {
363
+      return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
364
+        || '{{ csrf_token() }}';
365
+    }
221 366
 
222
-  $(document).ready(function(){
223
-    $.ajaxSetup({
224
-      headers: {
225
-        'X-CSRF-TOKEN': '{{csrf_token()}}'
367
+    function metaFromCheckbox(cb) {
368
+      return {
369
+        azienda_id: cb.getAttribute('data-azienda-id') || '',
370
+        stato: cb.getAttribute('data-stato') || '',
371
+        priorita: cb.getAttribute('data-priorita') || '',
372
+        tipo: cb.getAttribute('data-tipo') || '',
373
+        assegnata_a_id: cb.getAttribute('data-assegnata-a-id') || '',
374
+      };
375
+    }
376
+
377
+    function ids() {
378
+      return Array.from(selected.keys());
379
+    }
380
+
381
+    function setSelected(id, meta, on) {
382
+      id = String(id);
383
+      if (on) {
384
+        selected.set(id, meta || selected.get(id) || {});
385
+      } else {
386
+        selected.delete(id);
226 387
       }
227
-    });
228
-
229
-    $("#dataTable_segnalazione").on('dblclick', 'tbody td', function () {
230
-      var id = $(this).closest('tr').attr('id');
231
-      window.location.href = "{{ route('admin.segnalazione.show', ['id' => '__ID__']) }}".replace('__ID__', id);
232
-    });
233
-
234
-    $("#dataTable_segnalazione").on('click', 'a.editor_delete', function (e) {
235
-      e.preventDefault();
236
-      editor.remove($(this).closest('tr'), {
237
-        title: 'Cancella record',
238
-        message: 'Sei sicuro di voler eliminare il record selezionato?',
239
-        buttons: 'Cancella record'
388
+    }
389
+
390
+    function syncRows() {
391
+      const table = document.getElementById('dataTable_segnalazione');
392
+      if (!table) return;
393
+      let pageChecked = 0;
394
+      let pageTotal = 0;
395
+      table.querySelectorAll('tbody tr').forEach(function (tr) {
396
+        if (tr.classList.contains('child')) return;
397
+        const cb = tr.querySelector('.js-segnalazione-select');
398
+        if (!cb) return;
399
+        pageTotal += 1;
400
+        const on = selected.has(String(cb.value));
401
+        cb.checked = on;
402
+        tr.classList.toggle('segnalazione-row-selected', on);
403
+        if (on) pageChecked += 1;
240 404
       });
241
-    });
405
+      const selectAll = document.getElementById('segnalazione-select-all');
406
+      if (selectAll) {
407
+        selectAll.checked = pageTotal > 0 && pageChecked === pageTotal;
408
+        selectAll.indeterminate = pageChecked > 0 && pageChecked < pageTotal;
409
+      }
410
+      syncBar();
411
+    }
242 412
 
243
-  });
244
-</script>
413
+    function syncBar() {
414
+      const bar = document.getElementById('segnalazione-batch-bar');
415
+      const count = selected.size;
416
+      const countEl = document.getElementById('segnalazione-batch-count');
417
+      const labelEl = document.getElementById('segnalazione-batch-count-label');
418
+      if (countEl) countEl.textContent = String(count);
419
+      if (labelEl) labelEl.textContent = count === 1 ? 'selezionata' : 'selezionate';
420
+      if (bar) {
421
+        bar.hidden = count === 0;
422
+      }
423
+      document.body.classList.toggle('segnalazione-batch-bar-open', count > 0);
424
+    }
425
+
426
+    function toggleId(id, meta, force) {
427
+      id = String(id);
428
+      const on = force === undefined ? !selected.has(id) : !!force;
429
+      setSelected(id, meta, on);
430
+      syncRows();
431
+    }
432
+
433
+    function notify(type, text) {
434
+      if (typeof Swal !== 'undefined') {
435
+        Swal.fire({ icon: type, title: type === 'success' ? 'Fatto' : 'Attenzione', text: text, timer: type === 'success' ? 2200 : undefined, showConfirmButton: type !== 'success' });
436
+        return;
437
+      }
438
+      window.alert(text);
439
+    }
440
+
441
+    function reloadTable() {
442
+      if (window.LaravelDataTables && window.LaravelDataTables['dataTable_segnalazione']) {
443
+        window.LaravelDataTables['dataTable_segnalazione'].ajax.reload(null, false);
444
+        return;
445
+      }
446
+      const dt = $('#dataTable_segnalazione').DataTable();
447
+      if (dt) dt.ajax.reload(null, false);
448
+    }
449
+
450
+    function resetForm() {
451
+      const form = document.getElementById('formBatchSegnalazione');
452
+      if (!form) return;
453
+      form.reset();
454
+      ['batch_azienda_id', 'batch_stato', 'batch_priorita', 'batch_tipo', 'batch_assegnata_a_id'].forEach(function (id) {
455
+        const el = document.getElementById(id);
456
+        if (el) el.value = '';
457
+      });
458
+      const skip = document.getElementById('batch_skip_notifica_cliente');
459
+      if (skip) skip.checked = false;
460
+    }
461
+
462
+    function prefillForm() {
463
+      resetForm();
464
+      const countEl = document.getElementById('modalBatchCount');
465
+      if (countEl) countEl.textContent = String(selected.size);
466
+      if (selected.size !== 1) return;
467
+      const meta = selected.values().next().value || {};
468
+      const map = {
469
+        batch_azienda_id: meta.azienda_id,
470
+        batch_stato: meta.stato,
471
+        batch_priorita: meta.priorita,
472
+        batch_tipo: meta.tipo,
473
+        batch_assegnata_a_id: meta.assegnata_a_id,
474
+      };
475
+      Object.keys(map).forEach(function (id) {
476
+        const el = document.getElementById(id);
477
+        if (!el || map[id] === undefined || map[id] === null || map[id] === '') return;
478
+        if ([].some.call(el.options, function (opt) { return opt.value === String(map[id]); })) {
479
+          el.value = String(map[id]);
480
+        }
481
+      });
482
+    }
483
+
484
+    function bind() {
485
+      if (bound) {
486
+        syncRows();
487
+        return;
488
+      }
489
+      bound = true;
490
+
491
+      const table = $('#dataTable_segnalazione');
492
+      const header = document.querySelector('#dataTable_segnalazione thead th:not(.dtr-control)');
493
+      if (header && !document.getElementById('segnalazione-select-all')) {
494
+        header.innerHTML = '<input type="checkbox" class="form-check-input" id="segnalazione-select-all" title="Seleziona tutte le righe visibili" aria-label="Seleziona tutte">';
495
+      }
496
+
497
+      table.on('click', '#segnalazione-select-all', function (e) {
498
+        e.stopPropagation();
499
+        const on = this.checked;
500
+        document.querySelectorAll('#dataTable_segnalazione tbody .js-segnalazione-select').forEach(function (cb) {
501
+          setSelected(cb.value, metaFromCheckbox(cb), on);
502
+        });
503
+        syncRows();
504
+      });
505
+
506
+      table.on('click', '.js-segnalazione-select', function (e) {
507
+        e.stopPropagation();
508
+        setSelected(this.value, metaFromCheckbox(this), this.checked);
509
+        syncRows();
510
+      });
511
+
512
+      table.on('click', 'tbody tr', function (e) {
513
+        if (this.classList.contains('child')) return;
514
+        if (e.target.closest('a, button, .dropdown, input, label, .js-segnalazione-select')) return;
515
+        const cb = this.querySelector('.js-segnalazione-select');
516
+        if (!cb) return;
517
+        const rowId = cb.value;
518
+        const meta = metaFromCheckbox(cb);
519
+        if (clickTimer) {
520
+          clearTimeout(clickTimer);
521
+          clickTimer = null;
522
+          return;
523
+        }
524
+        clickTimer = setTimeout(function () {
525
+          clickTimer = null;
526
+          toggleId(rowId, meta);
527
+        }, 220);
528
+      });
529
+
530
+      table.on('dblclick', 'tbody tr', function (e) {
531
+        if (this.classList.contains('child')) return;
532
+        if (e.target.closest('a, button, .dropdown, input')) return;
533
+        if (clickTimer) {
534
+          clearTimeout(clickTimer);
535
+          clickTimer = null;
536
+        }
537
+        const id = this.id || this.querySelector('.js-segnalazione-select')?.value;
538
+        if (!id) return;
539
+        window.location.href = urls.show.replace('__ID__', id);
540
+      });
541
+
542
+      table.on('draw.dt', function () {
543
+        syncRows();
544
+      });
545
+
546
+      document.getElementById('segnalazione-batch-clear')?.addEventListener('click', function () {
547
+        selected.clear();
548
+        syncRows();
549
+      });
550
+
551
+      document.getElementById('segnalazione-batch-edit')?.addEventListener('click', function () {
552
+        if (!canEdit || selected.size === 0) return;
553
+        prefillForm();
554
+        const modalEl = document.getElementById('modalBatchSegnalazione');
555
+        if (modalEl && window.bootstrap) {
556
+          bootstrap.Modal.getOrCreateInstance(modalEl).show();
557
+        }
558
+      });
559
+
560
+      document.getElementById('segnalazione-batch-delete')?.addEventListener('click', function () {
561
+        if (!canDelete || selected.size === 0) return;
562
+        const n = selected.size;
563
+        const msg = n === 1
564
+          ? 'Eliminare la segnalazione selezionata?'
565
+          : 'Eliminare le ' + n + ' segnalazioni selezionate?';
566
+        const run = function () {
567
+          fetch(urls.destroy, {
568
+            method: 'POST',
569
+            headers: {
570
+              'Content-Type': 'application/json',
571
+              'Accept': 'application/json',
572
+              'X-CSRF-TOKEN': csrfToken(),
573
+              'X-Requested-With': 'XMLHttpRequest',
574
+            },
575
+            body: JSON.stringify({ ids: ids() }),
576
+          }).then(function (res) { return res.json().then(function (data) { return { res: res, data: data }; }); })
577
+            .then(function (out) {
578
+              if (!out.res.ok || !out.data.success) {
579
+                notify('error', out.data.message || 'Impossibile eliminare.');
580
+                return;
581
+              }
582
+              selected.clear();
583
+              syncRows();
584
+              reloadTable();
585
+              notify('success', out.data.message);
586
+            })
587
+            .catch(function () {
588
+              notify('error', 'Errore di rete durante l\'eliminazione.');
589
+            });
590
+        };
591
+        if (typeof Swal !== 'undefined') {
592
+          Swal.fire({
593
+            icon: 'warning',
594
+            title: 'Elimina segnalazioni',
595
+            text: msg,
596
+            showCancelButton: true,
597
+            confirmButtonText: 'Elimina',
598
+            cancelButtonText: 'Annulla',
599
+            customClass: { confirmButton: 'btn btn-danger me-2', cancelButton: 'btn btn-outline-secondary' },
600
+            buttonsStyling: false,
601
+          }).then(function (result) {
602
+            if (result.isConfirmed) run();
603
+          });
604
+        } else if (window.confirm(msg)) {
605
+          run();
606
+        }
607
+      });
608
+
609
+      document.getElementById('formBatchSegnalazione')?.addEventListener('submit', function (e) {
610
+        e.preventDefault();
611
+        if (!canEdit || selected.size === 0) return;
612
+        const payload = { ids: ids(), skip_notifica_cliente: document.getElementById('batch_skip_notifica_cliente')?.checked ? 1 : 0 };
613
+        ['azienda_id', 'stato', 'priorita', 'tipo', 'assegnata_a_id'].forEach(function (name) {
614
+          const el = document.getElementById('batch_' + name);
615
+          if (el && el.value !== '') payload[name] = el.value;
616
+        });
617
+        if (Object.keys(payload).length <= 2) {
618
+          notify('error', 'Seleziona almeno un campo da aggiornare.');
619
+          return;
620
+        }
621
+        const submitBtn = document.getElementById('formBatchSegnalazioneSubmit');
622
+        if (submitBtn) submitBtn.disabled = true;
623
+        fetch(urls.update, {
624
+          method: 'POST',
625
+          headers: {
626
+            'Content-Type': 'application/json',
627
+            'Accept': 'application/json',
628
+            'X-CSRF-TOKEN': csrfToken(),
629
+            'X-Requested-With': 'XMLHttpRequest',
630
+          },
631
+          body: JSON.stringify(payload),
632
+        }).then(function (res) { return res.json().then(function (data) { return { res: res, data: data }; }); })
633
+          .then(function (out) {
634
+            if (submitBtn) submitBtn.disabled = false;
635
+            if (!out.res.ok || !out.data.success) {
636
+              const extra = Array.isArray(out.data.errors)
637
+                ? out.data.errors.join('\n')
638
+                : (out.data.errors ? Object.values(out.data.errors).flat().join('\n') : '');
639
+              notify('error', (out.data.message || 'Impossibile aggiornare.') + (extra ? '\n' + extra : ''));
640
+              return;
641
+            }
642
+            const modalEl = document.getElementById('modalBatchSegnalazione');
643
+            if (modalEl && window.bootstrap) {
644
+              bootstrap.Modal.getOrCreateInstance(modalEl).hide();
645
+            }
646
+            selected.clear();
647
+            syncRows();
648
+            reloadTable();
649
+            let text = out.data.message || 'Aggiornamento completato.';
650
+            if (out.data.errors && out.data.errors.length) {
651
+              text += '\n' + out.data.errors.join('\n');
652
+            }
653
+            notify(out.data.skipped ? 'warning' : 'success', text);
654
+          })
655
+          .catch(function () {
656
+            if (submitBtn) submitBtn.disabled = false;
657
+            notify('error', 'Errore di rete durante l\'aggiornamento.');
658
+          });
659
+      });
660
+
661
+      syncRows();
662
+    }
663
+
664
+    return { bind: bind };
665
+  })();
245 666
 
246
-<script>
247 667
   function initComplete_segnalazione(){
248 668
     $('div.dt-buttons button').removeClass('btn-secondary');
249 669
     if (window.DatatableOffcanvasFilters) {
250 670
       DatatableOffcanvasFilters.bind('#dt_filters_segnalazione');
251 671
     }
672
+    if (window.SegnalazioneBatch) {
673
+      SegnalazioneBatch.bind();
674
+    }
252 675
   }
253 676
 </script>
254 677
 @endsection

+ 2
- 0
routes/web.php Dosyayı Görüntüle

@@ -148,6 +148,8 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session')])->prefix('a
148 148
     Route::post('segnalazione/crea', [SegnalazioneController::class, 'admin_crea'])->name('admin.segnalazione.crea');
149 149
     Route::get('segnalazione/nuova', [SegnalazioneController::class, 'admin_nuova'])->name('admin.segnalazione.nuova');
150 150
     Route::get('segnalazione/options-by-azienda', [SegnalazioneController::class, 'admin_options_by_azienda'])->name('admin.segnalazione.options_by_azienda');
151
+    Route::post('segnalazione/batch-update', [SegnalazioneController::class, 'admin_batch_update'])->name('admin.segnalazione.batch_update');
152
+    Route::post('segnalazione/batch-delete', [SegnalazioneController::class, 'admin_batch_delete'])->name('admin.segnalazione.batch_delete');
151 153
     Route::get('segnalazione/{id}/show', [SegnalazioneController::class, 'admin_show'])->name('admin.segnalazione.show');
152 154
     Route::post('segnalazione/{id}/todo/{todoId}/toggle', [SegnalazioneController::class, 'admin_toggle_todo'])->name('admin.segnalazione.todo.toggle');
153 155
     Route::post('segnalazione/{id}/todo/{todoId}/note', [SegnalazioneController::class, 'admin_update_todo_note'])->name('admin.segnalazione.todo.note');

Loading…
İptal
Kaydet