Initial commit: Proyecto Portal ePromos completo con documentación unificada y seguridad base
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user