Bläddra i källkod

fix UC cassa, campi personalizzati punto vendita (non concluso),fix datatableOperatore -Create

marcofalabretti 2 timmar sedan
förälder
incheckning
2a8a9ef670

+ 5
- 7
app/DataTables/OperatoreDataTableEditor.php Visa fil

@@ -38,7 +38,7 @@ class OperatoreDataTableEditor extends DataTablesEditor
38 38
       'email' => 'required|email|unique:operatore,email',
39 39
       'telefono' => 'nullable',
40 40
       // 'username' => 'required|unique:operatore,username',
41
-      // 'password' => 'required',
41
+      'password' => 'nullable|sometimes',
42 42
       'is_attivo' => 'boolean',
43 43
     ];
44 44
   }
@@ -60,6 +60,7 @@ class OperatoreDataTableEditor extends DataTablesEditor
60 60
       'cognome' => 'required',
61 61
       'email' => 'nullable|email',
62 62
       'telefono' => 'nullable',
63
+      'password' => 'nullable|sometimes',
63 64
       // 'username' => 'required|unique:operatore,username',
64 65
       // 'password' => 'required',
65 66
       'is_attivo' => 'boolean',
@@ -85,10 +86,10 @@ class OperatoreDataTableEditor extends DataTablesEditor
85 86
   {
86 87
     // $model->roles()->sync([$data['ruolo_id']]);
87 88
     $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
88
-    if(isset($data['password'])){
89
-      $data['password'] = Hash::make($data['password']);
89
+    if(!isset($data['password'])){
90
+      $data['password'] = Str::random(8);
90 91
     }
91
-    $data['username'] = Str::slug($data['nome'] . '.' . $data['cognome']);
92
+    $data['username'] = $this->createUsername($data);
92 93
     return $data;
93 94
   }
94 95
 
@@ -97,9 +98,6 @@ class OperatoreDataTableEditor extends DataTablesEditor
97 98
     // dd($data['ruolo']);
98 99
     // $model->roles()->sync([$data['ruolo_id']]);
99 100
     $data['is_attivo'] = isset($data['is_attivo']) ? true : false;
100
-    if(isset($data['password'])){
101
-    $data['password'] = Hash::make($data['password']);
102
-    }
103 101
     $data['username'] = $this->createUsername($data);
104 102
     return $data;
105 103
   }

+ 2
- 2
app/DataTables/RigaOrdineDataTable.php Visa fil

@@ -118,10 +118,9 @@ class RigaOrdineDataTable extends DataTable
118 118
                     ->dom('rtip')
119 119
                     // ->buttons($buttons)
120 120
                     ->orderBy(0, 'asc')
121
-                    ->scrollY("48vh")
122 121
                     ->paging(false)
123 122
                     ->selectStyleSingle()
124
-                    ->responsive(true)
123
+                    ->responsive($this->vista === 'catalogo')
125 124
                     // ->editor(
126 125
                     //     Editor::make()
127 126
                     //         ->fields([
@@ -139,6 +138,7 @@ class RigaOrdineDataTable extends DataTable
139 138
      */
140 139
     public function getColumns(): array
141 140
     {
141
+ 
142 142
         if($this->vista == 'catalogo'){
143 143
             return [
144 144
                 Column::make('id')->visible(false),

+ 40
- 26
app/Http/Controllers/CarrelloController.php Visa fil

@@ -189,34 +189,48 @@ class CarrelloController extends Controller
189 189
     }
190 190
 
191 191
     public function azzera(Request $request){
192
-        $ordine = Ordine::find($request->ordine_id);
193
-        if($ordine == null){
194
-            return response()->json([
195
-                'success' => false,
196
-                'message' => 'Ordine non trovato',
197
-                'data' => null,
198
-            ]);
199
-        }
200
-        $righe = RigaOrdine::where('ordine_id', $ordine->id)->delete();
201
-        $ordine->prezzo = 0;
202
-        $ordine->save();
203
-        $ordine->delete();
204
-
205
-        switch(Dispositivo::cassa()->first()->tipo){
206
-            case Dispositivo::CASSA:
207
-                return redirect()->route('punto-vendita.show');
208
-                break;
209
-            case Dispositivo::TIPO_KIOSK:
210
-                // return redirect()->route('punto_vendita.kiosk.index');
211
-                return redirect()->route('punto-vendita.show');
192
+        $request->validate([
193
+            'ordine_id' => 'required|integer|exists:ordine,id',
194
+        ]);
195
+        $result = $this->carrelloService->azzeraCarrello($request->ordine_id);
196
+        return response()->json([
197
+            'success' => $result['success'],
198
+            'message' => $result['message'],
199
+            'data' => $result['data'],
200
+        ]);
201
+        // $ordine = Ordine::find($request->ordine_id);
202
+        // if($ordine == null){
203
+        //     return response()->json([
204
+        //         'success' => false,
205
+        //         'message' => 'Ordine non trovato',
206
+        //         'data' => null,
207
+        //     ]);
208
+        // }
209
+        // $righe = RigaOrdine::where('ordine_id', $ordine->id)->delete();
210
+        // $ordine->prezzo = 0;
211
+        // $ordine->save();
212
+        // $ordine->delete();
212 213
 
213
-                break;
214
-            case Dispositivo::CAMERIERE:
215
-                // return redirect()->route('punto_vendita.cameriere.index');
216
-                return redirect()->route('punto-vendita.show');
214
+        // return response()->json([
215
+        //     'success' => true,
216
+        //     'message' => 'Carrello svuotato',
217
+        //     'data' => null,
218
+        // ]);
219
+        // // switch(Dispositivo::cassa()->first()->tipo){
220
+        // //     case Dispositivo::CASSA:
221
+        // //         return redirect()->route('punto-vendita.show');
222
+        // //         break;
223
+        // //     case Dispositivo::TIPO_KIOSK:
224
+        // //         // return redirect()->route('punto_vendita.kiosk.index');
225
+        // //         return redirect()->route('punto-vendita.show');
226
+
227
+        // //         break;
228
+        // //     case Dispositivo::CAMERIERE:
229
+        // //         // return redirect()->route('punto_vendita.cameriere.index');
230
+        // //         return redirect()->route('punto-vendita.show');
217 231
                 
218
-                break;
219
-        }
232
+        // //         break;
233
+        // // }
220 234
     }
221 235
 
222 236
     public function totale(Request $request){

+ 20
- 0
app/Http/Controllers/PuntoVenditaController.php Visa fil

@@ -87,6 +87,7 @@ class PuntoVenditaController extends Controller
87 87
             'data_apertura_dispositivo' => 'nullable|date',
88 88
             'data_chiusura_dispositivo' => 'nullable|date',
89 89
             'endpoint_id' => 'sometimes|nullable|exists:endpoint,id',
90
+            'info' => 'sometimes|nullable|json',
90 91
         ],[
91 92
             'nome.required' => 'Il nome è richiesto',
92 93
             'tipo.required' => 'Il tipo è richiesto',
@@ -106,8 +107,27 @@ class PuntoVenditaController extends Controller
106 107
             'pin_sblocco.max' => 'Il pin di sblocco deve essere lungo al massimo 4 caratteri',
107 108
             'pin_sblocco.regex' => 'Il pin di sblocco deve contenere solo numeri',
108 109
             'pin_sblocco.unique' => 'Il pin di sblocco deve essere unico',
110
+            'info.json' => 'Il campo info deve essere un JSON valido',
109 111
         ]);
110 112
 
113
+        //Esempio di info:
114
+        // {
115
+        //     "campi_ordine" :{
116
+        //             "cliente" :{
117
+        //                 "label" : "Cliente",
118
+        //                 "placeholder" : "Inserisci il tup nome",
119
+        //                 "mostra" : true,
120
+        //                 "required" : false,
121
+        //                 "type" : "text",
122
+        //                 "stampa" : true,
123
+        //             }
124
+        //         }
125
+        // }
126
+        
127
+        if($request->has('info')){
128
+            $validated['info'] = json_decode($request->info, true);
129
+        }
130
+
111 131
             $puntoVendita->update($validated);
112 132
             return response()->json(['success' => true, 'message' => 'Punto di vendita aggiornato con successo']);
113 133
         }else{

+ 2
- 0
app/Models/AbstractModels/AbstractDispositivo.php Visa fil

@@ -52,6 +52,7 @@ abstract class AbstractDispositivo extends \Illuminate\Foundation\Auth\User
52 52
         'data_chiusura_dispositivo' => 'datetime',
53 53
         'cucina_id' => 'integer',
54 54
         'occupato' => 'boolean',
55
+        'info' => 'json',
55 56
         'created_at' => 'datetime',
56 57
         'updated_at' => 'datetime'
57 58
     ];
@@ -79,6 +80,7 @@ abstract class AbstractDispositivo extends \Illuminate\Foundation\Auth\User
79 80
         'data_chiusura_dispositivo',
80 81
         'cucina_id',
81 82
         'occupato',
83
+        'info',
82 84
         'created_at',
83 85
         'updated_at'
84 86
     ];

+ 35
- 2
app/Services/Carrello/CarrelloService.php Visa fil

@@ -95,9 +95,42 @@ class CarrelloService
95 95
         };
96 96
     }
97 97
 
98
-    public function azzeraCarrello(int $ordineId): void
98
+    public function azzeraCarrello(int $ordine_id): array
99 99
     {
100
-        RigaOrdine::where('ordine_id', $ordineId)->delete();
100
+        $ordine = Ordine::find($ordine_id);
101
+
102
+        if($ordine == null){
103
+            return [
104
+                'success' => false,
105
+                'message' => 'Ordine non trovato',
106
+                'data' => null,  
107
+            ];
108
+        }
109
+        $righe = RigaOrdine::where('ordine_id', $ordine->id)->delete();
110
+        $ordine->prezzo = 0;
111
+        $ordine->save();
112
+        $ordine->delete();
113
+
114
+        return [
115
+            'success' => true,
116
+            'message' => 'Carrello svuotato',
117
+            'data' => null,
118
+        ];
119
+        // switch(Dispositivo::cassa()->first()->tipo){
120
+        //     case Dispositivo::CASSA:
121
+        //         return redirect()->route('punto-vendita.show');
122
+        //         break;
123
+        //     case Dispositivo::TIPO_KIOSK:
124
+        //         // return redirect()->route('punto_vendita.kiosk.index');
125
+        //         return redirect()->route('punto-vendita.show');
126
+
127
+        //         break;
128
+        //     case Dispositivo::CAMERIERE:
129
+        //         // return redirect()->route('punto_vendita.cameriere.index');
130
+        //         return redirect()->route('punto-vendita.show');
131
+                
132
+        //         break;
133
+        // }
101 134
     }
102 135
 
103 136
     public function aggiornaNota(int $rigaOrdineId, string $note): RigaOrdine

+ 28
- 0
database/migrations/2026_06_19_131553_aggiungi_info_su_dispositivo.php Visa fil

@@ -0,0 +1,28 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::table('dispositivo', function (Blueprint $table) {
15
+            $table->json('info')->nullable();
16
+        });
17
+    }
18
+
19
+    /**
20
+     * Reverse the migrations.
21
+     */
22
+    public function down(): void
23
+    {
24
+        Schema::table('dispositivo', function (Blueprint $table) {
25
+            $table->dropColumn('info');
26
+        });
27
+    }
28
+};

+ 33
- 5
resources/views/punto_vendita/cassa/_partials/carrello/pagamento/checkout.blade.php Visa fil

@@ -27,7 +27,7 @@ $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
27 27
       </p>
28 28
 
29 29
 
30
-      </form>
30
+    
31 31
       @if($metodi->isEmpty())
32 32
         <p class="text-warning mt-4 mb-0">Nessun metodo di pagamento attivo.</p>
33 33
       @else
@@ -126,9 +126,12 @@ $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
126 126
           <h5 class="mb-0">€ {{ number_format($totaleOrdine, 2, ',', '.') }}</h5>
127 127
         </div>
128 128
         <div class="d-grid mt-5">
129
-          <button type="submit" class="btn btn-success" {{ $metodi->isEmpty() ? 'disabled' : '' }}>
130
-            <span class="me-2">Conferma pagamento</span>
131
-            <i class="bx bx-right-arrow-alt scaleX-n1-rtl"></i>
129
+          <button type="submit" id="btn-conferma-pagamento" class="btn btn-success" {{ $metodi->isEmpty() ? 'disabled' : '' }}>
130
+
131
+          <span class="spinner-border spinner-border-sm d-none" id="checkout-submit-spinner" role="status"></span>
132
+
133
+            <span class="me-2" id="checkout-submit-label">Conferma pagamento</span>
134
+            <i class="bx bx-right-arrow-alt scaleX-n1-rtl" id="checkout-submit-icon"></i>
132 135
           </button>
133 136
 
134 137
         </div>
@@ -143,14 +146,15 @@ $nRighe = $ordine ? $ordine->righe_ordine->count() : 0;
143 146
   </div>
144 147
 </div>
145 148
 
146
-
147 149
 </form>
148 150
 
149 151
 <script>
150 152
 $(document).ready(function(){
153
+  bindCheckoutSpinner();
151 154
     $('input[name="metodo_pagamento_id"]').on('change', function(){
152 155
 
153 156
       checkUX($(this));
157
+
154 158
   });
155 159
 });
156 160
 
@@ -192,6 +196,30 @@ function chiE(tag_id){
192 196
   });
193 197
 }
194 198
 
199
+function showCheckoutSpinner() {
200
+  $('#btn-conferma-pagamento').prop('disabled', true);
201
+  $('#checkout-submit-spinner').removeClass('d-none');
202
+  $('#checkout-submit-label').text('Elaborazione pagamento…');
203
+  $('#checkout-submit-icon').addClass('d-none');
204
+}
205
+
206
+function hideCheckoutSpinner() {
207
+  $('#checkout-submit-spinner').addClass('d-none');
208
+  $('#checkout-submit-label').text('Conferma pagamento');
209
+  $('#checkout-submit-icon').removeClass('d-none');
210
+  $('#btn-conferma-pagamento').prop('disabled', false);
211
+}
212
+
213
+function bindCheckoutSpinner() {
214
+  $('form[action="{{ route('carrello.paga_ordine') }}"]').on('submit', function () {
215
+    if ($('#btn-conferma-pagamento').prop('disabled')) {
216
+      return false;
217
+    }
218
+    showCheckoutSpinner();
219
+    return true;
220
+  });
221
+}
222
+
195 223
 /////////////////////// spostato in INDEX
196 224
 // let nfcAbortController = null;
197 225
 

+ 6
- 6
resources/views/punto_vendita/cassa/_partials/carrello/riepilogo.blade.php Visa fil

@@ -1,13 +1,13 @@
1
-<div class="col-12 mb-6">
1
+<div class="col-12">
2 2
     <div class="card">
3 3
       <div class="card-widget-separator-wrapper">
4
-        <div class="card-body card-widget-separator">
4
+        <div class="p-2 card-widget-separator">
5 5
           <div class="gy-4 gy-sm-1 justify-content-around row">
6 6
             <div class="col-sm-6 col-md-6 col-lg-6">
7 7
               <div class="d-flex justify-content-between align-items-center card-widget-1  pb-4 pb-sm-0">
8 8
                 <div>
9 9
                   <h4 class="mb-0">
10
-                    <input type="text" placeholder="Cliente" class="form-control text-primary border-0 fw-bold border-bottom border-primary border-radius-0" style="border-radius: 0px;" id="cliente" value="{{ $ordine->info['cliente']??'' }}" aria-describedby="defaultFormControlHelp">
10
+                    <input type="text" placeholder="Cliente" class="form-control text-primary border-0 fw-bold border-bottom border-primary border-radius-0" style="border-radius: 0px;" id="cliente" value="{{ $ordine->info['cliente']??'' }}" aria-describedby="defaultFormControlHelp" @if($punto_vendita->info['campi_ordine']['cliente']['required'] ?? false) required @endif>
11 11
                 </h4>
12 12
                   <small class="mb-0 text-primary">Cliente</small>
13 13
                 </div>
@@ -19,7 +19,7 @@
19 19
               <div class="d-flex justify-content-between align-items-center card-widget-2  pb-4 pb-sm-0">
20 20
                 <div>
21 21
                   <h4 class="mb-0">
