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 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 = '| Fecha | Tipo | Cantidad | Costo | Empleado | Notas |
|---|---|---|---|---|---|
| '; + } + html += ' |