Procházet zdrojové kódy

feat(cassa): applica ordinamento piatti e semplifica catalogo

Co-authored-by: Cursor <cursoragent@cursor.com>
marcofalabretti před 2 týdny
rodič
revize
13949e7c82

+ 9
- 1
app/Http/Controllers/OperatoreController.php Zobrazit soubor

@@ -217,7 +217,15 @@ class OperatoreController extends Controller
217 217
    
218 218
 
219 219
         // dd(Cookie::get('binding_token'));
220
-        $cucine = $puntoVendita->cucine()->where('is_attiva', true)->orderBy('nome')->get();
220
+        $cucine = $puntoVendita->cucine()
221
+            ->where('is_attiva', true)
222
+            ->with(['piatti' => function ($query) {
223
+                $query->where('is_attivo', true)
224
+                    ->with('allergeni')
225
+                    ->ordinatiInCassa();
226
+            }])
227
+            ->orderBy('nome')
228
+            ->get();
221 229
 
222 230
 
223 231
 

+ 1
- 1
app/Http/Controllers/PuntoVenditaController.php Zobrazit soubor

@@ -334,7 +334,7 @@ class PuntoVenditaController extends Controller
334 334
             ->with(['piatti' => function ($query) {
335 335
                 $query->where('is_attivo', true)
336 336
                     ->with('allergeni')
337
-                    ->orderBy('nome');
337
+                    ->ordinatiInCassa();
338 338
             }])
339 339
             ->orderBy('nome')
340 340
             ->get();

+ 42
- 38
resources/views/punto_vendita/cassa/_partials/menu_cucine.blade.php Zobrazit soubor

@@ -129,49 +129,20 @@
129 129
             aria-labelledby="tab-cucina-{{ $cucina->id }}"
130 130
             tabindex="0"
131 131
           >
132
-            <div class="gap-4 row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-xl-4 row-cols-xxl-5 g-2 cassa-piatti-grid">
132
+            <div class="cassa-piatti-grid">
133 133
               @php
134
-                $piatti = $cucina->piatti ?? collect();
135
-                $preferiti = $piatti
136
-                    ->filter(fn ($piatto) => (bool) ($piatto->in_evidenza ?? false))
137
-                    ->sortBy(fn ($piatto) => mb_strtolower((string) ($piatto->nome ?? '')), SORT_NATURAL);
138
-
139
-                $nonPreferiti = $piatti
140
-                    ->reject(fn ($piatto) => (bool) ($piatto->in_evidenza ?? false))
141
-                    ->sortBy(fn ($piatto) => mb_strtolower((string) ($piatto->nome ?? '')), SORT_NATURAL);
142
-
143
-                $gruppiAlfabetici = $nonPreferiti->groupBy(function ($piatto) {
144
-                    $nome = trim((string) ($piatto->nome ?? ''));
145
-                    if ($nome === '') {
146
-                        return '#';
147
-                    }
148
-
149
-                    $iniziale = mb_strtoupper(mb_substr($nome, 0, 1));
150
-                    return preg_match('/[A-Z]/u', $iniziale) ? $iniziale : '#';
151
-                });
134
+                $piattiOrdinati = \App\Models\Piatto::ordinaCollezioneInCassa($cucina->piatti ?? []);
152 135
               @endphp
153 136
 
154
-              @if($preferiti->isNotEmpty())
155
-                <div class="col-12 cassa-piatti-divider">
156
-                  <span class="cassa-piatti-divider-pill"><i class="bx bxs-star me-1"></i>Preferiti</span>
137
+              @if($piattiOrdinati->isEmpty())
138
+                <p class="text-muted mb-0">Nessun piatto attivo per questa cucina.</p>
139
+              @else
140
+                <div class="cassa-piatti-giornale">
141
+                  @foreach ($piattiOrdinati as $piatto)
142
+                    @include('punto_vendita.cassa._partials.piatto', ['piatto' => $piatto, 'cucina' => $cucina])
143
+                  @endforeach
157 144
                 </div>
