Ver código fonte

segnalazione

Roberto Santini 1 mês atrás
pai
commit
ed95f5714a

+ 1
- 0
app/Events/NuovaSegnalazione.php Ver arquivo

@@ -18,6 +18,7 @@ class NuovaSegnalazione
18 18
     public function __construct(
19 19
         public Segnalazione $segnalazione,
20 20
         public bool $notificaApertaDa = false,
21
+        public bool $notificaAdmin = true,
21 22
     ) {
22 23
     }
23 24
 

+ 133
- 6
app/Http/Controllers/HomePageController.php Ver arquivo

@@ -145,10 +145,8 @@ class HomePageController extends Controller
145 145
   {
146 146
     $now = Carbon::now();
147 147
     $today = $now->copy()->startOfDay();
148
-    $byDay = $this->tempoMinutiByDay(
149
-      $now->copy()->subYear()->startOfYear(),
150
-      $now->copy()->endOfDay()
151
-    );
148
+    $lookbackStart = $now->copy()->subYears(3)->startOfYear();
149
+    $byDay = $this->tempoMinutiByDay($lookbackStart, $now->copy()->endOfDay());
152 150
 
153 151
     $weekStart = $now->copy()->startOfWeek(Carbon::MONDAY)->startOfDay();
154 152
     $weekOffsetDays = $now->isoWeekday() - 1;
@@ -162,6 +160,8 @@ class HomePageController extends Controller
162 160
     $yearStart = $now->copy()->startOfYear();
163 161
     $prevYearEnd = $now->copy()->subYear()->endOfDay();
164 162
 
163
+    $giorniBrevi = [1 => 'lun', 2 => 'mar', 3 => 'mer', 4 => 'gio', 5 => 'ven', 6 => 'sab', 7 => 'dom'];
164
+
165 165
     return [
166 166
       $this->buildTempoPeriodo(
167 167
         'Oggi',
@@ -174,6 +174,8 @@ class HomePageController extends Controller
174 174
         $now,
175 175
         $today->copy()->subDay(),
176 176
         $today->copy()->subDay()->endOfDay(),
177
+        $this->mediaMinutiGiornaliera($byDay, $lookbackStart, $today, (int) $now->isoWeekday()),
178
+        'media '.$giorniBrevi[(int) $now->isoWeekday()],
177 179
       ),
178 180
       $this->buildTempoPeriodo(
179 181
         'Settimana',
@@ -186,6 +188,8 @@ class HomePageController extends Controller
186 188
         $now,
187 189
         $prevWeekStart,
188 190
         $prevWeekEnd,
191
+        $this->mediaMinutiSettimanale($byDay, $lookbackStart, $weekStart, $weekOffsetDays),
192
+        'media sett.',
189 193
       ),
190 194
       $this->buildTempoPeriodo(
191 195
         'Mese',
@@ -198,6 +202,8 @@ class HomePageController extends Controller
198 202
         $now,
199 203
         $prevMonthStart,
200 204
         $prevMonthSameDay,
205
+        $this->mediaMinutiMensile($byDay, $lookbackStart, $monthStart, (int) $now->day),
206
+        'media mesi',
201 207
       ),
202 208
       $this->buildTempoPeriodo(
203 209
         'Anno',
@@ -210,12 +216,15 @@ class HomePageController extends Controller
210 216
         $now,
211 217
         $yearStart->copy()->subYear(),
212 218
         $prevYearEnd,
219
+        $this->mediaMinutiAnnuale($byDay, $lookbackStart, $yearStart, $now),
220
+        'media anni',
213 221
       ),
214 222
     ];
215 223
   }
216 224
 
217 225
   /**
218 226
    * @param  array<string, int>  $byDay
227
+   * @param  array{minuti: int, campioni: int}  $media
219 228
    * @return array<string, mixed>
220 229
    */
