refactor(ui): Rediseño completo de UI con Nuxt UI 4

- Nuevo layout responsivo mobile-first con tabs inferiores
- Sidebar colapsable en desktop con cola de impresión
- Sistema de templates reutilizables con localStorage
- Soporte Dark/Light mode con UColorModeButton
- Composables usePrintQueue y useTemplates para estado global
- Componentes modulares: CommandBuilder, QuickActions, PrintQueue, QueueItem
- Navegación por tabs: Constructor | Cola | Templates
This commit is contained in:
2025-11-24 17:46:20 -06:00
parent f3c13b356b
commit 470ecef4f1
39 changed files with 16114 additions and 1856 deletions

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,158 +0,0 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Printer Central</title>
<style>
body { font-family: system-ui, sans-serif; margin: 24px; }
fieldset { margin-bottom: 16px; }
label { display: inline-block; margin: 6px 8px 6px 0; }
input[type="text"], textarea, select { padding: 6px; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.btn { padding: 8px 12px; cursor: pointer; }
#result { white-space: pre-wrap; background: #f6f8fa; padding: 8px; border: 1px solid #ddd; }
</style>
</head>
<body>
<h1>Panel de Impresión (Node → Epson ePOS)</h1>
<fieldset>
<legend>Texto</legend>
<div class="row">
<textarea id="text" rows="4" cols="60" placeholder="Escribe el texto a imprimir..."></textarea>
</div>
<div class="row">
<label>Align
<select id="align">
<option value="">(sin cambio)</option>
<option value="left">left</option>
<option value="center">center</option>
<option value="right">right</option>
</select>
</label>
<label>Font
<select id="font">
<option value="">(sin cambio)</option>
<option value="font_a">A</option>
<option value="font_b">B</option>
<option value="font_c">C</option>
<option value="font_d">D</option>
<option value="font_e">E</option>
<option value="special_a">Special A</option>
<option value="special_b">Special B</option>
</select>
</label>
<label>Width
<input id="w" type="number" min="1" max="8" step="1" placeholder="1" style="width:5rem" />
</label>
<label>Height
<input id="h" type="number" min="1" max="8" step="1" placeholder="1" style="width:5rem" />
</label>
<label><input type="checkbox" id="bold"/> Bold</label>
<label><input type="checkbox" id="underline"/> Underline</label>
<label><input type="checkbox" id="reverse"/> Reverse</label>
<label>Smooth <input type="checkbox" id="smooth"/></label>
<label>Color
<select id="color">
<option value="">(default)</option>
<option value="color_1">1</option>
<option value="color_2">2</option>
<option value="color_3">3</option>
<option value="color_4">4</option>
</select>
</label>
<label>Feed lines <input id="feedLines" type="number" min="0" max="255" step="1" style="width:5rem"/></label>
<label>Cut
<select id="cut">
<option value="">(no)</option>
<option value="no_feed">no_feed</option>
<option value="feed">feed</option>
<option value="reserve">reserve</option>
<option value="no_feed_fullcut">no_feed_fullcut</option>
<option value="feed_fullcut">feed_fullcut</option>
<option value="reserve_fullcut">reserve_fullcut</option>
</select>
</label>
<button class="btn" id="btnPrintText">Imprimir texto</button>
</div>
</fieldset>
<fieldset>
<legend>Avanzado (componer operaciones)</legend>
<div class="row">
<button class="btn" data-op="feedLine" data-arg="line:2">Feed 2 líneas</button>
<button class="btn" data-op="cut" data-arg="type:feed">Cortar (feed)</button>
<button class="btn" id="btnDrawer">Abrir cajón (pulse)</button>
<button class="btn" id="btnQRCode">QR demo</button>
<button class="btn" id="btnBarcode">Barcode demo</button>
<button class="btn" id="btnEnviar">Enviar lote</button>
<button class="btn" id="btnLimpiar">Limpiar lote</button>
</div>
<div>Operaciones en cola:</div>
<pre id="ops"></pre>
</fieldset>
<fieldset>
<legend>Resultado</legend>
<pre id="result">Listo.</pre>
</fieldset>
<script>
const $ = (id) => document.getElementById(id);
const ops = [];
function refreshOps() { $('ops').textContent = JSON.stringify(ops, null, 2); }
refreshOps();
async function callApi(path, body) {
const res = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) });
const data = await res.json();
$('result').textContent = JSON.stringify(data, null, 2);
return data;
}
$('btnPrintText').onclick = async () => {
const text = $('text').value;
const options = {};
const align = $('align').value; if (align) options.align = align;
const font = $('font').value; if (font) options.font = font;
const w = parseInt($('w').value || '');
const h = parseInt($('h').value || '');
if (!isNaN(w) || !isNaN(h)) options.size = { width: isNaN(w)? undefined : w, height: isNaN(h)? undefined : h };
options.style = { em: $('bold').checked, ul: $('underline').checked, reverse: $('reverse').checked };
const color = $('color').value; if (color) options.style.color = color;
if ($('smooth').checked) options.smooth = true; // optional, not wired in shortcut endpoint
const feedLines = parseInt($('feedLines').value || ''); if (!isNaN(feedLines)) options.feedLines = feedLines;
const cut = $('cut').value; if (cut) options.cut = cut;
await callApi('/api/print/text', { text, options });
};
// Buttons to queue operations
document.querySelectorAll('[data-op]').forEach(btn => {
btn.onclick = () => {
const op = btn.getAttribute('data-op');
const arg = btn.getAttribute('data-arg');
const item = { op };
if (arg) {
// parse simple key:value pairs
arg.split(',').forEach(kv => { const [k,v] = kv.split(':'); item[k.trim()] = isNaN(v) ? v : Number(v); });
}
ops.push(item); refreshOps();
};
});
$('btnDrawer').onclick = () => { ops.push({ op: 'pulse', drawer: 'drawer_1', time: 'pulse_200' }); refreshOps(); };
$('btnQRCode').onclick = () => {
ops.push({ op: 'qrcode', data: 'https://example.com', model: 'qrcode_model_2', level: 'level_m', size: 6 });
refreshOps();
};
$('btnBarcode').onclick = () => {
ops.push({ op: 'barcode', data: '123456789012', type: 'ean13', hri: 'below', width: 3, height: 80 });
refreshOps();
};
$('btnEnviar').onclick = async () => { await callApi('/api/print', { operations: ops }); };
$('btnLimpiar').onclick = () => { ops.length = 0; refreshOps(); };
</script>
</body>
</html>

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow: