From 23dbf54f3fea4f49b1f7ec014ee1c1cf12f1c4ea Mon Sep 17 00:00:00 2001 From: consultoria-as Date: Tue, 26 May 2026 08:39:32 +0000 Subject: [PATCH] =?UTF-8?q?feat(ui):=20POS=20UI=20polish=20kit=20=E2=80=94?= =?UTF-8?q?=20skeletons,=20toasts,=20empty=20states,=20Cmd+K,=20tooltips,?= =?UTF-8?q?=20badges,=20scrollbars,=20focus=20rings,=20bulk=20toolbar,=20b?= =?UTF-8?q?readcrumbs,=20avatars,=20connection=20indicator,=20sparklines,?= =?UTF-8?q?=20animations,=20touch=20mode,=20image=20comparator,=20ticket?= =?UTF-8?q?=20preview,=20resizable=20columns,=20sticky=20headers,=20densit?= =?UTF-8?q?y=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pos/static/css/pos-ui.css | 757 ++++++++++++++++++++++++ pos/static/js/inventory.js | 55 +- pos/static/js/pos-utils.js | 249 +++++++- pos/templates/accounting.html | 3 +- pos/templates/catalog.html | 3 +- pos/templates/config.html | 3 +- pos/templates/customers.html | 3 +- pos/templates/dashboard.html | 3 +- pos/templates/diagrams.html | 3 +- pos/templates/fleet.html | 3 +- pos/templates/inventory.html | 5 +- pos/templates/invoicing.html | 3 +- pos/templates/marketplace.html | 1 + pos/templates/marketplace_external.html | 1 + pos/templates/pos.html | 3 +- pos/templates/quotations.html | 3 +- pos/templates/reports.html | 3 +- pos/templates/whatsapp.html | 3 +- 18 files changed, 1060 insertions(+), 44 deletions(-) create mode 100644 pos/static/css/pos-ui.css diff --git a/pos/static/css/pos-ui.css b/pos/static/css/pos-ui.css new file mode 100644 index 0000000..86ea43b --- /dev/null +++ b/pos/static/css/pos-ui.css @@ -0,0 +1,757 @@ +/** + * pos-ui.css — Nexus POS UI Polish Kit + * Backlog visual improvements: skeletons, toasts, empty states, + * scrollbars, focus rings, badges, tooltips, compact mode, etc. + * Load AFTER tokens.css and common.css, BEFORE page-specific CSS. + */ + +/* ═══════════════════════════════════════════════════════════════ + 1. CUSTOM SCROLLBAR (global) + ═══════════════════════════════════════════════════════════════ */ +* { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb, #444) var(--scrollbar-track, transparent); } +*::-webkit-scrollbar { width: 6px; height: 6px; } +*::-webkit-scrollbar-track { background: var(--scrollbar-track, transparent); } +*::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb, #444); border-radius: var(--radius-full, 999px); } +*::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover, #666); } + +/* ═══════════════════════════════════════════════════════════════ + 2. SKELETON SCREENS + ═══════════════════════════════════════════════════════════════ */ +@keyframes skeleton-shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} +.skeleton { + background: linear-gradient(90deg, + var(--color-surface-1, #1a1a1a) 25%, + var(--color-surface-2, #2a2a2a) 50%, + var(--color-surface-1, #1a1a1a) 75%); + background-size: 200% 100%; + animation: skeleton-shimmer 1.6s ease-in-out infinite; + border-radius: var(--radius-md, 8px); + pointer-events: none; + user-select: none; +} +.skeleton--text { height: 1em; width: 100%; } +.skeleton--text-sm { height: 0.75em; width: 60%; } +.skeleton--circle { width: 40px; height: 40px; border-radius: var(--radius-full, 999px); } +.skeleton--rect { height: 80px; width: 100%; } +.skeleton--btn { height: 36px; width: 100px; border-radius: var(--radius-md, 8px); } +.skeleton--table-row td { border: none !important; background: transparent !important; } +.skeleton--table-row td .skeleton { height: 16px; } +.skeleton--table-row td:nth-child(1) .skeleton { width: 40px; } +.skeleton--table-row td:nth-child(2) .skeleton { width: 90%; } +.skeleton--table-row td:nth-child(3) .skeleton { width: 70%; } +.skeleton--table-row td:nth-child(4) .skeleton { width: 50%; } +.skeleton--table-row td:nth-child(5) .skeleton { width: 60%; } + +/* ═══════════════════════════════════════════════════════════════ + 3. EMPTY STATES (illustrated) + ═══════════════════════════════════════════════════════════════ */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--space-8, 2rem); + text-align: center; + color: var(--color-text-muted, #888); +} +.empty-state__icon { + width: 80px; + height: 80px; + margin-bottom: var(--space-4, 1rem); + opacity: 0.4; + color: var(--color-text-muted, #888); +} +.empty-state__icon svg { + width: 100%; + height: 100%; +} +.empty-state__title { + font-size: var(--text-lg, 1.125rem); + font-weight: var(--font-weight-bold, 700); + color: var(--color-text-primary, #eee); + margin-bottom: var(--space-2, 0.5rem); +} +.empty-state__subtitle { + font-size: var(--text-sm, 0.875rem); + max-width: 360px; + line-height: 1.5; + margin-bottom: var(--space-4, 1rem); +} +.empty-state__action { + margin-top: var(--space-2, 0.5rem); +} + +/* ═══════════════════════════════════════════════════════════════ + 4. UNIFIED INPUT FOCUS RING + ═══════════════════════════════════════════════════════════════ */ +input:focus, select:focus, textarea:focus, +.inv-field input:focus, .inv-field select:focus, .inv-field textarea:focus, +.search-box:focus-within, .select-filter:focus, +.form-control:focus { + outline: none; + border-color: var(--color-primary, #F5A623) !important; + box-shadow: 0 0 0 3px var(--color-primary-muted, rgba(245,166,35,0.25)) !important; +} +input:disabled, select:disabled, textarea:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* ═══════════════════════════════════════════════════════════════ + 5. BADGES (enhanced + new variants) + ═══════════════════════════════════════════════════════════════ */ +.badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + border-radius: var(--radius-full, 999px); + font-size: 0.6875rem; + font-weight: var(--font-weight-bold, 700); + letter-spacing: var(--tracking-wide, 0.05em); + text-transform: uppercase; + white-space: nowrap; + line-height: 1.4; +} +.badge::before { + content: ''; + width: 6px; + height: 6px; + border-radius: var(--radius-full, 999px); + background: currentColor; + flex-shrink: 0; +} +.badge--ok { background: rgba(34, 197, 94, 0.12); color: var(--color-success, #22c55e); } +.badge--low { background: rgba(239, 68, 68, 0.12); color: var(--color-error, #ef4444); } +.badge--over { background: rgba(234, 179, 8, 0.12); color: var(--color-warning, #eab308); } +.badge--pending { background: var(--color-primary-muted, rgba(245,166,35,0.15)); color: var(--color-primary, #F5A623); } +.badge--complete { background: rgba(34, 197, 94, 0.12); color: var(--color-success, #22c55e); } +.badge--transit { background: rgba(99, 102, 241, 0.12); color: #818cf8; } +.badge--cancelled{ background: rgba(115, 115, 115, 0.12);color: var(--color-text-muted, #888); } +.badge--damage { background: rgba(239, 68, 68, 0.12); color: var(--color-error, #ef4444); } +.badge--shrinkage{ background: rgba(234, 179, 8, 0.12); color: var(--color-warning, #eab308); } +.badge--correction{ background: var(--color-primary-muted, rgba(245,166,35,0.15)); color: var(--color-primary, #F5A623); } +.badge--partial { background: rgba(234, 179, 8, 0.12); color: var(--color-warning, #eab308); } +.badge--ml { background: rgba(255, 230, 0, 0.15); color: #2D3277; } +.badge--ml::before { background: #2D3277; } +.badge--new { background: rgba(59, 130, 246, 0.12); color: #3b82f6; } +.badge--syncing { background: rgba(245, 166, 35, 0.12); color: var(--color-primary, #F5A623); animation: pulse-dot 1.5s ease-in-out infinite; } +@keyframes pulse-dot { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +/* ═══════════════════════════════════════════════════════════════ + 6. HOVER STATES (table + cards) + ═══════════════════════════════════════════════════════════════ */ +.data-table tbody tr { + border-bottom: 1px solid var(--color-border, #2a2a2a); + transition: background var(--duration-fast, 150ms) var(--ease-in-out, ease-in-out), + transform var(--duration-fast, 150ms) var(--ease-in-out, ease-in-out); +} +.data-table tbody tr:hover { + background: var(--color-surface-2, #222); + transform: translateX(2px); +} +.data-table tbody tr:active { + transform: translateX(0); +} +.card, .glass-card, .stat-card, .info-card { + transition: transform var(--duration-fast, 150ms) var(--ease-in-out, ease-in-out), + box-shadow var(--duration-fast, 150ms) var(--ease-in-out, ease-in-out); +} +.card:hover, .glass-card:hover, .stat-card:hover, .info-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.3)); +} + +/* ═══════════════════════════════════════════════════════════════ + 7. TOAST NOTIFICATIONS (enhanced) + ═══════════════════════════════════════════════════════════════ */ +#toast-container { + position: fixed; + top: 16px; + right: 16px; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; + max-width: 420px; +} +.toast { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 16px; + border-radius: var(--radius-lg, 12px); + font-size: 14px; + font-weight: 500; + line-height: 1.4; + box-shadow: 0 8px 24px rgba(0,0,0,0.25); + pointer-events: auto; + animation: toastSlideIn 0.35s cubic-bezier(0.16, 1, 0.3, 1); + position: relative; + overflow: hidden; + backdrop-filter: blur(8px); + border: 1px solid rgba(255,255,255,0.05); +} +.toast--ok { background: rgba(26, 122, 58, 0.95); color: #fff; } +.toast--error { background: rgba(192, 57, 43, 0.95); color: #fff; } +.toast--warn { background: rgba(212, 160, 23, 0.95); color: #000; } +.toast--info { background: rgba(40, 40, 45, 0.95); color: #eee; border-color: rgba(255,255,255,0.08); } +.toast__icon { flex-shrink: 0; width: 20px; height: 20px; margin-top: 1px; } +.toast__content { flex: 1; } +.toast__title { font-weight: 700; margin-bottom: 2px; font-size: 13px; } +.toast__msg { font-size: 13px; opacity: 0.9; } +.toast__action { margin-top: 8px; } +.toast__action button { + background: rgba(255,255,255,0.15); + border: none; + color: inherit; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: background 0.15s; +} +.toast__action button:hover { background: rgba(255,255,255,0.25); } +.toast__close { + background: none; + border: none; + color: inherit; + opacity: 0.6; + cursor: pointer; + font-size: 16px; + line-height: 1; + padding: 0; + margin-left: 4px; + flex-shrink: 0; +} +.toast__close:hover { opacity: 1; } +.toast__progress { + position: absolute; + bottom: 0; + left: 0; + height: 3px; + background: rgba(255,255,255,0.4); + border-radius: 0 0 0 var(--radius-lg, 12px); + animation: toastProgress linear forwards; +} +@keyframes toastSlideIn { + from { opacity: 0; transform: translateX(40px) scale(0.95); } + to { opacity: 1; transform: translateX(0) scale(1); } +} +@keyframes toastSlideOut { + from { opacity: 1; transform: translateX(0) scale(1); } + to { opacity: 0; transform: translateX(40px) scale(0.95); } +} +@keyframes toastProgress { + from { width: 100%; } + to { width: 0%; } +} + +/* ═══════════════════════════════════════════════════════════════ + 8. TOOLTIPS + ═══════════════════════════════════════════════════════════════ */ +[data-tooltip] { + position: relative; + cursor: help; +} +[data-tooltip]::after { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + 6px); + left: 50%; + transform: translateX(-50%) scale(0.9); + padding: 6px 10px; + background: var(--color-surface-3, #333); + color: var(--color-text-primary, #eee); + font-size: 12px; + font-weight: 500; + white-space: nowrap; + border-radius: var(--radius-md, 8px); + box-shadow: var(--shadow-md, 0 4px 12px rgba(0,0,0,0.2)); + opacity: 0; + pointer-events: none; + transition: opacity 0.15s, transform 0.15s; + z-index: 10000; + border: 1px solid var(--color-border, #2a2a2a); +} +[data-tooltip]:hover::after { + opacity: 1; + transform: translateX(-50%) scale(1); +} +[data-tooltip-pos="bottom"]::after { + bottom: auto; + top: calc(100% + 6px); +} +[data-tooltip-pos="left"]::after { + bottom: auto; + left: auto; + right: calc(100% + 6px); + top: 50%; + transform: translateY(-50%) scale(0.9); +} +[data-tooltip-pos="left"]:hover::after { + transform: translateY(-50%) scale(1); +} +[data-tooltip-pos="right"]::after { + bottom: auto; + left: calc(100% + 6px); + top: 50%; + transform: translateY(-50%) scale(0.9); +} +[data-tooltip-pos="right"]:hover::after { + transform: translateY(-50%) scale(1); +} + +/* ═══════════════════════════════════════════════════════════════ + 9. COMPACT / DENSE MODE + ═══════════════════════════════════════════════════════════════ */ +[data-density="compact"] .data-table th, +[data-density="compact"] .data-table td { + padding-top: 6px; + padding-bottom: 6px; + font-size: 0.8125rem; +} +[data-density="compact"] .data-table tbody tr { + height: 36px; +} +[data-density="compact"] .sidebar__nav-link { + padding: 8px 14px; +} +[data-density="compact"] .card, [data-density="compact"] .glass-card { + padding: var(--space-3, 0.75rem); +} +[data-density="compact"] .form-group { + margin-bottom: var(--space-2, 0.5rem); +} + +/* ═══════════════════════════════════════════════════════════════ + 10. STICKY TABLE HEADERS + ═══════════════════════════════════════════════════════════════ */ +.data-table thead { + position: sticky; + top: 0; + z-index: 10; +} +.data-table thead th { + position: sticky; + top: 0; + background: var(--color-surface-1, #1a1a1a); + z-index: 10; +} + +/* ═══════════════════════════════════════════════════════════════ + 11. RESIZABLE COLUMNS (visual cue) + ═══════════════════════════════════════════════════════════════ */ +.data-table th { + position: relative; + user-select: none; +} +.data-table th .resize-handle { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 4px; + cursor: col-resize; + background: transparent; + transition: background 0.15s; +} +.data-table th .resize-handle:hover, +.data-table th.is-resizing .resize-handle { + background: var(--color-primary, #F5A623); +} + +/* ═══════════════════════════════════════════════════════════════ + 12. BREADCRUMBS + ═══════════════════════════════════════════════════════════════ */ +.breadcrumbs { + display: flex; + align-items: center; + gap: var(--space-2, 0.5rem); + padding: var(--space-2, 0.5rem) var(--space-4, 1rem); + font-size: 0.8125rem; + color: var(--color-text-muted, #888); + border-bottom: 1px solid var(--color-border, #2a2a2a); +} +.breadcrumbs a { + color: var(--color-text-secondary, #aaa); + text-decoration: none; + transition: color 0.15s; +} +.breadcrumbs a:hover { + color: var(--color-primary, #F5A623); +} +.breadcrumbs__sep { + opacity: 0.4; +} +.breadcrumbs__current { + color: var(--color-text-primary, #eee); + font-weight: var(--font-weight-semibold, 600); +} + +/* ═══════════════════════════════════════════════════════════════ + 13. USER AVATARS (initials) + ═══════════════════════════════════════════════════════════════ */ +.avatar { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: var(--radius-full, 999px); + font-size: 14px; + font-weight: var(--font-weight-bold, 700); + color: #fff; + background: var(--color-primary, #F5A623); + flex-shrink: 0; +} +.avatar--sm { width: 28px; height: 28px; font-size: 12px; } +.avatar--lg { width: 48px; height: 48px; font-size: 18px; } +.avatar--circle { border-radius: var(--radius-full, 999px); } + +/* ═══════════════════════════════════════════════════════════════ + 14. CONNECTION INDICATOR + ═══════════════════════════════════════════════════════════════ */ +.connection-indicator { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + font-weight: 600; + padding: 4px 10px; + border-radius: var(--radius-full, 999px); + background: var(--color-surface-2, #222); +} +.connection-indicator::before { + content: ''; + width: 8px; + height: 8px; + border-radius: var(--radius-full, 999px); + background: var(--color-success, #22c55e); + box-shadow: 0 0 6px var(--color-success, #22c55e); +} +.connection-indicator--offline::before { + background: var(--color-error, #ef4444); + box-shadow: 0 0 6px var(--color-error, #ef4444); +} +.connection-indicator--syncing::before { + background: var(--color-primary, #F5A623); + animation: pulse-dot 1.5s ease-in-out infinite; +} + +/* ═══════════════════════════════════════════════════════════════ + 15. NOTIFICATIONS DROPDOWN + ═══════════════════════════════════════════════════════════════ */ +.notif-dropdown { + position: absolute; + top: calc(100% + 8px); + right: 0; + width: 360px; + max-height: 480px; + background: var(--color-surface-1, #1a1a1a); + border: 1px solid var(--color-border, #2a2a2a); + border-radius: var(--radius-lg, 12px); + box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.3)); + z-index: 1000; + overflow: hidden; + display: flex; + flex-direction: column; +} +.notif-dropdown__header { + padding: 12px 16px; + border-bottom: 1px solid var(--color-border, #2a2a2a); + font-weight: 700; + font-size: 14px; + display: flex; + justify-content: space-between; + align-items: center; +} +.notif-dropdown__list { + overflow-y: auto; + flex: 1; +} +.notif-dropdown__item { + padding: 12px 16px; + border-bottom: 1px solid var(--color-border, #2a2a2a); + display: flex; + gap: 12px; + align-items: flex-start; + cursor: pointer; + transition: background 0.15s; +} +.notif-dropdown__item:hover { background: var(--color-surface-2, #222); } +.notif-dropdown__item--unread { border-left: 3px solid var(--color-primary, #F5A623); } +.notif-dropdown__icon { flex-shrink: 0; width: 32px; height: 32px; border-radius: var(--radius-md, 8px); display: flex; align-items: center; justify-content: center; background: var(--color-surface-2, #222); font-size: 14px; } +.notif-dropdown__content { flex: 1; } +.notif-dropdown__title { font-size: 13px; font-weight: 600; color: var(--color-text-primary, #eee); margin-bottom: 2px; } +.notif-dropdown__time { font-size: 11px; color: var(--color-text-muted, #888); } +.notif-dropdown__empty { padding: 24px; text-align: center; color: var(--color-text-muted, #888); font-size: 13px; } + +/* ═══════════════════════════════════════════════════════════════ + 16. CMD+K SEARCH OVERLAY + ═══════════════════════════════════════════════════════════════ */ +.cmdk-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.6); + backdrop-filter: blur(4px); + z-index: 10000; + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 15vh; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; +} +.cmdk-overlay.is-open { opacity: 1; pointer-events: auto; } +.cmdk-modal { + width: 640px; + max-width: 90vw; + background: var(--color-surface-1, #1a1a1a); + border: 1px solid var(--color-border, #2a2a2a); + border-radius: var(--radius-xl, 16px); + box-shadow: var(--shadow-xl, 0 16px 48px rgba(0,0,0,0.4)); + overflow: hidden; + transform: scale(0.95); + transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1); +} +.cmdk-overlay.is-open .cmdk-modal { transform: scale(1); } +.cmdk-input-wrap { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + border-bottom: 1px solid var(--color-border, #2a2a2a); +} +.cmdk-input { + flex: 1; + background: transparent; + border: none; + color: var(--color-text-primary, #eee); + font-size: 16px; + outline: none; +} +.cmdk-input::placeholder { color: var(--color-text-muted, #888); } +.cmdk-shortcut { font-size: 11px; color: var(--color-text-muted, #888); background: var(--color-surface-2, #222); padding: 2px 6px; border-radius: 4px; } +.cmdk-results { max-height: 400px; overflow-y: auto; } +.cmdk-group { padding: 8px 0; } +.cmdk-group__label { padding: 4px 20px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-text-muted, #888); } +.cmdk-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 20px; + cursor: pointer; + transition: background 0.12s; + font-size: 14px; +} +.cmdk-item:hover, .cmdk-item.is-selected { background: var(--color-surface-2, #222); } +.cmdk-item__icon { width: 28px; height: 28px; border-radius: var(--radius-md, 8px); display: flex; align-items: center; justify-content: center; background: var(--color-surface-2, #222); font-size: 13px; } +.cmdk-item__meta { margin-left: auto; font-size: 11px; color: var(--color-text-muted, #888); } +.cmdk-footer { padding: 8px 20px; border-top: 1px solid var(--color-border, #2a2a2a); font-size: 11px; color: var(--color-text-muted, #888); display: flex; justify-content: space-between; } + +/* ═══════════════════════════════════════════════════════════════ + 17. ENTRANCE ANIMATIONS + ═══════════════════════════════════════════════════════════════ */ +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} +@keyframes scaleIn { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } +} +.animate-fade-in-up { animation: fadeInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both; } +.animate-fade-in { animation: fadeIn 0.3s ease both; } +.animate-scale-in { animation: scaleIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) both; } +/* Stagger delays */ +.stagger-1 { animation-delay: 0.05s; } +.stagger-2 { animation-delay: 0.10s; } +.stagger-3 { animation-delay: 0.15s; } +.stagger-4 { animation-delay: 0.20s; } +.stagger-5 { animation-delay: 0.25s; } + +/* ═══════════════════════════════════════════════════════════════ + 18. SPARKLINE (mini charts) + ═══════════════════════════════════════════════════════════════ */ +.sparkline { display: flex; align-items: flex-end; gap: 2px; height: 24px; } +.sparkline__bar { flex: 1; border-radius: 1px; background: var(--color-primary, #F5A623); opacity: 0.6; transition: opacity 0.15s; min-width: 3px; } +.sparkline__bar:hover { opacity: 1; } +.sparkline--up .sparkline__bar { background: var(--color-success, #22c55e); } +.sparkline--down .sparkline__bar { background: var(--color-error, #ef4444); } + +/* ═══════════════════════════════════════════════════════════════ + 19. SYNC SPINNER / ANIMATED ICONS + ═══════════════════════════════════════════════════════════════ */ +@keyframes spin-slow { to { transform: rotate(360deg); } } +@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } } +.icon-spin { animation: spin-slow 1.2s linear infinite; } +.icon-bounce { animation: bounce 1s ease-in-out infinite; } +.icon-pulse { animation: pulse-dot 1.5s ease-in-out infinite; } + +/* ═══════════════════════════════════════════════════════════════ + 20. KIOSK / TOUCH MODE + ═══════════════════════════════════════════════════════════════ */ +[data-touch="true"] .data-table th, +[data-touch="true"] .data-table td { padding: 16px 14px; font-size: 1rem; } +[data-touch="true"] .btn { padding: 14px 24px; font-size: 1rem; min-height: 48px; } +[data-touch="true"] .sidebar__nav-link { padding: 16px 18px; font-size: 1rem; } +[data-touch="true"] input, [data-touch="true"] select, [data-touch="true"] textarea { font-size: 16px; padding: 14px 12px; } +[data-touch="true"] .search-box { min-height: 48px; } + +/* ═══════════════════════════════════════════════════════════════ + 21. BULK ACTIONS TOOLBAR + ═══════════════════════════════════════════════════════════════ */ +.bulk-toolbar { + display: flex; + align-items: center; + gap: var(--space-3, 0.75rem); + padding: var(--space-3, 0.75rem) var(--space-4, 1rem); + background: var(--color-surface-2, #222); + border: 1px solid var(--color-border, #2a2a2a); + border-radius: var(--radius-lg, 12px); + margin-bottom: var(--space-4, 1rem); + animation: fadeInUp 0.25s ease both; +} +.bulk-toolbar__count { + font-size: 13px; + font-weight: 600; + color: var(--color-text-primary, #eee); + padding-right: var(--space-3, 0.75rem); + border-right: 1px solid var(--color-border, #2a2a2a); +} +.bulk-toolbar__actions { display: flex; gap: var(--space-2, 0.5rem); } + +/* ═══════════════════════════════════════════════════════════════ + 22. TICKET PREVIEW + ═══════════════════════════════════════════════════════════════ */ +.ticket-preview { + background: #fff; + color: #222; + padding: 24px; + border-radius: var(--radius-md, 8px); + font-family: 'Courier New', monospace; + font-size: 12px; + line-height: 1.6; + max-width: 320px; + margin: 0 auto; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} +.ticket-preview__header { text-align: center; border-bottom: 1px dashed #ccc; padding-bottom: 12px; margin-bottom: 12px; } +.ticket-preview__title { font-size: 16px; font-weight: 700; margin-bottom: 4px; } +.ticket-preview__meta { font-size: 11px; color: #666; } +.ticket-preview__row { display: flex; justify-content: space-between; margin-bottom: 4px; } +.ticket-preview__total { border-top: 1px dashed #ccc; padding-top: 8px; margin-top: 8px; font-size: 14px; font-weight: 700; } + +/* ═══════════════════════════════════════════════════════════════ + 23. IMAGE COMPARATOR + ═══════════════════════════════════════════════════════════════ */ +.img-compare { + position: relative; + overflow: hidden; + border-radius: var(--radius-lg, 12px); + user-select: none; +} +.img-compare__img { width: 100%; display: block; } +.img-compare__overlay { + position: absolute; + top: 0; left: 0; bottom: 0; + width: 50%; + overflow: hidden; + border-right: 2px solid var(--color-primary, #F5A623); +} +.img-compare__overlay img { height: 100%; width: auto; max-width: none; } +.img-compare__handle { + position: absolute; + top: 50%; left: 50%; + transform: translate(-50%, -50%); + width: 36px; height: 36px; + background: var(--color-primary, #F5A623); + border-radius: var(--radius-full, 999px); + display: flex; align-items: center; justify-content: center; + cursor: ew-resize; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); + font-size: 14px; + color: #fff; +} + +/* ═══════════════════════════════════════════════════════════════ + 24. CUSTOM LOADER / SPINNER + ═══════════════════════════════════════════════════════════════ */ +.nx-loader { + position: relative; + width: 48px; + height: 48px; +} +.nx-loader__ring { + position: absolute; + inset: 0; + border: 3px solid transparent; + border-top-color: var(--color-primary, #F5A623); + border-radius: 50%; + animation: spin-slow 1s linear infinite; +} +.nx-loader__ring:nth-child(2) { + inset: 6px; + border-top-color: var(--color-secondary, #3b82f6); + animation-duration: 1.3s; + animation-direction: reverse; +} +.nx-loader__ring:nth-child(3) { + inset: 12px; + border-top-color: var(--color-success, #22c55e); + animation-duration: 0.8s; +} +.nx-loader--sm { width: 24px; height: 24px; } +.nx-loader--sm .nx-loader__ring { border-width: 2px; } +.nx-loader--sm .nx-loader__ring:nth-child(2) { inset: 4px; } +.nx-loader--sm .nx-loader__ring:nth-child(3) { inset: 8px; } + +/* ═══════════════════════════════════════════════════════════════ + 25. SAVED FILTERS CHIPS + ═══════════════════════════════════════════════════════════════ */ +.filter-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--color-surface-2, #222); + border: 1px solid var(--color-border, #2a2a2a); + border-radius: var(--radius-full, 999px); + font-size: 12px; + font-weight: 500; + color: var(--color-text-secondary, #aaa); + cursor: pointer; + transition: all 0.15s; +} +.filter-chip:hover { border-color: var(--color-primary, #F5A623); color: var(--color-text-primary, #eee); } +.filter-chip.is-active { background: var(--color-primary-muted, rgba(245,166,35,0.15)); border-color: var(--color-primary, #F5A623); color: var(--color-primary, #F5A623); } +.filter-chip__remove { background: none; border: none; color: inherit; cursor: pointer; font-size: 14px; line-height: 1; padding: 0; opacity: 0.6; } +.filter-chip__remove:hover { opacity: 1; } + +/* ═══════════════════════════════════════════════════════════════ + 26. DARK MODE REFINEMENTS (subtle borders, depth) + ═══════════════════════════════════════════════════════════════ */ +[data-theme="modern"] .data-table thead th { + background: linear-gradient(180deg, var(--color-surface-1, #1a1a1a) 0%, var(--color-surface-2, #222) 100%); +} +[data-theme="modern"] .card, [data-theme="modern"] .glass-card { + border: 1px solid rgba(255,255,255,0.04); +} +[data-theme="industrial"] .data-table thead th { + background: var(--color-surface-1, #1a1a1a); + border-bottom: 2px solid var(--color-primary, #F5A623); +} diff --git a/pos/static/js/inventory.js b/pos/static/js/inventory.js index 083d09d..5e9605d 100644 --- a/pos/static/js/inventory.js +++ b/pos/static/js/inventory.js @@ -49,6 +49,11 @@ // --- Dashboard summary badges --- function loadSummary() { + var skeletonHtml = '
'; + ['inv-total-skus','inv-total-value','inv-low-stock','inv-no-movement'].forEach(function(id) { + var el = document.getElementById(id); + if (el) el.innerHTML = skeletonHtml; + }); apiFetch(API + '/summary').then(function(data) { if (!data) return; var totalSkusEl = document.getElementById('inv-total-skus'); @@ -112,6 +117,17 @@ }); } + // Register Cmd+K items + if (typeof registerCmdKItem === 'function') { + registerCmdKItem({ group: 'Inventario', label: 'Ver stock', href: '/pos/inventory#stock', icon: '📦' }); + registerCmdKItem({ group: 'Inventario', label: 'Alertas de stock', href: '/pos/inventory#alertas', icon: '⚠️' }); + registerCmdKItem({ group: 'Inventario', label: 'Entradas de mercancía', href: '/pos/inventory#entradas', icon: '📥' }); + registerCmdKItem({ group: 'Inventario', label: 'Salidas / Ventas', href: '/pos/inventory#salidas', icon: '📤' }); + registerCmdKItem({ group: 'Inventario', label: 'Traspasos', href: '/pos/inventory#traspasos', icon: '🚚' }); + registerCmdKItem({ group: 'Inventario', label: 'Ajustes', href: '/pos/inventory#ajustes', icon: '⚙️' }); + registerCmdKItem({ group: 'Inventario', label: 'Conteos físicos', href: '/pos/inventory#conteos', icon: '🔢' }); + } + // Handle hash-based tab switching (e.g. /pos/inventory#alertas) (function handleHashTab() { var hash = window.location.hash.replace('#', ''); @@ -206,13 +222,20 @@ var params = new URLSearchParams({ page: currentPage, per_page: 50 }); if (currentSearch) params.set('q', currentSearch); + var tbody = document.getElementById('productTableBody'); + if (tbody) tbody.innerHTML = renderSkeletonRows(12, 8); + apiFetch(API + '/items?' + params.toString()).then(function (data) { if (!data) return; - var tbody = document.getElementById('productTableBody'); var items = data.data || []; if (!items.length) { - tbody.innerHTML = 'Sin productos'; + tbody.innerHTML = '' + renderEmptyState({ + icon: '', + title: 'Sin productos', + subtitle: currentSearch ? 'No se encontraron resultados para "' + esc(currentSearch) + '". Intenta con otro término.' : 'El inventario está vacío. Crea tu primer producto para empezar.', + action: currentSearch ? '' : '' + }) + ''; document.getElementById('productPagination').innerHTML = ''; return; } @@ -223,7 +246,7 @@ rowHeight: 48, buffer: 3, renderRow: renderInventoryRow, - emptyHtml: 'Sin productos' + emptyHtml: '' + renderEmptyState({ title: 'Sin productos', subtitle: 'El inventario está vacío.' }) + '' }); } inventoryVS.setData(items); @@ -653,14 +676,24 @@ // ===================================================================== function loadAlerts() { + var container = document.getElementById('alertsContent'); + if (container) container.innerHTML = '
' + renderEmptyState({ + icon: '', + title: 'Cargando alertas...', + subtitle: 'Revisando el estado del inventario' + }) + '
'; + apiFetch(API + '/alerts').then(function (data) { if (!data) return; var alerts = data.data || []; - var container = document.getElementById('alertsContent'); if (!container) return; if (!alerts.length) { - container.innerHTML = '

Sin alertas activas

'; + container.innerHTML = renderEmptyState({ + icon: '', + title: 'Todo en orden', + subtitle: 'No hay alertas activas en el inventario. Los niveles de stock están dentro de los límites configurados.' + }); return; } @@ -726,7 +759,11 @@ var history = data.data || []; var html = ''; if (!history.length) { - html = '

Sin movimientos

'; + html = renderEmptyState({ + icon: '', + title: 'Sin movimientos', + subtitle: 'Este producto aún no tiene historial de entradas, salidas ni ajustes.' + }); } else { html = ''; history.forEach(function (h) { @@ -763,11 +800,11 @@ headers: token ? { 'Authorization': 'Bearer ' + token } : {} }).then(function(r) { return r.json(); }) .then(function(data) { - if (data.error) { alert('Error: ' + data.error); return; } - showToast('Artículo eliminado'); + if (data.error) { showToast(data.error, 'error', { title: 'Error al eliminar' }); return; } + showToast('El artículo fue eliminado correctamente.', 'ok', { title: 'Eliminado' }); loadItems(currentPage); if (window.loadInventoryStats) window.loadInventoryStats(); - }).catch(function() { alert('Error al eliminar artículo'); }); + }).catch(function() { showToast('No se pudo eliminar el artículo. Intenta de nuevo.', 'error', { title: 'Error' }); }); } // ===================================================================== diff --git a/pos/static/js/pos-utils.js b/pos/static/js/pos-utils.js index 7c89971..928edb2 100644 --- a/pos/static/js/pos-utils.js +++ b/pos/static/js/pos-utils.js @@ -76,38 +76,245 @@ window.print(); }; - // ── Toast (simple, non-blocking notification) ────────────────── - // Only creates its own toast if the page doesn't already have one. - window.showToast = function(msg, type) { + // ── Toast (enhanced with icons, progress bar, close button, actions) ── + var _toastIcons = { + ok: '', + error: '', + warn: '', + info: '' + }; + var _toastTitles = { ok: 'Éxito', error: 'Error', warn: 'Advertencia', info: 'Información' }; + + window.showToast = function(msg, type, opts) { type = type || 'info'; + opts = opts || {}; var container = document.getElementById('toast-container'); if (!container) { container = document.createElement('div'); container.id = 'toast-container'; - container.style.cssText = 'position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;pointer-events:none;'; document.body.appendChild(container); } - var colors = { - ok: 'background:#1a7a3a;color:#fff;', - error: 'background:#c0392b;color:#fff;', - warn: 'background:#d4a017;color:#000;', - info: 'background:var(--color-surface-3,#333);color:var(--color-text-primary,#fff);', - }; - var toast = document.createElement('div'); - toast.style.cssText = (colors[type] || colors.info) + - 'padding:10px 20px;border-radius:8px;font-size:14px;font-weight:500;' + - 'box-shadow:0 4px 12px rgba(0,0,0,0.3);pointer-events:auto;' + - 'animation:slideInRight 0.3s ease;max-width:400px;'; - toast.textContent = msg; + toast.className = 'toast toast--' + type; + + var iconHtml = '
' + (_toastIcons[type] || _toastIcons.info) + '
'; + var titleHtml = opts.title ? '
' + opts.title + '
' : ''; + var actionHtml = ''; + if (opts.action && opts.action.text) { + actionHtml = '
'; + toast.__toastAction = function() { + if (opts.action.callback) opts.action.callback(); + _removeToast(toast); + }; + } + var progressHtml = '
'; + + toast.innerHTML = iconHtml + + '
' + titleHtml + '
' + msg + '
' + actionHtml + '
' + + '' + + progressHtml; + container.appendChild(toast); - setTimeout(function() { - toast.style.opacity = '0'; - toast.style.transition = 'opacity 0.3s'; - setTimeout(function() { toast.remove(); }, 300); - }, 3000); + var timer = setTimeout(function() { _removeToast(toast); }, opts.duration || 4000); + toast.__toastTimer = timer; + + toast.addEventListener('mouseenter', function() { clearTimeout(timer); var p = toast.querySelector('.toast__progress'); if (p) p.style.animationPlayState = 'paused'; }); + toast.addEventListener('mouseleave', function() { var p = toast.querySelector('.toast__progress'); if (p) p.style.animationPlayState = 'running'; timer = setTimeout(function() { _removeToast(toast); }, 2000); toast.__toastTimer = timer; }); + }; + + window._removeToast = function(toast) { + if (!toast || toast.__toastRemoved) return; + toast.__toastRemoved = true; + if (toast.__toastTimer) clearTimeout(toast.__toastTimer); + toast.style.animation = 'toastSlideOut 0.25s ease forwards'; + setTimeout(function() { toast.remove(); }, 260); + }; + + // ── Skeleton helpers ────────────────────────────────────────── + window.renderSkeletonRows = function(cols, rows) { + rows = rows || 6; + var html = ''; + for (var i = 0; i < rows; i++) { + html += ''; + for (var j = 0; j < cols; j++) { + html += ''; + } + html += ''; + } + return html; + }; + + window.showSkeleton = function(containerSelector, cols, rows) { + var el = typeof containerSelector === 'string' ? document.querySelector(containerSelector) : containerSelector; + if (!el) return; + el.dataset.originalContent = el.innerHTML; + el.innerHTML = renderSkeletonRows(cols || 6, rows || 6); + }; + + window.hideSkeleton = function(containerSelector) { + var el = typeof containerSelector === 'string' ? document.querySelector(containerSelector) : containerSelector; + if (!el || el.dataset.originalContent === undefined) return; + el.innerHTML = el.dataset.originalContent; + delete el.dataset.originalContent; + }; + + // ── Empty state helper ──────────────────────────────────────── + window.renderEmptyState = function(opts) { + opts = opts || {}; + var icon = opts.icon || ''; + var title = opts.title || 'Sin datos'; + var subtitle = opts.subtitle || 'No hay información disponible en este momento.'; + var action = opts.action ? '
' + opts.action + '
' : ''; + return '
' + + '
' + icon + '
' + + '
' + title + '
' + + '
' + subtitle + '
' + + action + '
'; + }; + + // ── Cmd+K Global Search ─────────────────────────────────────── + (function() { + var cmdkOverlay = null, cmdkInput = null, cmdkResults = null, cmdkSelected = -1; + var cmdkItems = []; + + function buildCmdK() { + if (cmdkOverlay) return; + cmdkOverlay = document.createElement('div'); + cmdkOverlay.className = 'cmdk-overlay'; + cmdkOverlay.innerHTML = + ''; + document.body.appendChild(cmdkOverlay); + cmdkInput = cmdkOverlay.querySelector('.cmdk-input'); + cmdkResults = cmdkOverlay.querySelector('.cmdk-results'); + + cmdkOverlay.addEventListener('click', function(e) { if (e.target === cmdkOverlay) closeCmdK(); }); + cmdkInput.addEventListener('input', function() { filterCmdK(this.value); }); + cmdkInput.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { closeCmdK(); return; } + if (e.key === 'ArrowDown') { e.preventDefault(); moveCmdK(1); } + if (e.key === 'ArrowUp') { e.preventDefault(); moveCmdK(-1); } + if (e.key === 'Enter') { e.preventDefault(); activateCmdK(); } + }); + } + + function openCmdK() { + buildCmdK(); + cmdkOverlay.classList.add('is-open'); + cmdkInput.value = ''; + cmdkInput.focus(); + filterCmdK(''); + } + + function closeCmdK() { + if (cmdkOverlay) cmdkOverlay.classList.remove('is-open'); + } + + function moveCmdK(dir) { + var items = cmdkResults.querySelectorAll('.cmdk-item'); + if (!items.length) return; + cmdkSelected += dir; + if (cmdkSelected < 0) cmdkSelected = items.length - 1; + if (cmdkSelected >= items.length) cmdkSelected = 0; + items.forEach(function(it, i) { it.classList.toggle('is-selected', i === cmdkSelected); }); + var sel = items[cmdkSelected]; + if (sel) sel.scrollIntoView({ block: 'nearest' }); + } + + function activateCmdK() { + var items = cmdkResults.querySelectorAll('.cmdk-item'); + var sel = items[cmdkSelected]; + if (sel && sel.dataset.href) { closeCmdK(); window.location.href = sel.dataset.href; } + } + + function filterCmdK(q) { + q = (q || '').toLowerCase().trim(); + var groups = {}; + cmdkItems.forEach(function(item) { + if (!q || item.label.toLowerCase().indexOf(q) !== -1 || (item.keywords || '').toLowerCase().indexOf(q) !== -1) { + groups[item.group] = groups[item.group] || []; + groups[item.group].push(item); + } + }); + var html = ''; + var total = 0; + Object.keys(groups).forEach(function(g) { + html += '
' + g + '
'; + groups[g].forEach(function(item) { + total++; + html += '
' + + '
' + (item.icon || '→') + '
' + + '
' + item.label + '
' + + (item.meta ? '
' + item.meta + '
' : '') + + '
'; + }); + html += '
'; + }); + if (!total) html = '
Sin resultados
'; + cmdkResults.innerHTML = html; + cmdkSelected = 0; + var first = cmdkResults.querySelector('.cmdk-item'); + if (first) first.classList.add('is-selected'); + var footer = cmdkOverlay.querySelector('.cmdk-footer span:last-child'); + if (footer) footer.textContent = total + ' resultados'; + } + + document.addEventListener('keydown', function(e) { + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); openCmdK(); } + }); + + window.registerCmdKItem = function(item) { + if (!item || !item.label) return; + cmdkItems.push(item); + }; + + window.openCmdK = openCmdK; + window.closeCmdK = closeCmdK; + })(); + + // ── Connection indicator helper ─────────────────────────────── + window.ConnectionStatus = { + online: function() { return navigator.onLine; }, + render: function(containerId) { + var el = document.getElementById(containerId); + if (!el) return; + function update() { + var isOnline = navigator.onLine; + el.className = 'connection-indicator' + (isOnline ? '' : ' connection-indicator--offline'); + el.innerHTML = '' + (isOnline ? 'En línea' : 'Sin conexión'); + } + update(); + window.addEventListener('online', update); + window.addEventListener('offline', update); + } + }; + + // ── Bulk toolbar helper ─────────────────────────────────────── + window.renderBulkToolbar = function(count, actionsHtml) { + return '
' + + '
' + count + ' seleccionado' + (count !== 1 ? 's' : '') + '
' + + '
' + actionsHtml + '
' + + '
'; + }; + + // ── Entrance animation helper ───────────────────────────────── + window.animateEntrance = function(selector, animClass, stagger) { + animClass = animClass || 'animate-fade-in-up'; + stagger = stagger || 0.05; + var els = document.querySelectorAll(selector); + els.forEach(function(el, i) { + el.style.animationDelay = (i * stagger) + 's'; + el.classList.add(animClass); + }); }; // ── "Próximamente" placeholder for features not yet built ────── diff --git a/pos/templates/accounting.html b/pos/templates/accounting.html index 3f97649..b23d655 100644 --- a/pos/templates/accounting.html +++ b/pos/templates/accounting.html @@ -8,6 +8,7 @@ + @@ -490,7 +491,7 @@ - + diff --git a/pos/templates/catalog.html b/pos/templates/catalog.html index c6be397..c3418dc 100644 --- a/pos/templates/catalog.html +++ b/pos/templates/catalog.html @@ -7,6 +7,7 @@ Catalogo — Nexus Autoparts POS + @@ -286,7 +287,7 @@ - + diff --git a/pos/templates/config.html b/pos/templates/config.html index 1f8de3c..1a42110 100644 --- a/pos/templates/config.html +++ b/pos/templates/config.html @@ -8,6 +8,7 @@ + @@ -741,7 +742,7 @@ - + diff --git a/pos/templates/customers.html b/pos/templates/customers.html index 33197a4..08b1df0 100644 --- a/pos/templates/customers.html +++ b/pos/templates/customers.html @@ -8,6 +8,7 @@ + @@ -621,7 +622,7 @@ - + diff --git a/pos/templates/dashboard.html b/pos/templates/dashboard.html index 09073c5..ac431f3 100644 --- a/pos/templates/dashboard.html +++ b/pos/templates/dashboard.html @@ -8,6 +8,7 @@ + @@ -483,7 +484,7 @@ - + diff --git a/pos/templates/diagrams.html b/pos/templates/diagrams.html index 85a6b13..1ddb020 100644 --- a/pos/templates/diagrams.html +++ b/pos/templates/diagrams.html @@ -8,6 +8,7 @@ + @@ -150,7 +151,7 @@ - + diff --git a/pos/templates/fleet.html b/pos/templates/fleet.html index 8328a75..902f30d 100644 --- a/pos/templates/fleet.html +++ b/pos/templates/fleet.html @@ -8,6 +8,7 @@ + @@ -303,7 +304,7 @@ - + diff --git a/pos/templates/inventory.html b/pos/templates/inventory.html index 9a68ed4..aa4b4e0 100644 --- a/pos/templates/inventory.html +++ b/pos/templates/inventory.html @@ -8,6 +8,7 @@ + @@ -915,10 +916,10 @@ - + - + diff --git a/pos/templates/invoicing.html b/pos/templates/invoicing.html index 1b06933..ec72173 100644 --- a/pos/templates/invoicing.html +++ b/pos/templates/invoicing.html @@ -8,6 +8,7 @@ + @@ -1052,7 +1053,7 @@ - + diff --git a/pos/templates/marketplace.html b/pos/templates/marketplace.html index 944a5df..52340d8 100644 --- a/pos/templates/marketplace.html +++ b/pos/templates/marketplace.html @@ -8,6 +8,7 @@ + diff --git a/pos/templates/marketplace_external.html b/pos/templates/marketplace_external.html index cde8192..df9586c 100644 --- a/pos/templates/marketplace_external.html +++ b/pos/templates/marketplace_external.html @@ -8,6 +8,7 @@ + diff --git a/pos/templates/pos.html b/pos/templates/pos.html index 6677817..b7a3e21 100644 --- a/pos/templates/pos.html +++ b/pos/templates/pos.html @@ -7,6 +7,7 @@ Nexus Autoparts — Punto de Venta + @@ -563,7 +564,7 @@ ================================================================ --> - + diff --git a/pos/templates/quotations.html b/pos/templates/quotations.html index b3268aa..07913ad 100644 --- a/pos/templates/quotations.html +++ b/pos/templates/quotations.html @@ -8,12 +8,13 @@ + - + diff --git a/pos/templates/reports.html b/pos/templates/reports.html index 7da89fc..cd1a613 100644 --- a/pos/templates/reports.html +++ b/pos/templates/reports.html @@ -8,6 +8,7 @@ + @@ -318,7 +319,7 @@ - + diff --git a/pos/templates/whatsapp.html b/pos/templates/whatsapp.html index d7e3ed1..c4b5cc5 100644 --- a/pos/templates/whatsapp.html +++ b/pos/templates/whatsapp.html @@ -11,6 +11,7 @@ + @@ -133,7 +134,7 @@ function posLogout(){localStorage.removeItem('pos_token');window.location.href=' - +
FechaTipoCantidadCostoEmpleadoNotas