158
-                @foreach ($preferiti as $piatto)
159
-                  @include('punto_vendita.cassa._partials.piatto', ['piatto' => $piatto, 'cucina' => $cucina])
160
-                @endforeach
161 145
               @endif
162
-
163
-              @forelse ($gruppiAlfabetici as $lettera => $piattiGruppo)
164
-                <div class="col-12 cassa-piatti-divider">
165
-                  <span class="cassa-piatti-divider-pill">{{ $lettera }}</span>
166
-                </div>
167
-                @foreach ($piattiGruppo as $piatto)
168
-                  @include('punto_vendita.cassa._partials.piatto', ['piatto' => $piatto, 'cucina' => $cucina])
169
-                @endforeach
170
-              @empty
171
-                <div class="col-12">
172
-                  <p class="text-muted mb-0">Nessun piatto attivo per questa cucina.</p>
173
-                </div>
174
-              @endforelse
175 146
             </div>
176 147
           </div>
177 148
         @endforeach
@@ -302,6 +273,33 @@
302 273
     padding-bottom: 0.35rem;
303 274
     scrollbar-width: thin;
304 275
   }
276
+  .cassa-piatti-giornale {
277
+    column-count: 2;
278
+    column-gap: 0.5rem;
279
+    column-fill: balance;
280
+  }
281
+  .cassa-piatti-giornale > .col {
282
+    display: inline-block;
283
+    width: 100%;
284
+    max-width: 100%;
285
+    margin-bottom: 0.5rem;
286
+    break-inside: avoid;
287
+    -webkit-column-break-inside: avoid;
288
+    page-break-inside: avoid;
289
+    vertical-align: top;
290
+  }
291
+  .cassa-piatti-giornale .cassa-piatto-card.h-100 {
292
+    height: auto !important;
293
+  }
294
+  @media (min-width: 576px) {
295
+    .cassa-piatti-giornale { column-count: 3; }
296
+  }
297
+  @media (min-width: 768px) {
298
+    .cassa-piatti-giornale { column-count: 4; }
299
+  }
300
+  @media (min-width: 1400px) {
301
+    .cassa-piatti-giornale { column-count: 5; }
302
+  }
305 303
   @media (max-width: 1199.98px) {
306 304
     .cassa-cucine-card {
307 305
       height: 100% !important;
@@ -321,6 +319,12 @@
321 319
     font-size: 0.95rem;
322 320
     line-height: 1;
323 321
   }
322
+  .cassa-piatti-gruppo-gap {
323
+    flex: 0 0 100% !important;
324
+    width: 100% !important;
325
+    max-width: 100% !important;
326
+    height: 0.85rem;
327
+  }
324 328
   .cassa-piatti-divider {
325 329
     position: static;
326 330
     flex: 0 0 100% !important;

+ 0
- 34
resources/views/punto_vendita/cassa/viste/catalogo/_partials/style.blade.php Zobrazit soubor

@@ -451,40 +451,6 @@
451 451
     z-index: 40;
452 452
   }
453 453
 
454
-  .cassa-sb-letter-divider {
455
-    align-self: center;
456
-    min-height: 0;
457
-    height: auto;
458
-    margin-top: .62rem;
459
-    margin-bottom: -.38rem;
460
-    padding: 0 .14rem 0;
461
-    pointer-events: none;
462
-  }
463
-
464
-  .cassa-sb-products > .cassa-sb-letter-divider:first-child {
465
-    margin-top: 0;
466
-  }
467
-
468
-  .cassa-sb-letter-divider-label {
469
-    display: flex;
470
-    align-items: center;
471
-    justify-content: flex-start;
472
-    gap: .4rem;
473
-    width: 100%;
474
-    font-size: .9rem;
475
-    font-weight: 800;
476
-    letter-spacing: .04em;
477
-    text-transform: uppercase;
478
-    color: var(--sb-primary);
479
-    line-height: 1;
480
-  }
481
-
482
-  .cassa-sb-letter-divider-label::after {
483
-    content: "";
484
-    flex: 1 1 auto;
485
-    border-top: 1px solid color-mix(in srgb, var(--sb-primary) 24%, var(--sb-border) 76%);
486
-  }
487
-
488 454
   .cassa-sb-product:hover {
489 455
     border-color: color-mix(in srgb, var(--sb-primary) 55%, var(--sb-product-border) 45%);
490 456
     background: var(--sb-product-bg-hover);

+ 24
- 53
resources/views/punto_vendita/cassa/viste/catalogo/index.blade.php Zobrazit soubor

@@ -224,13 +224,14 @@ $ordineCoperti = (string) ($infoOrdine['coperti'] ?? '');
224 224
         <div class="cassa-sb-products-wrap" id="product-panel">
225 225
           <div class="cassa-sb-products" id="product-grid">
226 226
             @foreach($cucine as $cucina)
227
-              @foreach(($cucina->piatti ?? collect()) as $piatto)
227
+              @foreach(\App\Models\Piatto::ordinaCollezioneInCassa($cucina->piatti ?? []) as $piatto)
228 228
                 
229 229
                 <article
230 230
                   class="cassa-sb-product"
231 231
                   data-product-item
232 232
                   data-kitchen-id="{{ $cucina->id }}"
233 233
                   data-name="{{ mb_strtolower((string) ($piatto->nome ?? ''), 'UTF-8') }}"
234
+                  data-ordine-visualizzazione="{{ \App\Models\Piatto::ordineVisualizzazioneImpostato($piatto->ordine_visualizzazione) ? (int) $piatto->ordine_visualizzazione : 0 }}"
234 235
                   data-piatto-id="{{ $piatto->id }}"
235 236
                   data-title="{{ $piatto->nome }}"
236 237
                   data-price="{{ (float) ($piatto->prezzo ?? 0) }}"
@@ -296,8 +297,10 @@ $ordineCoperti = (string) ($infoOrdine['coperti'] ?? '');
296 297
       <div class="cassa-sb-footer">
297 298
         <div class="cassa-sb-pay-grid">
298 299
           <button type="button" class="cassa-sb-main-btn" onclick="checkout()">Paga ora</button>
300
+          {{--
299 301
           <button type="button" class="cassa-sb-cash-btn">Contanti</button>
300 302
           <button type="button" class="cassa-sb-card-btn">Carta</button>
303
+          --}}
301 304
           <button type="button" class="cassa-sb-clear-btn" id="cart-reset" onclick="azzeraCarrello()">Svuota</button>
302 305
         </div>
303 306
       </div>
@@ -451,50 +454,23 @@ $ordineCoperti = (string) ($infoOrdine['coperti'] ?? '');
451 454
       });
