colors and prompts fix
This commit is contained in:
@@ -1,130 +0,0 @@
|
|||||||
:root {
|
|
||||||
--bg: #ebedef;
|
|
||||||
--card-bg: #ffffff;
|
|
||||||
--text-main: #2e3338;
|
|
||||||
--text-muted: #4f5660;
|
|
||||||
--border: #e3e5e8;
|
|
||||||
--header-bg: #ffffff;
|
|
||||||
--input-bg: #e3e5e8;
|
|
||||||
--table-head: #f2f3f5;
|
|
||||||
--price-color: #2e3338;
|
|
||||||
--accent: #5865F2;
|
|
||||||
--accent-hover: #4752c4;
|
|
||||||
--danger: #ed4245;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] {
|
|
||||||
--bg: #36393f;
|
|
||||||
--card-bg: #2f3136;
|
|
||||||
--text-main: #ffffff;
|
|
||||||
--text-muted: #b9bbbe;
|
|
||||||
--border: #202225;
|
|
||||||
--header-bg: #202225;
|
|
||||||
--input-bg: #202225;
|
|
||||||
--table-head: #292b2f;
|
|
||||||
--price-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'gg sans', 'Segoe UI', sans-serif;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text-main);
|
|
||||||
margin: 0;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--header-bg);
|
|
||||||
padding: 0 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
height: 50px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-container { display: flex; gap: 20px; }
|
|
||||||
.column { flex: 1; min-width: 0; } /* min-width: 0 prevents flex blow-out */
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#display-img { max-width: 200px; max-height: 200px; object-fit: contain; margin-bottom: 10px; }
|
|
||||||
.price-tag { font-size: 2.5em; font-weight: 800; margin: 5px 0; }
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--input-bg);
|
|
||||||
color: var(--text-main);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
button { padding: 10px 15px; cursor: pointer; border-radius: 4px; border: none; font-weight: 600; }
|
|
||||||
.btn-save { background: var(--accent); color: white; width: 100%; }
|
|
||||||
.btn-edit { background: var(--accent); color: white; margin-right: 5px; }
|
|
||||||
.btn-del { background: var(--danger); color: white; }
|
|
||||||
|
|
||||||
/* Table Handling */
|
|
||||||
.table-wrapper { overflow-x: auto; width: 100%; border-radius: 8px; }
|
|
||||||
table { width: 100%; border-collapse: collapse; min-width: 450px; }
|
|
||||||
th, td { padding: 12px; border-bottom: 1px solid var(--border); text-align: left; }
|
|
||||||
th { background: var(--table-head); color: var(--text-muted); font-size: 0.75em; text-transform: uppercase; }
|
|
||||||
|
|
||||||
input[type="checkbox"] { width: 20px !important; height: 20px !important; margin: 0; cursor: pointer; display: inline-block; }
|
|
||||||
|
|
||||||
/* Bulk Actions Bar */
|
|
||||||
.bulk-actions {
|
|
||||||
display: flex;
|
|
||||||
background: var(--accent);
|
|
||||||
color: white;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 55px;
|
|
||||||
}
|
|
||||||
.bulk-controls { display: flex; align-items: center; gap: 8px; }
|
|
||||||
.bulk-actions input#bulk-price-input { width: 100px !important; margin: 0 !important; height: 35px !important; background: rgba(0,0,0,0.2) !important; color: white !important; border: 1px solid rgba(255,255,255,0.2) !important; }
|
|
||||||
|
|
||||||
#new-product-prompt {
|
|
||||||
display: none;
|
|
||||||
background: var(--accent);
|
|
||||||
color: white;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Overrides */
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.main-container { flex-direction: column; }
|
|
||||||
.header-bar span { display: none; } /* Hide user text on tiny screens */
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
body { padding: 10px; }
|
|
||||||
th, td { padding: 8px 5px; font-size: 0.8em; }
|
|
||||||
.btn-edit, .btn-del { padding: 5px; font-size: 0.75em; }
|
|
||||||
/* Hide barcode text column on mobile to fit */
|
|
||||||
td:nth-child(2), th:nth-child(2) { display: none; }
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
:root {
|
|
||||||
/* Discord Branding Colors */
|
|
||||||
--bg: #5865F2;
|
|
||||||
--card-bg: #ffffff;
|
|
||||||
--text: #2e3338;
|
|
||||||
--input-bg: #e3e5e8;
|
|
||||||
--input-border: transparent;
|
|
||||||
--accent: #5865F2;
|
|
||||||
--accent-hover: #4752c4;
|
|
||||||
--error: #ed4245;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="dark"] {
|
|
||||||
/* Discord Dark Mode */
|
|
||||||
--bg: #36393f;
|
|
||||||
--card-bg: #2f3136;
|
|
||||||
--text: #ffffff;
|
|
||||||
--input-bg: #202225;
|
|
||||||
--input-border: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'gg sans', 'Segoe UI', Tahoma, sans-serif;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
background: var(--bg);
|
|
||||||
margin: 0;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-box {
|
|
||||||
background: var(--card-bg);
|
|
||||||
padding: 32px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text);
|
|
||||||
width: 400px;
|
|
||||||
transition: background 0.2s, color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.sub-text {
|
|
||||||
color: var(--text);
|
|
||||||
opacity: 0.7;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
margin: 16px 0;
|
|
||||||
padding: 12px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--input-bg);
|
|
||||||
color: var(--text);
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus {
|
|
||||||
outline: 2px solid var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background: var(--accent);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background: var(--accent-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-msg {
|
|
||||||
background: var(--error);
|
|
||||||
color: white;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
@@ -1,96 +1,471 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="es">
|
<html lang="es" data-theme="light">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>SekiPOS - Inventory</title>
|
<title>SekiPOS - Inventory</title>
|
||||||
|
<link rel="shortcut icon" href="./static/favicon.png" type="image/x-icon">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
||||||
<link rel="stylesheet" href="./static/styleIndex.css">
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #ebedef;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--text-main: #2e3338;
|
||||||
|
--text-muted: #4f5660;
|
||||||
|
--border: #e3e5e8;
|
||||||
|
--navbar-bg: #ffffff;
|
||||||
|
--input-bg: #e3e5e8;
|
||||||
|
--table-head: #f2f3f5;
|
||||||
|
--accent: #5865f2;
|
||||||
|
--accent-hover: #4752c4;
|
||||||
|
--danger: #ed4245;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg: #36393f;
|
||||||
|
--card-bg: #2f3136;
|
||||||
|
--text-main: #dcddde;
|
||||||
|
--text-muted: #b9bbbe;
|
||||||
|
--border: #202225;
|
||||||
|
--navbar-bg: #202225;
|
||||||
|
--input-bg: #202225;
|
||||||
|
--table-head: #292b2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-family: "gg sans", "Segoe UI", sans-serif;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Navbar ── */
|
||||||
|
.navbar {
|
||||||
|
background: var(--navbar-bg) !important;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
color: var(--text-main) !important;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link,
|
||||||
|
.dropdown-item {
|
||||||
|
color: var(--text-main) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background: var(--input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.text-danger {
|
||||||
|
color: var(--danger) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Cards ── */
|
||||||
|
.discord-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Inputs ── */
|
||||||
|
.form-control,
|
||||||
|
.form-control:focus {
|
||||||
|
background: var(--input-bg);
|
||||||
|
color: var(--text-main);
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Buttons ── */
|
||||||
|
.btn-accent {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-accent:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-discord {
|
||||||
|
background: var(--danger);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger-discord:hover {
|
||||||
|
background: #c23235;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Price tag ── */
|
||||||
|
.price-tag {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Table ── */
|
||||||
|
.table {
|
||||||
|
color: var(--text-main);
|
||||||
|
--bs-table-color: var(--text-main);
|
||||||
|
--bs-table-bg: transparent;
|
||||||
|
--bs-table-border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- Checkbox Size Fix -- */
|
||||||
|
#select-all {
|
||||||
|
transform: scale(1.3);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .table {
|
||||||
|
--bs-table-color: var(--text-main);
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background: var(--table-head);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody td {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Bulk bar ── */
|
||||||
|
.bulk-bar {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-bar .form-control {
|
||||||
|
width: 110px;
|
||||||
|
background: rgba(0, 0, 0, 0.2) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-bar .form-control::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── New-product prompt ── */
|
||||||
|
.new-product-prompt {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Product image ── */
|
||||||
|
#display-img {
|
||||||
|
max-width: 160px;
|
||||||
|
max-height: 160px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Checkbox ── */
|
||||||
|
input[type="checkbox"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Mobile: hide barcode column ── */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.col-barcode {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit-sm,
|
||||||
|
.btn-del-sm {
|
||||||
|
padding: 4px 7px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text-main);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header,
|
||||||
|
.modal-footer {
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
/* Makes the X button visible in dark mode */
|
||||||
|
filter: var(--bs-theme-placeholder, invert(0.7) grayscale(100%) brightness(200%));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .btn-close {
|
||||||
|
filter: invert(1) grayscale(100%) brightness(200%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="header-bar">
|
|
||||||
<h2>SekiPOS v1.5</h2>
|
|
||||||
<div style="display: flex; align-items: center; gap: 10px;">
|
|
||||||
<button onclick="toggleTheme()" id="theme-text" style="padding: 5px 10px; font-size: 0.8em;">Modo Oscuro</button>
|
|
||||||
<a href="/logout" style="color: var(--danger); text-decoration: none; font-size: 0.9em; font-weight: bold;">Salir</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main-container">
|
<!-- ════════════════════════ NAVBAR ════════════════════════ -->
|
||||||
<div class="column">
|
<nav class="navbar navbar-expand-md sticky-top px-3 mb-3">
|
||||||
<div id="new-product-prompt">
|
<span class="navbar-brand">SekiPOS <small class="text-muted fw-normal"
|
||||||
<span>Nuevo: <b id="new-barcode-display"></b></span>
|
style="font-size:0.65rem;">v1.5</small></span>
|
||||||
<button onclick="dismissPrompt()" style="background:rgba(0,0,0,0.2); color:white;">Omitir</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<!-- Always-visible dropdown on the right -->
|
||||||
<h3 style="color: var(--text-muted); margin-top:0;">Último Escaneado</h3>
|
<div class="ms-auto">
|
||||||
<img id="display-img" src="./static/placeholder.png">
|
<div class="dropdown">
|
||||||
<h2 id="display-name">Esperando scan...</h2>
|
<button class="btn btn-accent dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||||
<div class="price-tag" id="display-price">$0</div>
|
aria-expanded="false">
|
||||||
<p id="display-barcode" style="font-family: monospace; opacity: 0.5;"></p>
|
<i class="bi bi-person-circle me-1"></i>
|
||||||
</div>
|
<span class="d-none d-sm-inline">{{ user.username }}</span>
|
||||||
|
</button>
|
||||||
<div class="card">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<h3 id="form-title" style="margin-top:0;">Editar/Crear</h3>
|
<li>
|
||||||
<form action="/upsert" method="POST" id="product-form">
|
<button class="dropdown-item" onclick="toggleTheme()">
|
||||||
<input type="text" name="barcode" id="form-barcode" placeholder="Barcode" required>
|
<i class="bi bi-moon-stars me-2" id="theme-icon"></i>
|
||||||
<input type="text" name="name" id="form-name" placeholder="Nombre" required>
|
<span id="theme-label">Modo Oscuro</span>
|
||||||
<input type="number" name="price" id="form-price" placeholder="Precio (CLP)" required>
|
</button>
|
||||||
<input type="text" name="image_url" id="form-image" placeholder="URL Imagen">
|
</li>
|
||||||
<button type="submit" class="btn-save">Guardar Cambios</button>
|
<li>
|
||||||
</form>
|
<hr class="dropdown-divider" style="border-color: var(--border);">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item text-danger" href="/logout">
|
||||||
|
<i class="bi bi-box-arrow-right me-2"></i>Salir
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div class="column">
|
<!-- ════════════════════════ MAIN ════════════════════════ -->
|
||||||
<div class="card">
|
<div class="container-fluid px-3">
|
||||||
<div id="bulk-bar" class="bulk-actions">
|
<div class="row g-3">
|
||||||
<span><b id="selected-count">0</b> listos</span>
|
|
||||||
<div class="bulk-controls">
|
<!-- ── LEFT COLUMN ── -->
|
||||||
<input type="number" id="bulk-price-input" placeholder="Precio">
|
<div class="col-12 col-lg-4">
|
||||||
<button onclick="applyBulkPrice()" style="background:white; color:var(--accent);">OK</button>
|
|
||||||
<button onclick="clearSelection()" class="btn-del">✕</button>
|
<!-- New product prompt -->
|
||||||
|
<div id="new-product-prompt"
|
||||||
|
class="new-product-prompt p-3 mb-3 d-none d-flex justify-content-between align-items-center">
|
||||||
|
<span>Nuevo: <b id="new-barcode-display"></b></span>
|
||||||
|
<button onclick="dismissPrompt()" class="btn btn-sm"
|
||||||
|
style="background:rgba(0,0,0,0.25);color:#fff;">
|
||||||
|
Omitir
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Last scanned card -->
|
||||||
|
<div class="discord-card p-3 mb-3 text-center">
|
||||||
|
<p class="mb-1 fw-semibold"
|
||||||
|
style="color:var(--text-muted); font-size:0.8rem; text-transform:uppercase; letter-spacing:.05em;">
|
||||||
|
Último Escaneado</p>
|
||||||
|
<img id="display-img" src="./static/placeholder.png" class="mb-2" alt="product">
|
||||||
|
<h5 id="display-name" class="mb-1">Esperando scan...</h5>
|
||||||
|
<div class="price-tag" id="display-price">$0</div>
|
||||||
|
<p id="display-barcode" class="mb-0 mt-1"
|
||||||
|
style="font-family:monospace; opacity:.5; font-size:.8rem;"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit / Create card -->
|
||||||
|
<div class="discord-card p-3">
|
||||||
|
<h6 id="form-title" class="mb-3 fw-bold">Editar / Crear</h6>
|
||||||
|
<form action="/upsert" method="POST" id="product-form">
|
||||||
|
<input class="form-control mb-2" type="text" name="barcode" id="form-barcode"
|
||||||
|
placeholder="Barcode" required>
|
||||||
|
<input class="form-control mb-2" type="text" name="name" id="form-name" placeholder="Nombre"
|
||||||
|
required>
|
||||||
|
<input class="form-control mb-2" type="number" name="price" id="form-price"
|
||||||
|
placeholder="Precio (CLP)" required>
|
||||||
|
<input class="form-control mb-3" type="text" name="image_url" id="form-image"
|
||||||
|
placeholder="URL Imagen">
|
||||||
|
<button type="submit" class="btn btn-accent w-100">
|
||||||
|
<i class="bi bi-floppy me-1"></i>Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ── RIGHT COLUMN ── -->
|
||||||
|
<div class="col-12 col-lg-8">
|
||||||
|
<div class="discord-card p-3">
|
||||||
|
|
||||||
|
<!-- Bulk actions bar -->
|
||||||
|
<div id="bulk-bar" class="bulk-bar p-2 mb-3 d-flex justify-content-between align-items-center">
|
||||||
|
<span><b id="selected-count">0</b> seleccionados</span>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<input type="number" id="bulk-price-input" class="form-control form-control-sm"
|
||||||
|
placeholder="Precio">
|
||||||
|
<button onclick="applyBulkPrice()" class="btn btn-sm"
|
||||||
|
style="background:#fff; color:var(--accent); font-weight:600;">OK</button>
|
||||||
|
<button onclick="clearSelection()" class="btn btn-sm btn-danger-discord">
|
||||||
|
<i class="bi bi-x-lg"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search -->
|
||||||
|
<input type="text" id="searchInput" class="form-control mb-3" onkeyup="searchTable()"
|
||||||
|
placeholder="Filtrar productos...">
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-borderless mb-0" id="inventoryTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:36px;">
|
||||||
|
<input class="form-check-input" type="checkbox" id="select-all"
|
||||||
|
onclick="toggleAll(this)">
|
||||||
|
</th>
|
||||||
|
<th class="col-barcode">Código</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Precio</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for p in products %}
|
||||||
|
<tr data-barcode="{{ p[0] }}">
|
||||||
|
<td>
|
||||||
|
<input class="form-check-input product-checkbox" type="checkbox"
|
||||||
|
onclick="updateBulkBar()">
|
||||||
|
</td>
|
||||||
|
<td class="col-barcode" style="font-family:monospace; font-size:.8rem;">{{ p[0] }}
|
||||||
|
</td>
|
||||||
|
<td class="name-cell">{{ p[1] }}</td>
|
||||||
|
<td class="price-cell" data-value="{{ p[2] }}"></td>
|
||||||
|
<td style="white-space:nowrap;">
|
||||||
|
<button class="btn btn-accent btn-sm btn-edit-sm me-1"
|
||||||
|
onclick="editProduct('{{ p[0] }}', '{{ p[1] }}', '{{ p[2] }}', '{{ p[3] }}')"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editModal">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger-discord btn-sm btn-del-sm" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#deleteModal" data-barcode="{{ p[0] }}"
|
||||||
|
data-name="{{ p[1] }}">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><!-- /row -->
|
||||||
|
</div><!-- /container -->
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Editar Producto</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>¿Quieres editar <strong id="editProductName"></strong>?</p>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<button class="btn btn-accent" onclick="confirmEdit()">
|
||||||
|
<i class="bi bi-pencil me-1"></i>Editar
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="text" id="searchInput" onkeyup="searchTable()" placeholder="Filtrar productos...">
|
|
||||||
|
|
||||||
<div class="table-wrapper">
|
|
||||||
<table id="inventoryTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th><input type="checkbox" id="select-all" onclick="toggleAll(this)"></th>
|
|
||||||
<th>Código</th>
|
|
||||||
<th>Nombre</th>
|
|
||||||
<th>Precio</th>
|
|
||||||
<th>Acciones</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for p in products %}
|
|
||||||
<tr data-barcode="{{ p[0] }}">
|
|
||||||
<td><input type="checkbox" class="product-checkbox" onclick="updateBulkBar()"></td>
|
|
||||||
<td style="font-family: monospace;">{{ p[0] }}</td>
|
|
||||||
<td class="name-cell">{{ p[1] }}</td>
|
|
||||||
<td class="price-cell" data-value="{{ p[2] }}"></td>
|
|
||||||
<td style="white-space: nowrap;">
|
|
||||||
<button class="btn-edit" onclick="editProduct('{{ p[0] }}', '{{ p[1] }}', '{{ p[2] }}', '{{ p[3] }}')">E</button>
|
|
||||||
<form action="/delete/{{ p[0] }}" method="POST" style="display:inline;">
|
|
||||||
<button type="submit" class="btn-del" onclick="return confirm('¿Borrar?')">D</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Modal -->
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Eliminar Producto</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>¿Seguro que quieres eliminar <strong id="deleteProductName"></strong>?</p>
|
||||||
|
<p class="text-muted small">Esta acción no se puede deshacer.</p>
|
||||||
|
<div class="d-grid gap-2 mt-3">
|
||||||
|
<button class="btn btn-danger-discord" onclick="confirmDelete()">
|
||||||
|
<i class="bi bi-trash me-1"></i>Eliminar
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="bulkConfirmModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Confirmar Cambio Masivo</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center">
|
||||||
|
<i class="bi bi-exclamation-triangle text-warning" style="font-size: 3rem;"></i>
|
||||||
|
<p class="mt-3">Vas a actualizar <strong id="bulk-count-text">0</strong> productos al precio de
|
||||||
|
<strong id="bulk-price-text"></strong>.
|
||||||
|
</p>
|
||||||
|
<div class="d-grid gap-2 mt-3">
|
||||||
|
<button class="btn btn-accent" onclick="executeBulkPrice()">
|
||||||
|
Confirmar Cambios
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var socket = io();
|
/* ── Theme ── */
|
||||||
|
function applyTheme(t) {
|
||||||
|
document.documentElement.setAttribute('data-theme', t);
|
||||||
|
const isDark = t === 'dark';
|
||||||
|
document.getElementById('theme-icon').className = isDark ? 'bi bi-sun me-2' : 'bi bi-moon-stars me-2';
|
||||||
|
document.getElementById('theme-label').innerText = isDark ? 'Modo Claro' : 'Modo Oscuro';
|
||||||
|
}
|
||||||
|
function toggleTheme() {
|
||||||
|
const next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||||||
|
localStorage.setItem('theme', next);
|
||||||
|
applyTheme(next);
|
||||||
|
}
|
||||||
|
applyTheme(localStorage.getItem('theme') || 'light');
|
||||||
|
|
||||||
|
/* ── Socket.IO ── */
|
||||||
|
const socket = io();
|
||||||
const clp = new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP', minimumFractionDigits: 0 });
|
const clp = new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP', minimumFractionDigits: 0 });
|
||||||
|
|
||||||
function formatAll() {
|
function formatAll() {
|
||||||
@@ -105,14 +480,14 @@
|
|||||||
document.getElementById('display-price').innerText = clp.format(d.price);
|
document.getElementById('display-price').innerText = clp.format(d.price);
|
||||||
document.getElementById('display-barcode').innerText = d.barcode;
|
document.getElementById('display-barcode').innerText = d.barcode;
|
||||||
document.getElementById('display-img').src = d.image || './static/placeholder.png';
|
document.getElementById('display-img').src = d.image || './static/placeholder.png';
|
||||||
updateForm(d.barcode, d.name, d.price, d.image, "Editando: " + d.name);
|
updateForm(d.barcode, d.name, d.price, d.image, 'Editando: ' + d.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('scan_error', d => {
|
socket.on('scan_error', d => {
|
||||||
const prompt = document.getElementById('new-product-prompt');
|
const prompt = document.getElementById('new-product-prompt');
|
||||||
document.getElementById('new-barcode-display').innerText = d.barcode;
|
document.getElementById('new-barcode-display').innerText = d.barcode;
|
||||||
prompt.style.display = 'flex';
|
prompt.classList.remove('d-none');
|
||||||
updateForm(d.barcode, d.name || '', '', d.image || '', "Crear: " + d.barcode);
|
updateForm(d.barcode, d.name || '', '', d.image || '', 'Crear: ' + d.barcode);
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateForm(b, n, p, i, t) {
|
function updateForm(b, n, p, i, t) {
|
||||||
@@ -124,32 +499,85 @@
|
|||||||
document.getElementById('form-title').innerText = t;
|
document.getElementById('form-title').innerText = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dismissPrompt() { document.getElementById('new-product-prompt').style.display = 'none'; }
|
function dismissPrompt() {
|
||||||
|
document.getElementById('new-product-prompt').classList.add('d-none');
|
||||||
function editProduct(b, n, p, i) {
|
|
||||||
updateForm(b, n, p, i, "Editando: " + n);
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleAll(source) {
|
function editProduct(b, n, p, i) {
|
||||||
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = source.checked);
|
document.getElementById('editProductName').innerText = n;
|
||||||
|
document.getElementById('editModal').dataset.barcode = b;
|
||||||
|
document.getElementById('editModal').dataset.name = n;
|
||||||
|
document.getElementById('editModal').dataset.price = p;
|
||||||
|
document.getElementById('editModal').dataset.image = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmEdit() {
|
||||||
|
const modal = document.getElementById('editModal');
|
||||||
|
updateForm(
|
||||||
|
modal.dataset.barcode,
|
||||||
|
modal.dataset.name,
|
||||||
|
modal.dataset.price,
|
||||||
|
modal.dataset.image,
|
||||||
|
'Editando: ' + modal.dataset.name
|
||||||
|
);
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
bootstrap.Modal.getInstance(modal).hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmDelete() {
|
||||||
|
const modal = document.getElementById('deleteModal');
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.action = `/delete/${modal.dataset.barcode}`;
|
||||||
|
form.method = 'POST';
|
||||||
|
form.style.display = 'none';
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete modal setup
|
||||||
|
document.getElementById('deleteModal').addEventListener('show.bs.modal', e => {
|
||||||
|
const button = e.relatedTarget;
|
||||||
|
document.getElementById('deleteProductName').innerText = button.dataset.name;
|
||||||
|
document.getElementById('deleteModal').dataset.barcode = button.dataset.barcode;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ── Bulk selection ── */
|
||||||
|
function toggleAll(src) {
|
||||||
|
const visibleRows = document.querySelectorAll('#inventoryTable tbody tr:not([style*="display: none"])');
|
||||||
|
visibleRows.forEach(row => {
|
||||||
|
const cb = row.querySelector('.product-checkbox');
|
||||||
|
if (cb) cb.checked = src.checked;
|
||||||
|
});
|
||||||
updateBulkBar();
|
updateBulkBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBulkBar() {
|
function updateBulkBar() {
|
||||||
const count = document.querySelectorAll('.product-checkbox:checked').length;
|
document.getElementById('selected-count').innerText =
|
||||||
document.getElementById('selected-count').innerText = count;
|
document.querySelectorAll('.product-checkbox:checked').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelection() {
|
function clearSelection() {
|
||||||
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = false);
|
document.querySelectorAll('.product-checkbox').forEach(cb => cb.checked = false);
|
||||||
document.getElementById('select-all').checked = false;
|
document.getElementById('select-all').checked = false;
|
||||||
updateBulkBar();
|
updateBulkBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyBulkPrice() {
|
function applyBulkPrice() {
|
||||||
|
const price = document.getElementById('bulk-price-input').value;
|
||||||
|
const checked = document.querySelectorAll('.product-checkbox:checked');
|
||||||
|
|
||||||
|
if (!price || checked.length === 0) return;
|
||||||
|
|
||||||
|
// Set text in the pretty modal
|
||||||
|
document.getElementById('bulk-count-text').innerText = checked.length;
|
||||||
|
document.getElementById('bulk-price-text').innerText = clp.format(price);
|
||||||
|
|
||||||
|
// Show the modal
|
||||||
|
const bulkModal = new bootstrap.Modal(document.getElementById('bulkConfirmModal'));
|
||||||
|
bulkModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeBulkPrice() {
|
||||||
const price = document.getElementById('bulk-price-input').value;
|
const price = document.getElementById('bulk-price-input').value;
|
||||||
if (!price) return;
|
|
||||||
const checked = document.querySelectorAll('.product-checkbox:checked');
|
const checked = document.querySelectorAll('.product-checkbox:checked');
|
||||||
const barcodes = Array.from(checked).map(cb => cb.closest('tr').getAttribute('data-barcode'));
|
const barcodes = Array.from(checked).map(cb => cb.closest('tr').getAttribute('data-barcode'));
|
||||||
|
|
||||||
@@ -161,29 +589,30 @@
|
|||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
checked.forEach(cb => {
|
checked.forEach(cb => {
|
||||||
const row = cb.closest('tr');
|
const cell = cb.closest('tr').querySelector('.price-cell');
|
||||||
const cell = row.querySelector('.price-cell');
|
|
||||||
cell.setAttribute('data-value', price);
|
cell.setAttribute('data-value', price);
|
||||||
cell.innerText = clp.format(price);
|
cell.innerText = clp.format(price);
|
||||||
cb.checked = false;
|
cb.checked = false;
|
||||||
});
|
});
|
||||||
|
document.getElementById('bulk-price-input').value = '';
|
||||||
updateBulkBar();
|
updateBulkBar();
|
||||||
|
|
||||||
|
// Hide modal
|
||||||
|
const modalEl = document.getElementById('bulkConfirmModal');
|
||||||
|
bootstrap.Modal.getInstance(modalEl).hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* ── Search ── */
|
||||||
function searchTable() {
|
function searchTable() {
|
||||||
let q = document.getElementById("searchInput").value.toUpperCase();
|
const q = document.getElementById('searchInput').value.toUpperCase();
|
||||||
document.querySelectorAll("#inventoryTable tbody tr").forEach(tr => {
|
document.querySelectorAll('#inventoryTable tbody tr').forEach(tr => {
|
||||||
tr.style.display = tr.innerText.toUpperCase().includes(q) ? "" : "none";
|
tr.style.display = tr.innerText.toUpperCase().includes(q) ? '' : 'none';
|
||||||
});
|
});
|
||||||
|
// Reset select-all when search changes
|
||||||
|
document.getElementById('select-all').checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTheme() {
|
|
||||||
const t = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
|
||||||
document.documentElement.setAttribute('data-theme', t);
|
|
||||||
localStorage.setItem('theme', t);
|
|
||||||
}
|
|
||||||
document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'light');
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,35 +1,119 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="es">
|
<html lang="es" data-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>SekiPOS Login</title>
|
<title>SekiPOS Login</title>
|
||||||
<link rel="shortcut icon" href="./static/favicon.png" type="image/x-icon">
|
<link rel="shortcut icon" href="./static/favicon.png" type="image/x-icon">
|
||||||
<link rel="stylesheet" href="./static/styleLogin.css">
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #5865f2;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--text: #2e3338;
|
||||||
|
--input-bg: #e3e5e8;
|
||||||
|
--accent: #5865f2;
|
||||||
|
--accent-hover: #4752c4;
|
||||||
|
--error: #ed4245;
|
||||||
|
}
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg: #36393f;
|
||||||
|
--card-bg: #2f3136;
|
||||||
|
--text: #ffffff;
|
||||||
|
--input-bg: #202225;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--bg);
|
||||||
|
font-family: "gg sans", "Segoe UI", sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.2s;
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-box {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
background: var(--input-bg) !important;
|
||||||
|
color: var(--text) !important;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.form-control:focus {
|
||||||
|
box-shadow: 0 0 0 2px var(--accent);
|
||||||
|
}
|
||||||
|
.form-control::placeholder { color: #8a8e94; }
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.btn-login:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-alert {
|
||||||
|
background: var(--error);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: .88rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="login-box">
|
<div class="login-box text-center">
|
||||||
<h2>SekiPOS</h2>
|
<h2 class="fw-bold mb-1">SekiPOS</h2>
|
||||||
<p class="sub-text">¡Hola de nuevo!</p>
|
<p class="mb-4" style="opacity:.7;">¡Hola de nuevo!</p>
|
||||||
|
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages() %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="error-msg">{{ messages[0] }}</div>
|
<div class="error-alert p-2 mb-3">{{ messages[0] }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="text" name="username" placeholder="Usuario" required>
|
<input
|
||||||
<input type="password" name="password" placeholder="Contraseña" required>
|
class="form-control mb-3"
|
||||||
<button type="submit">Iniciar Sesión</button>
|
type="text"
|
||||||
|
name="username"
|
||||||
|
placeholder="Usuario"
|
||||||
|
required
|
||||||
|
autofocus
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="form-control mb-3"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Contraseña"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<button type="submit" class="btn btn-login w-100">
|
||||||
|
Iniciar Sesión
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Sync theme with main page
|
const t = localStorage.getItem('theme') || 'light';
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
if (t === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
|
||||||
if (savedTheme === 'dark') {
|
|
||||||
document.documentElement.setAttribute('data-theme', 'dark');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user