corregidos bugs de: ret, vs delivery, efectivo vs link, charsets, price query

This commit is contained in:
Lucas Tettamanti
2026-01-26 23:27:47 -03:00
parent 53293ce9b3
commit 493f26af17
13 changed files with 757 additions and 193 deletions

View File

@@ -1,13 +1,13 @@
import { api } from "../lib/api.js";
const DAYS = [
{ id: "lun", label: "Lun" },
{ id: "mar", label: "Mar" },
{ id: "mie", label: "Mié" },
{ id: "jue", label: "Jue" },
{ id: "vie", label: "Vie" },
{ id: "sab", label: "Sáb" },
{ id: "dom", label: "Dom" },
{ id: "lun", label: "Lunes", short: "Lun" },
{ id: "mar", label: "Martes", short: "Mar" },
{ id: "mie", label: "Miércoles", short: "Mié" },
{ id: "jue", label: "Jueves", short: "Jue" },
{ id: "vie", label: "Viernes", short: "Vie" },
{ id: "sab", label: "Sábado", short: "Sáb" },
{ id: "dom", label: "Domingo", short: "Dom" },
];
class SettingsCrud extends HTMLElement {
@@ -50,11 +50,11 @@ class SettingsCrud extends HTMLElement {
button.secondary { background:#253245; }
button.secondary:hover { background:#2d3e52; }
.toggle-row { display:flex; align-items:center; gap:12px; margin-bottom:12px; }
.toggle-row { display:flex; align-items:center; gap:12px; margin-bottom:16px; }
.toggle {
position:relative; width:48px; height:26px;
background:#253245; border-radius:13px; cursor:pointer;
transition:background .2s;
transition:background .2s; flex-shrink:0;
}
.toggle.active { background:#1f6feb; }
.toggle::after {
@@ -65,20 +65,38 @@ class SettingsCrud extends HTMLElement {
.toggle.active::after { transform:translateX(22px); }
.toggle-label { font-size:14px; color:#e7eef7; }
.days-selector { display:flex; gap:6px; flex-wrap:wrap; }
.day-btn {
padding:8px 12px; border-radius:6px; font-size:12px; font-weight:600;
background:#253245; color:#8aa0b5; border:none; cursor:pointer;
transition:all .15s;
/* Schedule grid */
.schedule-grid { display:flex; flex-direction:column; gap:8px; }
.schedule-row {
display:grid;
grid-template-columns:90px 32px 1fr;
gap:12px;
align-items:center;
padding:8px 12px;
background:#0f1520;
border-radius:8px;
border:1px solid #1e2a3a;
}
.day-btn:hover { background:#2d3e52; color:#e7eef7; }
.day-btn.selected { background:#1f6feb; color:#fff; }
.hours-row { display:flex; align-items:center; gap:12px; margin-top:12px; }
.hours-row input { width:90px; text-align:center; font-family:monospace; font-size:15px; letter-spacing:1px; }
.hours-row input::placeholder { color:#6c7a89; }
.hours-row span { color:#8aa0b5; }
.hours-row .hour-hint { font-size:11px; color:#6c7a89; margin-left:8px; }
.schedule-row.disabled { opacity:0.4; }
.day-label { font-size:13px; color:#e7eef7; font-weight:500; }
.day-toggle {
width:32px; height:18px; background:#253245; border-radius:9px;
cursor:pointer; position:relative; transition:background .2s;
}
.day-toggle.active { background:#2ecc71; }
.day-toggle::after {
content:''; position:absolute; top:2px; left:2px;
width:14px; height:14px; background:#fff; border-radius:50%;
transition:transform .2s;
}
.day-toggle.active::after { transform:translateX(14px); }
.hours-inputs { display:flex; align-items:center; gap:8px; }
.hours-inputs input {
width:70px; text-align:center; font-family:monospace;
font-size:13px; padding:6px 8px; letter-spacing:1px;
}
.hours-inputs span { color:#6c7a89; font-size:12px; }
.hours-inputs.disabled input { opacity:0.4; pointer-events:none; }
.actions { display:flex; gap:12px; margin-top:24px; }
.loading { text-align:center; padding:60px; color:#8aa0b5; }
@@ -93,6 +111,8 @@ class SettingsCrud extends HTMLElement {
color:#e74c3c; padding:12px 16px; border-radius:8px;
margin-bottom:16px; font-size:14px;
}
.min-order-field { margin-top:16px; padding-top:16px; border-top:1px solid #1e2a3a; }
</style>
<div class="container">
@@ -114,6 +134,10 @@ class SettingsCrud extends HTMLElement {
try {
this.settings = await api.getSettings();
// Asegurar que schedule existe
if (!this.settings.schedule) {
this.settings.schedule = { delivery: {}, pickup: {} };
}
this.loading = false;
this.render();
} catch (e) {
@@ -123,6 +147,60 @@ class SettingsCrud extends HTMLElement {
}
}
getScheduleSlot(type, dayId) {
return this.settings?.schedule?.[type]?.[dayId] || null;
}
setScheduleSlot(type, dayId, slot) {
if (!this.settings.schedule) {
this.settings.schedule = { delivery: {}, pickup: {} };
}
if (!this.settings.schedule[type]) {
this.settings.schedule[type] = {};
}
this.settings.schedule[type][dayId] = slot;
}
renderScheduleGrid(type, enabled) {
const defaultStart = type === "delivery" ? "09:00" : "08:00";
const defaultEnd = type === "delivery" ? "18:00" : "20:00";
return DAYS.map(day => {
const slot = this.getScheduleSlot(type, day.id);
const isActive = slot !== null && slot !== undefined;
const start = slot?.start || defaultStart;
const end = slot?.end || defaultEnd;
return `
<div class="schedule-row ${!enabled ? 'disabled' : ''}">
<span class="day-label">${day.label}</span>
<div class="day-toggle ${isActive ? 'active' : ''}"
data-type="${type}" data-day="${day.id}"
${!enabled ? 'style="pointer-events:none"' : ''}></div>
<div class="hours-inputs ${!isActive ? 'disabled' : ''}">
<input type="text"
class="hour-start"
data-type="${type}"
data-day="${day.id}"
value="${start}"
placeholder="09:00"
maxlength="5"
${!enabled || !isActive ? 'disabled' : ''} />
<span>a</span>
<input type="text"
class="hour-end"
data-type="${type}"
data-day="${day.id}"
value="${end}"
placeholder="18:00"
maxlength="5"
${!enabled || !isActive ? 'disabled' : ''} />
</div>
</div>
`;
}).join("");
}
render() {
const content = this.shadowRoot.getElementById("content");
@@ -137,8 +215,6 @@ class SettingsCrud extends HTMLElement {
}
const s = this.settings;
const deliveryDays = (s.delivery_days || "").split(",").filter(d => d);
const pickupDays = (s.pickup_days || "").split(",").filter(d => d);
content.innerHTML = `
<!-- Info del Negocio -->
@@ -185,26 +261,13 @@ class SettingsCrud extends HTMLElement {
<span class="toggle-label">Delivery habilitado</span>
</div>
<div id="deliveryOptions" style="${s.delivery_enabled ? "" : "opacity:0.5;pointer-events:none;"}">
<div class="schedule-grid" id="deliverySchedule">
${this.renderScheduleGrid("delivery", s.delivery_enabled)}
</div>
<div class="min-order-field">
<div class="field">
<label>Días disponibles</label>
<div class="days-selector" id="deliveryDays">
${DAYS.map(d => `
<button type="button" class="day-btn ${deliveryDays.includes(d.id) ? "selected" : ""}" data-day="${d.id}">${d.label}</button>
`).join("")}
</div>
</div>
<div class="hours-row">
<span>Horario:</span>
<input type="text" id="deliveryStart" value="${s.delivery_hours_start || "09:00"}" placeholder="09:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span>a</span>
<input type="text" id="deliveryEnd" value="${s.delivery_hours_end || "18:00"}" placeholder="18:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span class="hour-hint">(formato 24hs)</span>
</div>
<div class="field" style="margin-top:12px;">
<label>Pedido mínimo ($)</label>
<label>Pedido mínimo para delivery ($)</label>
<input type="number" id="deliveryMinOrder" value="${s.delivery_min_order || 0}" min="0" step="100" style="width:150px;" />
</div>
</div>
@@ -222,23 +285,8 @@ class SettingsCrud extends HTMLElement {
<span class="toggle-label">Retiro en tienda habilitado</span>
</div>
<div id="pickupOptions" style="${s.pickup_enabled ? "" : "opacity:0.5;pointer-events:none;"}">
<div class="field">
<label>Días disponibles</label>
<div class="days-selector" id="pickupDays">
${DAYS.map(d => `
<button type="button" class="day-btn ${pickupDays.includes(d.id) ? "selected" : ""}" data-day="${d.id}">${d.label}</button>
`).join("")}
</div>
</div>
<div class="hours-row">
<span>Horario:</span>
<input type="text" id="pickupStart" value="${s.pickup_hours_start || "08:00"}" placeholder="08:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span>a</span>
<input type="text" id="pickupEnd" value="${s.pickup_hours_end || "20:00"}" placeholder="20:00" pattern="([01]?[0-9]|2[0-3]):[0-5][0-9]" maxlength="5" />
<span class="hour-hint">(formato 24hs)</span>
</div>
<div class="schedule-grid" id="pickupSchedule">
${this.renderScheduleGrid("pickup", s.pickup_enabled)}
</div>
</div>
@@ -266,36 +314,43 @@ class SettingsCrud extends HTMLElement {
this.render();
});
// Delivery days
this.shadowRoot.querySelectorAll("#deliveryDays .day-btn").forEach(btn => {
btn.addEventListener("click", () => {
const day = btn.dataset.day;
let days = (this.settings.delivery_days || "").split(",").filter(d => d);
if (days.includes(day)) {
days = days.filter(d => d !== day);
// Day toggles
this.shadowRoot.querySelectorAll(".day-toggle").forEach(toggle => {
toggle.addEventListener("click", () => {
const type = toggle.dataset.type;
const day = toggle.dataset.day;
const currentSlot = this.getScheduleSlot(type, day);
if (currentSlot) {
// Desactivar día
this.setScheduleSlot(type, day, null);
} else {
days.push(day);
// Activar día con horarios default
const defaultStart = type === "delivery" ? "09:00" : "08:00";
const defaultEnd = type === "delivery" ? "18:00" : "20:00";
this.setScheduleSlot(type, day, { start: defaultStart, end: defaultEnd });
}
// Ordenar días
days.sort((a, b) => DAYS.findIndex(d => d.id === a) - DAYS.findIndex(d => d.id === b));
this.settings.delivery_days = days.join(",");
this.render();
});
});
// Pickup days
this.shadowRoot.querySelectorAll("#pickupDays .day-btn").forEach(btn => {
btn.addEventListener("click", () => {
const day = btn.dataset.day;
let days = (this.settings.pickup_days || "").split(",").filter(d => d);
if (days.includes(day)) {
days = days.filter(d => d !== day);
// Hour inputs - update on blur
this.shadowRoot.querySelectorAll(".hour-start, .hour-end").forEach(input => {
input.addEventListener("blur", () => {
const type = input.dataset.type;
const day = input.dataset.day;
const isStart = input.classList.contains("hour-start");
const slot = this.getScheduleSlot(type, day);
if (!slot) return;
const value = input.value.trim();
if (isStart) {
slot.start = value || (type === "delivery" ? "09:00" : "08:00");
} else {
days.push(day);
slot.end = value || (type === "delivery" ? "18:00" : "20:00");
}
days.sort((a, b) => DAYS.findIndex(d => d.id === a) - DAYS.findIndex(d => d.id === b));
this.settings.pickup_days = days.join(",");
this.render();
this.setScheduleSlot(type, day, slot);
});
});
@@ -306,25 +361,43 @@ class SettingsCrud extends HTMLElement {
this.shadowRoot.getElementById("resetBtn")?.addEventListener("click", () => this.load());
}
collectScheduleFromInputs() {
const schedule = { delivery: {}, pickup: {} };
for (const type of ["delivery", "pickup"]) {
this.shadowRoot.querySelectorAll(`.hour-start[data-type="${type}"]`).forEach(input => {
const day = input.dataset.day;
const endInput = this.shadowRoot.querySelector(`.hour-end[data-type="${type}"][data-day="${day}"]`);
const toggle = this.shadowRoot.querySelector(`.day-toggle[data-type="${type}"][data-day="${day}"]`);
if (toggle?.classList.contains("active")) {
schedule[type][day] = {
start: input.value.trim() || (type === "delivery" ? "09:00" : "08:00"),
end: endInput?.value.trim() || (type === "delivery" ? "18:00" : "20:00"),
};
}
});
}
return schedule;
}
async save() {
// Collect form data BEFORE re-rendering
// Collect schedule from inputs
const schedule = this.collectScheduleFromInputs();
const data = {
store_name: this.shadowRoot.getElementById("storeName")?.value || "",
bot_name: this.shadowRoot.getElementById("botName")?.value || "",
store_address: this.shadowRoot.getElementById("storeAddress")?.value || "",
store_phone: this.shadowRoot.getElementById("storePhone")?.value || "",
delivery_enabled: this.settings.delivery_enabled,
delivery_days: this.settings.delivery_days,
delivery_hours_start: this.shadowRoot.getElementById("deliveryStart")?.value || "09:00",
delivery_hours_end: this.shadowRoot.getElementById("deliveryEnd")?.value || "18:00",
delivery_min_order: parseFloat(this.shadowRoot.getElementById("deliveryMinOrder")?.value) || 0,
pickup_enabled: this.settings.pickup_enabled,
pickup_days: this.settings.pickup_days,
pickup_hours_start: this.shadowRoot.getElementById("pickupStart")?.value || "08:00",
pickup_hours_end: this.shadowRoot.getElementById("pickupEnd")?.value || "20:00",
delivery_min_order: parseFloat(this.shadowRoot.getElementById("deliveryMinOrder")?.value) || 0,
schedule,
};
// Update settings with form values so they persist through render
// Update settings with form values
this.settings = { ...this.settings, ...data };
this.saving = true;