feat: AutoBooking initial commit — PHP WordPress Plugin (REST API, wpdb, WP_User_Query)

This commit is contained in:
2026-07-03 12:15:26 -04:00
commit c2b493117b
21 changed files with 10438 additions and 0 deletions
+675
View File
@@ -0,0 +1,675 @@
/* AutoBooking Admin Dashboard — admin-dashboard.js v1.0.0 */
(function () {
'use strict';
const CFG = window.AB_ADMIN_CFG || {};
const API = CFG.api_root + 'autobooking/v1/admin/';
const NONCE = CFG.nonce;
let lang = 'es';
let currentTab = 'overview';
let driversPage = 1, companiesPage = 1, tripsPage = 1, incidentsPage = 1;
let reasonCallback = null;
let currentIncidentId = null;
let zoneMap = null, zoneMarker = null, zoneCircle = null;
let tripMap = null, incidentMap = null, zonesOverviewMap = null;
/* ── API ── */
function apiFetch(path, opts) {
opts = opts || {};
return fetch(API + path, Object.assign({ headers: { 'X-WP-Nonce': NONCE, 'Content-Type': 'application/json' } }, opts)).then(function(r){ return r.json(); });
}
/* ── DOM ── */
function qs(sel, ctx) { return (ctx || document).querySelector(sel); }
function setText(id, val) { var el = qs('#' + id); if (el) el.textContent = val; }
function show(id) { var el = qs('#' + id); if (el) el.style.display = ''; }
function hide(id) { var el = qs('#' + id); if (el) el.style.display = 'none'; }
function applyLang() {
document.querySelectorAll('[data-es]').forEach(function(el) {
el.textContent = lang === 'en' ? (el.dataset.en || el.dataset.es) : el.dataset.es;
});
var btn = qs('#abad-lang-btn');
if (btn) btn.textContent = lang === 'en' ? 'EN' : 'ES';
}
function fmtMoney(v, currency) { return (currency || 'USD') + ' ' + parseFloat(v || 0).toFixed(2); }
function fmtDate(dt) {
if (!dt) return '—';
return new Date(dt).toLocaleString('es-CO', { dateStyle: 'short', timeStyle: 'short' });
}
function escHtml(str) {
if (str == null) return '';
return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function statusDot(online) {
return '<span class="abad-dot ' + (online ? 'abad-dot--green' : 'abad-dot--gray') + '"></span>';
}
function renderPagination(containerId, currentPage, total, perPage, onClick) {
var container = qs('#' + containerId);
if (!container) return;
var totalPages = Math.ceil(total / perPage) || 1;
container.innerHTML = '';
if (totalPages <= 1) return;
function mkBtn(label, page, active) {
var btn = document.createElement('button');
btn.className = 'abad-page-btn' + (active ? ' abad-page-btn--active' : '');
btn.textContent = label;
btn.disabled = (page < 1 || page > totalPages);
btn.onclick = function() { onClick(page); };
container.appendChild(btn);
}
mkBtn(lang === 'en' ? 'Prev' : 'Ant', currentPage - 1, false);
for (var i = 1; i <= totalPages; i++) {
if (totalPages > 7 && Math.abs(i - currentPage) > 2 && i !== 1 && i !== totalPages) continue;
mkBtn(i, i, i === currentPage);
}
mkBtn(lang === 'en' ? 'Next' : 'Sig', currentPage + 1, false);
}
function darkMapStyle() {
return [
{ elementType: 'geometry', stylers: [{ color: '#212121' }] },
{ elementType: 'labels.text.fill', stylers: [{ color: '#757575' }] },
{ featureType: 'road', elementType: 'geometry', stylers: [{ color: '#2c2c2c' }] },
{ featureType: 'water', elementType: 'geometry', stylers: [{ color: '#000000' }] },
];
}
function downloadCSV(csv, filename) {
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url; a.download = filename || 'export.csv';
document.body.appendChild(a); a.click();
document.body.removeChild(a); URL.revokeObjectURL(url);
}
/* ── Tabs ── */
function switchTab(tab) {
currentTab = tab;
document.querySelectorAll('.abad-tab').forEach(function(b) {
b.classList.toggle('abad-tab--active', b.dataset.tab === tab);
});
document.querySelectorAll('.abad-panel').forEach(function(p) { p.style.display = 'none'; });
var panel = qs('#panel-' + tab);
if (panel) panel.style.display = '';
loadTab(tab);
}
function loadTab(tab) {
if (tab === 'overview') loadOverview();
if (tab === 'drivers') { loadDrivers(); loadPending(); }
if (tab === 'companies') loadCompanies();
if (tab === 'trips') loadTrips();
if (tab === 'incidents') loadIncidents();
if (tab === 'zones') loadZones();
if (tab === 'settings') loadSettings();
}
/* ── Overview ── */
function loadOverview() {
apiFetch('overview').then(function(data) {
setText('kpi-trips-today', data.trips_today != null ? data.trips_today : '—');
setText('kpi-trips-week', data.trips_week != null ? data.trips_week : '—');
setText('kpi-active', data.active_trips != null ? data.active_trips : '—');
setText('kpi-online', data.drivers_online != null ? data.drivers_online : '—');
setText('kpi-revenue', '$' + parseFloat(data.revenue_today || 0).toFixed(2));
setText('kpi-sos', data.sos_open != null ? data.sos_open : 0);
setText('kpi-pending', data.pending_drivers != null ? data.pending_drivers : 0);
var sosWrap = qs('#kpi-sos-wrap');
var pendWrap = qs('#kpi-pending-wrap');
if (sosWrap) sosWrap.style.display = data.sos_open > 0 ? '' : 'none';
if (pendWrap) pendWrap.style.display = data.pending_drivers > 0 ? '' : 'none';
var sosInd = qs('#abad-sos-indicator');
if (sosInd) { sosInd.style.display = data.sos_open > 0 ? 'flex' : 'none'; setText('abad-sos-count', data.sos_open); }
var sosBadge = qs('#tab-sos-badge');
var pendBadge = qs('#tab-pending-badge');
if (sosBadge) { sosBadge.style.display = data.sos_open > 0 ? '' : 'none'; sosBadge.textContent = data.sos_open; }
if (pendBadge) { pendBadge.style.display = data.pending_drivers > 0 ? '' : 'none'; pendBadge.textContent = data.pending_drivers; }
var inline = qs('#pending-count-inline');
if (inline) inline.textContent = data.pending_drivers > 0 ? data.pending_drivers : '';
renderChart(data.chart || []);
});
}
function renderChart(chart) {
var container = qs('#abad-overview-chart');
if (!container) return;
container.innerHTML = '';
if (!chart.length) { container.innerHTML = '<p class="abad-empty-state">Sin datos.</p>'; return; }
var max = Math.max.apply(null, chart.map(function(d){ return parseInt(d.total) || 0; })) || 1;
chart.forEach(function(day) {
var bar = document.createElement('div');
bar.className = 'abad-bar-chart__bar';
var pct = Math.max(4, Math.round((parseInt(day.total || 0) / max) * 100));
bar.style.height = pct + '%';
bar.title = day.the_date + ': ' + day.total + ' viajes | $' + parseFloat(day.revenue || 0).toFixed(2);
container.appendChild(bar);
});
}
/* ── Drivers ── */
function loadDrivers(page) {
if (page) driversPage = page;
var search = (qs('#driver-search') || {}).value || '';
var filter = (qs('#driver-filter') || {}).value || '';
apiFetch('drivers?page=' + driversPage + '&search=' + encodeURIComponent(search) + '&filter=' + filter).then(function(data) {
var tbody = qs('#drivers-tbody');
if (!tbody) return;
if (!data.drivers || !data.drivers.length) {
tbody.innerHTML = '<tr><td colspan="6" class="abad-empty-state">Sin conductores</td></tr>';
return;
}
tbody.innerHTML = data.drivers.map(function(d) {
return '<tr>' +
'<td>' + (d.photo_url ? '<img src="' + escHtml(d.photo_url) + '" style="width:30px;height:30px;border-radius:50%;object-fit:cover;vertical-align:middle;margin-right:8px;">' : '') +
escHtml(d.name) + '<br><span style="font-size:11px;color:var(--ab-muted)">' + escHtml(d.email) + '</span></td>' +
'<td>' + escHtml(d.vehicle_type || '—') + ' ' + escHtml(d.vehicle_plate || '') + '</td>' +
'<td>' + statusDot(d.online) + (d.online ? 'Online' : 'Offline') + '</td>' +
'<td>' + (d.avg_rating > 0 ? '&#9733; ' + d.avg_rating : '—') + '</td>' +
'<td>' + d.trips_total + '</td>' +
'<td><button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadSuspendDriver(' + d.id + ',\'' + escHtml(d.name) + '\')">Suspender</button></td>' +
'</tr>';
}).join('');
applyLang();
renderPagination('drivers-pagination', driversPage, data.total, data.per_page, loadDrivers);
});
}
function loadPending() {
apiFetch('drivers/pending').then(function(data) {
var list = qs('#abad-pending-list');
var count = (data.drivers || []).length;
var badge = qs('#tab-pending-badge');
var inline = qs('#pending-count-inline');
if (badge) { badge.style.display = count > 0 ? '' : 'none'; badge.textContent = count; }
if (inline) { inline.textContent = count > 0 ? count : ''; }
if (!list) return;
if (!count) { list.innerHTML = '<p class="abad-empty-state">No hay conductores pendientes.</p>'; return; }
list.innerHTML = data.drivers.map(function(d) {
return '<div class="abad-item-card abad-item-card--yellow">' +
'<div class="abad-item-card__name">' + escHtml(d.name) + '</div>' +
'<div class="abad-item-card__meta">' + escHtml(d.email) + ' | ' + escHtml(d.phone || '—') + '<br>' +
'Vehiculo: ' + escHtml(d.vehicle_type || '—') + ' ' + escHtml(d.vehicle_plate || '') + '<br>' +
'Registro: ' + fmtDate(d.registered) + '<br>' +
'<span style="color:var(--ab-blue);font-size:11px">TODO: documentos (ver CHANGES.md)</span>' +
'</div>' +
'<div class="abad-item-card__actions">' +
'<button class="abad-btn abad-btn--brand abad-btn--sm" onclick="abadApproveDriver(' + d.id + ')">Aprobar</button>' +
'<button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadRejectDriver(' + d.id + ',\'' + escHtml(d.name) + '\')">Rechazar</button>' +
'</div>' +
'</div>';
}).join('');
});
}
window.abadApproveDriver = function(uid) {
if (!confirm(lang === 'en' ? 'Approve driver?' : '¿Aprobar conductor?')) return;
apiFetch('drivers/approve', { method: 'POST', body: JSON.stringify({ user_id: uid }) }).then(function(r) {
if (r.ok) { loadDrivers(); loadPending(); loadOverview(); } else alert(r.message || 'Error');
});
};
window.abadRejectDriver = function(uid, name) {
showReasonModal((lang === 'en' ? 'Reject: ' : 'Rechazar: ') + name, function(reason) {
apiFetch('drivers/reject', { method: 'POST', body: JSON.stringify({ user_id: uid, reason: reason }) }).then(function(r) {
if (r.ok) { loadDrivers(); loadPending(); loadOverview(); } else alert(r.message || 'Error');
});
});
};
window.abadSuspendDriver = function(uid, name) {
showReasonModal((lang === 'en' ? 'Suspend: ' : 'Suspender: ') + name, function(reason) {
apiFetch('drivers/suspend', { method: 'POST', body: JSON.stringify({ user_id: uid, reason: reason }) }).then(function(r) {
if (r.ok) loadDrivers(); else alert(r.message || 'Error');
});
});
};
/* ── Companies ── */
function loadCompanies(page) {
if (page) companiesPage = page;
var search = (qs('#company-search') || {}).value || '';
apiFetch('companies?page=' + companiesPage + '&search=' + encodeURIComponent(search)).then(function(data) {
var tbody = qs('#companies-tbody');
if (!tbody) return;
if (!data.companies || !data.companies.length) {
tbody.innerHTML = '<tr><td colspan="5" class="abad-empty-state">Sin empresas</td></tr>';
return;
}
tbody.innerHTML = data.companies.map(function(c) {
var isActive = c.active == 1;
return '<tr>' +
'<td>' + escHtml(c.name) + '<br><span style="font-size:11px;color:var(--ab-muted)">' + escHtml(c.email) + '</span></td>' +
'<td>' + escHtml(c.contact_name || '—') + '<br><span style="font-size:11px;color:var(--ab-muted)">' + escHtml(c.phone || '') + '</span></td>' +
'<td>' + (c.trips_count || 0) + '</td>' +
'<td><span class="abad-dot ' + (isActive ? 'abad-dot--green' : 'abad-dot--gray') + '"></span>' + (isActive ? 'Activa' : 'Inactiva') + '</td>' +
'<td>' +
(isActive
? '<button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadDeactivateCompany(' + c.id + ')">Desactivar</button>'
: '<button class="abad-btn abad-btn--brand abad-btn--sm" onclick="abadActivateCompany(' + c.id + ')">Activar</button>') +
' <button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadCompanyInvoices(' + c.id + ',\'' + escHtml(c.name) + '\')">Facturas</button>' +
'</td>' +
'</tr>';
}).join('');
renderPagination('companies-pagination', companiesPage, data.total, data.per_page, loadCompanies);
});
}
window.abadActivateCompany = function(cid) {
apiFetch('companies/activate', { method: 'POST', body: JSON.stringify({ company_id: cid }) }).then(function(r) {
if (r.ok) loadCompanies(); else alert(r.message || 'Error');
});
};
window.abadDeactivateCompany = function(cid) {
if (!confirm(lang === 'en' ? 'Deactivate company?' : '¿Desactivar empresa?')) return;
apiFetch('companies/deactivate', { method: 'POST', body: JSON.stringify({ company_id: cid }) }).then(function(r) {
if (r.ok) loadCompanies(); else alert(r.message || 'Error');
});
};
window.abadCompanyInvoices = function(cid) {
apiFetch(cid + '/invoices').then(function(data) {
console.log('Facturas empresa ' + cid + ':', data.invoices);
alert(lang === 'en' ? 'Invoices logged to console (' + (data.invoices || []).length + ' records).' : 'Facturas en consola (' + (data.invoices || []).length + ' registros).');
});
};
/* ── Trips ── */
function loadTrips(page) {
if (page) tripsPage = page;
var search = (qs('#trip-search') || {}).value || '';
var from = (qs('#trip-date-from') || {}).value || '';
var to = (qs('#trip-date-to') || {}).value || '';
var status = (qs('#trip-status-filter') || {}).value || '';
var params = 'page=' + tripsPage + '&search=' + encodeURIComponent(search) + '&date_from=' + from + '&date_to=' + to + '&status=' + status;
apiFetch('trips?' + params).then(function(data) {
var tbody = qs('#trips-tbody');
if (!tbody) return;
if (!data.trips || !data.trips.length) { tbody.innerHTML = '<tr><td colspan="8" class="abad-empty-state">Sin viajes</td></tr>'; return; }
tbody.innerHTML = data.trips.map(function(t) {
return '<tr>' +
'<td style="white-space:nowrap">' + fmtDate(t.created_at) + '</td>' +
'<td style="font-family:monospace;font-size:11px">' + escHtml((t.trip_uuid || '').slice(0,10)) + '…</td>' +
'<td>' + escHtml(t.driver_name || '—') + '</td>' +
'<td>' + escHtml(t.passenger_name || '—') + '</td>' +
'<td>' + fmtMoney(t.fare_total_amount, t.currency) + '</td>' +
'<td><span style="font-size:11px;padding:2px 7px;border-radius:20px;background:rgba(255,255,255,.08)">' + escHtml(t.status) + '</span></td>' +
'<td>' + (t.driver_rating ? '&#9733; ' + t.driver_rating : '—') + '</td>' +
'<td><button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadTripDetail(' + t.id + ')">Ver</button></td>' +
'</tr>';
}).join('');
renderPagination('trips-pagination', tripsPage, data.total, data.per_page, loadTrips);
});
}
window.abadTripDetail = function(tid) {
apiFetch('trips/' + tid).then(function(data) {
var trip = data.trip || {};
show('modal-trip');
var content = qs('#modal-trip-content');
if (content) {
content.innerHTML = '<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:14px;font-size:13px">' +
'<div><strong>Conductor</strong><br>' + escHtml(trip.driver_name || '—') + '</div>' +
'<div><strong>Pasajero</strong><br>' + escHtml(trip.passenger_name || '—') + '</div>' +
'<div><strong>Estado</strong><br>' + escHtml(trip.status || '—') + '</div>' +
'<div><strong>Total</strong><br>' + fmtMoney(trip.fare_total_amount, trip.currency) + '</div>' +
'<div><strong>Comision</strong><br>' + fmtMoney(trip.platform_fee_amount, trip.currency) + '</div>' +
'<div><strong>Neto conductor</strong><br>'+ fmtMoney(trip.driver_payout_amount, trip.currency) + '</div>' +
'<div><strong>Rating</strong><br>' + (trip.driver_rating ? '&#9733; ' + trip.driver_rating : '—') + '</div>' +
'<div><strong>Fecha</strong><br>' + fmtDate(trip.created_at) + '</div>' +
'</div>' +
(data.chat && data.chat.length ? '<div style="margin-top:14px"><strong>Chat</strong><div style="max-height:100px;overflow-y:auto;margin-top:6px">' +
data.chat.map(function(m) { return '<div style="margin-bottom:4px;font-size:12px"><b>' + escHtml(m.sender) + '</b>: ' + escHtml(m.message) + '</div>'; }).join('') +
'</div></div>' : '');
}
setTimeout(function() {
var mapEl = qs('#modal-trip-map');
if (!mapEl || !window.google || !data.positions || !data.positions.length) return;
var mid = data.positions[Math.floor(data.positions.length / 2)];
tripMap = new google.maps.Map(mapEl, { zoom: 13, center: { lat: parseFloat(mid.lat), lng: parseFloat(mid.lng) }, styles: darkMapStyle() });
new google.maps.Polyline({ path: data.positions.map(function(p){ return { lat: parseFloat(p.lat), lng: parseFloat(p.lng) }; }), geodesic: true, strokeColor: '#FF6F00', strokeOpacity: 0.9, strokeWeight: 3, map: tripMap });
}, 150);
});
};
/* ── Incidents ── */
function loadIncidents(page) {
if (page) incidentsPage = page;
var filter = (qs('#incident-filter') || {}).value || 'active';
apiFetch('incidents?filter=' + filter + '&page=' + incidentsPage).then(function(data) {
var list = qs('#abad-incidents-list');
if (!list) return;
if (!data.incidents || !data.incidents.length) { list.innerHTML = '<p class="abad-empty-state">No hay incidentes.</p>'; return; }
list.innerHTML = data.incidents.map(function(inc) {
return '<div class="abad-item-card ' + (inc.status === 'active' ? 'abad-item-card--red' : '') + '">' +
'<div class="abad-item-card__name"><span class="abad-dot ' + (inc.status === 'active' ? 'abad-dot--red' : 'abad-dot--green') + '"></span>' +
escHtml(inc.driver_name || 'Conductor #' + inc.driver_id) + '</div>' +
'<div class="abad-item-card__meta">' + fmtDate(inc.incident_at) + ' | Estado: <strong>' + escHtml(inc.status) + '</strong>' +
(inc.has_audio ? ' | <span style="color:var(--ab-blue)">Audio</span>' : '') +
(inc.notes ? '<br>Nota: ' + escHtml(inc.notes) : '') + '</div>' +
'<div class="abad-item-card__actions">' +
'<button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadOpenIncident(' + inc.id + ',' + (inc.lat||0) + ',' + (inc.lng||0) + ')">Ver mapa</button>' +
(inc.status === 'active' ?
'<button class="abad-btn abad-btn--brand abad-btn--sm" onclick="abadResolveIncident(' + inc.id + ',\'attended\')">Atendido</button>' +
'<button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadResolveIncident(' + inc.id + ',\'resolved\')">Resuelto</button>' : '') +
'</div></div>';
}).join('');
renderPagination('incidents-pagination', incidentsPage, data.total, 20, loadIncidents);
});
}
window.abadOpenIncident = function(id, lat, lng) {
currentIncidentId = id;
show('modal-incident');
var mapEl = qs('#modal-incident-map');
if (mapEl && window.google && lat && lng) {
setTimeout(function() {
incidentMap = new google.maps.Map(mapEl, { zoom: 15, center: { lat: parseFloat(lat), lng: parseFloat(lng) }, styles: darkMapStyle() });
new google.maps.Marker({ position: { lat: parseFloat(lat), lng: parseFloat(lng) }, map: incidentMap, icon: { path: google.maps.SymbolPath.CIRCLE, scale: 10, fillColor: '#ef4444', fillOpacity: 1, strokeColor: '#fff', strokeWeight: 2 } });
}, 150);
}
};
window.abadResolveIncident = function(id, status) {
var note = (qs('#incident-note') || {}).value || '';
apiFetch('incidents/' + id + '/resolve', { method: 'POST', body: JSON.stringify({ status: status, note: note }) }).then(function(r) {
if (r.ok) { hide('modal-incident'); loadIncidents(); loadOverview(); } else alert(r.message || 'Error');
});
};
/* ── Zones ── */
function loadZones() {
apiFetch('zones').then(function(zones) {
var list = qs('#abad-zones-list');
if (list) {
list.innerHTML = zones.length ? zones.map(function(z) {
return '<div class="abad-item-card">' +
'<div class="abad-item-card__name"><span class="abad-dot ' + (z.active ? 'abad-dot--green' : 'abad-dot--gray') + '"></span>' +
escHtml(z.title) + ' <span style="font-size:11px;color:var(--ab-muted)">[' + escHtml(z.type) + ']</span></div>' +
'<div class="abad-item-card__meta">Radio: ' + z.radius_km + ' km | Bono: ' + (z.bonus_amount > 0 ? '$'+z.bonus_amount+' '+z.bonus_currency : 'ninguno') +
(z.expires_at ? '<br>Expira: ' + fmtDate(z.expires_at) : '') + '</div>' +
'<div class="abad-item-card__actions">' +
'<button class="abad-btn abad-btn--outline abad-btn--sm" onclick="abadEditZone(' + z.id + ')">Editar</button>' +
(z.active ? '<button class="abad-btn abad-btn--danger abad-btn--sm" onclick="abadDeactivateZone(' + z.id + ')">Desactivar</button>' : '') +
'</div></div>';
}).join('') : '<p class="abad-empty-state">No hay zonas configuradas.</p>';
}
initZonesMap(zones);
});
}
function initZonesMap(zones) {
var mapEl = qs('#abad-zones-map');
if (!mapEl || !window.google) return;
setTimeout(function() {
zonesOverviewMap = new google.maps.Map(mapEl, { zoom: 11, center: { lat: 4.7110, lng: -74.0721 }, styles: darkMapStyle() });
zones.filter(function(z){ return z.active && z.lat && z.lng; }).forEach(function(z) {
new google.maps.Circle({ center: { lat: parseFloat(z.lat), lng: parseFloat(z.lng) }, radius: parseFloat(z.radius_km)*1000, map: zonesOverviewMap, strokeColor: '#FF6F00', strokeOpacity: 0.7, strokeWeight: 2, fillColor: '#FF6F00', fillOpacity: 0.12 });
});
}, 200);
}
window.abadEditZone = function(id) {
apiFetch('zones').then(function(zones) {
var z = null;
for (var i = 0; i < zones.length; i++) { if (zones[i].id == id) { z = zones[i]; break; } }
if (!z) return;
qs('#zone-id').value = z.id;
qs('#zone-title').value = z.title;
qs('#zone-type').value = z.type;
qs('#zone-description').value = z.description || '';
qs('#zone-radius').value = z.radius_km;
qs('#zone-bonus').value = z.bonus_amount;
qs('#zone-countdown').value = z.countdown_seconds;
qs('#zone-expires').value = z.expires_at ? z.expires_at.replace(' ','T').slice(0,16) : '';
qs('#zone-lat').value = z.lat;
qs('#zone-lng').value = z.lng;
show('modal-zone');
initZoneModal(parseFloat(z.lat), parseFloat(z.lng), parseFloat(z.radius_km));
});
};
window.abadDeactivateZone = function(id) {
if (!confirm(lang === 'en' ? 'Deactivate zone?' : '¿Desactivar zona?')) return;
apiFetch('zones/' + id + '/deactivate', { method: 'POST' }).then(function(r) { if (r.ok) loadZones(); });
};
function initZoneModal(lat, lng, radiusKm) {
var mapEl = qs('#zone-modal-map');
if (!mapEl || !window.google) return;
setTimeout(function() {
var center = (lat && lng) ? { lat: lat, lng: lng } : { lat: 4.7110, lng: -74.0721 };
zoneMap = new google.maps.Map(mapEl, { zoom: 12, center: center, styles: darkMapStyle() });
if (lat && lng) {
zoneMarker = new google.maps.Marker({ position: center, map: zoneMap });
zoneCircle = new google.maps.Circle({ center: center, radius: (radiusKm||2)*1000, map: zoneMap, strokeColor: '#FF6F00', strokeOpacity: 0.8, strokeWeight: 2, fillColor: '#FF6F00', fillOpacity: 0.15 });
}
zoneMap.addListener('click', function(e) {
var pos = { lat: e.latLng.lat(), lng: e.latLng.lng() };
qs('#zone-lat').value = pos.lat;
qs('#zone-lng').value = pos.lng;
if (zoneMarker) zoneMarker.setMap(null);
if (zoneCircle) zoneCircle.setMap(null);
zoneMarker = new google.maps.Marker({ position: pos, map: zoneMap });
var r = parseFloat((qs('#zone-radius')||{}).value || 2) * 1000;
zoneCircle = new google.maps.Circle({ center: pos, radius: r, map: zoneMap, strokeColor: '#FF6F00', strokeOpacity: 0.8, strokeWeight: 2, fillColor: '#FF6F00', fillOpacity: 0.15 });
});
}, 200);
}
/* ── Settings ── */
function loadSettings() {
apiFetch('settings').then(function(s) {
if (qs('#cfg-name')) qs('#cfg-name').value = s.platform_name || '';
if (qs('#cfg-logo')) qs('#cfg-logo').value = s.platform_logo || '';
if (qs('#cfg-sos-email')) qs('#cfg-sos-email').value = s.sos_email || '';
if (qs('#cfg-fee-pct')) qs('#cfg-fee-pct').value = (parseFloat(s.platform_fee_pct||0.20)*100).toFixed(1);
});
loadFareConfig();
}
function loadFareConfig() {
apiFetch('fare-config').then(function(rows) {
var list = qs('#abad-fare-list');
if (!list) return;
if (!rows.length) { list.innerHTML = '<p class="abad-empty-state">Sin tarifas.</p>'; return; }
list.innerHTML = '<table class="abad-table" style="margin-top:8px"><thead><tr><th>Pais</th><th>Moneda</th><th>Base</th><th>km</th><th>min</th><th>Comision</th><th>Min.</th></tr></thead><tbody>' +
rows.map(function(r) {
return '<tr><td><strong>' + escHtml(r.country_code) + '</strong></td><td>' + escHtml(r.currency) + '</td>' +
'<td>$' + parseFloat(r.base_fare).toFixed(2) + '</td>' +
'<td>$' + parseFloat(r.per_km).toFixed(3) + '</td>' +
'<td>$' + parseFloat(r.per_minute).toFixed(3) + '</td>' +
'<td>' + (parseFloat(r.platform_fee_pct)*100).toFixed(1) + '%</td>' +
'<td>$' + parseFloat(r.minimum_fare).toFixed(2) + '</td></tr>';
}).join('') + '</tbody></table>';
});
}
/* ── Reason modal ── */
function showReasonModal(title, callback) {
reasonCallback = callback;
var el = qs('#modal-reason-title');
if (el) el.textContent = title;
var txt = qs('#modal-reason-text');
if (txt) txt.value = '';
show('modal-reason');
}
/* ── Init ── */
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.abad-tab').forEach(function(btn) {
btn.addEventListener('click', function(){ switchTab(btn.dataset.tab); });
});
var langBtn = qs('#abad-lang-btn');
if (langBtn) langBtn.addEventListener('click', function() { lang = lang === 'es' ? 'en' : 'es'; applyLang(); });
var driverSearch = qs('#driver-search');
if (driverSearch) driverSearch.addEventListener('keydown', function(e){ if(e.key==='Enter'){ driversPage=1; loadDrivers(); } });
var driverFilter = qs('#driver-filter');
if (driverFilter) driverFilter.addEventListener('change', function(){ driversPage=1; loadDrivers(); });
var btnPending = qs('#btn-show-pending');
if (btnPending) btnPending.addEventListener('click', function() {
var sec = qs('#abad-pending-section');
if (sec) sec.style.display = sec.style.display === 'none' ? '' : 'none';
});
var companySearch = qs('#company-search');
if (companySearch) companySearch.addEventListener('keydown', function(e){ if(e.key==='Enter'){ companiesPage=1; loadCompanies(); } });
var btnTripSearch = qs('#btn-trip-search');
if (btnTripSearch) btnTripSearch.addEventListener('click', function(){ tripsPage=1; loadTrips(); });
var btnExportCSV = qs('#btn-export-trips-csv');
if (btnExportCSV) btnExportCSV.addEventListener('click', function() {
var from = (qs('#trip-date-from')||{}).value||'';
var to = (qs('#trip-date-to')||{}).value||'';
var status = (qs('#trip-status-filter')||{}).value||'';
apiFetch('trips/export-csv?date_from=' + from + '&date_to=' + to + '&status=' + status).then(function(r){ if(r.csv) downloadCSV(r.csv, r.filename||'viajes.csv'); });
});
var incidentFilter = qs('#incident-filter');
if (incidentFilter) incidentFilter.addEventListener('change', function(){ incidentsPage=1; loadIncidents(); });
var btnRefreshInc = qs('#btn-refresh-incidents');
if (btnRefreshInc) btnRefreshInc.addEventListener('click', loadIncidents);
var incClose = qs('#modal-incident-close');
if (incClose) incClose.addEventListener('click', function(){ hide('modal-incident'); });
var btnAttended = qs('#btn-incident-attended');
if (btnAttended) btnAttended.addEventListener('click', function(){ if(currentIncidentId) window.abadResolveIncident(currentIncidentId,'attended'); });
var btnResolved = qs('#btn-incident-resolved');
if (btnResolved) btnResolved.addEventListener('click', function(){ if(currentIncidentId) window.abadResolveIncident(currentIncidentId,'resolved'); });
var tripClose = qs('#modal-trip-close');
if (tripClose) tripClose.addEventListener('click', function(){ hide('modal-trip'); });
var reasonConfirm = qs('#modal-reason-confirm');
if (reasonConfirm) reasonConfirm.addEventListener('click', function() {
var reason = (qs('#modal-reason-text')||{}).value || '';
hide('modal-reason');
if (reasonCallback) { reasonCallback(reason); reasonCallback = null; }
});
var reasonCancel = qs('#modal-reason-cancel');
if (reasonCancel) reasonCancel.addEventListener('click', function(){ hide('modal-reason'); reasonCallback=null; });
var btnNewZone = qs('#btn-new-zone');
if (btnNewZone) btnNewZone.addEventListener('click', function() {
['#zone-id','#zone-title','#zone-description','#zone-expires','#zone-lat','#zone-lng'].forEach(function(s){ var el=qs(s); if(el) el.value=''; });
qs('#zone-radius').value=2; qs('#zone-bonus').value=0; qs('#zone-countdown').value=180;
show('modal-zone');
initZoneModal(0,0,2);
});
var zoneClose = qs('#modal-zone-close');
var zonCancel = qs('#btn-zone-cancel');
if (zoneClose) zoneClose.addEventListener('click', function(){ hide('modal-zone'); });
if (zonCancel) zonCancel.addEventListener('click', function(){ hide('modal-zone'); });
var btnZoneSave = qs('#btn-zone-save');
if (btnZoneSave) btnZoneSave.addEventListener('click', function() {
var lat = parseFloat((qs('#zone-lat')||{}).value||0);
var lng = parseFloat((qs('#zone-lng')||{}).value||0);
var title = (qs('#zone-title')||{}).value||'';
if (!title || !lat || !lng) { alert(lang==='en'?'Click the map and enter a title.':'Haz clic en el mapa y escribe un titulo.'); return; }
var expires = (qs('#zone-expires')||{}).value||'';
var payload = {
id: parseInt((qs('#zone-id')||{}).value||0),
title: title,
description: (qs('#zone-description')||{}).value||'',
type: (qs('#zone-type')||{}).value||'hotspot',
lat: lat, lng: lng,
radius_km: parseFloat((qs('#zone-radius')||{}).value||2),
bonus_amount: parseFloat((qs('#zone-bonus')||{}).value||0),
countdown_seconds: parseInt((qs('#zone-countdown')||{}).value||180),
expires_at: expires ? expires.replace('T',' ') : '',
};
apiFetch('zones/save', { method: 'POST', body: JSON.stringify(payload) }).then(function(r){ if(r.ok){ hide('modal-zone'); loadZones(); } else alert(r.message||'Error'); });
});
var btnSaveSettings = qs('#btn-save-settings');
if (btnSaveSettings) btnSaveSettings.addEventListener('click', function() {
var payload = {
platform_name: (qs('#cfg-name')||{}).value||'',
platform_logo: (qs('#cfg-logo')||{}).value||'',
sos_email: (qs('#cfg-sos-email')||{}).value||'',
platform_fee_pct: parseFloat((qs('#cfg-fee-pct')||{}).value||20)/100,
};
apiFetch('settings/save', { method:'POST', body: JSON.stringify(payload) }).then(function(r){
if(r.ok){ var el=qs('#cfg-success'); if(el){ el.style.display=''; setTimeout(function(){ el.style.display='none'; }, 2500); } }
});
});
var btnSaveFare = qs('#btn-save-fare');
if (btnSaveFare) btnSaveFare.addEventListener('click', function() {
var payload = {
country_code: ((qs('#fare-country')||{}).value||'').toUpperCase(),
currency: ((qs('#fare-currency')||{}).value||'USD').toUpperCase(),
base_fare: parseFloat((qs('#fare-base')||{}).value||3),
per_km: parseFloat((qs('#fare-per-km')||{}).value||1.80),
per_minute: parseFloat((qs('#fare-per-min')||{}).value||0.30),
minimum_fare: parseFloat((qs('#fare-minimum')||{}).value||5),
platform_fee_pct: parseFloat((qs('#cfg-fee-pct')||{}).value||20)/100,
};
apiFetch('fare-config/save', { method:'POST', body: JSON.stringify(payload) }).then(function(r){
if(r.ok){ loadFareConfig(); var el=qs('#fare-success'); if(el){ el.style.display=''; setTimeout(function(){ el.style.display='none'; }, 2500); } }
else alert(r.message||'Error');
});
});
applyLang();
switchTab('overview');
setInterval(function(){ if(currentTab==='overview') loadOverview(); }, 60000);
});
// ── SECURITY TAB ──────────────────────────────────────────
async function loadSecuritySettings() {
try {
const r = await apiFetch('blocked-ips');
const countEl = document.getElementById('sec-blocked-count');
if (countEl) countEl.textContent = r.blocked_24h + ' IPs bloqueadas en las últimas 24h';
const el = document.getElementById('sec-blocked-table');
if (!el) return;
if (!r.blocked.length) { el.innerHTML = '<p style="color:rgba(255,255,255,.4);font-size:13px">Sin IPs bloqueadas.</p>'; return; }
el.innerHTML = '<table class="abad-table"><thead><tr><th>IP</th><th>Motivo</th><th>Expira</th><th></th></tr></thead><tbody>'
+ r.blocked.map(b => '<tr><td>'+b.ip+'</td><td style="font-size:12px">'+b.reason+'</td><td style="font-size:12px">'+b.expires_at+'</td><td><button class="abad-btn abad-btn--outline abad-btn--sm" onclick="absUnblock('+b.id+')">Desbloquear</button></td></tr>').join('')
+ '</tbody></table>';
} catch(e) {}
}
window.absUnblock = async function(id) { await apiFetch('blocked-ips/'+id+'/unblock',{method:'POST'}); loadSecuritySettings(); };
document.getElementById('btn-save-sec') && document.getElementById('btn-save-sec').addEventListener('click', async function() {
await apiFetch('security-settings/save', {method:'POST', body: JSON.stringify({
whitelist: document.getElementById('sec-whitelist').value,
hcaptcha_site_key: document.getElementById('sec-hcaptcha-site').value,
hcaptcha_secret: document.getElementById('sec-hcaptcha-secret').value,
})});
var el = document.getElementById('sec-saved'); el.style.display='block'; setTimeout(function(){el.style.display='none';},3000);
});
document.querySelectorAll('.abad-tab').forEach(function(btn) {
if (btn.dataset.tab === 'settings') btn.addEventListener('click', loadSecuritySettings);
});
})();