feat(recipes): la ricetta decide cosa pretendere e come si misura
Tre cose che prima non erano di nessuno diventano regole della ricetta, decise da chi la scrive e fatte valere dal server. Punto 8 — tracciabilita' obbligatoria. Lotto e seriale si dichiarano obbligatori sulla ricetta. L'operatore li inserisce alla selezione, dove il pulsante non si attiva finche' mancano, e la stessa regola vale sulla lista task, raggiungibile anche per link diretto, e sul barcode: uno sbarramento che dura solo finche' qualcuno prende in mano il lettore non e' uno sbarramento. La produzione non si apre e la misura non si salva senza cio' che la ricetta pretende, perche' un valore senza il suo lotto non e' riconducibile a niente, e accorgersene dopo significa accorgersene tardi. Punto 9 — inserimento manuale. Una ricetta puo' vietare i valori digitati, ed e' il valore predefinito: il calibro e' lo strumento, digitare e' cio' che va concesso. Dove e' vietato il tastierino non viene disegnato (non nascosto con i CSS: il markup nascosto e' markup che si puo' rimostrare) e restano correzione e conferma, perche' una lettura sbagliata va cancellata. Il controllo vero e' sul server: una regola che vive solo nel frontend e' un consiglio. Migliorato al passaggio il riconoscimento del calibro. Contava solo la raffica di cifre, cosi' una lettura corta come "9.5" — tre battute — finiva registrata come digitata a mano; ora conta anche l'Invio che il wedge manda dentro la stessa raffica. Senza questa correzione il divieto avrebbe respinto misure legittime. Punto 11 — formattazione delle descrizioni. A capo e grassetto sopravvivono: chi scrive le ricette incolla dal PDF della scheda tecnica e il testo arrivava appiattito, da risistemare a mano ogni volta. Nessun HTML viene accettato o salvato — il testo viene escapato e gli unici tag nel risultato sono quelli prodotti dal renderer. La sanificazione e' questa: non c'e' niente da sanificare perche' non si accetta niente. Le stesse due regole in Jinja e in JS, cosi' una descrizione si legge uguale ovunque. E la descrizione ora si vede anche in esecuzione: era scritta per l'operatore e la vedeva solo chi la scriveva. Migrazione 009: tre colonne sulla ricetta. Le due di tracciabilita' partono false, che e' il comportamento di oggi; l'inserimento manuale parte *vero* sulle ricette gia' esistenti — il default della colonna e' falso, quindi le ricette nuove sono solo-calibro, ma spegnerlo d'ufficio su quelle in uso fermerebbe una linea alla misura successiva. Chi possiede la ricetta lo decide dall'editor. Test: +26 (291). Coprono il rifiuto sul server per lotto, seriale e valore digitato, il calibro sempre ammesso, le regole che sopravvivono alla nuova versione, il tastierino assente in pagina, l'Avvia sbarrato, e il renderer delle descrizioni compreso il caso in cui si prova a farci passare un tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
<!-- Numpad Component - Touch-friendly numeric keypad for measurement input -->
|
||||
{#
|
||||
allow_manual_input comes from the recipe. When it is false the digits are not
|
||||
drawn at all - not hidden with CSS: they are the only unambiguously typed way
|
||||
in, and markup that is merely hidden is still markup that can be shown again.
|
||||
|
||||
The keyboard listener stays either way: that is how the USB caliper arrives, and
|
||||
the component still checks at confirm time, because a wedge and a keyboard are
|
||||
the same device as far as the browser is concerned.
|
||||
#}
|
||||
{% set allow_manual = allow_manual_input if allow_manual_input is defined else true %}
|
||||
<div
|
||||
x-data="numpad()"
|
||||
x-data="numpad({ allowManual: {{ allow_manual|tojson }} })"
|
||||
@keydown.window="handleKeydown($event)"
|
||||
class="numpad-container w-full max-w-sm mx-auto"
|
||||
>
|
||||
@@ -18,6 +28,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not allow_manual %}
|
||||
<!-- Caliper only: no digits, just the corrections and the confirm -->
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-center gap-2 px-3 py-2 rounded-lg
|
||||
bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800">
|
||||
<svg class="w-4 h-4 text-primary shrink-0" fill="none" stroke="currentColor" stroke-width="1.75" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<span class="text-xs font-medium text-primary-700 dark:text-primary-300">
|
||||
{{ _('Misura solo da calibro: questa ricetta non ammette valori digitati') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<button @click="backspace()" type="button"
|
||||
class="min-h-[56px] font-mono text-xl font-semibold rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-100 dark:bg-slate-600 text-steel dark:text-steel-light hover:bg-slate-50 dark:hover:bg-slate-600 active:scale-95 transition-all duration-150 select-none cursor-pointer flex items-center justify-center touch-manipulation focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
>⌫</button>
|
||||
<button @click="clearAll()" type="button"
|
||||
class="min-h-[56px] font-mono text-xl font-semibold rounded-xl border border-slate-200 dark:border-slate-600 bg-slate-100 dark:bg-slate-600 text-measure-fail hover:bg-slate-50 dark:hover:bg-slate-600 active:scale-95 transition-all duration-150 select-none cursor-pointer flex items-center justify-center touch-manipulation focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
>C</button>
|
||||
<button @click="confirm()" type="button" :disabled="!hasValue"
|
||||
class="min-h-[56px] font-mono text-xl font-semibold rounded-xl border border-primary bg-primary text-white hover:bg-primary-dark active:scale-95 transition-all duration-150 select-none cursor-pointer flex items-center justify-center touch-manipulation focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-40 disabled:cursor-not-allowed disabled:active:scale-100 disabled:hover:bg-primary"
|
||||
>✓</button>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<!-- Keypad Grid 4x4 -->
|
||||
<div class="grid grid-cols-4 gap-2">
|
||||
<!-- Row 1: 7 8 9 ⌫ -->
|
||||
@@ -118,4 +154,5 @@
|
||||
class="min-h-[56px] min-w-[56px] font-mono text-xl font-semibold rounded-xl border border-primary bg-primary text-white hover:bg-primary-dark active:scale-95 transition-all duration-150 select-none cursor-pointer flex items-center justify-center touch-manipulation focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-40 disabled:cursor-not-allowed disabled:active:scale-100 disabled:hover:bg-primary"
|
||||
>✓</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{#
|
||||
The whole formatting toolbar a task description gets: bold, plus the reminder
|
||||
that a line break is a line break.
|
||||
|
||||
Whoever writes a recipe pastes from the PDF of the technical sheet; the text used
|
||||
to arrive flattened and had to be put back by hand every time. `target` is the
|
||||
x-ref of the textarea this toolbar acts on.
|
||||
#}
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<button type="button"
|
||||
@click="wrapSelectionBold($refs.{{ target }})"
|
||||
title="{{ _('Grassetto: racchiude il testo selezionato fra **') }}"
|
||||
class="px-2 py-0.5 rounded border border-[var(--border-color)]
|
||||
text-sm font-bold leading-none
|
||||
text-[var(--text-secondary)] hover:text-primary hover:border-primary
|
||||
transition-colors">
|
||||
B
|
||||
</button>
|
||||
<span class="text-[11px] text-[var(--text-muted)]">
|
||||
{{ _('**grassetto** · a capo = a capo') }}
|
||||
</span>
|
||||
</div>
|
||||
Reference in New Issue
Block a user