v15: config sorted alphabetical, vales nome-only, clientes free-text, datalist Enter 50ms, auto-accumulate names
Build Fechamento de Caixa / build (macos-14, app) (push) Has been cancelled
Build Fechamento de Caixa / build (ubuntu-22.04, deb) (push) Has been cancelled
Build Fechamento de Caixa / build (windows-2022, msi) (push) Has been cancelled

This commit is contained in:
root
2026-06-22 15:09:03 +00:00
parent d1939faf0a
commit 3999d8f4f0
+38 -18
View File
@@ -509,6 +509,11 @@
<div id="cfg-clientes-list"></div> <div id="cfg-clientes-list"></div>
<button class="btn-add-config" id="cfg-add-cliente">+ Adicionar cliente</button> <button class="btn-add-config" id="cfg-add-cliente">+ Adicionar cliente</button>
</div> </div>
<div class="cfg-section">
<div class="cfg-section-title">💰 Vales (nomes para autofill)</div>
<div id="cfg-vales-list"></div>
<button class="btn-add-config" id="cfg-add-vales">+ Adicionar nome</button>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn-primary" id="cfg-salvar">Salvar Configuração</button> <button class="btn-primary" id="cfg-salvar">Salvar Configuração</button>
@@ -554,7 +559,8 @@ let config = {
operadores: ['Isabelle', 'Silvanea', 'Rafael'], operadores: ['Isabelle', 'Silvanea', 'Rafael'],
gerentes: ['Carlos', 'Ana', 'João'], gerentes: ['Carlos', 'Ana', 'João'],
despesas: ['Energia', 'Água', 'Manutenção', 'Limpeza', 'Insumos'], despesas: ['Energia', 'Água', 'Manutenção', 'Limpeza', 'Insumos'],
clientes: [] clientes: [], // preenchimento livre — não é datalist fixo
vales_nomes: [], // nomes usados em vales ( histórico para autofill)
}; };
// ─── App State ──────────────────────────────────────────────────────────── // ─── App State ────────────────────────────────────────────────────────────
@@ -588,7 +594,7 @@ const TABLES = {
'tbl-sangrias': { field: 'sangrias', cols: ['hora','retirada','gerente'], enterNav: { hora:'retirada', retirada:'gerente', gerente:'_nextRow' } }, 'tbl-sangrias': { field: 'sangrias', cols: ['hora','retirada','gerente'], enterNav: { hora:'retirada', retirada:'gerente', gerente:'_nextRow' } },
'tbl-despesas': { field: 'despesas', cols: ['desc','obs','valor'], enterNav: { desc:'obs', obs:'valor', valor:'_nextRow' } }, 'tbl-despesas': { field: 'despesas', cols: ['desc','obs','valor'], enterNav: { desc:'obs', obs:'valor', valor:'_nextRow' } },
'tbl-pix': { field: 'pixcnpj', cols: ['nome','valor'], enterNav: { nome:'valor', valor:'_nextRow' } }, 'tbl-pix': { field: 'pixcnpj', cols: ['nome','valor'], enterNav: { nome:'valor', valor:'_nextRow' } },
'tbl-vales': { field: 'vales', cols: ['nome','obs','valor'], enterNav: { nome:'obs', obs:'valor', valor:'_nextRow' } }, 'tbl-vales': { field: 'vales', cols: ['nome','valor'], enterNav: { nome:'valor', valor:'_nextRow' } },
'tbl-receber': { field: 'receber', cols: ['nome','valor'], enterNav: { nome:'valor', valor:'_nextRow' } }, 'tbl-receber': { field: 'receber', cols: ['nome','valor'], enterNav: { nome:'valor', valor:'_nextRow' } },
'tbl-cancelamentos': { field: 'cancelamentos', cols: ['numero','valor','motivo'], enterNav: { numero:'valor', valor:'motivo', motivo:'_nextRow' } }, 'tbl-cancelamentos': { field: 'cancelamentos', cols: ['numero','valor','motivo'], enterNav: { numero:'valor', valor:'motivo', motivo:'_nextRow' } },
'tbl-cartao-credito': { field: 'cartoes.credito', cols: ['valor'], enterNav: { valor:'_nextRow' } }, 'tbl-cartao-credito': { field: 'cartoes.credito', cols: ['valor'], enterNav: { valor:'_nextRow' } },
@@ -619,10 +625,10 @@ function makeRow(tblId, rowIndex, rowData) {
inp.dataset.col = col; inp.dataset.col = col;
if (col === 'gerente') inp.setAttribute('list', 'dl-gerente'); if (col === 'gerente') inp.setAttribute('list', 'dl-gerente');
if (col === 'desc') inp.setAttribute('list', 'dl-despesa'); if (col === 'desc') inp.setAttribute('list', 'dl-despesa');
if (col === 'nome') { // tbl-receber: clientes free-text (sem datalist)
const listId = tblId === 'tbl-vales' ? 'dl-vales' : (tblId === 'tbl-receber' ? 'dl-cliente' : 'dl-generico'); // tbl-vales: usa dl-vales
inp.setAttribute('list', listId); if (col === 'nome' && tblId === 'tbl-vales') inp.setAttribute('list', 'dl-vales');
} if (col === 'nome' && tblId === 'tbl-pix') inp.setAttribute('list', 'dl-generico');
inp.addEventListener('input', onTableInput); inp.addEventListener('input', onTableInput);
inp.addEventListener('blur', onCellBlur); inp.addEventListener('blur', onCellBlur);
inp.addEventListener('keydown', onCellKeydown); inp.addEventListener('keydown', onCellKeydown);
@@ -677,8 +683,8 @@ function onCellBlur(e) {
function onCellKeydown(e) { function onCellKeydown(e) {
if (e.key !== 'Enter') return; if (e.key !== 'Enter') return;
// Don't preventDefault — let the browser fill the datalist value first, // 50ms delay so the browser finishes filling the datalist value
// then navigate after a tiny delay // before we read e.target.value and move focus
setTimeout(() => { setTimeout(() => {
const { tbl, row, col } = e.target.dataset; const { tbl, row, col } = e.target.dataset;
const cfg = TABLES[tbl]; const cfg = TABLES[tbl];
@@ -694,7 +700,7 @@ function onCellKeydown(e) {
} else if (next) { } else if (next) {
focusInput(tbl, parseInt(row), next); focusInput(tbl, parseInt(row), next);
} }
}, 0); }, 50);
} }
function focusInput(tblId, rowIndex, col) { function focusInput(tblId, rowIndex, col) {
@@ -930,18 +936,31 @@ function syncHeader() {
// ─── Datalists ───────────────────────────────────────────────────────────── // ─── Datalists ─────────────────────────────────────────────────────────────
function buildDatalists() { function buildDatalists() {
// Auto-build vales_nomes from current entries (autofill dinâmico)
const valesUsed = [...new Set(estado.vales.map(v => v.nome).filter(Boolean))];
config.vales_nomes = [...new Set([...config.vales_nomes, ...valesUsed])]
.sort((a, b) => a.localeCompare(b, 'pt-BR'));
// Gerentes e Operadores também acumulam dos lançamentos
const gerentesUsed = [...new Set(estado.sangrias.map(r => r.gerente).filter(Boolean))];
config.gerentes = [...new Set([...config.gerentes, ...gerentesUsed])]
.sort((a, b) => a.localeCompare(b, 'pt-BR'));
const lists = { const lists = {
'dl-operadores': config.operadores, 'dl-operadores': [...new Set([...config.operadores])].sort((a,b) => a.localeCompare(b, 'pt-BR')),
'dl-gerente': config.gerentes, 'dl-gerente': [...new Set([...config.gerentes])].sort((a,b) => a.localeCompare(b, 'pt-BR')),
'dl-despesa': config.despesas, 'dl-despesa': [...new Set([...config.despesas])].sort((a,b) => a.localeCompare(b, 'pt-BR')),
'dl-cliente': config.clientes, // Clientes: escrita livre — sem datalist fixo
'dl-vales': config.operadores, 'dl-vales': [...new Set([...config.vales_nomes])].sort((a,b) => a.localeCompare(b, 'pt-BR')),
}; };
Object.entries(lists).forEach(([id, items]) => { Object.entries(lists).forEach(([id, items]) => {
let dl = document.getElementById(id); let dl = document.getElementById(id);
if (!dl) { dl = document.createElement('datalist'); dl.id = id; document.body.appendChild(dl); } if (!dl) { dl = document.createElement('datalist'); dl.id = id; document.body.appendChild(dl); }
dl.innerHTML = items.map(v => `<option value="${v}">`).join(''); dl.innerHTML = items.map(v => `<option value="${v}">`).join('');
}); });
// Clientes: sem datalist (escrita livre)
const clienteDl = document.getElementById('dl-cliente');
if (clienteDl) clienteDl.remove();
} }
// ─── Init ───────────────────────────────────────────────────────────────── // ─── Init ─────────────────────────────────────────────────────────────────
@@ -1060,6 +1079,7 @@ function openConfig() {
renderCfgList('gerentes', 'cfg-gerentes-list'); renderCfgList('gerentes', 'cfg-gerentes-list');
renderCfgList('despesas', 'cfg-despesas-list'); renderCfgList('despesas', 'cfg-despesas-list');
renderCfgList('clientes', 'cfg-clientes-list'); renderCfgList('clientes', 'cfg-clientes-list');
renderCfgList('vales_nomes', 'cfg-vales-list');
} }
function closeConfig() { document.getElementById('modal-config').classList.remove('open'); } function closeConfig() { document.getElementById('modal-config').classList.remove('open'); }
function renderCfgList(key, containerId) { function renderCfgList(key, containerId) {
@@ -1083,10 +1103,10 @@ document.getElementById('btn-config').addEventListener('click', openConfig);
document.getElementById('modal-close').addEventListener('click', closeConfig); document.getElementById('modal-close').addEventListener('click', closeConfig);
document.getElementById('modal-config').addEventListener('click', e => { if (e.target.id === 'modal-config') closeConfig(); }); document.getElementById('modal-config').addEventListener('click', e => { if (e.target.id === 'modal-config') closeConfig(); });
['operador','gerente','despesa','cliente'].forEach((_, i) => { ['operador','gerente','despesa','cliente','vales'].forEach((_, i) => {
const btnMap = ['cfg-add-operador','cfg-add-gerente','cfg-add-despesa','cfg-add-cliente']; const btnMap = ['cfg-add-operador','cfg-add-gerente','cfg-add-despesa','cfg-add-cliente','cfg-add-vales'];
const keyMap = ['operadores','gerentes','despesas','clientes']; const keyMap = ['operadores','gerentes','despesas','clientes','vales_nomes'];
const listMap = ['cfg-operadores-list','cfg-gerentes-list','cfg-despesas-list','cfg-clientes-list']; const listMap = ['cfg-operadores-list','cfg-gerentes-list','cfg-despesas-list','cfg-clientes-list','cfg-vales-list'];
document.getElementById(btnMap[i]).addEventListener('click', () => { document.getElementById(btnMap[i]).addEventListener('click', () => {
const k = keyMap[i]; config[k].push(''); renderCfgList(k, listMap[i]); const k = keyMap[i]; config[k].push(''); renderCfgList(k, listMap[i]);
const inputs = document.getElementById(listMap[i]).querySelectorAll('input'); const inputs = document.getElementById(listMap[i]).querySelectorAll('input');