From feeaa02396d151cc0e0f8a35540d55857b99bc8e Mon Sep 17 00:00:00 2001 From: root Date: Mon, 22 Jun 2026 18:15:05 +0000 Subject: [PATCH] v22: modal confirm, validate operador/turno, print from confirm, fix parseNum/fmtNum --- src/index.html | 96 +++++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/src/index.html b/src/index.html index 0e7c7ed..b0aa3d9 100644 --- a/src/index.html +++ b/src/index.html @@ -576,11 +576,30 @@ let estado = { // ─── Helpers ────────────────────────────────────────────────────────────── const fmt = v => 'R$ ' + (parseFloat(v) || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 }); -const parseNum = v => parseFloat((String(v) || '0').replace(/\./g,'').replace(',','.')) || 0; +const parseNum = v => { + const s = String(v || '0'); + // Handle empty or whitespace + if (!s.trim()) return 0; + // Brazilian format: 1.234,56 → 1234.56 + // Strategy: remove thousand dots, replace decimal comma with dot + // "10,55" → "1055,00" (wrong) → fix: split on comma first + // "1.055,00" → parts=["1.055","00"] → intPart="1055", decPart="00" → 1055.00 + const lastComma = s.lastIndexOf(','); + if (lastComma === -1) { + // No comma: just remove thousand separators (dots) + return parseFloat(s.replace(/,/g, '').replace(/\./g, '')) || 0; + } + const intPart = s.slice(0, lastComma).replace(/\./g, '').replace(/,/g, ''); + const decPart = s.slice(lastComma + 1); + return parseFloat(intPart + '.' + decPart) || 0; +}; const setText = (id, t) => { const e = document.getElementById(id); if (e) e.textContent = t; }; const setVal = (id, v) => { const e = document.getElementById(id); if (e) e.value = v; }; const val = id => { const e = document.getElementById(id); return e ? e.value : ''; }; -const fmtNum = v => { const n = parseNum(v); return isNaN(n) ? '' : n.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); }; +const fmtNum = v => { + const n = typeof v === 'number' ? v : parseNum(v); + return isNaN(n) ? '' : n.toLocaleString('pt-BR', { minimumFractionDigits: 2 }); +}; // Format time: 4 digits → HH:MM function fmtTime(v) { @@ -839,45 +858,43 @@ function updateTotals() { // Strategy: detect "comma-only" input and convert to ",00" immediately. // For normal digit input: add ,00 on blur (not on every digit). -let _justEnteredDatalist = false; +// ─── Numeric input: auto ,00 on digit+blur pattern ─────────────────────── +// These fields are plain text inputs (no datalist). Format on blur only. +// For sys-troco which is user-editable: also format on Enter keydown. ['troco','credito','debito','alimentacao','vales','receber','pix'].forEach(k => { const el = document.getElementById(`sys-${k}`); if (!el) return; - // On input: handle comma-only typed as immediate ,00 + // On input: just track for autosave el.addEventListener('input', () => { - const v = el.value; - // If user just typed a comma as last char (and no ,00 yet), convert to ,00 - if (v.endsWith(',') && !v.endsWith(',00')) { - el.value = v + '00'; - // Move cursor before the trailing 00 - const pos = v.length; // position after the comma - setTimeout(() => { el.selectionStart = el.selectionEnd = pos; }, 0); - } - updateTotals(); scheduleSave(); + updateTotals(); + scheduleSave(); }); - // On Enter: trigger datalist, then blur+focus cycle + // On keydown Enter: format value directly (Enter does not trigger blur for tabindex=-1) el.addEventListener('keydown', e => { if (e.key === 'Enter') { - _justEnteredDatalist = true; - el.blur(); - el.focus(); - setTimeout(() => { _justEnteredDatalist = false; }, 400); + e.preventDefault(); + const raw = el.value.trim(); + if (!raw) { el.value = ''; return; } + const num = parseNum(raw); + if (isNaN(num) || num === 0) { el.value = ''; } + else { el.value = fmtNum(num); } + updateTotals(); + scheduleSave(); } }); - // On blur: format to proper number if it looks like a partial entry + // On blur: format the value el.addEventListener('blur', () => { - if (_justEnteredDatalist) return; - const v = parseNum(el.value); - if (isNaN(v) || v === 0) { - el.value = ''; - } else { - el.value = fmtNum(v); - } + const raw = el.value.trim(); + if (!raw) { el.value = ''; return; } + const num = parseNum(raw); + if (isNaN(num) || num === 0) { el.value = ''; } + else { el.value = fmtNum(num); } updateTotals(); + scheduleSave(); }); }); @@ -887,32 +904,23 @@ let _justEnteredDatalist = false; el.addEventListener('input', () => { const v = el.value; - if (v.endsWith(',') && !v.endsWith(',00')) { + if (v.endsWith(',') && !v.includes(',') && v !== ',') { el.value = v + '00'; const pos = v.length; setTimeout(() => { el.selectionStart = el.selectionEnd = pos; }, 0); } - updateTotals(); scheduleSave(); - }); - - el.addEventListener('keydown', e => { - if (e.key === 'Enter') { - _justEnteredDatalist = true; - el.blur(); - el.focus(); - setTimeout(() => { _justEnteredDatalist = false; }, 400); - } + updateTotals(); + scheduleSave(); }); el.addEventListener('blur', () => { - if (_justEnteredDatalist) return; - const v = parseNum(el.value); - if (isNaN(v) || v === 0) { - el.value = ''; - } else { - el.value = fmtNum(v); - } + const raw = el.value.trim(); + if (!raw) { el.value = ''; return; } + const num = parseNum(raw); + if (isNaN(num) || num === 0) { el.value = ''; } + else { el.value = fmtNum(num); } updateTotals(); + scheduleSave(); }); });