22
-                  <input type="text" placeholder="Tavolo" class="form-control text-primary border-0 fw-bold border-bottom border-primary border-radius-0"  style="border-radius: 0px;" id="tavolo" value="{{ $ordine->info['tavolo']??'' }}" aria-describedby="defaultFormControlHelp">
22
+                  <input type="text" placeholder="Tavolo" class="form-control border-0 fw-bold border-bottom border-primary border-radius-0"  style="border-radius: 0px;" id="tavolo" value="{{ $ordine->info['tavolo']??'' }}" aria-describedby="defaultFormControlHelp" @if($punto_vendita->info['campi_ordine']['tavolo']['required'] ?? false) required @endif>
23 23
                 </h4>
24 24
                   <small class="mb-0 text-primary">Tavolo</small>
25 25
                 </div>
@@ -27,7 +27,7 @@
27 27
               </div>
28 28
               <hr class="d-none d-sm-block d-lg-none">
29 29
             </div>
30
-            <div class="col-sm-3 col-md-3 col-lg-3">
30
+            <!-- <div class="col-sm-3 col-md-3 col-lg-3">
31 31
               <div class="d-flex justify-content-between align-items-center  pb-4 pb-sm-0 card-widget-3 ">
32 32
                 <div>
33 33
                   <h4 class="mb-0 text-primary fw-bold border-bottom border-primary border-radius-0" id="totale_carrello">
@@ -42,7 +42,7 @@
42 42
                 </div>
43 43
 
44 44
               </div>
45
-            </div>
45
+            </div> -->
46 46
 
47 47
           </div>
48 48
 

+ 10
- 9
resources/views/punto_vendita/cassa/_partials/menu_cucine.blade.php Visa fil

@@ -199,7 +199,8 @@
199 199
     min-width: 0;
200 200
   }
201 201
   .cassa-cucine-card .tab-content {
202
-    flex: 1 1 auto;
202
+    flex: 1 1 0;
203
+    height: 0;
203 204
     min-height: 0;
204 205
   }
205 206
   .cassa-cucine-card .tab-pane {
@@ -208,7 +209,8 @@
208 209
     overflow: hidden;
209 210
   }
210 211
   .cassa-cucine-card .tab-pane.active.show {
211
-    display: block;
212
+    display: flex;
213
+    flex-direction: column;
212 214
   }
213 215
   .cassa-cucine-tabs .nav-link {
214 216
     white-space: nowrap;
@@ -280,11 +282,14 @@
280 282
     min-width: 0;
281 283
   }
282 284
   .cassa-cucine-card .cassa-piatti-grid {
283
-    height: 100%;
285
+    flex: 1 1 0;
286
+    height: 0;
284 287
     min-height: 0;
285 288
     max-height: none;
286 289
     overflow-y: auto;
287 290
     overflow-x: hidden;
291
+    -webkit-overflow-scrolling: touch;
292
+    overscroll-behavior: contain;
288 293
     align-content: flex-start;
289 294
     margin: 0;
290 295
     padding-right: 0.35rem;
@@ -293,12 +298,8 @@
293 298
   }
294 299
   @media (max-width: 1199.98px) {
295 300
     .cassa-cucine-card {
296
-      height: auto;
297
-      min-height: 30rem;
298
-    }
299
-    .cassa-cucine-card .cassa-piatti-grid {
300
-      max-height: 20rem;
301
-      height: auto;
301
+      height: 100% !important;
302
+      min-height: 0;
302 303
     }
303 304
   }
304 305
   .cassa-piatto-add-icon {

+ 55
- 3
resources/views/punto_vendita/cassa/_partials/scripts.blade.php Visa fil

@@ -145,6 +145,9 @@ function aggiornaCarrello() {
145 145
         if (typeof initComplete_rigaordine === "function") {
146 146
             initComplete_rigaordine();
147 147
         }
148
+        if (typeof cassaSyncCartScroll === "function") {
149
+            cassaSyncCartScroll();
150
+        }
148 151
     });
149 152
     aggiornaTotaleCarrello();
150 153
 
@@ -179,7 +182,20 @@ function azzeraCarrelloAjax() {
179 182
             _token: "{{ csrf_token() }}",
180 183
         },
181 184
         success: function(response) {
182
-            window.location.reload();
185
+            if(response.success){
186
+                new Notyf({
187
+                    duration: 2000,
188
+                    position: { x: 'right', y: 'top' }
189
+                }).success(response.message);
190
+                window.location.reload();
191
+            }else{
192
+                Swal.fire({
193
+                    title: "Errore",
194
+                    text: response.message,
195
+                    icon: "error",
196
+                });
197
+            }
198
+          
183 199
         },