221 230
   private function buildTempoPeriodo(
@@ -229,6 +238,8 @@ class HomePageController extends Controller
229 238
     Carbon $currentTo,
230 239
     Carbon $previousFrom,
231 240
     Carbon $previousTo,
241
+    array $media,
242
+    string $mediaLabel,
232 243
   ): array {
233 244
     $minuti = $this->sumMinutiInRange($byDay, $currentFrom, $currentTo);
234 245
     $precedente = $this->sumMinutiInRange($byDay, $previousFrom, $previousTo);
@@ -242,7 +253,12 @@ class HomePageController extends Controller
242 253
       ? (int) round(($minuti / $atteso) * 100)
243 254
       : ($minuti > 0 ? 100 : 0);
244 255
 
245
-    $deltaSign = $delta > 0 ? '+' : ($delta < 0 ? '−' : '');
256
+    $mediaMinuti = (int) ($media['minuti'] ?? 0);
257
+    $mediaCampioni = (int) ($media['campioni'] ?? 0);
258
+    $deltaMedia = $minuti - $mediaMinuti;
259
+    $pctMedia = $mediaMinuti > 0
260
+      ? round(($deltaMedia / $mediaMinuti) * 100, 1)
261
+      : ($minuti > 0 ? 100.0 : 0.0);
246 262
 
247 263
     return [
248 264
       'label' => $label,
@@ -258,10 +274,17 @@ class HomePageController extends Controller
258 274
       'progress_width' => min(100, $pctAtteso),
259 275
       'has_target' => $atteso > 0,
260 276
       'vs_precedente_minuti' => $delta,
261
-      'vs_precedente_label' => $deltaSign.Config::formatTempo(abs($delta)),
277
+      'vs_precedente_label' => $this->formatTempoDelta($delta),
262 278
       'vs_precedente_percentuale' => $pctPrecedente,
263 279
       'precedente_label' => $previousLabel,
264 280
       'precedente_minuti_label' => Config::formatTempo($precedente),
281
+      'has_media' => $mediaCampioni > 0,
282
+      'media_campioni' => $mediaCampioni,
283
+      'media_label' => $mediaLabel,
284
+      'media_minuti_label' => Config::formatTempo($mediaMinuti),
285
+      'vs_media_minuti' => $deltaMedia,
286
+      'vs_media_label' => $this->formatTempoDelta($deltaMedia),
287
+      'vs_media_percentuale' => $pctMedia,
265 288
     ];
266 289
   }
267 290
 
@@ -301,6 +324,110 @@ class HomePageController extends Controller
301 324
     return $sum;
302 325
   }
303 326
 
