// admin.jsx — Back-office AUTOMOTORES OMEGA
const { useState, useEffect, useRef, useMemo } = React;
const ADMIN_PASSWORD = 'omega2026'; // Cambiable. En producción usar auth real.
// Icons reusados del sitio público
const AIcon = {
Arrow: (p) => ,
Plus: (p) => ,
Pencil: (p) => ,
Trash: (p) => ,
Check: (p) => ,
X: (p) => ,
Eye: (p) => ,
Upload: (p) => ,
Search: (p) => ,
Download: (p) => ,
Logout: (p) => ,
Drag: (p) => ,
Car: (p) => ,
Inbox: (p) => ,
Users: (p) =>
};
const fmtPriceARS = (n) => n ? '$' + new Intl.NumberFormat('es-AR').format(Math.round(n)) : '—';
const fmtKm = (n) => n ? new Intl.NumberFormat('es-AR').format(n) + ' km' : '—';
// === LOGIN (Supabase email + password) ===
const LoginScreen = ({ onAuth }) => {
const [email, setEmail] = useState('');
const [pwd, setPwd] = useState('');
const [err, setErr] = useState('');
const [loading, setLoading] = useState(false);
const submit = async (e) => {
e.preventDefault();
setLoading(true); setErr('');
try {
const _em = email.indexOf('@')<0 ? email.trim().toLowerCase()+'@automotoresomega.com' : email.trim(); await window.OMEGA_STORE.signIn(_em, pwd);
onAuth();
} catch (e) {
setErr(e.message || 'Credenciales inválidas');
setTimeout(() => setErr(''), 4000);
} finally {
setLoading(false);
}
};
return (
Back-office
Acceso al panel
Iniciá sesión con tu cuenta OMEGA.
{window.OMEGA_STORE?.__local && (
Modo local · datos guardados en este navegador.
Acceso: admin@omega / omega2026
)}
);
};
// === TOAST ===
const Toast = ({ msg, type, onClose }) => {
useEffect(() => {
const t = setTimeout(onClose, 3500);
return () => clearTimeout(t);
}, [onClose]);
return (
{type === 'success' ?
:
}
{msg}
);
};
// === CONFIRM MODAL ===
const ConfirmModal = ({ title, msg, onConfirm, onCancel, confirmLabel = 'Confirmar' }) => (
e.stopPropagation()}>
{title}
{msg}
);
// === DASHBOARD ===
const Dashboard = ({ vehicles, profile, onEdit, onNew, onDelete, onTogglePublished, onLogout, onTab }) => {
const [q, setQ] = useState('');
const [filterMarca, setFilterMarca] = useState('Todas');
const [filterStatus, setFilterStatus] = useState('todos');
const [confirm, setConfirm] = useState(null);
const marcas = ['Todas', ...Array.from(new Set(vehicles.map(v => v.marca))).sort()];
const filtered = useMemo(() => {
return vehicles.filter(v => {
if (filterMarca !== 'Todas' && v.marca !== filterMarca) return false;
if (filterStatus === 'publicados' && v.draft) return false;
if (filterStatus === 'borradores' && !v.draft) return false;
if (filterStatus === 'sinfotos' && v.photos && v.photos.length > 0) return false;
if (q && !(`${v.marca} ${v.modelo} ${v.patente||''}`.toLowerCase().includes(q.toLowerCase()))) return false;
return true;
});
}, [vehicles, q, filterMarca, filterStatus]);
const stats = useMemo(() => ({
total: vehicles.length,
publicados: vehicles.filter(v => !v.draft).length,
sinFotos: vehicles.filter(v => !v.photos || v.photos.length === 0).length,
valorTotal: vehicles.reduce((s,v) => s + (v.precio||0), 0)
}), [vehicles]);
return (
Espejo en vivo · sistema.automotoresomega.com
Catálogo OMEGA
Los autos se cargan y se dan de baja desde tu sistema de gestión. Acá agregás las fotos y los datos de vidriera para que se vean en la web.
Total en stock
{stats.total}
Publicados
{stats.publicados}
Sin fotos
{stats.sinFotos}
Valor de inventario
{fmtPriceARS(stats.valorTotal)}
{filtered.length === 0 ? (
Sin autos en stock
No hay autos para mostrar
Cuando cargues autos en el sistema de gestión (estado "en stock"), aparecen acá automáticamente para ponerles fotos.
) : (
| Foto |
Vehículo |
Año |
KM |
Precio |
Patente |
En la web |
Acciones |
{filtered.map(v => (
{v.photos && v.photos.length > 0
?
: SIN FOTO }
|
{v.marca}
{v.modelo} · {v.color}
|
{v.anio || '—'} |
{fmtKm(v.km)} |
{fmtPriceARS(v.precio)} |
{v.patente ? {v.patente} : —} |
|
{v.photos && v.photos.length > 0 && (
)}
|
))}
)}
{confirm && (
{ onDelete(confirm.id); setConfirm(null); }}
onCancel={() => setConfirm(null)}
/>
)}
);
};
// === TOP BAR compartida ===
const AdminTopBar = ({ profile, active, onTab, onLogout, extraActions }) => { const puede = (s) => profile?.role === 'admin' || (profile?.secciones || []).includes(s); return (
Back-office ●
{profile?.nombre || profile?.email} · {profile?.role}
Ver sitio
); };
Object.assign(window, { LoginScreen, Dashboard, AdminTopBar, Toast, ConfirmModal, AIcon, fmtPriceARS, fmtKm, ADMIN_PASSWORD });