Config: layout más amplio + toolbar sticky con Guardar

Antes: container 800px centrado, 3 panels stacked vertical, save al final.
Ahora:
- Container 1600px max, padding 24px.
- Toolbar sticky arriba con título "Configuración" + Restaurar + Guardar
  (reemplaza la sección final).
- Settings grid de 2 columnas (340px / 1fr) que colapsa a 1 col en <960px:
  - Izq: panel "Información del Negocio" (campos apilados, más densos) +
    panel "Retiro en Tienda" (toggle + grid).
  - Der: panel "Zonas de Entrega" full-width — el mapa ocupa la mayor parte
    de la pantalla (height:calc(100vh-220px), min 520px).
- zones-layout dentro del panel: 300px lista/form / 1fr mapa flex.
- Sin pérdida funcional: mismos campos, mismas validaciones, mismas tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Tettamanti
2026-05-02 17:35:31 -03:00
parent 448b3d7c44
commit cbbb88c052

View File

@@ -30,10 +30,33 @@ class SettingsCrud extends HTMLElement {
this.shadowRoot.innerHTML = ` this.shadowRoot.innerHTML = `
<style> <style>
:host { display:block; height:100%; padding:16px; overflow:auto; } :host { display:block; height:100%; padding:16px 24px 24px; overflow:auto; }
* { box-sizing:border-box; font-family:system-ui,Segoe UI,Roboto,Arial; } * { box-sizing:border-box; font-family:var(--font-sans, system-ui); }
.container { max-width:800px; margin:0 auto; } .container { max-width:1600px; margin:0 auto; }
.panel { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:20px; margin-bottom:16px; } .settings-grid {
display:grid;
grid-template-columns:minmax(320px, 360px) minmax(0, 1fr);
gap:16px;
align-items:start;
}
@media (max-width: 960px) {
.settings-grid { grid-template-columns:1fr; }
}
.col { display:flex; flex-direction:column; gap:16px; }
.panel { background:var(--panel); border:1px solid var(--border); border-radius:10px; padding:20px; }
.panel-zones { padding:16px 16px 20px; display:flex; flex-direction:column; min-height:560px; }
.toolbar {
display:flex; align-items:center; justify-content:space-between;
gap:16px; padding:12px 4px 16px;
position:sticky; top:0; z-index:5; background:var(--bg, #f7fafc);
border-bottom:1px solid var(--border);
margin-bottom:16px;
}
.toolbar-title { margin:0; font-size:18px; font-weight:600; color:var(--text); }
.toolbar-actions { display:flex; gap:8px; }
.toolbar-actions button { padding:8px 16px; font-size:13px; }
.zones-map-wrap { display:flex; min-height:520px; min-width:0; }
.zones-map-wrap zone-map-editor { flex:1; height:auto; min-height:520px; }
.panel-title { font-size:16px; font-weight:700; color:var(--text); margin-bottom:16px; display:flex; align-items:center; gap:8px; } .panel-title { font-size:16px; font-weight:700; color:var(--text); margin-bottom:16px; display:flex; align-items:center; gap:8px; }
.panel-title svg { width:20px; height:20px; fill:var(--accent); } .panel-title svg { width:20px; height:20px; fill:var(--accent); }
@@ -125,12 +148,13 @@ class SettingsCrud extends HTMLElement {
.min-order-field { margin-top:16px; padding-top:16px; border-top:1px solid var(--border); } .min-order-field { margin-top:16px; padding-top:16px; border-top:1px solid var(--border); }
/* Zonas de entrega — editor con mapa */ /* Zonas de entrega — editor con mapa */
.zones-layout { display:grid; grid-template-columns:280px 1fr; gap:16px; } .zones-layout { display:grid; grid-template-columns:300px minmax(0,1fr); gap:16px; height:calc(100vh - 220px); min-height:520px; }
.zones-side { display:flex; flex-direction:column; gap:8px; min-width:0; } @media (max-width: 1100px) { .zones-layout { grid-template-columns:1fr; height:auto; } }
.zones-side { display:flex; flex-direction:column; gap:8px; min-width:0; min-height:0; }
.zones-side-header { display:flex; align-items:center; justify-content:space-between; } .zones-side-header { display:flex; align-items:center; justify-content:space-between; }
.zones-side-header h4 { margin:0; font-size:13px; color:var(--text); } .zones-side-header h4 { margin:0; font-size:13px; color:var(--text); }
.zones-side-header button { padding:6px 10px; font-size:12px; } .zones-side-header button { padding:6px 10px; font-size:12px; }
.zones-list { display:flex; flex-direction:column; gap:6px; max-height:480px; overflow-y:auto; padding-right:4px; } .zones-list { display:flex; flex-direction:column; gap:6px; flex:1; min-height:120px; max-height:50%; overflow-y:auto; padding-right:4px; }
.zone-row { .zone-row {
display:flex; align-items:center; gap:10px; display:flex; align-items:center; gap:10px;
padding:10px 12px; border-radius:var(--r-md, 10px); padding:10px 12px; border-radius:var(--r-md, 10px);
@@ -376,89 +400,89 @@ class SettingsCrud extends HTMLElement {
const s = this.settings; const s = this.settings;
content.innerHTML = ` content.innerHTML = `
<!-- Info del Negocio --> <div class="toolbar">
<div class="panel"> <h2 class="toolbar-title">Configuración</h2>
<div class="panel-title"> <div class="toolbar-actions">
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg> <button id="resetBtn" class="secondary" type="button">Restaurar</button>
Información del Negocio <button id="saveBtn" type="button" ${this.saving ? "disabled" : ""}>
</div> ${this.saving ? "Guardando..." : "Guardar"}
</button>
<div class="form-row">
<div class="field">
<label>Nombre del negocio</label>
<input type="text" id="storeName" value="${this.escapeHtml(s.store_name || "")}" placeholder="Ej: Carnicería Don Pedro" />
<div class="field-hint">Se usa en los mensajes del bot</div>
</div>
<div class="field">
<label>Nombre del bot</label>
<input type="text" id="botName" value="${this.escapeHtml(s.bot_name || "")}" placeholder="Ej: Piaf" />
<div class="field-hint">El asistente virtual</div>
</div>
</div>
<div class="form-row">
<div class="field">
<label>Dirección</label>
<input type="text" id="storeAddress" value="${this.escapeHtml(s.store_address || "")}" placeholder="Ej: Av. Corrientes 1234, CABA" />
</div>
<div class="field">
<label>Teléfono</label>
<input type="text" id="storePhone" value="${this.escapeHtml(s.store_phone || "")}" placeholder="Ej: +54 11 1234-5678" />
</div>
</div> </div>
</div> </div>
<!-- Retiro en tienda --> <div class="settings-grid">
<div class="panel"> <div class="col">
<div class="panel-title"> <div class="panel">
<svg viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l4.59-4.58L18 11l-6 6z"/></svg> <div class="panel-title">
Retiro en Tienda <svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
</div> Información del Negocio
<div class="toggle-row">
<div class="toggle ${s.pickup_enabled ? "active" : ""}" id="pickupToggle"></div>
<span class="toggle-label">Retiro en tienda habilitado</span>
</div>
<div class="schedule-grid" id="pickupSchedule">
${this.renderScheduleGrid("pickup", s.pickup_enabled)}
</div>
</div>
<!-- Zonas de Entrega -->
<div class="panel">
<div class="panel-title">
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>
Zonas de Entrega
</div>
<div class="field-hint" style="margin-bottom:12px;">
Dibujá los polígonos de tus zonas en el mapa. Cada zona tiene su costo de envío, días y rango horario.
El bot valida la dirección del cliente con la ubicación que comparta por WhatsApp.
</div>
<div class="zones-layout">
<div class="zones-side">
<div class="zones-side-header">
<h4>Zonas</h4>
<button id="zoneCreateBtn" type="button">+ Crear zona</button>
</div> </div>
<div class="zones-list" id="zonesList"> <div class="field" style="margin-bottom:12px;">
${this.renderZonesList()} <label>Nombre del negocio</label>
<input type="text" id="storeName" value="${this.escapeHtml(s.store_name || "")}" placeholder="Ej: Carnicería Don Pedro" />
<div class="field-hint">Se usa en los mensajes del bot</div>
</div> </div>
<div id="zoneFormSlot"> <div class="field" style="margin-bottom:12px;">
${this.renderZoneForm()} <label>Nombre del bot</label>
<input type="text" id="botName" value="${this.escapeHtml(s.bot_name || "")}" placeholder="Ej: Piaf" />
<div class="field-hint">El asistente virtual</div>
</div>
<div class="field" style="margin-bottom:12px;">
<label>Dirección</label>
<input type="text" id="storeAddress" value="${this.escapeHtml(s.store_address || "")}" placeholder="Av. Corrientes 1234, CABA" />
</div>
<div class="field">
<label>Teléfono</label>
<input type="text" id="storePhone" value="${this.escapeHtml(s.store_phone || "")}" placeholder="+54 11 1234-5678" />
</div> </div>
${this.renderZonesSummary()}
</div> </div>
<div>
<zone-map-editor id="zoneMapEditor" height="520px"></zone-map-editor> <div class="panel">
<div class="panel-title">
<svg viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l4.59-4.58L18 11l-6 6z"/></svg>
Retiro en Tienda
</div>
<div class="toggle-row">
<div class="toggle ${s.pickup_enabled ? "active" : ""}" id="pickupToggle"></div>
<span class="toggle-label">Retiro habilitado</span>
</div>
<div class="schedule-grid" id="pickupSchedule">
${this.renderScheduleGrid("pickup", s.pickup_enabled)}
</div>
</div> </div>
</div> </div>
</div>
<div class="actions"> <div class="col">
<button id="saveBtn" ${this.saving ? "disabled" : ""}>${this.saving ? "Guardando..." : "Guardar Configuración"}</button> <div class="panel panel-zones">
<button id="resetBtn" class="secondary">Restaurar</button> <div class="panel-title" style="margin-bottom:6px;">
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>
Zonas de Entrega
</div>
<div class="field-hint" style="margin-bottom:12px;">
Dibujá los polígonos en el mapa. Cada zona tiene su costo, días y rango horario.
El bot matchea con la ubicación que el cliente comparta por WhatsApp.
</div>
<div class="zones-layout">
<div class="zones-side">
<div class="zones-side-header">
<h4>Zonas</h4>
<button id="zoneCreateBtn" type="button">+ Crear zona</button>
</div>
<div class="zones-list" id="zonesList">
${this.renderZonesList()}
</div>
<div id="zoneFormSlot">
${this.renderZoneForm()}
</div>
${this.renderZonesSummary()}
</div>
<div class="zones-map-wrap">
<zone-map-editor id="zoneMapEditor"></zone-map-editor>
</div>
</div>
</div>
</div>
</div> </div>
`; `;