327
+  /**
328
+   * Media dei giorni precedenti con lo stesso weekday (es. media dei martedì).
329
+   * I giorni senza registrazioni contano come 0.
330
+   *
331
+   * @param  array<string, int>  $byDay
332
+   * @return array{minuti: int, campioni: int}
333
+   */
334
+  private function mediaMinutiGiornaliera(array $byDay, Carbon $lookbackStart, Carbon $today, int $isoWeekday): array
335
+  {
336
+    $sum = 0;
337
+    $campioni = 0;
338
+    for ($d = $lookbackStart->copy()->startOfDay(); $d->lt($today); $d->addDay()) {
339
+      if ((int) $d->isoWeekday() !== $isoWeekday) {
340
+        continue;
341
+      }
342
+      $sum += $byDay[$d->toDateString()] ?? 0;
343
+      $campioni++;
344
+    }
345
+
346
+    return $this->mediaFromSum($sum, $campioni);
347
+  }
348
+
349
+  /**
350
+   * Media delle settimane precedenti, stessa porzione lun → weekday corrente.
351
+   *
352
+   * @param  array<string, int>  $byDay
353
+   * @return array{minuti: int, campioni: int}
354
+   */
355
+  private function mediaMinutiSettimanale(array $byDay, Carbon $lookbackStart, Carbon $weekStart, int $weekOffsetDays): array
356
+  {
357
+    $sum = 0;
358
+    $campioni = 0;
359
+    for ($cursor = $weekStart->copy()->subWeek(); $cursor->gte($lookbackStart); $cursor->subWeek()) {
360
+      $from = $cursor->copy()->startOfDay();
361
+      $to = $cursor->copy()->addDays($weekOffsetDays)->endOfDay();
362
+      $sum += $this->sumMinutiInRange($byDay, $from, $to);
363
+      $campioni++;
364
+    }
365
+
366
+    return $this->mediaFromSum($sum, $campioni);
367
+  }
368
+
369
+  /**
370
+   * Media dei mesi precedenti, dal 1 al medesimo giorno del mese.
371
+   *
372
+   * @param  array<string, int>  $byDay
373
+   * @return array{minuti: int, campioni: int}
374
+   */
375
+  private function mediaMinutiMensile(array $byDay, Carbon $lookbackStart, Carbon $monthStart, int $dayOfMonth): array
376
+  {
377
+    $sum = 0;
378
+    $campioni = 0;
379
+    for ($cursor = $monthStart->copy()->subMonth(); $cursor->gte($lookbackStart); $cursor->subMonth()) {
380
+      $from = $cursor->copy()->startOfDay();
381
+      $day = min($dayOfMonth, (int) $cursor->copy()->endOfMonth()->day);
382
+      $to = $cursor->copy()->day($day)->endOfDay();
383
+      $sum += $this->sumMinutiInRange($byDay, $from, $to);
384
+      $campioni++;
385
+    }
386
+
387
+    return $this->mediaFromSum($sum, $campioni);
388
+  }
389
+
390
+  /**
391
+   * Media degli anni precedenti, dal 1° gennaio alla medesima data.
392
+   *
393
+   * @param  array<string, int>  $byDay
394
+   * @return array{minuti: int, campioni: int}
395
+   */
396
+  private function mediaMinutiAnnuale(array $byDay, Carbon $lookbackStart, Carbon $yearStart, Carbon $now): array
397
+  {
398
+    $sum = 0;
399
+    $campioni = 0;
400
+    for ($yearsBack = 1; ; $yearsBack++) {
401
+      $from = $yearStart->copy()->subYears($yearsBack);
402
+      if ($from->lt($lookbackStart)) {
403
+        break;
404
+      }
405
+      $to = $now->copy()->subYears($yearsBack)->endOfDay();
406
+      $sum += $this->sumMinutiInRange($byDay, $from, $to);
407
+      $campioni++;
408
+    }
409
+
410
+    return $this->mediaFromSum($sum, $campioni);
411
+  }
412
+
413
+  /**
414
+   * @return array{minuti: int, campioni: int}
415
+   */
416
+  private function mediaFromSum(int $sum, int $campioni): array
417
+  {
418
+    return [
419
+      'minuti' => $campioni > 0 ? (int) round($sum / $campioni) : 0,
420
+      'campioni' => $campioni,
421
+    ];
422
+  }
423
+
424
+  private function formatTempoDelta(int $minuti): string
425
+  {
426
+    $sign = $minuti > 0 ? '+' : ($minuti < 0 ? '−' : '');
427
+
428
+    return $sign.Config::formatTempo(abs($minuti));
429
+  }
430
+
304 431
   private function countWeekdays(Carbon $from, Carbon $to): int
305 432
   {
306 433
     $count = 0;

+ 1
- 0
app/Http/Controllers/SegnalazioneController.php Ver arquivo

@@ -374,6 +374,7 @@ class SegnalazioneController extends Controller implements HasMiddleware
374 374
         $segnalazione->origine = 'manuale';
375 375
       }
376 376
       $segnalazione->notificaApertaDa = $request->boolean('notifica_aperta_da');
377
+      $segnalazione->notificaAdmin = false;
377 378
       $this->applyCcBccFromRequest($segnalazione, $request);
378 379
       $categoriaSicurezzaValue = $request->input('categoria_sicurezza');
379 380
       if ($categoriaSicurezzaValue !== null && $categoriaSicurezzaValue !== '') {

+ 6
- 0
app/Http/Controllers/UserController.php Ver arquivo

@@ -27,6 +27,7 @@ use Intervention\Image\Drivers\Gd\Driver;
27 27
 use Illuminate\Support\Facades\Validator;
28 28
 use App\Models\Role;
29 29
 use App\Support\PasswordRequirements;
30
+use Illuminate\Validation\Rule;
30 31
 
31 32
 class UserController extends Controller implements HasMiddleware
32 33
 {
@@ -470,6 +471,7 @@ class UserController extends Controller implements HasMiddleware
470 471
             'email' => 'nullable|email|max:255',
471 472
             'telefono' => 'nullable|string|max:255',
472 473
             'piva' => 'nullable|string|max:50',
474
+            'azienda_id' => ['nullable', Rule::exists('users', 'id')->where('is_azienda', 1)],
473 475
           ]);