184 200
         error: function(xhr, status, error) {
185 201
             // console.log(xhr.responseText);
@@ -265,8 +281,14 @@ function azzeraCarrelloAjax() {
265 281
 
266 282
 <script>
267 283
   function initComplete_rigaordine(){
284
+    var $ = window.jQuery || window.$;
285
+    if (!$) {
286
+      cassaSyncCartScroll();
287
+      return;
288
+    }
289
+
268 290
     $('div.dt-buttons button').removeClass('btn-secondary');
269
-    $('div.dt-search').addClass('mt-0 mb-4');
291
+    $('div.dt-search').addClass('mt-0 mb-2');
270 292
     $('div.dt-buttons').removeClass('btn-group');
271 293
     $('.dt-type-numeric').each(function() {
272 294
       $(this).removeClass('dt-type-numeric');
@@ -274,8 +296,9 @@ function azzeraCarrelloAjax() {
274 296
     $('table.dataTable tbody').css('border-style', 'none !important');
275 297
     aggiornaTotaleCarrello();
276 298
     $(".dt-empty").text("Carrello vuoto");
277
-        $('.mb-6 .card').addClass('shadow-none');
299
+    $('.mb-6 .card').addClass('shadow-none');
278 300
     $('.mb-6').removeClass('mb-6');
301
+    cassaSyncCartScroll();
279 302
 
280 303
     @if(isset($vista) && $vista == 'catalogo')
281 304
       $('table').removeClass('datatable table dt-inline');
@@ -283,6 +306,35 @@ function azzeraCarrelloAjax() {
283 306
 
284 307
 }
285 308
 
309
+  function cassaSyncCartScroll() {
310
+    var card = document.querySelector('.cassa-cart-card');
311
+    var el = document.querySelector('.cassa-cart-scroll');
312
+    if (!card || !el) {
313
+      return;
314
+    }
315
+
316
+    var footer = document.querySelector('.cassa-footer');
317
+    var footerGap = footer ? footer.getBoundingClientRect().height + 8 : 8;
318
+    var available = window.innerHeight - el.getBoundingClientRect().top - footerGap;
319
+
320
+    if (available > 80) {
321
+      el.style.maxHeight = Math.floor(available) + 'px';
322
+    } else {
323
+      el.style.maxHeight = '';
324
+    }
325
+    el.style.overflowY = 'auto';
326
+
327
+    window.requestAnimationFrame(function() {
328
+      var dt = window.LaravelDataTables && window.LaravelDataTables['dataTable_rigaordine'];
329
+      if (dt && typeof dt.columns !== 'undefined') {
330
+        dt.columns.adjust();
331
+      }
332
+    });
333
+  }
334
+
335
+  window.addEventListener('resize', cassaSyncCartScroll);
336
+  window.addEventListener('load', cassaSyncCartScroll);
337
+
286 338
   function aggiornaTotaleCarrello() {
287 339
     $.ajax({
288 340
       url: "{{ route('carrello.totale') }}",

+ 113
- 47
resources/views/punto_vendita/cassa/index.blade.php Visa fil

@@ -47,77 +47,116 @@ $cucine = $cucine ?? collect();
47 47
   --svg: url("data:image/svg+xml,%3csvg width='24' height='24' fill='currentColor' viewBox='0 0 24 24' transform='' xmlns='http://www.w3.org/2000/svg'%3e%3c!--Boxicons v3.0.8 https://boxicons.com %7c License https://docs.boxicons.com/free--%3e%3cpath d='M17.13 5.54C16.33 3.42 14.32 2 12 2S7.67 3.42 6.87 5.54A5.506 5.506 0 0 0 2 11c0 2.07 1.18 3.95 3 4.88V18c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-2.12c1.82-.93 3-2.81 3-4.88 0-2.82-2.13-5.15-4.87-5.46m.53 8.75c-.4.14-.67.52-.67.94v1.78H7v-1.78c0-.42-.27-.8-.67-.94-1.4-.5-2.33-1.82-2.33-3.28 0-1.93 1.57-3.5 3.42-3.5.04 0 .14.01.18.02.49 0 .9-.31 1-.78.36-1.61 1.76-2.73 3.41-2.73s3.05 1.12 3.41 2.73c.1.47.51.81 1 .78.06 0 .12 0 .09-.01 1.93 0 3.5 1.57 3.5 3.5 0 1.47-.94 2.79-2.33 3.28ZM5 20h14v2H5z'%3e%3c/path%3e%3c/svg%3e");
48 48
 }
49 49
 
50
+html:has(.cassa-front-page),
51
+html:has(.cassa-front-page) body {
52
+  height: 100%;
53
+  overflow: hidden;
54
+}
55
+
50 56
 .cassa-front-page{
51
-  --cassa-panels-height: calc(100dvh - 10.4rem);
52
-  height: calc(100dvh - 2px);
53
-  display: grid;
54
-  grid-template-rows: auto auto minmax(0, 1fr) auto;
57
+  height: 100dvh;
58
+  max-height: 100dvh;
59
+  display: flex;
60
+  flex-direction: column;
55 61
   overflow: hidden;
56 62
   max-width: none;
57
-  padding-top: 0.75rem;
58
-  padding-bottom: 0.5rem;
63
+  padding: 0.5rem 0.75rem 0.35rem;
64
+  box-sizing: border-box;
65
+}
66
+.cassa-front-page > .row.cassa-main-row{
67
+  flex: 1 1 0;
59 68
 }
60 69
 .cassa-title{
61
-  margin-bottom: 0.5rem;
70
+  flex-shrink: 0;
71
+  margin-bottom: 0.35rem;
72
+}
73
+.cassa-front-page > .alert{
74
+  flex-shrink: 0;
75
+  margin-bottom: 0.35rem;
62 76
 }
63 77
 .cassa-main-row{
64
-  height: var(--cassa-panels-height);
65
-  flex: 0 0 auto;
78
+  flex: 1 1 auto;
66 79
   min-height: 0;
80
+  height: auto;
81
+  display: flex;
82
+  flex-wrap: wrap;
83
+  align-content: stretch;
84
+  align-items: stretch;
67 85
   --bs-gutter-y: 0.5rem;
68 86
   --bs-gutter-x: 0.75rem;
69 87
   margin-top: 0 !important;
70 88
   overflow: hidden;
71 89
 }
72 90
 .cassa-main-row > [class*='col-']{
91
+  display: flex;
92
+  flex-direction: column;
73 93
   height: 100%;
74 94
   max-height: 100%;
75 95
   min-height: 0;
96
+  overflow: hidden;
76 97
 }
77 98
 .cassa-cart-card{
78 99
   height: 100%;
79 100
   min-height: 0;
101
+  max-height: 100%;
80 102
   display: flex;
81 103
   flex-direction: column;
104
+  overflow: hidden;
82 105
 }
83
-.cassa-cart-card .carrello-container{
84
-  min-height: 0;
106
+.cassa-cart-header{
107
+  flex-shrink: 0;
85 108
 }
86
-.cassa-cart-list{
87
-  flex: 1 1 auto;
109
+.cassa-cart-riepilogo{
110
+  flex-shrink: 0;
88 111
   min-height: 0;
89
-  display: flex;
90
-  flex-direction: column;
91 112
 }
92
-.cassa-cart-list .accordion{
113
+.cassa-cart-card .carrello-container:not(.cassa-cart-scroll){
114
+  flex-shrink: 0;
115
+}
116
+.cassa-cart-card .carrello-container .mb-6{
117
+  margin-bottom: 0 !important;
118
+}
119
+.cassa-cart-card .card-widget-separator .card-body{
120
+  padding: 0.55rem 0.75rem;
121
+}
122
+.cassa-cart-card .card-widget-separator .pb-4{
123
+  padding-bottom: 0.25rem !important;
124
+}
125
+.cassa-cart-scroll{
93 126
   flex: 1 1 auto;
94
-  min-height: 0;
95
-  overflow-y: auto;
127
+  min-height: 6rem;
128
+  overflow-y: auto !important;
96 129
   overflow-x: hidden;
97
-  padding-right: 0.2rem;
130
+  -webkit-overflow-scrolling: touch;
131
+  overscroll-behavior: contain;
132
+  padding-right: 0.15rem;
98 133
   scrollbar-width: thin;
99
-  margin-top: 0.5rem !important;
134
+}
135
+.cassa-cart-scroll .dataTables_wrapper{
136
+  width: 100%;
137
+}
138
+.cassa-cart-scroll table.dataTable{
139
+  margin-bottom: 0;
140
+}
141
+.cassa-cart-scroll .dataTables_filter{
142
+  margin-bottom: 0.35rem;
143
+}
144
+.cassa-cart-scroll .dataTables_info{
145
+  padding-top: 0.35rem;
100 146
 }
101 147
 .cassa-footer{
102 148
   flex-shrink: 0;
103
-  margin-top: 0.45rem !important;
149
+  margin-top: 0.25rem !important;
150
+  padding-top: 0.15rem;
104 151
   line-height: 1.2;
105 152
 }
106 153
 @media (max-width: 1199.98px){
107
-  .cassa-front-page{
108
-    height: auto;
109
-    min-height: 100dvh;
110
-    overflow: visible;
111
-    display: block;
112
-    --cassa-panels-height: auto;
113
-  }
114 154
   .cassa-main-row{
115
-    height: auto;
116
-    flex: 1 1 auto;
117
-    overflow: visible;
155
+    flex-direction: column;
118 156
   }
119 157
   .cassa-main-row > [class*='col-']{
120
-    height: auto;
158
+    flex: 1 1 0;
159
+    min-height: 0;
121 160
     max-height: none;
122 161
   }
123 162
 }
@@ -161,11 +200,23 @@ $cucine = $cucine ?? collect();
161 200
       @if($punto_vendita->binding_token)
162 201
         <span class="px-3 py-1 alert alert-success ms-2"> 
163 202
           <i class="bx bx-link"></i>
164
-          Linked
203
+          <!-- Linked -->
165 204
         </span>
166 205
       @endif
167 206
 
168
-      <a href="{{ route('punto-vendita.show', ['punto_vendita_id' => $punto_vendita->id , 'vista' => 'catalogo']) }}" class="ms-2 px-3 py-1 alert text-bg-primary"> <!-- btn btn-primary -->
207
+      <span class="dropdown">
208
+            <button class="btn text-muted p-0" type="button" id="transactionID" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
209
+              <i class="bx bx-menu bx-lg text-primary fs-2"></i> <!-- bx-dots-vertical-rounded-->
210
+            </button>
211
+            <div class="dropdown-menu dropdown-menu-end" aria-labelledby="transactionID">
212
+              <a class="dropdown-item" href="javascript:void(0);" onclick="showInfoPuntoVendita()">Informazioni punto vendita</a>
213
+              <!-- <a class="dropdown-item" href="javascript:void(0);" onclick="stampaRiepilogo()">Stampa riepilogo</a> -->
214
+              <a class="dropdown-item" href="javascript:void(0);" onclick="dissociaDispositivo()">Dissocia dispositivo</a>
215
+              <!-- <a class="dropdown-item" href="javascript:void(0);" onclick="chiudiPuntoVendita(alert('Chiudi {{ $punto_vendita->tipo}}'))">Chiudi {{ $punto_vendita->tipo}}</a> -->
216
+            </div>
217
+          </span>
218
+
219
+      <a href="{{ route('punto-vendita.show', ['punto_vendita_id' => $punto_vendita->id , 'vista' => 'catalogo']) }}" class=" d-none ms-2 px-3 py-1 alert text-bg-primary"> <!-- btn btn-primary -->
169 220
         <i class="bx bx-list-ul mx-1"></i>
170 221
         vista catalogo
171 222
       </a>
@@ -177,16 +228,33 @@ $cucine = $cucine ?? collect();
177 228
 
178 229
   @include('_partials.status', ['success' => session('success'), 'error' => session('error')])
179 230
 
180
-  <div class="row g-2 justify-content-center mt-0 cassa-main-row">
181
-    <div class="col-12 col-xl-8 d-flex min-h-0">
231
+  <div class="row g-2 justify-content-center mt-0 cassa-main-row flex-grow-1 min-h-0">
232
+    <div class="col-12 col-xl-8 d-flex flex-column min-h-0">
182 233
       @include('punto_vendita.cassa._partials.menu_cucine', ['cucine' => $cucine, 'punto_vendita' => $punto_vendita])
183 234
     </div>
184
-    <div class="col-12 col-xl-4 d-flex flex-column min-h-0">
185
-      <div class="card border shadow-sm w-100 h-100 cassa-cart-card">
186
-        <div class="card-body text-center pt-2 d-flex align-items-center justify-content-between">
187
-          <i class="bx bx-cart fs-4 text-muted"></i>
188
-          <h6 class="mt-2 mb-0">Carrello (<span class="text-primary">#{{ $ordine->id }}</span>)</h6>
189
-          <div class="dropdown">
235
+
236
+
237
+    <div class="col-12 col-xl-4 d-flex flex-column min-h-0 flex-grow-1">
238
+      <div class="card border shadow-sm w-100 h-100 min-h-0 cassa-cart-card">
239
+        <div class=" text-center p-2 cassa-cart-header d-flex align-items-center justify-content-between alert alert-primary m-1" style="height:8%;">
240
+          
241
+        <div class="d-flex align-items-center justify-content-center gap-2"><i class="bx bx-cart fs-4 text-muted"></i><span class="text-primary fs-large fw-bold">#{{ $ordine->id }}</span></div>
242
+
243
+        <div class=""> <!-- col-sm-3 col-md-3 col-lg-3 -->
244
+              
245
+                  <h4 class="mb-0 text-primary fw-bold" id="totale_carrello">
246
+                    @if($ordine->totale > 0)
247
+                     € {{ number_format($ordine->totale, 2, ',', '.') }}
248
+                    @else
249
+                    € {{ number_format($ordine->righe_ordine->sum('prezzo'), 2, ',', ',') }}
250
+                    @endif
251
+
252
+                  </h4>
253
+                  <!-- <small class="mb-0 text-primary">Totale</small> -->
254
+              
255
+            </div>
256
+          <!-- <h6 class="mt-2 mb-0">Carrello ()</h6> -->
257
+          <!-- <div class="dropdown">
190 258
             <button class="btn text-muted p-0" type="button" id="transactionID" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
191 259
               <i class="bx bx-dots-vertical-rounded bx-lg text-primary fs-4"></i>
192 260
             </button>
@@ -196,18 +264,16 @@ $cucine = $cucine ?? collect();
196 264
               <a class="dropdown-item" href="javascript:void(0);" onclick="dissociaDispositivo()">Dissocia dispositivo</a>
197 265
               <a class="dropdown-item" href="javascript:void(0);" onclick="chiudiPuntoVendita(alert('Chiudi {{ $punto_vendita->tipo}}'))">Chiudi {{ $punto_vendita->tipo}}</a>
198 266
             </div>
199
-          </div>
267
+          </div> -->
200 268
         </div>
201
-        <div class=" carrello-container card-body p-0 text-start">
269
+        <div class="carrello-container card-body p-0 text-start cassa-cart-riepilogo">
202 270
           @include('punto_vendita.cassa._partials.carrello.riepilogo')
203 271
         </div>
204
-        <div class=" carrello-container card-body pt-0 text-start cassa-cart-list">
205
-        <div id="accordionPopoutIcon" class="accordion accordion-popout">
272
+        <div class="card-body pt-0 pb-2 text-start cassa-cart-scroll">
206 273
           {{ $dataTable_rigaordine->table() }}
207 274
           <!-- @foreach($ordine->righe_ordine as $riga_ordine)
208 275
             @include('punto_vendita.cassa._partials.carrello.righe_ordine' , ['riga_ordine' => $riga_ordine])
209 276
           @endforeach -->
210
-          </div>
211 277
         </div>
212 278
       </div>
213 279
     </div>

+ 487
- 0
resources/views/punto_vendita/edit.blade.php Visa fil

@@ -69,6 +69,35 @@ $configData = Helper::appClasses();
69 69
   $puoDissociareDispositivo = Auth::user()->hasRole('superadmin')
70 70
     || Auth::user()->hasRole('amministratore')
71 71
     || Auth::user()->hasRole('administrator');
72
+
73
+  $campiOrdineCatalogo = [
74
+    'cliente' => ['label' => 'Cliente', 'icon' => 'bx-user', 'type' => 'text', 'mostra' => true, 'required' => true, 'stampa' => true, 'placeholder' => 'Cliente'],
75
+    'tavolo' => ['label' => 'Tavolo', 'icon' => 'bx-chair', 'type' => 'text', 'mostra' => true, 'required' => true, 'stampa' => true, 'placeholder' => 'Tavolo'],
76
+    'telefono' => ['label' => 'Telefono', 'icon' => 'bx-phone', 'type' => 'tel', 'mostra' => false, 'required' => false, 'stampa' => false, 'placeholder' => 'Telefono'],
77
+    'note_ordine' => ['label' => 'Note ordine', 'icon' => 'bx-note', 'type' => 'text', 'mostra' => false, 'required' => false, 'stampa' => true, 'placeholder' => 'Note per la cucina'],
78
+    'coperti' => ['label' => 'N. coperti', 'icon' => 'bx-group', 'type' => 'number', 'mostra' => false, 'required' => false, 'stampa' => false, 'placeholder' => 'Coperti'],
79
+  ];
80
+  $campiOrdineSalvati = $puntoVendita->info['campi_ordine'] ?? [];
81
+  $campiOrdine = [];
82
+  foreach ($campiOrdineCatalogo as $chiave => $defaults) {
83
+    $campiOrdine[$chiave] = array_merge($defaults, $campiOrdineSalvati[$chiave] ?? []);
84
+    $campiOrdine[$chiave]['custom'] = false;
85
+  }
86
+  foreach ($campiOrdineSalvati as $chiave => $campo) {
87
+    if (! array_key_exists($chiave, $campiOrdineCatalogo)) {
88
+      $campiOrdine[$chiave] = array_merge([
89
+        'label' => $campo['label'] ?? ucfirst(str_replace('_', ' ', $chiave)),
90
+        'icon' => 'bx-edit',
91
+        'type' => $campo['type'] ?? 'text',
92
+        'mostra' => false,
93
+        'required' => false,
94
+        'stampa' => false,
95
+        'placeholder' => $campo['placeholder'] ?? ($campo['label'] ?? $chiave),
96
+        'custom' => true,
97
+      ], $campo);
98
+      $campiOrdine[$chiave]['custom'] = true;
99
+    }
100
+  }
72 101
 @endphp
73 102
 
74 103
 <div class="nav-align-top">
@@ -83,6 +112,11 @@ $configData = Helper::appClasses();
83 112
         <i class="bx bx-cog me-1"></i>Generali
84 113
       </button>
85 114
     </li>
115
+    <li class="nav-item" role="presentation">
116
+      <button type="button" class="nav-link" role="tab" data-bs-toggle="tab" data-bs-target="#pv-tab-dati-ordine" aria-controls="pv-tab-dati-ordine" aria-selected="false">
117
+        <i class="bx bx-notepad me-1"></i>Dati ordine
118
+      </button>
119
+    </li>
86 120
     <li class="nav-item" role="presentation">
87 121
       <button type="button" class="nav-link" role="tab" data-bs-toggle="tab" data-bs-target="#pv-tab-cucine" aria-controls="pv-tab-cucine" aria-selected="false">
88 122
         <i class="bx bx-restaurant me-1"></i>Cucine
@@ -171,6 +205,9 @@ $configData = Helper::appClasses();
171 205
             <button type="button" class="btn btn-sm btn-outline-primary" data-pv-go-tab="#pv-tab-generali">
172 206
               <i class="bx bx-cog me-1"></i> Modifica generali
173 207
             </button>
208
+            <button type="button" class="btn btn-sm btn-outline-primary" data-pv-go-tab="#pv-tab-dati-ordine">
209
+              <i class="bx bx-notepad me-1"></i> Dati ordine
210
+            </button>
174 211
             <button type="button" class="btn btn-sm btn-outline-primary" data-pv-go-tab="#pv-tab-cucine">
175 212
               <i class="bx bx-restaurant me-1"></i> Gestisci cucine
176 213
             </button>
@@ -405,6 +442,177 @@ $configData = Helper::appClasses();
405 442
       </form>
406 443
     </div>
407 444
 
445
+    <div class="tab-pane fade" id="pv-tab-dati-ordine" role="tabpanel">
446
+      <div class="mt-1">
447
+        <div class="alert alert-secondary d-flex align-items-start gap-2 mb-4" role="alert">
448
+          <i class="bx bx-info-circle fs-5 mt-1"></i>
449
+          <div>
450
+            <strong>Campi mostrati in cassa</strong>
451
+            <p class="mb-0 small">Configura i campi predefiniti o aggiungine di personalizzati. Per i campi nuovi inserisci solo etichetta e tipo: la chiave tecnica viene generata automaticamente e identifica il valore in <code>Ordine-&gt;info</code>.</p>
452
+          </div>
453
+        </div>
454
+
455
+        <form id="puntoVenditaEditCampiOrdineForm">
456
+          <div class="card mb-4 border-primary border-opacity-25">
457
+            <div class="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
458
+              <h6 class="card-title mb-0"><i class="bx bx-plus-circle me-1"></i> Aggiungi campo personalizzato</h6>
459
+            </div>
460
+            <div class="card-body">
461
+              <div class="row g-3 align-items-end">
462
+                <div class="col-md-4">
463
+                  <label for="pv_nuovo_campo_label" class="form-label">Etichetta</label>
464
+                  <input type="text" class="form-control form-control-sm" id="pv_nuovo_campo_label" placeholder="es. Referente evento" autocomplete="off">
465
+                  <div class="form-text">Mostrata all'operatore in cassa</div>
466
+                </div>
467
+                <div class="col-md-3">
468
+                  <label for="pv_nuovo_campo_type" class="form-label">Tipo</label>
469
+                  <select class="form-select form-select-sm" id="pv_nuovo_campo_type">
470
+                    <option value="text">Testo</option>
471
+                    <option value="tel">Telefono</option>
472
+                    <option value="email">Email</option>
473
+                    <option value="number">Numero</option>
474
+                    <option value="checkbox">Checkbox</option>
475
+                  </select>
476
+                  <div class="form-text">Tipo di input mostrato all'operatore</div>
477
+                </div>
478
+                <div class="col-md-3">
479
+                  <label class="form-label">Chiave generata</label>
480
+                  <div class="form-control form-control-sm bg-light text-muted" id="pv_nuovo_campo_chiave_preview" aria-live="polite">—</div>
481
+                  <div class="form-text">Calcolata dall'etichetta</div>
482
+                </div>
483
+                <div class="col-md-2">
484
+                  <button type="button" class="btn btn-sm btn-primary w-100" id="pvAggiungiCampoOrdine">
485
+                    <i class="bx bx-plus me-1"></i> Aggiungi
486
+                  </button>
487
+                  <div class="form-text">poi salva la configurazione</div>
488
+                </div>
489
+              </div>
490
+            </div>
491
+          </div>
492
+
493
+          <div class="table-responsive">
494
+            <table class="table table-hover align-middle mb-0">
495
+              <thead class="table-light">
496
+                <tr>
497
+                  <th scope="col">Campo</th>
498
+                  <th scope="col">Didascalia</th>
499
+                  <th scope="col" class="text-center" style="width: 8rem;">Mostra</th>
500
+                  <th scope="col" class="text-center" style="width: 8rem;">Obbligatorio</th>
501
+                  <th scope="col" class="text-center" style="width: 8rem;">Stampa</th>
502
+                  <th scope="col" class="text-center" style="width: 5rem;"></th>
503
+                </tr>
504
+              </thead>
505
+              <tbody id="pvCampiOrdineBody">
506
+                @foreach($campiOrdine as $chiave => $campo)
507
+                  <tr
508
+                    data-pv-campo-ordine="{{ $chiave }}"
509
+                    data-pv-campo-type="{{ $campo['type'] ?? 'text' }}"
510
+                    data-pv-campo-label="{{ $campo['label'] ?? $chiave }}"
511
+                    data-pv-campo-custom="{{ !empty($campo['custom']) ? '1' : '0' }}"
512
+                  >
513
+                    <td>
514
+                      <div class="d-flex align-items-center gap-2">
515
+                        <span class="avatar avatar-sm">
516
+                          <span class="avatar-initial rounded bg-label-primary">
517
+                            <i class="bx {{ $campo['icon'] ?? 'bx-edit' }}"></i>
518
+                          </span>
519
+                        </span>
520
+                        <div>
521
+                          <span class="fw-medium">{{ $campo['label'] ?? $chiave }}</span>
522
+                          <div class="text-muted small">
523
+                            Chiave: <code>{{ $chiave }}</code>
524
+                            @if(!empty($campo['custom']))
525
+                              <span class="badge bg-label-warning ms-1">Personalizzato</span>
526
+                            @endif
527
+                          </div>
528
+                        </div>
529
+                      </div>
530
+                    </td>
531
+                    <td>
532
+                      <input type="text" class="form-control form-control-sm" id="pv_campo_{{ $chiave }}_placeholder" data-pv-campo-placeholder value="{{ $campo['placeholder'] ?? ($campo['label'] ?? $chiave) }}" placeholder="{{ $campo['placeholder'] ?? ($campo['label'] ?? $chiave) }}">
533
+                    </td>
534
+                    <td class="text-center">
535
+                      <div class="form-check form-switch d-inline-block mb-0">
536
+                        <input class="form-check-input" type="checkbox" id="pv_campo_{{ $chiave }}_mostra" data-pv-campo-mostra {{ !empty($campo['mostra']) ? 'checked' : '' }}>
537
+                      </div>
538
+                    </td>
539
+                    <td class="text-center">
540
+                      <div class="form-check form-switch d-inline-block mb-0">
541
+                        <input class="form-check-input" type="checkbox" id="pv_campo_{{ $chiave }}_required" data-pv-campo-required {{ !empty($campo['required']) ? 'checked' : '' }}>
542
+                      </div>
543
+                    </td>
544
+                    <td class="text-center">
545
+                      <div class="form-check form-switch d-inline-block mb-0">
546
+                        <input class="form-check-input" type="checkbox" id="pv_campo_{{ $chiave }}_stampa" data-pv-campo-stampa {{ !empty($campo['stampa']) ? 'checked' : '' }}>
547
+                      </div>
548
+                    </td>
549
+                    <td class="text-center">
550
+                      @if(!empty($campo['custom']))
551
+                        <button type="button" class="btn btn-sm btn-icon btn-label-danger" data-pv-campo-rimuovi title="Rimuovi campo">
552
+                          <i class="bx bx-trash"></i>
553
+                        </button>
554
+                      @endif
555
+                    </td>
556
+                  </tr>
557
+                @endforeach
558
+              </tbody>
559
+            </table>
560
+          </div>
561
+
562
+          <div class="row g-4 mt-2">
563
+            <div class="col-lg-6">
564
+              <div class="card h-100">
565
+                <div class="card-header">
566
+                  <h6 class="card-title mb-0">
567
+                    <i class="bx bx-show me-1"></i> Anteprima cassa
568
+                  </h6>
569
+                </div>
570
+                <div class="card-body">
571
+                  <div id="pvCampiOrdineAnteprima" class="row g-3">
572
+                    @foreach($campiOrdine as $chiave => $campo)
573
+                      <div class="col-md-6 pv-campo-anteprima" data-pv-anteprima="{{ $chiave }}" data-pv-anteprima-type="{{ $campo['type'] ?? 'text' }}" style="{{ !empty($campo['mostra']) ? '' : 'display:none;' }}">
574
+                        @if(($campo['type'] ?? 'text') === 'checkbox')
575
+                          <div class="form-check form-switch mt-1">
576
+                            <input class="form-check-input" type="checkbox" id="pv_anteprima_{{ $chiave }}" disabled>
577
+                            <label class="form-check-label small text-primary" for="pv_anteprima_{{ $chiave }}" data-pv-anteprima-label="{{ $chiave }}">{{ $campo['label'] ?? $chiave }}</label>
578
+                          </div>
579
+                        @else
580
+                          <label class="form-label small text-primary mb-1" data-pv-anteprima-label="{{ $chiave }}">{{ ($campo['label'] ?? $chiave) }}</label>
581
+                          <input type="{{ $campo['type'] ?? 'text' }}" class="form-control form-control-sm border-primary border-top-0 border-start-0 border-end-0 rounded-0 bg-transparent" disabled placeholder="{{ $campo['placeholder'] ?? $chiave }}">
582
+                        @endif
583
+                      </div>
584
+                    @endforeach
585
+                    <div class="col-md-6" data-pv-anteprima-totale>
586
+                      <label class="form-label small text-primary mb-1">Totale</label>
587
+                      <div class="fw-bold text-primary border-bottom border-primary pb-1">€ 0,00</div>
588
+                    </div>
589
+                  </div>
590
+                  <p class="text-muted small mb-0 mt-3">Il totale resta sempre visibile in cassa.</p>
591
+                </div>
592
+              </div>
593
+            </div>
594
+            <div class="col-lg-6">
595
+              <div class="card h-100 border-primary border-opacity-25">
596
+                <div class="card-body d-flex flex-column">
597
+                  <h6 class="mb-2">Suggerimento</h6>
598
+                  <p class="text-muted small mb-3">I campi predefiniti restano sempre disponibili. I campi personalizzati si aggiungono con la chiave che verrà usata in <code>Ordine-&gt;info</code>.</p>
599
+                  <ul class="small text-muted mb-4 ps-3">
600
+                    <li><strong>Mostra</strong> — compare nel riepilogo ordine in cassa</li>
601
+                    <li><strong>Obbligatorio</strong> — richiesto prima del pagamento</li>
602
+                    <li><strong>Stampa</strong> — compare sullo scontrino</li>
603
+                    <li><strong>Didascalia</strong> — placeholder mostrato all'operatore</li>
604
+                  </ul>
605
+                  <button type="button" class="btn btn-primary mt-auto align-self-start" id="saveCampiOrdine">
606
+                    <i class="bx bx-save me-1"></i> Salva configurazione
607
+                  </button>
608
+                </div>
609
+              </div>
610
+            </div>
611
+          </div>
612
+        </form>
613
+      </div>
614
+    </div>
615
+
408 616
     <div class="tab-pane fade" id="pv-tab-cucine" role="tabpanel">
409 617
       <form id="puntoVenditaEditCucineForm" class="mt-1" action="{{ route('punto-vendita.associa-cucina') }}" method="POST">
410 618
         @csrf
@@ -589,6 +797,285 @@ $configData = Helper::appClasses();
589 797
       }
590 798
     });
591 799
 
800
+    var pvInfoEsistente = @json($puntoVendita->info ?? []);
801
+
802
+    function pvNormalizzaChiaveCampo(valore) {
803
+      var chiave = String(valore || '')
804
+        .trim()
805
+        .toLowerCase()
806
+        .normalize('NFD')
807
+        .replace(/[\u0300-\u036f]/g, '')
808
+        .replace(/\s+/g, '_')
809
+        .replace(/[^a-z0-9_]/g, '')
810
+        .replace(/_+/g, '_')
811
+        .replace(/^_|_$/g, '');
812
+
813
+      if (chiave && /^[0-9]/.test(chiave)) {
814
+        chiave = 'campo_' + chiave;
815
+      }
816
+
817
+      return chiave;
818
+    }
819
+
820
+    function pvGeneraChiaveCampoUnica(etichetta) {
821
+      var base = pvNormalizzaChiaveCampo(etichetta);
822
+      if (!base) {
823
+        return '';
824
+      }
825
+      if (!/^[a-z]/.test(base)) {
826
+        base = 'campo_' + base;
827
+      }
828
+
829
+      var chiave = base;
830
+      var i = 2;
831
+      while (pvCampoOrdineEsiste(chiave)) {
832
+        chiave = base + '_' + i;
833
+        i += 1;
834
+      }
835
+      return chiave;
836
+    }
837
+
838
+    function pvAggiornaAnteprimaChiaveCampo() {
839
+      var etichetta = ($('#pv_nuovo_campo_label').val() || '').trim();
840
+      var $preview = $('#pv_nuovo_campo_chiave_preview');
841
+      if (!etichetta) {
842
+        $preview.text('—').removeClass('text-danger text-success').addClass('text-muted');
843
+        return;
844
+      }
845
+      var chiave = pvGeneraChiaveCampoUnica(etichetta);
846
+      if (!chiave) {
847
+        $preview.text('Etichetta non valida').removeClass('text-muted text-success').addClass('text-danger');
848
+        return;
849
+      }
850
+      $preview.text(chiave).removeClass('text-muted text-danger').addClass('text-success');
851
+    }
852
+
853
+    function pvCampoOrdineEsiste(chiave) {
854
+      return $('#pvCampiOrdineBody [data-pv-campo-ordine="' + chiave + '"]').length > 0;
855
+    }
856
+
857
+    function pvRaccogliCampiOrdine() {
858
+      var campi = {};
859
+      $('#pvCampiOrdineBody [data-pv-campo-ordine]').each(function() {
860
+        var $row = $(this);
861
+        var chiave = $row.data('pv-campo-ordine');
862
+        campi[chiave] = {
863
+          label: $row.data('pv-campo-label') || chiave,
864
+          placeholder: $('#pv_campo_' + chiave + '_placeholder').val() || chiave,
865
+          mostra: $('#pv_campo_' + chiave + '_mostra').is(':checked'),
866
+          required: $('#pv_campo_' + chiave + '_required').is(':checked'),
867
+          stampa: $('#pv_campo_' + chiave + '_stampa').is(':checked'),
868
+          type: $row.data('pv-campo-type') || 'text',
869
+          icon: String($row.data('pv-campo-custom')) === '1' ? 'bx-edit' : undefined
870
+        };
871
+        if (String($row.data('pv-campo-custom')) === '1') {
872
+          campi[chiave].custom = true;
873
+        }
874
+      });
875
+      return campi;
876
+    }
877
+
878
+    function pvAggiornaAnteprimaCampiOrdine() {
879
+      $('#pvCampiOrdineBody [data-pv-campo-ordine]').each(function() {
880
+        var $row = $(this);
881
+        var chiave = $row.data('pv-campo-ordine');
882
+        var mostra = $('#pv_campo_' + chiave + '_mostra').is(':checked');
883
+        var label = $row.data('pv-campo-label') || chiave;
884
+        var placeholder = $('#pv_campo_' + chiave + '_placeholder').val() || label;
885
+        var $anteprima = $('[data-pv-anteprima="' + chiave + '"]');
886
+        if (!$anteprima.length) {
887
+          return;
888
+        }
889
+        $anteprima.toggle(mostra);
890
+        $('[data-pv-anteprima-label="' + chiave + '"]').text(label);
891
+        if ($anteprima.data('pv-anteprima-type') !== 'checkbox') {
892
+          $anteprima.find('input:not([type=checkbox])').attr('placeholder', placeholder);
893
+        }
894
+      });
895
+    }
896
+
897
+    function pvAggiungiAnteprimaCampo(chiave, label, type, mostra) {
898
+      if ($('[data-pv-anteprima="' + chiave + '"]').length) {
899
+        return;
900
+      }
901
+      type = type || 'text';
902
+      var $wrap = $('<div>', {
903
+        class: 'col-md-6 pv-campo-anteprima',
904
+        'data-pv-anteprima': chiave,
905
+        'data-pv-anteprima-type': type
906
+      }).toggle(!!mostra);
907
+
908
+      if (type === 'checkbox') {
909
+        var inputId = 'pv_anteprima_' + chiave;
910
+        $wrap.append(
911
+          $('<div>', { class: 'form-check form-switch mt-1' }).append(
912
+            $('<input>', {
913
+              class: 'form-check-input',
914
+              type: 'checkbox',
915
+              id: inputId,
916
+              disabled: true
917
+            }),
918
+            $('<label>', {
919
+              class: 'form-check-label small text-primary',
920
+              for: inputId,
921
+              'data-pv-anteprima-label': chiave,
922
+              text: label
923
+            })
924
+          )
925
+        );
926
+      } else {
927
+        $wrap.append(
928
+          $('<label>', {
929
+            class: 'form-label small text-primary mb-1',
930
+            'data-pv-anteprima-label': chiave,
931
+            text: label
932
+          })
933
+        );
934
+        $wrap.append(
935
+          $('<input>', {
936
+            type: type,
937
+            class: 'form-control form-control-sm border-primary border-top-0 border-start-0 border-end-0 rounded-0 bg-transparent',
938
+            disabled: true,
939
+            placeholder: label
940
+          })
941
+        );
942
+      }
943
+
944
+      $('#pvCampiOrdineAnteprima [data-pv-anteprima-totale]').before($wrap);
945
+    }
946
+
947
+    function pvAggiungiRigaCampoOrdine(chiave, label, type, options) {
948
+      options = options || {};
949
+      var mostra = options.mostra !== undefined ? options.mostra : true;
950
+      var required = !!options.required;
951
+      var stampa = !!options.stampa;
952
+      var custom = options.custom !== false;
953
+
954
+      var $row = $('<tr>', {
955
+        'data-pv-campo-ordine': chiave,
956
+        'data-pv-campo-type': type || 'text',
957
+        'data-pv-campo-label': label,
958
+        'data-pv-campo-custom': custom ? '1' : '0'
959
+      });
960
+
961
+      $row.append(
962
+        '<td><div class="d-flex align-items-center gap-2">' +
963
+          '<span class="avatar avatar-sm"><span class="avatar-initial rounded bg-label-primary"><i class="bx bx-edit"></i></span></span>' +
964
+          '<div><span class="fw-medium">' + $('<div>').text(label).html() + '</span>' +
965
+          '<div class="text-muted small">Chiave: <code>' + chiave + '</code>' +
966
+          (custom ? ' <span class="badge bg-label-warning ms-1">Personalizzato</span>' : '') +
967
+          '</div></div></div></td>'
968
+      );
969
+      $row.append(
970
+        '<td><input type="text" class="form-control form-control-sm" id="pv_campo_' + chiave + '_placeholder" data-pv-campo-placeholder value="' + $('<div>').text(label).html() + '"></td>'
971
+      );
972
+      $row.append(
973
+        '<td class="text-center"><div class="form-check form-switch d-inline-block mb-0">' +
974
+          '<input class="form-check-input" type="checkbox" id="pv_campo_' + chiave + '_mostra" data-pv-campo-mostra' + (mostra ? ' checked' : '') + '></div></td>'
975
+      );
976
+      $row.append(
977
+        '<td class="text-center"><div class="form-check form-switch d-inline-block mb-0">' +
978
+          '<input class="form-check-input" type="checkbox" id="pv_campo_' + chiave + '_required" data-pv-campo-required' + (required ? ' checked' : '') + '></div></td>'
979
+      );
980
+      $row.append(
981
+        '<td class="text-center"><div class="form-check form-switch d-inline-block mb-0">' +
982
+          '<input class="form-check-input" type="checkbox" id="pv_campo_' + chiave + '_stampa" data-pv-campo-stampa' + (stampa ? ' checked' : '') + '></div></td>'
983
+      );
984
+      $row.append(
985
+        '<td class="text-center">' +
986
+          (custom ? '<button type="button" class="btn btn-sm btn-icon btn-label-danger" data-pv-campo-rimuovi title="Rimuovi campo"><i class="bx bx-trash"></i></button>' : '') +
987
+        '</td>'
988
+      );
989
+
990
+      $('#pvCampiOrdineBody').append($row);
991
+      pvAggiungiAnteprimaCampo(chiave, label, type, mostra);
992
+    }
993
+
994
+    function pvSalvaCampiOrdineServer() {
995
+      var info = $.extend(true, {}, pvInfoEsistente || {});
996
+      info.campi_ordine = pvRaccogliCampiOrdine();
997
+
998
+      $.ajax({
999
+        url: '{{ route('punto-vendita.update') }}',
1000
+        method: 'POST',
1001
+        data: {
1002
+          punto_vendita_id: pvId,
1003
+          info: JSON.stringify(info),
1004
+          _token: '{{ csrf_token() }}'
1005
+        },
1006
+        headers: { 'Accept': 'application/json' }
1007
+      })
1008
+      .done(function(response) {
1009
+        if (response.success) {
1010
+          pvInfoEsistente = info;
1011
+          pvNotyf('success', response.message || 'Configurazione salvata');
1012
+        } else {
1013
+          pvNotyf('error', response.message || 'Errore nel salvataggio');
1014
+        }
1015
+      })
1016
+      .fail(function(xhr) {
1017
+        pvAjaxError(xhr, 'Errore nel salvataggio');
1018
+      });
1019
+    }
1020
+
1021
+    pvAggiornaAnteprimaCampiOrdine();
1022
+
1023
+    $(document).on('change.pvCampiOrdine', '[data-pv-campo-mostra], [data-pv-campo-required]', function() {
1024
+      var $row = $(this).closest('[data-pv-campo-ordine]');
1025
+      var chiave = $row.data('pv-campo-ordine');
1026
+      if ($(this).is('[data-pv-campo-mostra]') && !$(this).is(':checked')) {
1027
+        $('#pv_campo_' + chiave + '_required').prop('checked', false);
1028
+      }
1029
+      pvAggiornaAnteprimaCampiOrdine();
1030
+    });
1031
+
1032
+    $(document).on('input.pvCampiOrdine', '[data-pv-campo-placeholder]', function() {
1033
+      pvAggiornaAnteprimaCampiOrdine();
1034
+    });
1035
+
1036
+    $(document).on('input.pvNuovoCampoLabel', '#pv_nuovo_campo_label', function() {
1037
+      pvAggiornaAnteprimaChiaveCampo();
1038
+    });
1039
+
1040
+    pvAggiornaAnteprimaChiaveCampo();
1041
+
1042
+    $(document).on('click.pvAggiungiCampoOrdine', '#pvAggiungiCampoOrdine', function() {
1043
+      var label = ($('#pv_nuovo_campo_label').val() || '').trim();
1044
+      var type = $('#pv_nuovo_campo_type').val() || 'text';
1045
+      var chiave = pvGeneraChiaveCampoUnica(label);
1046
+
1047
+      if (!label) {
1048
+        pvNotyf('error', 'Inserisci un\'etichetta');
1049
+        return;
1050
+      }
1051
+      if (!chiave) {
1052
+        pvNotyf('error', 'Impossibile generare una chiave valida da questa etichetta');
1053
+        return;
1054
+      }
1055
+
1056
+      pvAggiungiRigaCampoOrdine(chiave, label, type, { mostra: true, required: false, stampa: false, custom: true });
1057
+      pvAggiornaAnteprimaCampiOrdine();
1058
+
1059
+      $('#pv_nuovo_campo_label').val('');
1060
+      $('#pv_nuovo_campo_type').val('text');
1061
+      pvAggiornaAnteprimaChiaveCampo();
1062
+      pvNotyf('success', 'Campo "' + label + '" aggiunto (chiave: ' + chiave + '). Salva la configurazione.');
1063
+    });
1064
+
1065
+    $(document).on('click.pvRimuoviCampoOrdine', '[data-pv-campo-rimuovi]', function() {
1066
+      var $row = $(this).closest('[data-pv-campo-ordine]');
1067
+      var chiave = $row.data('pv-campo-ordine');
1068
+      if (!confirm('Rimuovere il campo "' + chiave + '"?')) {
1069
+        return;
1070
+      }
1071
+      $row.remove();
1072
+      $('[data-pv-anteprima="' + chiave + '"]').remove();
1073
+    });
1074
+
1075
+    $(document).on('click.pvSaveCampiOrdine', '#saveCampiOrdine', function() {
1076
+      pvSalvaCampiOrdineServer();
1077
+    });
1078
+
592 1079
     $(document).on('click.pvSaveCucine', '#saveCucine', function() {
593 1080
       var $form = $('#puntoVenditaEditCucineForm');
594 1081
       var cucineIds = [];

Laddar…
Avbryt
Spara