1705 lines
71 KiB
Plaintext
1705 lines
71 KiB
Plaintext
<!DOCTYPE html>
|
|
<!--
|
|
GKACHELE™ SaaS PageBuilder
|
|
© 2025 GKACHELE. Todos los derechos reservados.
|
|
Desarrollado desde noviembre 2024 por GKACHELE
|
|
Código propiedad de GKACHELE - Prohibida su reproducción sin autorización
|
|
-->
|
|
<html lang="es">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Personalizar - {{ site_name or 'Sitio' }}</title>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.15.0/Sortable.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--gk-admin-bar: #0f172a;
|
|
--gk-sidebar-bg: #1e293b;
|
|
--gk-item-bg: #334155;
|
|
--gk-white: #ffffff;
|
|
--gk-border: #334155;
|
|
--gk-text: #f8fafc;
|
|
--gk-text-muted: #94a3b8;
|
|
--gk-accent: #38bdf8;
|
|
--gk-accent-hover: #7dd3fc;
|
|
--gk-red: #ef4444;
|
|
--transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Outfit', 'Inter', sans-serif;
|
|
color: var(--gk-text);
|
|
background: #0f172a;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.gk-full-overlay {
|
|
display: flex;
|
|
height: 100vh;
|
|
}
|
|
|
|
.gk-sidebar {
|
|
width: 340px;
|
|
background: var(--gk-sidebar-bg);
|
|
border-right: 1px solid var(--gk-border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
z-index: 100;
|
|
box-shadow: 10px 0 30px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.gk-header {
|
|
height: 60px;
|
|
background: var(--gk-admin-bar);
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 20px;
|
|
border-bottom: 1px solid var(--gk-border);
|
|
}
|
|
|
|
.gk-logo {
|
|
font-weight: 800;
|
|
font-size: 18px;
|
|
letter-spacing: -0.5px;
|
|
color: var(--gk-accent);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.customize-pane-child {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.accordion-section {
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.accordion-section-title {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 16px 20px;
|
|
cursor: pointer;
|
|
background: var(--gk-sidebar-bg);
|
|
transition: all var(--transition);
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
color: var(--gk-text-muted);
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.accordion-section-title i {
|
|
width: 20px;
|
|
text-align: center;
|
|
margin-right: 12px;
|
|
color: var(--gk-accent);
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.accordion-section-title:hover {
|
|
background: var(--gk-item-bg);
|
|
color: var(--gk-white);
|
|
}
|
|
|
|
.accordion-section-title.open {
|
|
border-left-color: var(--gk-accent);
|
|
background: var(--gk-item-bg);
|
|
color: var(--gk-white);
|
|
}
|
|
|
|
.accordion-section-title:after {
|
|
content: '\f054';
|
|
font-family: 'Font Awesome 6 Free';
|
|
font-weight: 900;
|
|
font-size: 10px;
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.accordion-section-title.open:after {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.accordion-section-content {
|
|
display: none;
|
|
padding: 20px;
|
|
background: #0f172a;
|
|
border-bottom: 1px solid var(--gk-border);
|
|
}
|
|
|
|
.accordion-section-content.visible {
|
|
display: block;
|
|
animation: slideDown 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes slideDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* INPUTS PREMIUM */
|
|
.customize-control {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.customize-control-title {
|
|
display: block;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: var(--gk-text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
input[type="text"],
|
|
input[type="url"],
|
|
input[type="email"],
|
|
input[type="number"],
|
|
input[type="tel"],
|
|
textarea,
|
|
select {
|
|
width: 100%;
|
|
padding: 10px 14px;
|
|
background: #1e293b;
|
|
border: 1px solid #334155;
|
|
border-radius: 8px;
|
|
color: var(--gk-white);
|
|
font-size: 13px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
input:focus,
|
|
textarea:focus,
|
|
select:focus {
|
|
border-color: var(--gk-accent);
|
|
background: #0f172a;
|
|
outline: none;
|
|
box-shadow: 0 0 0 2px rgba(56, 189, 248, 0.2);
|
|
}
|
|
|
|
/* BUTTONS PREMIUM */
|
|
.button {
|
|
padding: 10px 16px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
border: none;
|
|
}
|
|
|
|
.button-primary {
|
|
background: var(--gk-accent);
|
|
color: #0f172a;
|
|
}
|
|
|
|
.button-primary:hover {
|
|
background: var(--gk-accent-hover);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(56, 189, 248, 0.3);
|
|
}
|
|
|
|
.button-secondary {
|
|
background: transparent;
|
|
border: 1px solid #334155;
|
|
color: var(--gk-text-muted);
|
|
}
|
|
|
|
.button-secondary:hover {
|
|
border-color: var(--gk-accent);
|
|
color: var(--gk-white);
|
|
}
|
|
|
|
/* FOOTER ACTIONS */
|
|
.gk-full-overlay-footer {
|
|
height: 70px;
|
|
background: var(--gk-admin-bar);
|
|
border-top: 1px solid var(--gk-border);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 20px;
|
|
}
|
|
|
|
/* IMMERSIVE APPLE-STYLE STUDIO */
|
|
.gk-full-overlay-main {
|
|
flex: 1;
|
|
background: #020617;
|
|
/* Deep base */
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
overflow: hidden;
|
|
padding: 40px;
|
|
}
|
|
|
|
.preview-iframe-wrapper {
|
|
background: #fff;
|
|
border-radius: 12px;
|
|
box-shadow: 0 50px 100px -20px rgba(0, 0, 0, 0.8);
|
|
overflow: hidden;
|
|
transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1);
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 1200px;
|
|
height: 100%;
|
|
min-width: 0;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Scaled Container (Inner) */
|
|
.preview-iframe-wrapper iframe {
|
|
flex: 1;
|
|
width: 100%;
|
|
height: 100%;
|
|
border: 0;
|
|
background: #fff;
|
|
}
|
|
|
|
/* Modern Browser Frame (Overlay) */
|
|
.preview-iframe-wrapper::before {
|
|
content: '';
|
|
display: block;
|
|
height: 32px;
|
|
background: #e2e8f0;
|
|
border-bottom: 1px solid #cbd5e1;
|
|
position: relative;
|
|
background-image: radial-gradient(circle, #ff5f56 6px, transparent 7px),
|
|
radial-gradient(circle, #ffbd2e 6px, transparent 7px),
|
|
radial-gradient(circle, #27c93f 6px, transparent 7px);
|
|
background-size: 18px 18px, 18px 18px, 18px 18px;
|
|
background-position: 12px center, 32px center, 52px center;
|
|
background-repeat: no-repeat;
|
|
z-index: 10;
|
|
}
|
|
|
|
.gk-sidebar {
|
|
width: 320px;
|
|
background: rgba(30, 41, 59, 0.9);
|
|
/* High clarity glass */
|
|
backdrop-filter: blur(30px);
|
|
-webkit-backdrop-filter: blur(30px);
|
|
border-right: 1px solid rgba(255, 255, 255, 0.05);
|
|
display: flex;
|
|
flex-direction: column;
|
|
z-index: 100;
|
|
box-shadow: 10px 0 30px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
/* RESPONSIVE TOGGLES (Bottom left) */
|
|
.devices-wrapper {
|
|
position: absolute;
|
|
bottom: 12px;
|
|
left: 20px;
|
|
display: flex;
|
|
gap: 15px;
|
|
z-index: 20;
|
|
}
|
|
|
|
.device-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #a7aaad;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
padding: 0;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.device-btn:hover,
|
|
.device-btn.active {
|
|
color: #1d2327;
|
|
}
|
|
|
|
/* COLOR PICKER */
|
|
.color-picker-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.color-picker-wrapper input[type="color"] {
|
|
width: 40px;
|
|
height: 30px;
|
|
padding: 0 2px;
|
|
border: 1px solid #8c8f94;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* BLOCKS LIST */
|
|
.blocks-list-item {
|
|
background: #fff;
|
|
border: 1px solid #dcdcde;
|
|
border-radius: 4px;
|
|
margin-bottom: 8px;
|
|
padding: 8px 12px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
cursor: move;
|
|
/* Future DnD */
|
|
}
|
|
|
|
.blocks-list-item:hover {
|
|
border-color: #8c8f94;
|
|
}
|
|
|
|
.block-title {
|
|
font-weight: 500;
|
|
color: #1d2327;
|
|
}
|
|
|
|
.block-actions {
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.notification-toast {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(40, 40, 40, 0.9);
|
|
color: white;
|
|
padding: 20px 40px;
|
|
border-radius: 5px;
|
|
font-size: 16px;
|
|
z-index: 9999;
|
|
display: none;
|
|
}
|
|
|
|
/* MODAL PARA GESTIONAR PLATOS */
|
|
.modal-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
z-index: 10000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal-overlay.active {
|
|
display: flex;
|
|
}
|
|
|
|
.modal-content {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
padding: 30px;
|
|
max-width: 600px;
|
|
width: 90%;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid var(--wp-border);
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
color: var(--gk-text);
|
|
}
|
|
|
|
.modal-close {
|
|
background: none;
|
|
border: none;
|
|
font-size: 24px;
|
|
color: #787c82;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
width: 30px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal-close:hover {
|
|
color: var(--wp-red);
|
|
}
|
|
|
|
.menu-item-form {
|
|
background: #f6f7f7;
|
|
padding: 15px;
|
|
border-radius: 4px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.menu-item-form input,
|
|
.menu-item-form textarea {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.menu-item-list {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.menu-item-card {
|
|
background: #fff;
|
|
border: 1px solid var(--wp-border);
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-bottom: 10px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.menu-item-info h4 {
|
|
margin: 0 0 5px 0;
|
|
font-size: 16px;
|
|
color: var(--gk-text);
|
|
}
|
|
|
|
.menu-item-info p {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
color: #646970;
|
|
}
|
|
|
|
.menu-item-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* MODAL INLINE EDIT */
|
|
.modal-body {
|
|
max-width: 500px;
|
|
}
|
|
|
|
/* ESTILOS WORDPRESS PARA ELEMENTOS EDITABLES EN PREVIEW */
|
|
.gk-editable {
|
|
position: relative;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.gk-editable:hover {
|
|
outline: 2px dashed #2271b1;
|
|
outline-offset: 2px;
|
|
background: rgba(34, 113, 177, 0.05);
|
|
}
|
|
|
|
.gk-editable::before {
|
|
content: '✏️';
|
|
position: absolute;
|
|
top: -8px;
|
|
right: -8px;
|
|
background: #2271b1;
|
|
color: white;
|
|
font-size: 12px;
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
z-index: 1000;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.gk-editable:hover::before {
|
|
opacity: 1;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="gk-full-overlay">
|
|
<!-- SIDEBAR CONTROLS -->
|
|
<div class="gk-sidebar">
|
|
<div class="gk-header">
|
|
<div class="gk-logo">GKACHELE™</div>
|
|
<div style="font-size: 12px; font-weight: 500;">{{ user_plan|upper }}</div>
|
|
<a href="/dashboard" class="close-btn" style="color: #fff; text-decoration: none;"><i
|
|
class="fa-solid fa-xmark"></i></a>
|
|
</div>
|
|
|
|
<div class="customize-pane-child">
|
|
|
|
|
|
<!-- IDENTIDAD DEL SITIO -->
|
|
<!-- IDENTIDAD DEL SITIO -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-id-card" style="margin-right:8px; color:#555;"></i> Identidad del
|
|
Sitio</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Nombre del Sitio</span>
|
|
<input type="text" id="site_name" value="{{ content.site_name or '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Título Hero</span>
|
|
<input type="text" id="hero_title" value="{{ content.hero_title or '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Descripción Corta</span>
|
|
<textarea id="hero_description"
|
|
oninput="updatePreview()">{{ content.hero_description or '' }}</textarea>
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Logo URL</span>
|
|
<input type="url" id="media_logo" value="{{ content.media.logo if content.media else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- COLORES PREMIUM -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-palette" style="margin-right:8px; color:#555;"></i> Paleta de
|
|
Colores</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="color-grid" style="display: flex; flex-direction: column; gap: 15px;">
|
|
<div class="color-row"
|
|
style="display: flex; align-items: center; justify-content: space-between; background: rgba(255,255,255,0.03); padding: 12px; border-radius: 12px;">
|
|
<div>
|
|
<span class="customize-control-title" style="margin-bottom:0">Primario</span>
|
|
<small style="font-size: 10px; color: var(--gk-text-muted);">Botones y
|
|
acentos</small>
|
|
</div>
|
|
<div class="color-picker-wrapper">
|
|
<input type="color" id="color_primary"
|
|
value="{{ content.colors.primary if content.colors else '#2271b1' }}"
|
|
oninput="syncColor('color_primary')">
|
|
<input type="text" id="val_color_primary"
|
|
value="{{ content.colors.primary if content.colors else '#2271b1' }}"
|
|
oninput="syncTextLimit('color_primary')"
|
|
style="width: 70px; border:none; background: transparent; font-family: monospace; text-align: right;">
|
|
</div>
|
|
</div>
|
|
<div class="color-row"
|
|
style="display: flex; align-items: center; justify-content: space-between; background: rgba(255,255,255,0.03); padding: 12px; border-radius: 12px;">
|
|
<div>
|
|
<span class="customize-control-title" style="margin-bottom:0">Secundario</span>
|
|
<small style="font-size: 10px; color: var(--gk-text-muted);">Fondos suaves</small>
|
|
</div>
|
|
<div class="color-picker-wrapper">
|
|
<input type="color" id="color_secondary"
|
|
value="{{ content.colors.secondary if content.colors else '#f0f0f1' }}"
|
|
oninput="syncColor('color_secondary')">
|
|
<input type="text" id="val_color_secondary"
|
|
value="{{ content.colors.secondary if content.colors else '#f0f0f1' }}"
|
|
oninput="syncTextLimit('color_secondary')"
|
|
style="width: 70px; border:none; background: transparent; font-family: monospace; text-align: right;">
|
|
</div>
|
|
</div>
|
|
<div class="color-row"
|
|
style="display: flex; align-items: center; justify-content: space-between; background: rgba(255,255,255,0.03); padding: 12px; border-radius: 12px;">
|
|
<div>
|
|
<span class="customize-control-title" style="margin-bottom:0">Texto</span>
|
|
<small style="font-size: 10px; color: var(--gk-text-muted);">Cuerpo y
|
|
títulos</small>
|
|
</div>
|
|
<div class="color-picker-wrapper">
|
|
<input type="color" id="color_text"
|
|
value="{{ content.colors.text if content.colors else '#2c3338' }}"
|
|
oninput="syncColor('color_text')">
|
|
<input type="text" id="val_color_text"
|
|
value="{{ content.colors.text if content.colors else '#2c3338' }}"
|
|
oninput="syncTextLimit('color_text')"
|
|
style="width: 70px; border:none; background: transparent; font-family: monospace; text-align: right;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DATOS DE CONTACTO (WhatsApp, Mapas) -->
|
|
<!-- DATOS DE CONTACTO (WhatsApp, Mapas) -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-location-dot" style="margin-right:8px; color:#555;"></i> Contacto &
|
|
Ubicación</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Dirección</span>
|
|
<input type="text" id="contacto_direccion"
|
|
value="{{ content.direccion if content.direccion else '' }}" oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Teléfono</span>
|
|
<input type="tel" id="contacto_telefono"
|
|
value="{{ content.telefono if content.telefono else '' }}" oninput="updatePreview()">
|
|
</div>
|
|
<!-- NUEVO: WhatsApp -->
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">WhatsApp (Solo números)</span>
|
|
<input type="tel" id="redes_whatsapp" placeholder="34600000000"
|
|
value="{{ content.redes_sociales.whatsapp if content.redes_sociales else '' }}"
|
|
oninput="updatePreview()">
|
|
<span class="customize-control-description">Añadirá un botón flotante si se rellena.</span>
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Email</span>
|
|
<input type="email" id="contacto_email" value="{{ content.email if content.email else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- REDES SOCIALES -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-share-nodes" style="margin-right:8px; color:#555;"></i> Redes
|
|
Sociales</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Instagram URL</span>
|
|
<input type="url" id="redes_instagram"
|
|
value="{{ content.redes_sociales.instagram if content.redes_sociales else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Facebook URL</span>
|
|
<input type="url" id="redes_facebook"
|
|
value="{{ content.redes_sociales.facebook if content.redes_sociales else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">TikTok URL</span>
|
|
<input type="url" id="redes_tiktok"
|
|
value="{{ content.redes_sociales.tiktok if content.redes_sociales else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">LinkedIn URL</span>
|
|
<input type="url" id="redes_linkedin"
|
|
value="{{ content.redes_sociales.linkedin if content.redes_sociales else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">YouTube URL</span>
|
|
<input type="url" id="redes_youtube"
|
|
value="{{ content.redes_sociales.youtube if content.redes_sociales else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HORARIOS -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-clock" style="margin-right:8px; color:#555;"></i> Horarios de
|
|
Atención</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Lunes - Viernes</span>
|
|
<input type="text" id="horarios_lunes_viernes"
|
|
value="{{ content.horarios.lunes_viernes if content.horarios else '12:00 PM - 10:00 PM' }}"
|
|
placeholder="12:00 PM - 10:00 PM" oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Sábados</span>
|
|
<input type="text" id="horarios_sabados"
|
|
value="{{ content.horarios.sabados if content.horarios else '12:00 PM - 11:00 PM' }}"
|
|
placeholder="12:00 PM - 11:00 PM" oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Domingos</span>
|
|
<input type="text" id="horarios_domingos"
|
|
value="{{ content.horarios.domingos if content.horarios else '12:00 PM - 9:00 PM' }}"
|
|
placeholder="12:00 PM - 9:00 PM" oninput="updatePreview()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ESPECIALIDAD CULINARIA -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-star" style="margin-right:8px; color:#555;"></i> Especialidad
|
|
Culinaria</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Título</span>
|
|
<input type="text" id="especialidad_titulo"
|
|
value="{{ content.especialidad_culinaria.titulo if content.especialidad_culinaria else 'Nuestra Especialidad' }}"
|
|
placeholder="Nuestra Especialidad" oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Descripción</span>
|
|
<textarea id="especialidad_descripcion"
|
|
placeholder="Cada plato es una obra de arte culinaria..."
|
|
oninput="updatePreview()">{{ content.especialidad_culinaria.descripcion if content.especialidad_culinaria else 'Cada plato es una obra de arte culinaria, preparado con ingredientes frescos y técnicas tradicionales que honran la autenticidad de nuestros sabores.' }}</textarea>
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">URL de Imagen</span>
|
|
<input type="url" id="especialidad_imagen"
|
|
value="{{ content.especialidad_culinaria.imagen if content.especialidad_culinaria else '' }}"
|
|
placeholder="https://..." oninput="updatePreview()">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CAPACIDAD -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-users" style="margin-right:8px; color:#555;"></i> Capacidad</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Capacidad del Restaurante</span>
|
|
<input type="number" id="capacidad"
|
|
value="{{ content.capacidad if content.capacidad else '50' }}" placeholder="50" min="1"
|
|
oninput="updatePreview()">
|
|
<span class="customize-control-description">Número de personas que puede albergar el
|
|
restaurante.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GESTIÓN DE BLOQUES -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-cubes" style="margin-right:8px; color:#555;"></i> Bloques de
|
|
Contenido</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<button class="button button-secondary" style="width: 100%"
|
|
onclick="document.getElementById('add-block-panel').style.display='block'">+
|
|
Añadir
|
|
Bloque</button>
|
|
</div>
|
|
<div id="blocks_list_container">
|
|
<!-- JS populated list -->
|
|
</div>
|
|
|
|
<!-- Mini Panel para añadir (toggle) -->
|
|
<div id="add-block-panel"
|
|
style="display:none; border-top: 1px solid #ddd; margin-top: 10px; padding-top: 10px;">
|
|
<span class="customize-control-title">Nuevo Bloque</span>
|
|
<select id="block_type_selector" style="margin-bottom: 5px;">
|
|
<option value="texto">Texto</option>
|
|
<option value="imagen">Imagen</option>
|
|
<option value="video">Video</option>
|
|
<option value="mapa">Mapa</option>
|
|
</select>
|
|
<button class="button button-primary" onclick="addNewBlockClientSide()">Insertar</button>
|
|
<button class="button button-link"
|
|
style="color:red; border:none; background:none; cursor:pointer; font-size:12px;"
|
|
onclick="document.getElementById('add-block-panel').style.display='none'">Cancelar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MENÚ Y PLATOS -->
|
|
<div class="accordion-section">
|
|
<div class="accordion-section-title" onclick="toggleSection(this)">
|
|
<span><i class="fa-solid fa-utensils" style="margin-right:8px; color:#555;"></i> Menú
|
|
Restaurante</span>
|
|
</div>
|
|
<div class="accordion-section-content">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Enlace PDF Menú</span>
|
|
<input type="url" id="menu_url" value="{{ content.menu_url if content.menu_url else '' }}"
|
|
oninput="updatePreview()">
|
|
</div>
|
|
<div class="customize-control">
|
|
<button class="button button-secondary" onclick="gestionarPlatosMenu()">Gestionar
|
|
Platos</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- FOOTER: PUBLISH ACTIONS -->
|
|
<div class="wp-full-overlay-footer">
|
|
<div class="spinner" id="saving-spinner"></div>
|
|
<!-- Separate Save Logic -->
|
|
<button type="button" class="button button-secondary"
|
|
onclick="window.location.reload()">Descartar</button>
|
|
<button type="button" class="button button-primary" id="save-btn"
|
|
onclick="publishChanges()">Publicar</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PREVIEW AREA -->
|
|
<div class="wp-full-overlay-main">
|
|
<div class="preview-iframe-wrapper">
|
|
<iframe id="preview-iframe" src="/api/customizer/preview-frame/{{ site_id }}"></iframe>
|
|
</div>
|
|
|
|
<!-- DEVICE TOGGLES -->
|
|
<div class="devices-wrapper">
|
|
<button class="device-btn active" onclick="setDevice('desktop')" title="Escritorio"><i
|
|
class="fa-solid fa-desktop"></i></button>
|
|
<button class="device-btn" onclick="setDevice('tablet')" title="Tablet"><i
|
|
class="fa-solid fa-tablet-screen-button"></i></button>
|
|
<button class="device-btn" onclick="setDevice('mobile')" title="Móvil"><i
|
|
class="fa-solid fa-mobile-screen-button"></i></button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- NOTIFICATION TOAST -->
|
|
<div id="notification" class="notification-toast"></div>
|
|
|
|
<!-- MODAL GESTIÓN PLATOS -->
|
|
<div id="menu-modal" class="modal-overlay" onclick="if(event.target === this) closeMenuModal()">
|
|
<div class="modal-content" onclick="event.stopPropagation()">
|
|
<div class="modal-header">
|
|
<h3><i class="fa-solid fa-utensils" style="margin-right:8px;"></i>Gestionar Platos del Menú</h3>
|
|
<button class="modal-close" onclick="closeMenuModal()">×</button>
|
|
</div>
|
|
<div class="menu-item-form">
|
|
<span class="customize-control-title">Añadir/Editar Plato</span>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 8px;">
|
|
<input type="text" id="new-plato-nombre" placeholder="Nombre del plato">
|
|
<select id="new-plato-categoria">
|
|
<option value="Entrantes">Entrantes</option>
|
|
<option value="Principales">Principales</option>
|
|
<option value="Postres">Postres</option>
|
|
<option value="Bebidas">Bebidas</option>
|
|
</select>
|
|
</div>
|
|
<textarea id="new-plato-descripcion" placeholder="Descripción del plato (ingredientes, preparación...)"
|
|
style="margin-bottom: 8px; min-height: 60px;"></textarea>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 8px;">
|
|
<input type="text" id="new-plato-precio" placeholder="Precio (ej: 25.00)">
|
|
<div
|
|
style="display: flex; gap: 10px; align-items: center; border: 1px solid #ddd; padding: 5px; border-radius: 4px; background: white;">
|
|
<label style="font-size: 11px; display: flex; align-items: center; gap: 2px;"><input
|
|
type="checkbox" class="plato-tag-checkbox" value="vegano"> 🌿</label>
|
|
<label style="font-size: 11px; display: flex; align-items: center; gap: 2px;"><input
|
|
type="checkbox" class="plato-tag-checkbox" value="sin-gluten"> 🌾</label>
|
|
<label style="font-size: 11px; display: flex; align-items: center; gap: 2px;"><input
|
|
type="checkbox" class="plato-tag-checkbox" value="picante"> 🔥</label>
|
|
</div>
|
|
</div>
|
|
<button class="button button-primary" style="width: 100%;" onclick="addMenuPlato()">Guardar
|
|
Plato</button>
|
|
</div>
|
|
<div class="menu-item-list" id="menu-items-list">
|
|
<!-- JS populated -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MODAL EDICIÓN BLOQUES -->
|
|
<div id="block-edit-modal" class="modal-overlay" onclick="if(event.target === this) closeBlockEditModal()">
|
|
<div class="modal-content" onclick="event.stopPropagation()">
|
|
<div class="modal-header">
|
|
<h3><i class="fa-solid fa-pen-to-square" style="margin-right:8px;"></i>Editar Bloque</h3>
|
|
<button class="modal-close" onclick="closeBlockEditModal()">×</button>
|
|
</div>
|
|
<div class="modal-body" id="block-edit-form" style="padding: 20px;">
|
|
<!-- JS populated -->
|
|
</div>
|
|
<div style="display: flex; gap: 10px; justify-content: flex-end; padding: 0 20px 20px;">
|
|
<button class="button button-secondary" onclick="closeBlockEditModal()">Cancelar</button>
|
|
<button class="button button-primary" onclick="saveBlockEdit()">Guardar Cambios</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const siteId = {{ site_id }};
|
|
let currentTheme = '{{ theme }}';
|
|
|
|
// ==========================================
|
|
// UI INTERACTION
|
|
// ==========================================
|
|
|
|
function toggleSection(element) {
|
|
// Close others (Accordion behavior)
|
|
/*
|
|
document.querySelectorAll('.accordion-section-title').forEach(el => {
|
|
if(el !== element) {
|
|
el.classList.remove('open');
|
|
el.nextElementSibling.classList.remove('visible');
|
|
}
|
|
});
|
|
*/
|
|
// Toggle current
|
|
element.classList.toggle('open');
|
|
element.nextElementSibling.classList.toggle('visible');
|
|
}
|
|
|
|
function setDevice(device) {
|
|
const wrapper = document.querySelector('.preview-iframe-wrapper');
|
|
const btns = document.querySelectorAll('.device-btn');
|
|
const mainArea = document.querySelector('.gk-full-overlay-main');
|
|
|
|
if (!wrapper || !mainArea) return;
|
|
|
|
btns.forEach(b => b.classList.remove('active'));
|
|
if (device === 'desktop') btns[0] && btns[0].classList.add('active');
|
|
if (device === 'tablet') btns[1] && btns[1].classList.add('active');
|
|
if (device === 'mobile') btns[2] && btns[2].classList.add('active');
|
|
|
|
// Reset transitions and scales
|
|
wrapper.style.transition = 'all 0.4s cubic-bezier(0.16, 1, 0.3, 1)';
|
|
wrapper.style.transform = 'scale(1)';
|
|
|
|
const availableWidth = mainArea.clientWidth - 40;
|
|
const availableHeight = mainArea.clientHeight - 40;
|
|
|
|
if (device === 'desktop') {
|
|
const targetWidth = 1200; // Optimal desktop preview width
|
|
wrapper.style.width = targetWidth + 'px';
|
|
wrapper.style.minWidth = targetWidth + 'px';
|
|
wrapper.style.height = '90%'; // Fill most height
|
|
|
|
if (availableWidth < targetWidth) {
|
|
const scale = availableWidth / targetWidth;
|
|
wrapper.style.transform = `scale(${scale})`;
|
|
wrapper.style.transformOrigin = 'center center';
|
|
}
|
|
} else if (device === 'tablet') {
|
|
const targetWidth = 768;
|
|
wrapper.style.width = targetWidth + 'px';
|
|
wrapper.style.minWidth = targetWidth + 'px';
|
|
wrapper.style.height = '90%';
|
|
if (availableWidth < targetWidth) {
|
|
const scale = availableWidth / targetWidth;
|
|
wrapper.style.transform = `scale(${scale})`;
|
|
}
|
|
} else if (device === 'mobile') {
|
|
const targetWidth = 375;
|
|
wrapper.style.width = targetWidth + 'px';
|
|
wrapper.style.minWidth = targetWidth + 'px';
|
|
wrapper.style.height = '85%';
|
|
if (availableWidth < targetWidth) {
|
|
const scale = availableWidth / targetWidth;
|
|
wrapper.style.transform = `scale(${scale})`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize and listen
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
setTimeout(() => setDevice('desktop'), 500); // Wait for layout
|
|
});
|
|
|
|
window.addEventListener('resize', () => {
|
|
const activeBtn = document.querySelector('.device-btn.active');
|
|
if (activeBtn) {
|
|
const device = activeBtn.title === 'Escritorio' ? 'desktop' :
|
|
activeBtn.title === 'Tablet' ? 'tablet' : 'mobile';
|
|
setDevice(device);
|
|
}
|
|
});
|
|
|
|
function syncColor(id) {
|
|
const colorVal = document.getElementById(id).value;
|
|
document.getElementById('val_' + id).value = colorVal;
|
|
updatePreview();
|
|
}
|
|
|
|
function syncTextLimit(id) {
|
|
const textVal = document.getElementById('val_' + id).value;
|
|
document.getElementById(id).value = textVal;
|
|
updatePreview();
|
|
}
|
|
|
|
function showNotification(msg) {
|
|
const n = document.getElementById('notification');
|
|
n.innerText = msg;
|
|
n.style.display = 'block';
|
|
setTimeout(() => n.style.display = 'none', 2000);
|
|
}
|
|
|
|
// ==========================================
|
|
// DATA & PREVIEW LOGIC
|
|
// ==========================================
|
|
|
|
function getFormData() {
|
|
const direccion = document.getElementById('contacto_direccion').value;
|
|
let mapa_url = '';
|
|
|
|
// Generar mapa automáticamente desde dirección si no se ha pegado un iframe manualmente
|
|
if (direccion) {
|
|
// Limpiar dirección de caracteres extraños y codificarla
|
|
const cleanAddress = direccion.trim().replace(/\s+/g, ' ');
|
|
const encodedAddress = encodeURIComponent(cleanAddress);
|
|
mapa_url = `https://www.google.com/maps?q=${encodedAddress}&output=embed`;
|
|
}
|
|
|
|
return {
|
|
site_name: document.getElementById('site_name').value,
|
|
hero_title: document.getElementById('hero_title').value,
|
|
hero_description: document.getElementById('hero_description').value,
|
|
direccion: direccion,
|
|
telefono: document.getElementById('contacto_telefono').value,
|
|
email: document.getElementById('contacto_email').value,
|
|
capacidad: document.getElementById('capacidad').value || '50',
|
|
media: {
|
|
logo: document.getElementById('media_logo').value
|
|
},
|
|
colors: {
|
|
primary: document.getElementById('color_primary').value,
|
|
secondary: document.getElementById('color_secondary').value,
|
|
text: document.getElementById('color_text').value
|
|
},
|
|
redes_sociales: {
|
|
instagram: document.getElementById('redes_instagram').value,
|
|
facebook: document.getElementById('redes_facebook').value,
|
|
tiktok: document.getElementById('redes_tiktok').value,
|
|
linkedin: document.getElementById('redes_linkedin').value,
|
|
youtube: document.getElementById('redes_youtube').value,
|
|
whatsapp: document.getElementById('redes_whatsapp').value
|
|
},
|
|
horarios: {
|
|
lunes_viernes: document.getElementById('horarios_lunes_viernes').value,
|
|
sabados: document.getElementById('horarios_sabados').value,
|
|
domingos: document.getElementById('horarios_domingos').value
|
|
},
|
|
especialidad_culinaria: {
|
|
titulo: document.getElementById('especialidad_titulo').value,
|
|
descripcion: document.getElementById('especialidad_descripcion').value,
|
|
imagen: document.getElementById('especialidad_imagen').value
|
|
},
|
|
mapa_url: mapa_url,
|
|
blocks: window.currentBlocks || [],
|
|
menu_url: document.getElementById('menu_url').value,
|
|
menu_items: window.currentMenuItems || {}
|
|
};
|
|
}
|
|
|
|
function updatePreview() {
|
|
const data = getFormData();
|
|
const iframe = document.getElementById('preview-iframe');
|
|
|
|
// Send data to iframe (postMessage) without saving to DB
|
|
iframe.contentWindow.postMessage({
|
|
type: 'update-content',
|
|
content: data
|
|
}, '*');
|
|
|
|
// Change Save button state to indicate unsaved changes (Visual cue)
|
|
const saveBtn = document.getElementById('save-btn');
|
|
saveBtn.innerText = 'Publicar *';
|
|
}
|
|
|
|
function publishChanges() {
|
|
const saveBtn = document.getElementById('save-btn');
|
|
const spinner = document.getElementById('saving-spinner');
|
|
|
|
saveBtn.disabled = true;
|
|
saveBtn.innerText = 'Publicando...';
|
|
spinner.style.display = 'block';
|
|
|
|
const data = {
|
|
site_id: siteId,
|
|
content: getFormData(),
|
|
blocks: window.currentBlocks
|
|
};
|
|
|
|
fetch('/api/customizer/save', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(r => r.json())
|
|
.then(res => {
|
|
spinner.style.display = 'none';
|
|
saveBtn.disabled = false;
|
|
if (res.success) {
|
|
saveBtn.innerText = 'Publicado';
|
|
showNotification('✅ ¡Cambios publicados correctamente! Redirigiendo...');
|
|
setTimeout(() => window.location.href = '/dashboard', 1500);
|
|
} else {
|
|
saveBtn.innerText = 'Error';
|
|
alert('Error al guardar: ' + res.error);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
spinner.style.display = 'none';
|
|
saveBtn.disabled = false;
|
|
alert('Error de conexión');
|
|
});
|
|
}
|
|
|
|
// Theme change function removed - plan selector eliminated
|
|
|
|
// ==========================================
|
|
// BLOCKS LOGIC (Client Side First)
|
|
// ==========================================
|
|
|
|
window.currentBlocks = []; // Local source of truth
|
|
|
|
function loadBlocksList() {
|
|
// Initial Load from Server
|
|
fetch('/api/customizer/get-blocks/' + siteId)
|
|
.then(r => r.json())
|
|
.then(blocks => {
|
|
window.currentBlocks = blocks;
|
|
renderBlocksList();
|
|
});
|
|
}
|
|
|
|
function renderBlocksList() {
|
|
const list = document.getElementById('blocks_list_container');
|
|
list.innerHTML = '';
|
|
|
|
if (window.currentBlocks.length === 0) {
|
|
list.innerHTML = '<div style="text-align: center; color: #999; padding: 10px; font-style: italic;">No hay bloques extra.</div>';
|
|
return;
|
|
}
|
|
|
|
window.currentBlocks.sort((a, b) => a.order - b.order).forEach((block, index) => {
|
|
const item = document.createElement('div');
|
|
item.className = 'blocks-list-item';
|
|
item.dataset.id = block.id; // For SortableJS
|
|
|
|
let icon = 'fa-cube';
|
|
if (block.type === 'video') icon = 'fa-video';
|
|
if (block.type === 'imagen') icon = 'fa-image';
|
|
if (block.type === 'texto') icon = 'fa-font';
|
|
if (block.type === 'mapa') icon = 'fa-map-location-dot';
|
|
|
|
item.innerHTML = `
|
|
<div class="block-title"><i class="fa-solid ${icon}" style="margin-right:8px; color:#aaa;"></i> ${block.type.toUpperCase()}</div>
|
|
<div class="block-actions">
|
|
<button class="button button-small" style="color:#d63638; border:none; background:none;" onclick="removeBlockClientSide('${block.id}')" title="Eliminar"><i class="fa-solid fa-trash"></i></button>
|
|
</div>
|
|
`;
|
|
list.appendChild(item);
|
|
});
|
|
|
|
// Re-init Sortable if list re-rendered
|
|
initSortable();
|
|
}
|
|
|
|
function addNewBlockClientSide() {
|
|
const type = document.getElementById('block_type_selector').value;
|
|
if (!type) return;
|
|
|
|
const tempId = 'new_' + Date.now();
|
|
|
|
const newBlock = {
|
|
id: tempId,
|
|
type: type,
|
|
content: {},
|
|
order: window.currentBlocks.length
|
|
};
|
|
|
|
// Add to local state
|
|
window.currentBlocks.push(newBlock);
|
|
|
|
// Render UI
|
|
renderBlocksList();
|
|
document.getElementById('add-block-panel').style.display = 'none';
|
|
|
|
// Update Preview (Send 'add-block' message)
|
|
const iframe = document.getElementById('preview-iframe');
|
|
iframe.contentWindow.postMessage({
|
|
type: 'add-block',
|
|
block: newBlock
|
|
}, '*');
|
|
|
|
// Mark as dirty
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
showNotification('Bloque añadido (Draft)');
|
|
}
|
|
|
|
function removeBlockClientSide(blockId) {
|
|
if (!confirm('¿Eliminar este bloque?')) return;
|
|
|
|
// Remove from local state
|
|
window.currentBlocks = window.currentBlocks.filter(b => b.id !== blockId);
|
|
|
|
// Render UI
|
|
renderBlocksList();
|
|
|
|
// Update Preview
|
|
const iframe = document.getElementById('preview-iframe');
|
|
iframe.contentWindow.postMessage({
|
|
type: 'remove-block',
|
|
block_id: blockId
|
|
}, '*');
|
|
|
|
// Mark as dirty
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
}
|
|
|
|
function initSortable() {
|
|
const el = document.getElementById('blocks_list_container');
|
|
if (!el) return;
|
|
|
|
new Sortable(el, {
|
|
animation: 150,
|
|
ghostClass: 'sortable-ghost',
|
|
onEnd: function (evt) {
|
|
// Reorder window.currentBlocks based on DOM order
|
|
const newOrderIds = Array.from(el.children).map(child => child.dataset.id);
|
|
|
|
// Sort internal array based on new ID order
|
|
const reorderedBlocks = [];
|
|
newOrderIds.forEach(id => {
|
|
const block = window.currentBlocks.find(b => b.id === id);
|
|
if (block) reorderedBlocks.push(block);
|
|
});
|
|
window.currentBlocks = reorderedBlocks;
|
|
|
|
// Update indices
|
|
window.currentBlocks.forEach((b, i) => b.order = i);
|
|
|
|
// Send reorder message to iframe
|
|
const iframe = document.getElementById('preview-iframe');
|
|
iframe.contentWindow.postMessage({
|
|
type: 'reorder-blocks',
|
|
blockIds: newOrderIds
|
|
}, '*');
|
|
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
}
|
|
});
|
|
}
|
|
|
|
// ==========================================
|
|
// MENÚ PLATOS MANAGEMENT
|
|
// ==========================================
|
|
window.currentMenuItems = {};
|
|
|
|
function loadMenuItems() {
|
|
// Cargar desde el objeto inicial renderizado por Jinja2
|
|
const initialContent = {{ content | tojson | safe
|
|
}};
|
|
|
|
if (initialContent && initialContent.menu_items && Object.keys(initialContent.menu_items).length > 0) {
|
|
window.currentMenuItems = initialContent.menu_items;
|
|
} else {
|
|
// Platos por defecto si no hay nada guardado
|
|
window.currentMenuItems = {
|
|
'1': { nombre: 'Plato Especial 1', descripcion: 'Descripción del plato con ingredientes frescos.', precio: '25.00' },
|
|
'2': { nombre: 'Plato Especial 2', descripcion: 'Descripción del plato con sabores únicos.', precio: '28.00' },
|
|
'3': { nombre: 'Plato Especial 3', descripcion: 'Descripción del plato tradicional.', precio: '30.00' }
|
|
};
|
|
}
|
|
renderMenuItems();
|
|
}
|
|
|
|
function renderMenuItems() {
|
|
const list = document.getElementById('menu-items-list');
|
|
list.innerHTML = '';
|
|
|
|
if (Object.keys(window.currentMenuItems).length === 0) {
|
|
list.innerHTML = '<p style="text-align: center; color: #999; padding: 20px;">No hay platos. Añade el primero arriba.</p>';
|
|
return;
|
|
}
|
|
|
|
Object.keys(window.currentMenuItems).forEach(itemId => {
|
|
const item = window.currentMenuItems[itemId];
|
|
const card = document.createElement('div');
|
|
card.className = 'menu-item-card';
|
|
card.innerHTML = `
|
|
<div class="menu-item-info">
|
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
<h4>${item.nombre || 'Sin nombre'}</h4>
|
|
<span style="background: #eee; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600;">${item.categoria || 'Sin Cat.'}</span>
|
|
</div>
|
|
<p>${item.descripcion || 'Sin descripción'}</p>
|
|
<div style="display: flex; gap: 5px; margin-top: 5px;">
|
|
${item.tags ? item.tags.map(tag => `<span title="${tag}" style="font-size: 12px;">${tag === 'vegano' ? '🌿' : tag === 'picante' ? '🔥' : tag === 'sin-gluten' ? '🌾' : ''}</span>`).join('') : ''}
|
|
</div>
|
|
<p><strong>Precio: $${item.precio || '0.00'}</strong></p>
|
|
</div>
|
|
<div class="menu-item-actions">
|
|
<button class="button button-secondary" onclick="editMenuPlato('${itemId}')">Editar</button>
|
|
<button class="button" style="background: #d63638; color: white; border-color: #d63638;" onclick="deleteMenuPlato('${itemId}')">Eliminar</button>
|
|
</div>
|
|
`;
|
|
list.appendChild(card);
|
|
});
|
|
}
|
|
|
|
function addMenuPlato() {
|
|
const nombre = document.getElementById('new-plato-nombre').value;
|
|
const descripcion = document.getElementById('new-plato-descripcion').value;
|
|
const precio = document.getElementById('new-plato-precio').value;
|
|
const categoria = document.getElementById('new-plato-categoria').value;
|
|
const tags = Array.from(document.querySelectorAll('.plato-tag-checkbox:checked')).map(cb => cb.value);
|
|
|
|
if (!nombre) {
|
|
alert('Por favor, ingresa un nombre para el plato.');
|
|
return;
|
|
}
|
|
|
|
const newId = Date.now().toString();
|
|
window.currentMenuItems[newId] = {
|
|
nombre: nombre,
|
|
descripcion: descripcion || 'Descripción del plato.',
|
|
precio: precio || '0.00',
|
|
categoria: categoria || 'Principales',
|
|
tags: tags
|
|
};
|
|
|
|
// Limpiar formulario
|
|
document.getElementById('new-plato-nombre').value = '';
|
|
document.getElementById('new-plato-descripcion').value = '';
|
|
document.getElementById('new-plato-precio').value = '';
|
|
document.querySelectorAll('.plato-tag-checkbox').forEach(cb => cb.checked = false);
|
|
|
|
renderMenuItems();
|
|
updatePreview();
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
}
|
|
|
|
function editMenuPlato(itemId) {
|
|
const item = window.currentMenuItems[itemId];
|
|
if (!item) return;
|
|
|
|
// Llenar formulario con datos existentes
|
|
document.getElementById('new-plato-nombre').value = item.nombre || '';
|
|
document.getElementById('new-plato-descripcion').value = item.descripcion || '';
|
|
document.getElementById('new-plato-precio').value = item.precio || '';
|
|
document.getElementById('new-plato-categoria').value = item.categoria || 'Principales';
|
|
|
|
// Reset and set tags
|
|
document.querySelectorAll('.plato-tag-checkbox').forEach(cb => {
|
|
cb.checked = item.tags && item.tags.includes(cb.value);
|
|
});
|
|
|
|
// Eliminar el plato actual (se añadirá de nuevo con los cambios)
|
|
deleteMenuPlato(itemId, false);
|
|
}
|
|
|
|
function deleteMenuPlato(itemId, confirmDelete = true) {
|
|
if (confirmDelete && !confirm('¿Eliminar este plato?')) return;
|
|
|
|
delete window.currentMenuItems[itemId];
|
|
renderMenuItems();
|
|
updatePreview();
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
}
|
|
|
|
function gestionarPlatosMenu() {
|
|
loadMenuItems();
|
|
document.getElementById('menu-modal').classList.add('active');
|
|
}
|
|
|
|
function closeMenuModal() {
|
|
document.getElementById('menu-modal').classList.remove('active');
|
|
}
|
|
|
|
|
|
// ==========================================
|
|
// WORDPRESS-STYLE INLINE EDITING
|
|
// ==========================================
|
|
|
|
// Escuchar mensajes del iframe (click to edit)
|
|
window.addEventListener('message', function (e) {
|
|
if (e.data && e.data.type === 'gk-edit-field') {
|
|
handleInlineEdit(e.data.field, e.data.value, e.data.type_field);
|
|
} else if (e.data && e.data.type === 'gk-edit-block') {
|
|
handleEditBlock(e.data.block_id);
|
|
}
|
|
});
|
|
|
|
function handleEditBlock(blockId) {
|
|
const block = window.currentBlocks.find(b => b.id === blockId);
|
|
if (!block) return;
|
|
|
|
const form = document.getElementById('block-edit-form');
|
|
form.innerHTML = '';
|
|
window.currentEditingBlockId = blockId;
|
|
|
|
if (block.type === 'texto') {
|
|
form.innerHTML = `
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Título</span>
|
|
<input type="text" id="edit-block-titulo" value="${block.content.titulo || ''}" style="width:100%; margin-bottom:15px;">
|
|
</div>
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">Contenido</span>
|
|
<textarea id="edit-block-contenido" style="width:100%; min-height:100px;">${block.content.contenido || ''}</textarea>
|
|
</div>
|
|
`;
|
|
} else if (block.type === 'imagen' || block.type === 'video' || block.type === 'mapa') {
|
|
form.innerHTML = `
|
|
<div class="customize-control">
|
|
<span class="customize-control-title">URL del Recurso</span>
|
|
<input type="text" id="edit-block-url" value="${block.content.url || ''}" style="width:100%; margin-bottom:15px;">
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
document.getElementById('block-edit-modal').classList.add('active');
|
|
}
|
|
|
|
function closeBlockEditModal() {
|
|
document.getElementById('block-edit-modal').classList.remove('active');
|
|
window.currentEditingBlockId = null;
|
|
}
|
|
|
|
function saveBlockEdit() {
|
|
const blockId = window.currentEditingBlockId;
|
|
const block = window.currentBlocks.find(b => b.id === blockId);
|
|
if (!block) return;
|
|
|
|
if (block.type === 'texto') {
|
|
block.content.titulo = document.getElementById('edit-block-titulo').value;
|
|
block.content.contenido = document.getElementById('edit-block-contenido').value;
|
|
} else {
|
|
block.content.url = document.getElementById('edit-block-url').value;
|
|
}
|
|
|
|
closeBlockEditModal();
|
|
updatePreview();
|
|
renderBlocksList();
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
showNotification('Bloque actualizado');
|
|
}
|
|
|
|
function handleInlineEdit(field, currentValue, fieldType) {
|
|
// Encontrar el input correspondiente en el sidebar
|
|
let input = null;
|
|
|
|
// Mapear campos a IDs de inputs
|
|
const fieldMap = {
|
|
'site_name': 'site_name',
|
|
'hero_title': 'hero_title',
|
|
'hero_description': 'hero_description',
|
|
'direccion': 'contacto_direccion',
|
|
'telefono': 'contacto_telefono',
|
|
'email': 'contacto_email',
|
|
'capacidad': 'capacidad',
|
|
'horarios.lunes_viernes': 'horarios_lunes_viernes',
|
|
'horarios.sabados': 'horarios_sabados',
|
|
'horarios.domingos': 'horarios_domingos',
|
|
'especialidad_culinaria.titulo': 'especialidad_titulo',
|
|
'especialidad_culinaria.descripcion': 'especialidad_descripcion',
|
|
'especialidad_culinaria.imagen': 'especialidad_imagen'
|
|
};
|
|
|
|
const inputId = fieldMap[field];
|
|
if (inputId) {
|
|
input = document.getElementById(inputId);
|
|
}
|
|
|
|
if (input) {
|
|
// Expandir la sección correspondiente
|
|
const section = input.closest('.accordion-section');
|
|
if (section) {
|
|
const title = section.querySelector('.accordion-section-title');
|
|
if (title && !title.classList.contains('open')) {
|
|
toggleSection(title);
|
|
}
|
|
}
|
|
|
|
// Enfocar y seleccionar el input
|
|
input.focus();
|
|
input.select();
|
|
|
|
// Scroll al input
|
|
input.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
} else {
|
|
// Si es un campo anidado (menu_items, blocks), abrir modal
|
|
if (field.startsWith('menu_items.')) {
|
|
gestionarPlatosMenu();
|
|
// Buscar el plato específico y editarlo
|
|
const parts = field.split('.');
|
|
const itemId = parts[1];
|
|
const itemField = parts[2];
|
|
setTimeout(() => {
|
|
const item = window.currentMenuItems[itemId];
|
|
if (item) {
|
|
editMenuPlato(itemId);
|
|
}
|
|
}, 300);
|
|
} else if (field.startsWith('blocks.')) {
|
|
const blockId = field.split('.')[1];
|
|
handleEditBlock(blockId);
|
|
} else {
|
|
// Modal genérico para otros campos
|
|
openEditModal(field, currentValue, fieldType);
|
|
}
|
|
}
|
|
}
|
|
|
|
function openEditModal(field, currentValue, fieldType) {
|
|
// Crear modal si no existe
|
|
let modal = document.getElementById('inline-edit-modal');
|
|
if (!modal) {
|
|
modal = document.createElement('div');
|
|
modal.id = 'inline-edit-modal';
|
|
modal.className = 'modal-overlay';
|
|
modal.innerHTML = `
|
|
<div class="modal-content" onclick="event.stopPropagation()">
|
|
<div class="modal-header">
|
|
<h3>Editar Campo</h3>
|
|
<button class="modal-close" onclick="closeEditModal()">×</button>
|
|
</div>
|
|
<div class="modal-body" style="padding: 20px;">
|
|
<div class="customize-control">
|
|
<span class="customize-control-title" id="modal-field-label">Campo</span>
|
|
<input type="text" id="modal-field-input" style="width: 100%; margin-bottom: 15px;">
|
|
<textarea id="modal-field-textarea" style="width: 100%; min-height: 100px; display: none;"></textarea>
|
|
</div>
|
|
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
|
<button class="button button-secondary" onclick="closeEditModal()">Cancelar</button>
|
|
<button class="button button-primary" onclick="saveInlineEdit()">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(modal);
|
|
}
|
|
|
|
// Configurar modal
|
|
const label = document.getElementById('modal-field-label');
|
|
const textInput = document.getElementById('modal-field-input');
|
|
const textarea = document.getElementById('modal-field-textarea');
|
|
|
|
label.textContent = field.replace(/\./g, ' → ');
|
|
window.currentEditField = field;
|
|
window.currentEditFieldType = fieldType;
|
|
|
|
if (fieldType === 'textarea') {
|
|
textInput.style.display = 'none';
|
|
textarea.style.display = 'block';
|
|
textarea.value = currentValue || '';
|
|
textarea.focus();
|
|
} else {
|
|
textInput.style.display = 'block';
|
|
textarea.style.display = 'none';
|
|
textInput.value = currentValue || '';
|
|
textInput.focus();
|
|
textInput.select();
|
|
}
|
|
|
|
modal.classList.add('active');
|
|
}
|
|
|
|
function closeEditModal() {
|
|
const modal = document.getElementById('inline-edit-modal');
|
|
if (modal) {
|
|
modal.classList.remove('active');
|
|
window.currentEditField = null;
|
|
}
|
|
}
|
|
|
|
function saveInlineEdit() {
|
|
const field = window.currentEditField;
|
|
if (!field) return;
|
|
|
|
const textInput = document.getElementById('modal-field-input');
|
|
const textarea = document.getElementById('modal-field-textarea');
|
|
const value = window.currentEditFieldType === 'textarea'
|
|
? textarea.value
|
|
: textInput.value;
|
|
|
|
// Actualizar el valor en el formulario
|
|
updateFieldValue(field, value);
|
|
updatePreview();
|
|
closeEditModal();
|
|
document.getElementById('save-btn').innerText = 'Publicar *';
|
|
}
|
|
|
|
function updateFieldValue(field, value) {
|
|
// Actualizar el input correspondiente o el objeto de datos
|
|
const fieldMap = {
|
|
'site_name': 'site_name',
|
|
'hero_title': 'hero_title',
|
|
'hero_description': 'hero_description',
|
|
'direccion': 'contacto_direccion',
|
|
'telefono': 'contacto_telefono',
|
|
'email': 'contacto_email',
|
|
'capacidad': 'capacidad',
|
|
'horarios.lunes_viernes': 'horarios_lunes_viernes',
|
|
'horarios.sabados': 'horarios_sabados',
|
|
'horarios.domingos': 'horarios_domingos',
|
|
'especialidad_culinaria.titulo': 'especialidad_titulo',
|
|
'especialidad_culinaria.descripcion': 'especialidad_descripcion',
|
|
'especialidad_culinaria.imagen': 'especialidad_imagen'
|
|
};
|
|
|
|
const inputId = fieldMap[field];
|
|
if (inputId) {
|
|
const input = document.getElementById(inputId);
|
|
if (input) {
|
|
input.value = value;
|
|
}
|
|
} else if (field.startsWith('menu_items.')) {
|
|
// Actualizar menu_items
|
|
const parts = field.split('.');
|
|
const itemId = parts[1];
|
|
const itemField = parts[2];
|
|
if (window.currentMenuItems[itemId]) {
|
|
window.currentMenuItems[itemId][itemField] = value;
|
|
}
|
|
} else if (field.startsWith('blocks.')) {
|
|
// Actualizar blocks
|
|
const parts = field.split('.');
|
|
const blockId = parts[1];
|
|
const blockField = parts.slice(2).join('.');
|
|
const block = window.currentBlocks.find(b => b.id === blockId);
|
|
if (block) {
|
|
const fieldParts = blockField.split('.');
|
|
let obj = block.content;
|
|
for (let i = 0; i < fieldParts.length - 1; i++) {
|
|
if (!obj[fieldParts[i]]) obj[fieldParts[i]] = {};
|
|
obj = obj[fieldParts[i]];
|
|
}
|
|
obj[fieldParts[fieldParts.length - 1]] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cerrar modal al hacer click fuera
|
|
document.addEventListener('click', function (e) {
|
|
const modal = document.getElementById('inline-edit-modal');
|
|
if (modal && modal.classList.contains('active') && e.target === modal) {
|
|
closeEditModal();
|
|
}
|
|
});
|
|
|
|
// Init
|
|
loadMenuItems();
|
|
loadBlocksList();
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|