474 476
 
475 477
           $isAzienda = $request->boolean('is_azienda');
@@ -498,6 +500,9 @@ class UserController extends Controller implements HasMiddleware
498 500
           $user->email = $validated['email'] ?? null;
499 501
           $user->telefono = $validated['telefono'] ?? null;
500 502
           $user->piva = $validated['piva'] ?? null;
503
+          if (! $isAzienda) {
504
+            $user->azienda_id = $validated['azienda_id'] ?? null;
505
+          }
501 506
           $user->save();
502 507
 
503 508
           $role = Role::where('name', 'user')->first();
@@ -512,6 +517,7 @@ class UserController extends Controller implements HasMiddleware
512 517
               'id' => $user->id,
513 518
               'text' => $user->full_name,
514 519
               'is_azienda' => (bool) $user->is_azienda,
520
+              'azienda_id' => $user->azienda_id,
515 521
             ],
516 522
           ]);
517 523
         }

+ 7
- 2
app/Listeners/InviaNotificaSegnalazione.php Ver arquivo

@@ -19,14 +19,19 @@ class InviaNotificaSegnalazione
19 19
         $segnalazione = $event->segnalazione->loadMissing(['user', 'assegnataA', 'azienda']);
20 20
 
21 21
         $configNotifiche = ConfigNotifiche::where('event', ConfigNotifiche::NUOVA_SEGNALAZIONE_ADMIN)->first();
