150 lines
6.1 KiB
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>
|