panel admin: inventario y promociones
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f4f6f9;
|
||||
color: #333;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* --- MENÚ LATERAL (SIDEBAR) --- */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background-color: #1e2229;
|
||||
color: #fff;
|
||||
padding: 20px 0;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar .brand {
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #323743;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar .brand h3 {
|
||||
color: #e02828;
|
||||
font-size: 1.4rem;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidebar-menu li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 15px 25px;
|
||||
color: #b8c7ce;
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-menu li a:hover,
|
||||
.sidebar-menu li.active a {
|
||||
color: #fff;
|
||||
background-color: #e02828;
|
||||
border-left: 4px solid #fff;
|
||||
}
|
||||
|
||||
.sidebar-menu li a i {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* --- CONTENEDOR PRINCIPAL --- */
|
||||
.main-content {
|
||||
margin-left: 260px;
|
||||
padding: 40px;
|
||||
width: calc(100% - 260px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
/* --- TARJETAS INFORMATIVAS (DASHBOARD) --- */
|
||||
.dashboard-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card .card-info h3 {
|
||||
font-size: 1.8rem;
|
||||
color: #1e2229;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.card .card-info p {
|
||||
color: #777;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card i {
|
||||
font-size: 2.2rem;
|
||||
color: #e02828;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* --- TABLAS DE GESTIÓN (PEDIDOS E INVENTARIO) --- */
|
||||
.table-container {
|
||||
background: #fff;
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.admin-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.admin-table th {
|
||||
background-color: #f8fafc;
|
||||
color: #64748b;
|
||||
padding: 14px;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #edf2f7;
|
||||
}
|
||||
|
||||
.admin-table td {
|
||||
padding: 14px;
|
||||
border-bottom: 1px solid #edf2f7;
|
||||
font-size: 0.95rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.admin-table tr:hover {
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
/* --- BADGES (ESTADOS DEL PEDIDO CONCORDANTES) --- */
|
||||
.badge {
|
||||
padding: 6px 12px;
|
||||
border-radius: 50px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.badge-pendiente { background-color: #ffeeba; color: #856404; }
|
||||
.badge-cocina { background-color: #ffdaf0; color: #b52b65; }
|
||||
.badge-listo { background-color: #d1ecf1; color: #0c5460; }
|
||||
.badge-camino { background-color: #cce5ff; color: #004085; }
|
||||
.badge-entregado { background-color: #d4edda; color: #155724; }
|
||||
.badge-cancelado { background-color: #f8d7da; color: #721c24; }
|
||||
|
||||
/* --- CONTROLES Y SELECTORES --- */
|
||||
.select-status {
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #cbd5e1;
|
||||
font-size: 0.85rem;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
include("../db.php");
|
||||
|
||||
if (!isset($_SESSION['rol']) || $_SESSION['rol'] !== 'admin') {
|
||||
header("Location: ../login.php?error=acceso_denegado");
|
||||
exit();
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['archivo_csv'])) {
|
||||
|
||||
$ruta_temporal = $_FILES['archivo_csv']['tmp_name'];
|
||||
$archivo = fopen($ruta_temporal, "r");
|
||||
|
||||
if ($archivo !== FALSE) {
|
||||
fgetcsv($archivo, 1000, ";");
|
||||
|
||||
$importados = 0;
|
||||
$errores = 0;
|
||||
|
||||
$stmt = $conexion->prepare("
|
||||
INSERT INTO productos (id, nombre, precio, categoria, imagen, activo, stock)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
nombre = VALUES(nombre),
|
||||
precio = VALUES(precio),
|
||||
categoria = VALUES(categoria),
|
||||
imagen = VALUES(imagen),
|
||||
activo = VALUES(activo),
|
||||
stock = VALUES(stock)
|
||||
");
|
||||
|
||||
while (($datos = fgetcsv($archivo, 1000, ";")) !== FALSE) {
|
||||
|
||||
// Protección por si viene una fila vacía al final del CSV
|
||||
if(count($datos) < 5) continue;
|
||||
|
||||
$id = trim($datos[0]);
|
||||
$nombre = trim($datos[1]);
|
||||
$precio = trim($datos[2]);
|
||||
$categoria = trim($datos[3]);
|
||||
$imagen = trim($datos[4]);
|
||||
|
||||
$activo = isset($datos[5]) ? trim($datos[5]) : 1;
|
||||
$stock = (isset($datos[6]) && trim($datos[6]) !== '') ? trim($datos[6]) : 0;
|
||||
|
||||
if (empty($nombre) || empty($categoria) || !is_numeric($precio) || (float)$precio <= 0 || !is_numeric($stock) || (int)$stock < 0) {
|
||||
$errores++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$stock = (int)$stock;
|
||||
|
||||
$stmt->bind_param("isdssii", $id, $nombre, $precio, $categoria, $imagen, $activo, $stock);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$importados++;
|
||||
} else {
|
||||
$errores++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($archivo);
|
||||
|
||||
if ($errores == 0 && $importados > 0) {
|
||||
header("Location: inventario.php?status=ok_csv&cant=" . $importados);
|
||||
} elseif ($importados > 0) {
|
||||
header("Location: inventario.php?status=ok_csv&cant=" . $importados . "&warnings=" . $errores);
|
||||
} else {
|
||||
header("Location: inventario.php?status=error");
|
||||
}
|
||||
} else {
|
||||
header("Location: inventario.php?status=error");
|
||||
}
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
include("../db.php");
|
||||
|
||||
if (!isset($_SESSION['rol']) || $_SESSION['rol'] !== 'admin') {
|
||||
header("Location: ../login.php?error=acceso_denegado");
|
||||
exit();
|
||||
}
|
||||
|
||||
$mensaje = "";
|
||||
$tipo_mensaje = "";
|
||||
if (isset($_GET['status'])) {
|
||||
if ($_GET['status'] === 'ok_manual') {
|
||||
$mensaje = "¡Producto agregado con éxito!";
|
||||
$tipo_mensaje = "success";
|
||||
} elseif ($_GET['status'] === 'ok_csv') {
|
||||
$cant = isset($_GET['cant']) ? (int)$_GET['cant'] : 0;
|
||||
$mensaje = "Catálogo actualizado — $cant productos procesados desde el CSV.";
|
||||
$tipo_mensaje = "success";
|
||||
} elseif ($_GET['status'] === 'error') {
|
||||
$mensaje = "Hubo un problema al procesar la solicitud. Verificá los formatos de los datos.";
|
||||
$tipo_mensaje = "error";
|
||||
}
|
||||
}
|
||||
|
||||
$query_productos = "SELECT id, nombre, precio, stock, imagen, categoria FROM productos WHERE activo = 1 ORDER BY categoria ASC, nombre ASC";
|
||||
$res_productos = mysqli_query($conexion, $query_productos);
|
||||
$todos_productos = mysqli_fetch_all($res_productos, MYSQLI_ASSOC);
|
||||
|
||||
// Categorías únicas para los filtros
|
||||
$categorias_unicas = array_unique(array_column($todos_productos, 'categoria'));
|
||||
sort($categorias_unicas);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Inventario - La Pecosa</title>
|
||||
<link rel="stylesheet" href="../css/all.min.css">
|
||||
<link rel="stylesheet" href="admin_style.css">
|
||||
<style>
|
||||
/* ── ALERT ── */
|
||||
.alert-ui {
|
||||
padding: 13px 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 22px;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.alert-ui.success { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
|
||||
.alert-ui.error { background: #fef2f2; color: #991b1b; border: 1px solid #fee2e2; }
|
||||
|
||||
/* ── GRID FORMULARIOS ── */
|
||||
.grid-forms {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
@media (max-width: 860px) { .grid-forms { grid-template-columns: 1fr; } }
|
||||
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
.form-card-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-bottom: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.form-card-title .fc-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fc-icon.red { background: #fee2e2; color: #dc2626; }
|
||||
.fc-icon.green { background: #dcfce7; color: #16a34a; }
|
||||
|
||||
/* ── FORM FIELDS ── */
|
||||
.fg { margin-bottom: 13px; }
|
||||
.fg label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.fg input[type="text"],
|
||||
.fg input[type="number"],
|
||||
.fg input[type="file"],
|
||||
.fg select {
|
||||
width: 100%;
|
||||
padding: 9px 11px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 7px;
|
||||
font-size: 0.875rem;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.fg input:focus, .fg select:focus { border-color: #dc2626; }
|
||||
|
||||
.fg-row { display: flex; gap: 12px; }
|
||||
.fg-row .fg { flex: 1; }
|
||||
|
||||
.csv-hint {
|
||||
background: #f0fdf4;
|
||||
border-left: 3px solid #16a34a;
|
||||
border-radius: 0 6px 6px 0;
|
||||
padding: 10px 12px;
|
||||
font-size: 0.8rem;
|
||||
color: #166534;
|
||||
margin-bottom: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.csv-hint code {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
border: none;
|
||||
border-radius: 7px;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
transition: opacity 0.15s, transform 0.15s;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.btn-submit:hover { opacity: 0.88; transform: translateY(-1px); }
|
||||
.btn-submit.red { background: #dc2626; color: #fff; }
|
||||
.btn-submit.green { background: #16a34a; color: #fff; }
|
||||
|
||||
/* ── SECCIÓN CATÁLOGO ── */
|
||||
.catalogo-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.catalogo-header {
|
||||
padding: 16px 22px;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.catalogo-header h2 {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
margin: 0;
|
||||
}
|
||||
.catalogo-header h2 i { color: #dc2626; }
|
||||
|
||||
/* ── FILTROS DE CATEGORÍA ── */
|
||||
.cat-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.cat-btn {
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 20px;
|
||||
padding: 5px 13px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cat-btn:hover { background: #fee2e2; border-color: #fca5a5; color: #dc2626; }
|
||||
.cat-btn.active { background: #dc2626; border-color: #dc2626; color: #fff; }
|
||||
|
||||
/* ── CONTADOR ── */
|
||||
.cat-count {
|
||||
font-size: 0.78rem;
|
||||
color: #9ca3af;
|
||||
padding: 6px 22px;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
#cat-count-num { font-weight: 700; color: #374151; }
|
||||
|
||||
/* ── TABLA ── */
|
||||
.admin-table td { font-size: 0.875rem; vertical-align: middle; }
|
||||
.admin-table tr:last-child td { border-bottom: none; }
|
||||
|
||||
.img-preview {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
object-fit: cover;
|
||||
border-radius: 7px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
.cat-chip {
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.stock-ok { color: #374151; }
|
||||
.stock-warn { color: #92400e; font-weight: 700; }
|
||||
.stock-zero { color: #991b1b; font-weight: 700; }
|
||||
|
||||
tr.sin-stock { background: #fff5f5; }
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
color: #9ca3af;
|
||||
padding: 40px 14px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.no-results i { font-size: 1.5rem; display: block; margin-bottom: 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php include("sidebar.php"); ?>
|
||||
|
||||
<div class="main-content">
|
||||
|
||||
<div class="page-header">
|
||||
<h1><i class="fa-solid fa-boxes-stacked"></i> Gestión de inventario y menú</h1>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($mensaje)): ?>
|
||||
<div class="alert-ui <?php echo $tipo_mensaje; ?>">
|
||||
<i class="fa-solid <?php echo $tipo_mensaje === 'success' ? 'fa-circle-check' : 'fa-circle-exclamation'; ?>"></i>
|
||||
<?php echo $mensaje; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- FORMULARIOS -->
|
||||
<div class="grid-forms">
|
||||
|
||||
<!-- Agregar producto manual -->
|
||||
<div class="form-card">
|
||||
<div class="form-card-title">
|
||||
<span class="fc-icon red"><i class="fa-solid fa-plus"></i></span>
|
||||
Agregar un producto
|
||||
</div>
|
||||
<form action="procesar_manual.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="fg">
|
||||
<label>Nombre del producto</label>
|
||||
<input type="text" name="nombre" placeholder="Ej: Pizza especial La Pecosa" required>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>Categoría</label>
|
||||
<select name="categoria" required>
|
||||
<option value="Carne">Carne</option>
|
||||
<option value="Verdura">Verdura</option>
|
||||
<option value="Vinoteca">Vinoteca</option>
|
||||
<option value="Bebidas">Bebidas</option>
|
||||
<option value="Lácteos">Lácteos</option>
|
||||
<option value="Panadería">Panadería</option>
|
||||
<option value="Limpieza">Limpieza</option>
|
||||
<option value="Almacén">Almacén</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="fg-row">
|
||||
<div class="fg">
|
||||
<label>Precio ($)</label>
|
||||
<input type="number" name="precio" placeholder="4500" min="0" step="0.01" required>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>Stock inicial</label>
|
||||
<input type="number" name="stock" placeholder="20" min="0" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>Foto del producto</label>
|
||||
<input type="file" name="imagen" accept="image/*" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit red">
|
||||
<i class="fa-solid fa-floppy-disk"></i> Guardar en menú
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Carga masiva CSV -->
|
||||
<div class="form-card" style="display:flex; flex-direction:column; justify-content:space-between;">
|
||||
<div>
|
||||
<div class="form-card-title">
|
||||
<span class="fc-icon green"><i class="fa-solid fa-file-csv"></i></span>
|
||||
Carga masiva del catálogo (.csv)
|
||||
</div>
|
||||
<p style="font-size:0.855rem; color:#6b7280; line-height:1.55; margin-bottom:14px;">
|
||||
Armá el Excel y guardalo como CSV para actualizar precios o stocks de forma masiva sin cargar producto por producto.
|
||||
</p>
|
||||
<div class="csv-hint">
|
||||
<strong>Estructura de columnas requerida (separado por punto y coma):</strong>
|
||||
<code>id ; nombre ; precio ; categoria ; imagen ; activo ; stock</code>
|
||||
</div>
|
||||
</div>
|
||||
<form action="importar_csv.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="fg">
|
||||
<label>Seleccionar archivo CSV</label>
|
||||
<input type="file" name="archivo_csv" accept=".csv" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-submit green">
|
||||
<i class="fa-solid fa-upload"></i> Procesar catálogo
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- CATÁLOGO CON FILTROS -->
|
||||
<div class="catalogo-card">
|
||||
<div class="catalogo-header">
|
||||
<h2><i class="fa-solid fa-list"></i> Productos en la plataforma</h2>
|
||||
<div class="cat-filters">
|
||||
<button class="cat-btn active" onclick="filtrarCat(this, 'todos')">Todos</button>
|
||||
<?php foreach ($categorias_unicas as $cat): ?>
|
||||
<button class="cat-btn" onclick="filtrarCat(this, '<?php echo htmlspecialchars($cat); ?>')">
|
||||
<?php echo htmlspecialchars($cat); ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cat-count">
|
||||
Mostrando <span id="cat-count-num"><?php echo count($todos_productos); ?></span> productos
|
||||
</div>
|
||||
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="admin-table" id="tabla-productos">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Foto</th>
|
||||
<th>ID</th>
|
||||
<th>Nombre</th>
|
||||
<th>Categoría</th>
|
||||
<th>Precio</th>
|
||||
<th>Stock</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tbody-productos">
|
||||
<?php foreach ($todos_productos as $prod):
|
||||
$sin_stock = ($prod['stock'] <= 0);
|
||||
$stock_clase = $sin_stock ? 'stock-zero' : ($prod['stock'] <= 5 ? 'stock-warn' : 'stock-ok');
|
||||
?>
|
||||
<tr class="prod-row <?php echo $sin_stock ? 'sin-stock' : ''; ?>"
|
||||
data-cat="<?php echo htmlspecialchars($prod['categoria']); ?>">
|
||||
<td>
|
||||
<img src="../<?php echo htmlspecialchars($prod['imagen']); ?>"
|
||||
class="img-preview" alt="<?php echo htmlspecialchars($prod['nombre']); ?>">
|
||||
</td>
|
||||
<td style="color:#9ca3af;">#<?php echo $prod['id']; ?></td>
|
||||
<td><strong><?php echo htmlspecialchars($prod['nombre']); ?></strong></td>
|
||||
<td><span class="cat-chip"><?php echo htmlspecialchars($prod['categoria']); ?></span></td>
|
||||
<td>$<?php echo number_format($prod['precio'], 2, ',', '.'); ?></td>
|
||||
<td class="<?php echo $stock_clase; ?>">
|
||||
<?php if ($sin_stock): ?>
|
||||
<i class="fa-solid fa-triangle-exclamation"></i> Sin stock
|
||||
<?php elseif ($prod['stock'] <= 5): ?>
|
||||
<i class="fa-solid fa-circle-exclamation"></i> <?php echo $prod['stock']; ?> un.
|
||||
<?php else: ?>
|
||||
<?php echo $prod['stock']; ?> un.
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="no-results" class="no-results" style="display:none;">
|
||||
<i class="fa-solid fa-box-open"></i>
|
||||
No hay productos en esta categoría.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function filtrarCat(btn, cat) {
|
||||
// Actualizar botón activo
|
||||
document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
const filas = document.querySelectorAll('.prod-row');
|
||||
let visible = 0;
|
||||
|
||||
filas.forEach(fila => {
|
||||
const mostrar = cat === 'todos' || fila.dataset.cat === cat;
|
||||
fila.style.display = mostrar ? '' : 'none';
|
||||
if (mostrar) visible++;
|
||||
});
|
||||
|
||||
document.getElementById('cat-count-num').textContent = visible;
|
||||
document.getElementById('no-results').style.display = visible === 0 ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once("../db.php");
|
||||
|
||||
if (!isset($_SESSION['rol']) || $_SESSION['rol'] !== 'admin') {
|
||||
header("Location: ../login.php?error=acceso_denegado");
|
||||
exit();
|
||||
}
|
||||
|
||||
date_default_timezone_set('America/Argentina/Buenos_Aires');
|
||||
|
||||
$mensaje_alerta = "";
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['menu_del_dia'])) {
|
||||
$menu_del_dia = mysqli_real_escape_string($conexion, $_POST['menu_del_dia']);
|
||||
$precio_promo = floatval($_POST['precio_promo']);
|
||||
$stock_promo = intval($_POST['stock_promo']);
|
||||
|
||||
if (!empty($menu_del_dia)) {
|
||||
mysqli_begin_transaction($conexion);
|
||||
try {
|
||||
$ruta_imagen_db = "";
|
||||
if (isset($_FILES['imagen_promo']) && $_FILES['imagen_promo']['error'] === UPLOAD_ERR_OK) {
|
||||
$file_tmp = $_FILES['imagen_promo']['tmp_name'];
|
||||
$file_name = $_FILES['imagen_promo']['name'];
|
||||
$ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
|
||||
|
||||
$extensiones_validas = array("jpg", "jpeg", "png", "webp");
|
||||
if (in_array($ext, $extensiones_validas)) {
|
||||
$nombre_unico = "promo_" . time() . "." . $ext;
|
||||
$directorio_destino = "../imagenes/";
|
||||
|
||||
if (move_uploaded_file($file_tmp, $directorio_destino . $nombre_unico)) {
|
||||
$ruta_imagen_db = "imagenes/" . $nombre_unico;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$query_notif = "INSERT INTO promociones (mensaje, fecha_publicacion) VALUES ('$menu_del_dia', NOW())";
|
||||
mysqli_query($conexion, $query_notif);
|
||||
|
||||
if (!empty($ruta_imagen_db)) {
|
||||
$query_prod = "UPDATE productos SET precio = $precio_promo, stock = $stock_promo, imagen = '$ruta_imagen_db', activo = 1 WHERE id = 999";
|
||||
} else {
|
||||
$query_prod = "UPDATE productos SET precio = $precio_promo, stock = $stock_promo, activo = 1 WHERE id = 999";
|
||||
}
|
||||
mysqli_query($conexion, $query_prod);
|
||||
|
||||
mysqli_commit($conexion);
|
||||
$mensaje_alerta = "<div class='alert success'><i class='fa-solid fa-circle-check'></i> ¡Promoción lanzada con éxito! Cambios aplicados en vivo.</div>";
|
||||
} catch (Exception $e) {
|
||||
mysqli_rollback($conexion);
|
||||
$mensaje_alerta = "<div class='alert error'><i class='fa-solid fa-circle-xmark'></i> Error al procesar la promoción: " . $e->getMessage() . "</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Borrar una promoción vieja
|
||||
if (isset($_GET['eliminar'])) {
|
||||
$id_eliminar = (int)$_GET['eliminar'];
|
||||
mysqli_query($conexion, "DELETE FROM promociones WHERE id = $id_eliminar");
|
||||
header("Location: promo.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Traemos las últimas 5 promos enviadas
|
||||
$historial = mysqli_query($conexion, "SELECT * FROM promociones ORDER BY id DESC LIMIT 5");
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Panel La Pecosa - Promociones</title>
|
||||
<link rel="stylesheet" href="../css/all.min.css">
|
||||
<link rel="stylesheet" href="admin_style.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', sans-serif; }
|
||||
body { background-color: #f4f6f9; min-height: 100vh; display: flex; }
|
||||
|
||||
.main-wrapper {
|
||||
flex: 1;
|
||||
margin-left: 260px;
|
||||
padding: 40px;
|
||||
width: calc(100% - 260px);
|
||||
}
|
||||
|
||||
.header-title { display: flex; align-items: center; gap: 15px; margin-bottom: 30px; }
|
||||
.header-title h2 { font-size: 1.8rem; color: #1a202c; font-weight: 700; }
|
||||
|
||||
.card { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 4px 20px rgba(0,0,0,0.02); margin-bottom: 30px; border: 1px solid #edf2f7; }
|
||||
.card h3 { color: #2d3748; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; font-size: 1.2rem; font-weight: 600; padding-bottom: 10px; border-bottom: 1px solid #edf2f7; }
|
||||
|
||||
.form-grid { display: grid; grid-template-columns: 1.5fr 1fr; gap: 25px; }
|
||||
@media (max-width: 992px) { .form-grid { grid-template-columns: 1fr; } }
|
||||
|
||||
.form-left { display: flex; flex-direction: column; gap: 15px; }
|
||||
|
||||
.group-input { display: flex; flex-direction: column; gap: 6px; }
|
||||
.group-input label { font-size: 0.9rem; font-weight: 600; color: #4a5568; }
|
||||
|
||||
textarea { width: 100%; padding: 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.95rem; resize: none; outline: none; transition: 0.2s; background: #f8fafc; }
|
||||
textarea:focus { border-color: #e02828; background: white; box-shadow: 0 0 0 3px rgba(224, 40, 40, 0.08); }
|
||||
|
||||
.inputs-inline { display: flex; gap: 15px; }
|
||||
.inputs-inline .group-input { flex: 1; }
|
||||
|
||||
.group-input input[type="number"] { padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.95rem; outline: none; transition: 0.2s; background: #f8fafc; }
|
||||
.group-input input[type="number"]:focus { border-color: #e02828; background: white; }
|
||||
|
||||
.image-upload-wrapper { border: 2px dashed #cbd5e1; padding: 20px; border-radius: 10px; text-align: center; background: #f8fafc; transition: 0.2s; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; min-height: 180px; height: 100%; cursor: pointer; position: relative; }
|
||||
.image-upload-wrapper:hover { border-color: #e02828; background: #fff5f5; }
|
||||
.image-upload-wrapper i { font-size: 2.2rem; color: #a0aec0; }
|
||||
.image-upload-wrapper p { font-size: 0.85rem; color: #718096; }
|
||||
.image-upload-wrapper input[type="file"] { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
|
||||
|
||||
#preview-container { display: none; width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: white; border-radius: 10px; overflow: hidden; }
|
||||
#preview-container img { width: 100%; height: 100%; object-fit: cover; }
|
||||
#remove-preview-btn { position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.7); color: white; border: none; padding: 5px 10px; border-radius: 5px; cursor: pointer; font-size: 0.75rem; z-index: 10; }
|
||||
#remove-preview-btn:hover { background: #e02828; }
|
||||
|
||||
.footer-form { margin-top: 15px; display: flex; justify-content: flex-end; }
|
||||
.btn-lanzar { background: #e02828; color: white; border: none; padding: 12px 25px; border-radius: 50px; font-weight: 700; font-size: 0.95rem; cursor: pointer; transition: 0.2s; display: inline-flex; align-items: center; gap: 8px; }
|
||||
.btn-lanzar:hover { background: #c02020; transform: translateY(-1px); }
|
||||
|
||||
.alert { padding: 15px; border-radius: 8px; margin-bottom: 20px; font-weight: 600; display: flex; align-items: center; gap: 10px; font-size: 0.95rem; }
|
||||
.alert.success { background-color: #f0fdf4; color: #16a34a; border-left: 4px solid #16a34a; }
|
||||
.alert.error { background-color: #fef2f2; color: #dc2626; border-left: 4px solid #dc2626; }
|
||||
|
||||
.table-container { width: 100%; overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; text-align: left; font-size: 0.95rem; }
|
||||
th { background-color: #f8fafc; color: #718096; font-weight: 600; padding: 12px 15px; border-bottom: 2px solid #e2e8f0; font-size: 0.85rem; }
|
||||
td { padding: 12px 15px; border-bottom: 1px solid #edf2f7; color: #2d3748; }
|
||||
tr:hover td { background-color: #f8fafc; }
|
||||
.btn-delete { color: #a0aec0; text-decoration: none; font-size: 1.05rem; transition: 0.2s; }
|
||||
.btn-delete:hover { color: #e53e3e; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php include("sidebar.php"); ?>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="header-title">
|
||||
<i class="fa-solid fa-bullhorn" style="color: #e02828; font-size: 1.6rem;"></i>
|
||||
<h2>Gestión de promociones y menú del día</h2>
|
||||
</div>
|
||||
|
||||
<?php echo $mensaje_alerta; ?>
|
||||
|
||||
<div class="card">
|
||||
<h3><i class="fa-regular fa-paper-plane" style="color: #e02828;"></i> Lanzar nueva promoción en tiempo real</h3>
|
||||
|
||||
<form action="" method="POST" enctype="multipart/form-data">
|
||||
<div class="form-grid">
|
||||
|
||||
<div class="form-left">
|
||||
<div class="group-input">
|
||||
<label for="menu_del_dia">Mensaje promocional o descripción del plato</label>
|
||||
<textarea id="menu_del_dia" name="menu_del_dia" rows="4" required placeholder="Ej: ¡Promo imperdible! 2 Lomitos completos + papas fritas medianas 🍔🍟"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="inputs-inline">
|
||||
<div class="group-input">
|
||||
<label for="precio_promo">Precio final ($)</label>
|
||||
<input type="number" id="precio_promo" name="precio_promo" min="0" step="50" required placeholder="Ej: 8500">
|
||||
</div>
|
||||
<div class="group-input">
|
||||
<label for="stock_promo">Stock inicial</label>
|
||||
<input type="number" id="stock_promo" name="stock_promo" min="1" required placeholder="Ej: 20">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-right">
|
||||
<div class="group-input" style="height: 100%;">
|
||||
<label>Imagen publicitaria de la promo</label>
|
||||
<div class="image-upload-wrapper">
|
||||
<i class="fa-solid fa-cloud-arrow-up"></i>
|
||||
<p><strong>Hacé clic acá</strong> para cargar foto</p>
|
||||
<span style="font-size: 0.75rem; color: #a0aec0;">PNG, JPG o WEBP</span>
|
||||
<input type="file" id="imagen_promo" name="imagen_promo" accept="image/*">
|
||||
|
||||
<div id="preview-container">
|
||||
<button type="button" id="remove-preview-btn">Cambiar Foto</button>
|
||||
<img id="image-preview" src="#" alt="Vista previa">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer-form">
|
||||
<button type="submit" class="btn-lanzar">
|
||||
<i class="fa-solid fa-rocket"></i> Publicar promoción
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3><i class="fa-solid fa-clock-rotate-left" style="color: #718096;"></i> Historial de notificaciones recientes</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%;">Fecha y Hora</th>
|
||||
<th>Mensaje enviado a los clientes</th>
|
||||
<th style="width: 10%; text-align: center;">Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if($historial && mysqli_num_rows($historial) > 0): ?>
|
||||
<?php while($row = mysqli_fetch_assoc($historial)): ?>
|
||||
<tr>
|
||||
<td style="color: #718096; font-weight: 500;">
|
||||
<?php
|
||||
$fecha = isset($row['fecha_publicacion']) ? $row['fecha_publicacion'] : 'now';
|
||||
echo date("d/m - H:i", strtotime($fecha));
|
||||
?> hs
|
||||
</td>
|
||||
<td style="font-weight: 500; color: #2d3748;"><?php echo htmlspecialchars($row['mensaje']); ?></td>
|
||||
<td style="text-align: center;">
|
||||
<a href="promo.php?eliminar=<?php echo $row['id']; ?>" class="btn-delete" title="Eliminar del historial" onclick="return confirm('¿Seguro que querés quitar esta promo del historial?')">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endwhile; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="3" style="text-align: center; color: #a0aec0; padding: 30px 0;">No se enviaron promociones todavía.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const fileInput = document.getElementById('imagen_promo');
|
||||
const previewContainer = document.getElementById('preview-container');
|
||||
const imagePreview = document.getElementById('image-preview');
|
||||
const removePreviewBtn = document.getElementById('remove-preview-btn');
|
||||
|
||||
fileInput.addEventListener('change', function() {
|
||||
const file = this.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
imagePreview.setAttribute('src', e.target.result);
|
||||
previewContainer.style.display = 'block';
|
||||
}
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
|
||||
removePreviewBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
fileInput.value = "";
|
||||
previewContainer.style.display = 'none';
|
||||
imagePreview.setAttribute('src', '#');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user