22
-        if ($configNotifiche != null && $configNotifiche->is_attivo == true && count($configNotifiche->utenti) > 0) {
22
+        if (
23
+            $event->notificaAdmin
24
+            && $configNotifiche != null
25
+            && $configNotifiche->is_attivo == true
26
+            && count($configNotifiche->utenti) > 0
27
+        ) {
23 28
             Notification::send($configNotifiche->utenti, new NuovaSegnalazioneAdmin($segnalazione));
24 29
         }
25 30
 
26 31
         if ($segnalazione->assegnata_a_id != null) {
27 32
             $configNotifiche = ConfigNotifiche::where('event', ConfigNotifiche::NUOVA_SEGNALAZIONE_ASSEGNATA)->first();
28 33
             if ($configNotifiche != null && $configNotifiche->is_attivo == true) {
29
-                $targets = User::notifiableMembersForAssigneeId((int) $segnalazione->assegnata_a_id);
34
+                $targets = User::notifiableMembersForAssigneeIdExceptAuth((int) $segnalazione->assegnata_a_id);
30 35
                 if ($targets->isNotEmpty()) {
31 36
                     Notification::send($targets, new NuovaSegnalazioneUser($segnalazione, false));
32 37
                 }

+ 3
- 0
app/Models/Segnalazione.php Ver arquivo

@@ -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: notifica gli admin per nuova segnalazione (non persistito). */
18
+  public bool $notificaAdmin = true;
19
+
17 20
   /** Flag runtime: non notificare il cliente (Aperta da) su questo salvataggio. */
18 21
   public bool $skipNotificaCliente = false;
19 22
 

+ 17
- 0
app/Models/User.php Ver arquivo

@@ -10,6 +10,7 @@ use Laravel\Jetstream\HasProfilePhoto;
10 10
 use Laravel\Sanctum\HasApiTokens;
11 11
 use Spatie\Permission\Traits\HasRoles;
12 12
 use Carbon\Carbon;
13
+use Illuminate\Support\Facades\Auth;
13 14
 use Illuminate\Support\Facades\Storage;
14 15
 
15 16
 class User extends \App\Models\AbstractModels\AbstractUser
@@ -158,6 +159,22 @@ class User extends \App\Models\AbstractModels\AbstractUser
158 159
       ->get();
159 160
   }
160 161
 
162
+  /**
163
+   * Destinatari assegnazione, escluso l'utente autenticato (niente auto-notifica).
164
+   *
165
+   * @return \Illuminate\Support\Collection<int, self>
166
+   */
167
+  public static function notifiableMembersForAssigneeIdExceptAuth(?int $assigneeId): \Illuminate\Support\Collection
168
+  {
169
+    $targets = static::notifiableMembersForAssigneeId($assigneeId);
170
+    $authId = Auth::id();
171
+    if (! $authId) {
172
+      return $targets;
173
+    }
174
+
175
+    return $targets->reject(fn (self $user) => (int) $user->id === (int) $authId)->values();
176
+  }
177
+
161 178
   public function sendPasswordResetNotification($token): void
162 179
   {
163 180
     $this->notify(new ResetPasswordNotification($token));

+ 2
- 1
app/Observers/SegnalazioneObserver.php Ver arquivo

@@ -36,6 +36,7 @@ class SegnalazioneObserver
36 36
     event(new NuovaSegnalazione(
37 37
       $segnalazione,
38 38
       (bool) ($segnalazione->notificaApertaDa ?? false),
39
+      (bool) ($segnalazione->notificaAdmin ?? true),
39 40
     ));
40 41
   }
41 42
 
@@ -49,7 +50,7 @@ class SegnalazioneObserver
49 50
     if ($segnalazione->wasChanged('assegnata_a_id') && $segnalazione->assegnata_a_id !== null) {
50 51
       $config = ConfigNotifiche::where('event', ConfigNotifiche::NUOVA_SEGNALAZIONE_ASSEGNATA)->first();
51 52
       if ($config && $config->is_attivo) {
52
-        $targets = User::notifiableMembersForAssigneeId((int) $segnalazione->assegnata_a_id);
53
+        $targets = User::notifiableMembersForAssigneeIdExceptAuth((int) $segnalazione->assegnata_a_id);
53 54
         if ($targets->isNotEmpty()) {
54 55
           $previousId = $segnalazione->getOriginal('assegnata_a_id');
55 56
           $isReassignment = $previousId !== null;

+ 16
- 0
resources/views/dashboard/dashboards-manutenzioni.blade.php Ver arquivo

@@ -260,6 +260,22 @@ $configData = Helper::appClasses();
260 260
               vs {{ $periodo['precedente_label'] }}
261 261
               <span class="text-muted">({{ $periodo['precedente_minuti_label'] }})</span>
262 262
             </div>
263
+            @if($periodo['has_media'])
264
+            @php
265
+              $mediaDelta = $periodo['vs_media_minuti'];
266
+              $mediaClass = $mediaDelta > 0 ? 'success' : ($mediaDelta < 0 ? 'warning' : 'muted');
267
+              $mediaIcon = $mediaDelta > 0 ? 'bx-trending-up' : ($mediaDelta < 0 ? 'bx-trending-down' : 'bx-minus');
268
+            @endphp
269
+            <div class="small mt-1 text-{{ $mediaClass }}">
270
+              <i class="bx {{ $mediaIcon }} me-1"></i>
271
+              {{ $periodo['vs_media_label'] }}
272
+              @if($periodo['vs_media_minuti'] != 0)
273
+                ({{ $periodo['vs_media_percentuale'] > 0 ? '+' : '' }}{{ rtrim(rtrim(number_format($periodo['vs_media_percentuale'], 1, ',', ''), '0'), ',') }}%)
274
+              @endif
275
+              vs {{ $periodo['media_label'] }}
276
+              <span class="text-muted">({{ $periodo['media_minuti_label'] }})</span>
277
+            </div>
278
+            @endif
263 279
           </div>
264 280
         </div>
265 281
       </div>

+ 128
- 9
resources/views/segnalazione/admin/nuova.blade.php Ver arquivo

@@ -100,15 +100,25 @@ $selectedContrattoId = old('contratto_servizio_id');
100 100
                     <label class="form-check-label small" for="notifica_aperta_da">Notifica</label>
101 101
                   </div>
102 102
                 </div>
103
-                <select class="form-select" id="user_id" name="user_id" required>
104
-                  @foreach(\App\Models\User::soloUtenti()->orderBy('cognome')->orderBy('nome')->get() as $user)
105
-                  <option value="{{ $user->id }}"
106
-                    data-azienda-id="{{ $user->azienda_id }}"
107
-                    {{ (string) $selectedUserId === (string) $user->id ? 'selected' : '' }}>
108
-                    {{ $user->full_name }}
109
-                  </option>
110
-                  @endforeach
111
-                </select>
103
+                <div class="d-flex align-items-stretch gap-2">
104
+                  <div class="flex-grow-1">
105
+                    <select class="form-select" id="user_id" name="user_id" required data-placeholder="Cerca utente…">
106
+                      @foreach(\App\Models\User::soloUtenti()->orderBy('cognome')->orderBy('nome')->get() as $user)
107
+                      <option value="{{ $user->id }}"
108
+                        data-azienda-id="{{ $user->azienda_id }}"
109
+                        {{ (string) $selectedUserId === (string) $user->id ? 'selected' : '' }}>
110
+                        {{ $user->full_name }}
111
+                      </option>
112
+                      @endforeach
113
+                    </select>
114
+                  </div>
115
+                  @can('create-user')
116
+                  <button type="button" class="btn btn-icon btn-outline-info" id="btnApriNuovoCliente"
117
+                    title="Nuovo cliente" aria-label="Nuovo cliente">
118
+                    <i class="bx bx-plus"></i>
119
+                  </button>
120
+                  @endcan
121
+                </div>
112 122
               </div>
113 123
             </div>
114 124
           </div>
@@ -259,6 +269,49 @@ $selectedContrattoId = old('contratto_servizio_id');
259 269
   </div>
260 270
 </div>
261 271
 
272
+@can('create-user')
273
+<div class="modal fade" id="modalNuovoClienteSegnalazione" tabindex="-1" aria-hidden="true">
274
+  <div class="modal-dialog modal-dialog-centered">
275
+    <div class="modal-content">
276
+      <div class="modal-header">
277
+        <h5 class="modal-title">Nuovo cliente</h5>
278
+        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
279
+      </div>
280
+      <form id="formNuovoClienteSegnalazione">
281
+        @csrf
282
+        <input type="hidden" name="is_azienda" value="0">
283
+        <input type="hidden" name="azienda_id" id="nuovoClienteAziendaId" value="">
284
+        <div class="modal-body">
285
+          <div class="row g-3">
286
+            <div class="col-md-6">
287
+              <label class="form-label" for="nuovoClienteNome">Nome <span class="text-danger">*</span></label>
288
+              <input type="text" class="form-control" name="nome" id="nuovoClienteNome" required>
289
+            </div>
290
+            <div class="col-md-6">
291
+              <label class="form-label" for="nuovoClienteCognome">Cognome</label>
292
+              <input type="text" class="form-control" name="cognome" id="nuovoClienteCognome">
293
+            </div>
294
+            <div class="col-md-6">
295
+              <label class="form-label" for="nuovoClienteEmail">Email</label>
296
+              <input type="email" class="form-control" name="email" id="nuovoClienteEmail">
297
+            </div>
298
+            <div class="col-md-6">
299
+              <label class="form-label" for="nuovoClienteTelefono">Telefono</label>
300
+              <input type="text" class="form-control" name="telefono" id="nuovoClienteTelefono">
301
+            </div>
302
+          </div>
303
+          <div class="alert alert-danger mt-3 mb-0 d-none" id="nuovoClienteErrors"></div>
304
+        </div>
305
+        <div class="modal-footer">
306
+          <button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal">Annulla</button>
307
+          <button type="submit" class="btn btn-primary">Salva cliente</button>
308
+        </div>
309
+      </form>
310
+    </div>
311
+  </div>
312
+</div>
313
+@endcan
314
+
262 315
 @endsection
263 316
 
264 317
 @section('page-style')
@@ -289,6 +342,9 @@ $selectedContrattoId = old('contratto_servizio_id');
289 342
     margin-top: 0;
290 343
     margin-bottom: 0.25rem;
291 344
   }
345
+  #user_id + .select2-container {
346
+    width: 100% !important;
347
+  }
292 348
 </style>
293 349
 @endsection
294 350
 
@@ -334,6 +390,69 @@ $selectedContrattoId = old('contratto_servizio_id');
334 390
 
335 391
     const quill_descrizione = initQuillEditor('quill_descrizione', 'quill_descrizione_toolbar', 'descrizione');
336 392
 
393
+    const $userSelect = $('#user_id');
394
+    if ($userSelect.length && typeof $userSelect.select2 === 'function') {
395
+      $userSelect.select2({
396
+        placeholder: $userSelect.data('placeholder') || 'Cerca utente…',
397
+        width: '100%',
398
+        dropdownParent: $userSelect.parent(),
399
+        language: {
400
+          noResults: function () { return 'Nessun utente trovato'; },
401
+          searching: function () { return 'Ricerca…'; }
402
+        }
403
+      });
404
+    }
405
+
406
+    const modalNuovoClienteEl = document.getElementById('modalNuovoClienteSegnalazione');
407
+    const formNuovoCliente = document.getElementById('formNuovoClienteSegnalazione');
408
+    const btnApriNuovoCliente = document.getElementById('btnApriNuovoCliente');
409
+    if (modalNuovoClienteEl && formNuovoCliente && btnApriNuovoCliente && typeof bootstrap !== 'undefined') {
410
+      const modalNuovoCliente = bootstrap.Modal.getOrCreateInstance(modalNuovoClienteEl);
411
+      const errorsBox = document.getElementById('nuovoClienteErrors');
412
+
413
+      btnApriNuovoCliente.addEventListener('click', function () {
414
+        formNuovoCliente.reset();
415
+        document.getElementById('nuovoClienteAziendaId').value = $('#azienda_id').val() || '';
416
+        if (errorsBox) {
417
+          errorsBox.classList.add('d-none');
418
+          errorsBox.innerHTML = '';
419
+        }
420
+        modalNuovoCliente.show();
421
+      });
422
+
423
+      formNuovoCliente.addEventListener('submit', function (e) {
424
+        e.preventDefault();
425
+        if (errorsBox) {
426
+          errorsBox.classList.add('d-none');
427
+          errorsBox.innerHTML = '';
428
+        }
429
+        const formData = new FormData(formNuovoCliente);
430
+        formData.set('azienda_id', $('#azienda_id').val() || '');
431
+        formData.set('is_azienda', '0');
432
+
433
+        fetch('{{ route('admin.user.quick_store') }}', {
434
+          method: 'POST',
435
+          headers: { 'X-Requested-With': 'XMLHttpRequest', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' },
436
+          body: formData
437
+        }).then(async function (response) {
438
+          const data = await response.json();
439
+          if (!response.ok) throw data;
440
+          const option = new Option(data.user.text, data.user.id, true, true);
441
+          option.setAttribute('data-azienda-id', data.user.azienda_id || '');
442
+          $userSelect.append(option).val(String(data.user.id)).trigger('change');
443
+          modalNuovoCliente.hide();
444
+          if (typeof notyf !== 'undefined') notyf.success(data.message || 'Cliente creato');
445
+        }).catch(function (err) {
446
+          const errors = (err && err.errors) ? err.errors : { general: [(err && err.message) ? err.message : 'Errore durante il salvataggio'] };
447
+          const messages = Object.values(errors).flat();
448
+          if (errorsBox) {
449
+            errorsBox.classList.remove('d-none');
450
+            errorsBox.innerHTML = '<ul class="mb-0"><li>' + messages.join('</li><li>') + '</li></ul>';
451
+          }
452
+        });
453
+      });
454
+    }
455
+
337 456
     function fillSelect(select, items, emptyLabel, selectedId) {
338 457
       const $el = $(select);
339 458
       const current = selectedId != null ? String(selectedId) : String($el.val() || '');

Carregando…
Cancelar
Salvar