Files
portal-epromos/admin/landing_builder.php

150 lines
6.1 KiB
PHP

<?php
session_start();
require_once __DIR__ . '/db.php';
if (empty($_SESSION['admin_id'])) { header('Location: ../index.php'); exit; }
$landing_id = (int)($_GET['landing_id'] ?? 0);
if ($landing_id <= 0) die('landing_id requerido');
$landing = $conn->query("SELECT * FROM landing_pages WHERE id={$landing_id}")->fetch_assoc();
if (!$landing) die('Landing no existe');
$blocks = $conn->query("SELECT id,nombre,tipo FROM landing_blocks ORDER BY nombre ASC")->fetch_all(MYSQLI_ASSOC);
// bloques ya asignados (por zona y posición)
$assigned = [];
$rs = $conn->query("SELECT lpb.id, lpb.block_id, lpb.zona, lpb.posicion, lb.nombre, lb.tipo
FROM landing_page_blocks lpb
JOIN landing_blocks lb ON lb.id=lpb.block_id
WHERE lpb.landing_id={$landing_id}
ORDER BY lpb.zona, lpb.posicion");
while($r=$rs->fetch_assoc()){ $assigned[$r['zona']][] = $r; }
?>
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Landing Builder (Drag & Drop)</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<style>
.zone{min-height:80px;border:2px dashed #ddd;border-radius:.5rem;padding:10px}
.chip{cursor:grab;padding:8px 10px;border:1px solid #ddd;border-radius:.5rem;background:#fff;margin-bottom:6px}
.palette{max-height:50vh;overflow:auto;border:1px solid #eee;border-radius:.5rem}
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">Admin</a>
<div class="d-flex gap-2">
<a class="btn btn-outline-light btn-sm" href="landing.php">Landings</a>
<a class="btn btn-light btn-sm" href="#">Builder</a>
<a class="btn btn-outline-light btn-sm" href="landing_page_blocks.php?landing_id=<?= (int)$landing_id ?>">Lista</a>
<a class="btn btn-outline-light btn-sm" href="../index.php?action=logout">Salir</a>
</div>
</div>
</nav>
<div class="container py-4">
<div class="mb-3"><a class="btn btn-secondary btn-sm" href="landing.php">← Volver a Landings</a></div>
<h4 class="mb-3">Builder: <strong><?= htmlspecialchars($landing['titulo']) ?></strong></h4>
<div class="row g-3">
<div class="col-md-4">
<div class="card palette">
<div class="card-body">
<h5>Bloques disponibles</h5>
<div id="palette" class="zone">
<?php foreach($blocks as $b): ?>
<div class="chip" data-block-id="<?= (int)$b['id'] ?>">
[<?= htmlspecialchars($b['tipo']) ?>] <?= htmlspecialchars($b['nombre']) ?>
</div>
<?php endforeach; ?>
</div>
<p class="text-muted small mt-2">Arrastra un bloque a una zona.</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card mb-3"><div class="card-body">
<h6 class="mb-2">Header</h6>
<div id="zone-header" class="zone"><?php
if(!empty($assigned['header'])) foreach($assigned['header'] as $item){
echo '<div class="chip" data-assign-id="'.(int)$item['id'].'" data-block-id="'.(int)$item['block_id'].'">['.htmlspecialchars($item['tipo']).'] '.htmlspecialchars($item['nombre']).'</div>';
}
?></div>
</div></div>
<div class="card mb-3"><div class="card-body">
<h6 class="mb-2">Main</h6>
<div id="zone-main" class="zone"><?php
if(!empty($assigned['main'])) foreach($assigned['main'] as $item){
echo '<div class="chip" data-assign-id="'.(int)$item['id'].'" data-block-id="'.(int)$item['block_id'].'">['.htmlspecialchars($item['tipo']).'] '.htmlspecialchars($item['nombre']).'</div>';
}
?></div>
</div></div>
<div class="card mb-3"><div class="card-body">
<h6 class="mb-2">Footer</h6>
<div id="zone-footer" class="zone"><?php
if(!empty($assigned['footer'])) foreach($assigned['footer'] as $item){
echo '<div class="chip" data-assign-id="'.(int)$item['id'].'" data-block-id="'.(int)$item['block_id'].'">['.htmlspecialchars($item['tipo']).'] '.htmlspecialchars($item['nombre']).'</div>';
}
?></div>
</div></div>
<button id="save" class="btn btn-primary">Guardar distribución</button>
<div id="msg" class="mt-2"></div>
</div>
</div>
</div>
<script>
function mkSortable(el){ return new Sortable(el, { group:'zones', animation:150, sort:true, pull:true, put:true }); }
['palette','zone-header','zone-main','zone-footer'].forEach(id => mkSortable(document.getElementById(id)));
document.getElementById('save').addEventListener('click', function(){
const landing_id = <?= (int)$landing_id ?>;
const zones = ['header','main','footer'];
const payload = { landing_id, items: [] };
zones.forEach(z => {
const zoneEl = document.getElementById('zone-'+z);
Array.from(zoneEl.children).forEach((chip, idx) => {
payload.items.push({
assign_id: chip.dataset.assignId || null, // si ya existía
block_id: chip.dataset.blockId,
zona: z,
posicion: idx + 1
});
});
});
fetch('landing_builder_save.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify(payload)
})
.then(r=>r.json())
.then(res=>{
document.getElementById('msg').innerHTML =
res.ok ? '<div class="alert alert-success py-2">Guardado.</div>' :
'<div class="alert alert-danger py-2">'+(res.error||'Error')+'</div>';
// actualizar assign_id devueltos para chips nuevos
if(res.map_ids){
const map = res.map_ids;
document.querySelectorAll('.chip').forEach(ch=>{
const bid = ch.dataset.blockId;
const key = ch.dataset.assignId ? null : (bid + '@' + ch.parentElement.id.replace('zone-',''));
if(key && map[key]) ch.dataset.assignId = map[key];
});
}
})
.catch(()=>{ document.getElementById('msg').innerHTML='<div class="alert alert-danger py-2">Error de red</div>'; });
});
</script>
</body>
</html>