452 455
     };
453 456
 
454
-    const getProductInitialLetter = function (name) {
455
-      const trimmed = (name || '').trim();
456
-      if (trimmed === '') return '#';
457
-      const initial = [...trimmed][0]?.toLocaleUpperCase('it') || '#';
458
-      return /^[A-Z]$/.test(initial.normalize('NFD').replace(/\p{M}/gu, '')) ? initial : '#';
457
+    const prodottoHaOrdine = function (item) {
458
+      const n = Number.parseInt(item.dataset.ordineVisualizzazione || '0', 10);
459
+      return Number.isFinite(n) && n !== 0;
459 460
     };
460 461
 
461
-    const createLetterDivider = function (letter) {
462
-      const divider = document.createElement('div');
463
-      divider.className = 'cassa-sb-letter-divider js-letter-divider';
464
-      divider.setAttribute('data-letter', letter);
465
-      const label = document.createElement('span');
466
-      label.className = 'cassa-sb-letter-divider-label';
467
-      label.textContent = letter;
468
-      divider.appendChild(label);
469
-      return divider;
470
-    };
471
-
472
-    const clearLetterDividers = function () {
473
-      productGrid.querySelectorAll('.js-letter-divider').forEach(function (node) {
474
-        node.remove();
475
-      });
476
-    };
477
-
478
-    const LETTER_DIVIDER_MIN_ITEMS = 10;
479
-
480
-    const buildProductGridSequence = function (items, totalVisibleCount) {
481
-      const sequence = [];
482
-      const showLetterDividers = totalVisibleCount > LETTER_DIVIDER_MIN_ITEMS;
483
-      let lastLetter = null;
484
-
485
-      items.forEach(function (item) {
486
-        const letter = getProductInitialLetter(item.dataset.title || item.dataset.name || '');
487
-        if (showLetterDividers && letter !== lastLetter) {
488
-          sequence.push(createLetterDivider(letter));
489
-          lastLetter = letter;
490
-        }
491
-        if (!showLetterDividers) {
492
-          lastLetter = letter;
493
-        }
494
-        sequence.push(item);
495
-      });
496
-
497
-      return sequence;
462
+    const sortProductsForCassa = function (items) {
463
+      if (items.length > 0 && items.every(prodottoHaOrdine)) {
464
+        return items.slice().sort(function (a, b) {
465
+          const oa = Number.parseInt(a.dataset.ordineVisualizzazione, 10);
466
+          const ob = Number.parseInt(b.dataset.ordineVisualizzazione, 10);
467
+          if (oa !== ob) return oa - ob;
468
+          const nameA = (a.dataset.title || a.dataset.name || '').trim();
469
+          const nameB = (b.dataset.title || b.dataset.name || '').trim();
470
+          return nameA.localeCompare(nameB, 'it', { sensitivity: 'base' });
471
+        });
472
+      }
473
+      return sortProductsAlphabetically(items);
498 474
     };
499 475
 
500 476
     const renderProducts = function () {
@@ -524,7 +500,7 @@ $ordineCoperti = (string) ($infoOrdine['coperti'] ?? '');
524 500
       }
525 501
 
526 502
       const term = (productSearch?.value || '').trim().toLowerCase();
527
-      visibleProducts = sortProductsAlphabetically(productItems.filter(function (item) {
503
+      visibleProducts = sortProductsForCassa(productItems.filter(function (item) {
528 504
         const okKitchen = selectedKitchen === 'all' || item.dataset.kitchenId === selectedKitchen;
529 505
         const okSearch = term === '' || (item.dataset.name || '').includes(term);
530 506
         return okKitchen && okSearch;
@@ -537,14 +513,9 @@ $ordineCoperti = (string) ($infoOrdine['coperti'] ?? '');
537 513
 
538 514
       productItems.forEach(function (item) { item.style.display = 'none'; });
539 515
       const current = visibleProducts.slice(start, end);
540
-      clearLetterDividers();
541
-
542
-      const gridSequence = buildProductGridSequence(current, visibleProducts.length);
543
-      gridSequence.forEach(function (node) {
544
-        if (node.classList.contains('cassa-sb-product')) {
545
-          node.style.display = '';
546
-        }
547
-        productGrid.appendChild(node);
516
+      current.forEach(function (item) {
517
+        item.style.display = '';
518
+        productGrid.appendChild(item);
548 519
       });
549 520
 
550 521
       visibleProducts.forEach(function (item) {
@@ -560,7 +531,7 @@ $ordineCoperti = (string) ($infoOrdine['coperti'] ?? '');
560 531
       });
561 532
 
562 533
       productGrid.querySelectorAll('.js-product-placeholder').forEach(function (node) { node.remove(); });
563
-      applyProductGridLayout(gridSequence.length);
534
+      applyProductGridLayout(current.length);
564 535
 
565 536
       productStatus.textContent = productPage + ' / ' + maxPage;
566 537
       productPrev.disabled = productPage <= 1;

Loading…
Zrušit
Uložit