/* ═══════════════════════════════════════════════
APP SHELL — Sidebar + Header + Routing
═══════════════════════════════════════════════ */
const NAV_ITEMS = {
sala: [
{ id: 'registro', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2', label: 'Mis productos' },
{ id: 'catalogo', icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z', label: 'Catálogo' },
{ id: 'historial', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', label: 'Historial' }],
supervisor: [
{ id: 'dashboard', icon: 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm10 0a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zm10 0a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z', label: 'Dashboard' },
{ id: 'tabla', icon: 'M3 10h18M3 14h18M3 6h18M3 18h18', label: 'Tabla de datos' },
{ id: 'consolidado', icon: 'M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z', label: 'Consolidado' },
{ id: 'catalogo', icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z', label: 'Catálogo' },
{ id: 'auditoria', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', label: 'Historial' },
{ id: 'usuarios', icon: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z', label: 'Usuarios' },
{ id: 'sincronizacion', icon: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15', label: 'Sincronización' }],
admin: [
{ id: 'dashboard', icon: 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zm10 0a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zm10 0a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z', label: 'Dashboard' },
{ id: 'tabla', icon: 'M3 10h18M3 14h18M3 6h18M3 18h18', label: 'Tabla de datos' },
{ id: 'consolidado', icon: 'M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z', label: 'Consolidado' },
{ id: 'catalogo', icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z', label: 'Catálogo' },
{ id: 'auditoria', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z', label: 'Auditoría' },
{ id: 'usuarios', icon: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z', label: 'Usuarios' },
{ id: 'sincronizacion', icon: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15', label: 'Sincronización' }]
};
const SvgIcon = ({ d, size = 20, stroke = 'currentColor' }) =>
;
const BellIcon = () =>
;
const NotificationPanel = ({ notifications, onClose, onDelete, onDeleteAll, canManage = true }) => {
const ns = {
overlay: {
position: 'fixed', inset: 0, zIndex: 999,
background: 'rgba(0,0,0,0.2)', animation: 'fadeIn 0.2s ease'
},
panel: {
position: 'fixed', top: 8, right: 8, width: 380, maxHeight: 'calc(100vh - 16px)',
background: 'var(--bg-secondary)', borderRadius: 'var(--radius-lg)',
border: '1px solid var(--border-color)', boxShadow: 'var(--shadow-xl)',
zIndex: 1000, animation: 'slideDown 0.3s cubic-bezier(0.16,1,0.3,1) both',
overflow: 'hidden', display: 'flex', flexDirection: 'column'
},
header: {
padding: '14px 20px', display: 'flex', alignItems: 'center',
justifyContent: 'space-between', borderBottom: '1px solid var(--border-color)',
gap: 8,
},
title: { fontSize: 15, fontWeight: 700, flex: 1 },
item: (read) => ({
padding: '12px 16px', display: 'flex', gap: 10, alignItems: 'flex-start',
borderBottom: '1px solid var(--border-subtle)',
background: read ? 'transparent' : 'var(--accent-subtle)',
transition: 'background var(--transition-fast)',
}),
dot: (type) => ({
width: 8, height: 8, borderRadius: '50%', marginTop: 5, flexShrink: 0,
background: type === 'danger' ? 'var(--status-danger)' :
type === 'warning' ? 'var(--status-warning)' :
type === 'success' ? 'var(--status-success)' : 'var(--status-info)'
}),
text: { fontSize: 13, color: 'var(--text-primary)', lineHeight: 1.5 },
time: { fontSize: 11, color: 'var(--text-tertiary)', marginTop: 2 },
deleteBtn: {
flexShrink: 0, marginLeft: 'auto', background: 'none', border: 'none',
color: 'var(--text-tertiary)', cursor: 'pointer', padding: '2px 4px',
borderRadius: 4, fontSize: 16, lineHeight: 1,
transition: 'color var(--transition-fast)',
},
empty: {
padding: '40px 20px', textAlign: 'center',
color: 'var(--text-tertiary)', fontSize: 13,
},
};
return (
Notificaciones
{canManage && notifications.length > 0 && (
)}
{notifications.length === 0
?
Sin notificaciones
: notifications.map((n) => (
{canManage && (
)}
))
}
);
};
const AppShell = ({ user, currentView, onNavigate, onLogout, children, onToggleTheme, theme }) => {
const NOTIFICATION_POLL_MS = 10000;
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
const [notifOpen, setNotifOpen] = React.useState(false);
const [isMobile, setIsMobile] = React.useState(window.innerWidth < 769);
const [, setTick] = React.useState(0);
const navItems = NAV_ITEMS[user.role] || NAV_ITEMS.sala;
const salaDisplayCode = window.MOCK.getPreferredUbicacionForSala
? window.MOCK.getPreferredUbicacionForSala(user.sucursal || '')
: (user.sucursal || '');
const roleLabel = user.role === 'sala' ? `Sala · ${salaDisplayCode}` : user.role === 'admin' ? 'Administrador' : 'Supervisor';
const canManageNotifications = user.role !== 'sala';
const visibleNotifications = (() => {
const all = window.MOCK.MOCK_NOTIFICATIONS || [];
if (user.role === 'sala') {
const mySucursal = salaDisplayCode || user.sucursal || '';
return all.filter(n => n.event === 'status_change' && (n.targetSucursal || '') === mySucursal);
}
if (user.role === 'supervisor') {
return all.filter(n => {
if (n.event !== 'product_edited') return true;
const ids = (n.targetUserIds || '').split(',').map(x => x.trim()).filter(Boolean);
return ids.length === 0 || ids.includes(String(user.id));
});
}
return all;
})();
const unreadCount = visibleNotifications.filter((n) => !n.read).length;
const refreshNotifs = async () => {
try {
const notifs = await fetch(`${window.MOCK.getApiBase()}/notifications`).then(r => r.json());
window.MOCK.MOCK_NOTIFICATIONS = notifs;
setTick(t => t + 1);
} catch {}
};
// Re-render on data changes + refresh notifications periodically
React.useEffect(() => {
const handler = () => setTick(t => t + 1);
const onVisibility = () => {
if (document.visibilityState === 'visible') refreshNotifs();
};
window.addEventListener('vencidos_data_changed', handler);
document.addEventListener('visibilitychange', onVisibility);
refreshNotifs();
const timer = setInterval(refreshNotifs, NOTIFICATION_POLL_MS);
return () => {
window.removeEventListener('vencidos_data_changed', handler);
document.removeEventListener('visibilitychange', onVisibility);
clearInterval(timer);
};
}, []);
const openNotifPanel = async () => {
setNotifOpen(true);
try {
await refreshNotifs();
if (user.role === 'sala') {
const mySucursal = salaDisplayCode || user.sucursal || '';
const salaNotifs = (window.MOCK.MOCK_NOTIFICATIONS || []).filter(
n => n.event === 'status_change' && (n.targetSucursal || '') === mySucursal && !n.read
);
await Promise.all(
salaNotifs.map(n => fetch(`${window.MOCK.getApiBase()}/notifications/${n.id}/read`, { method: 'PUT' }).catch(() => null))
);
const salaIds = new Set(salaNotifs.map(n => n.id));
window.MOCK.MOCK_NOTIFICATIONS = window.MOCK.MOCK_NOTIFICATIONS.map(n =>
salaIds.has(n.id) ? { ...n, read: true } : n
);
} else {
await fetch(`${window.MOCK.getApiBase()}/notifications/read-all`, { method: 'PUT' });
window.MOCK.MOCK_NOTIFICATIONS = window.MOCK.MOCK_NOTIFICATIONS.map(n => ({ ...n, read: true }));
}
setTick(t => t + 1);
} catch {}
};
const handleDeleteNotif = async (id) => {
try {
await fetch(`${window.MOCK.getApiBase()}/notifications/${id}`, { method: 'DELETE' });
window.MOCK.MOCK_NOTIFICATIONS = window.MOCK.MOCK_NOTIFICATIONS.filter(n => n.id !== id);
setTick(t => t + 1);
} catch {}
};
const handleDeleteAllNotifs = async () => {
try {
await fetch(`${window.MOCK.getApiBase()}/notifications`, { method: 'DELETE' });
window.MOCK.MOCK_NOTIFICATIONS = [];
setTick(t => t + 1);
} catch {}
};
const [profileModalOpen, setProfileModalOpen] = React.useState(false);
const [profileForm, setProfileForm] = React.useState({
name: user.name || '',
avatar: user.avatar || '',
password: user.password || '123456',
confirmPassword: user.password || '123456'
});
const [profileError, setProfileError] = React.useState('');
const [profileSuccess, setProfileSuccess] = React.useState(false);
React.useEffect(() => {
if (profileModalOpen) {
setProfileForm({
name: user.name || '',
avatar: user.avatar || '',
password: user.password || '123456',
confirmPassword: user.password || '123456'
});
setProfileError('');
setProfileSuccess(false);
}
}, [profileModalOpen, user]);
const handlePhotoChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setProfileForm(prev => ({ ...prev, avatar: reader.result }));
};
reader.readAsDataURL(file);
}
};
const handleSaveProfile = async () => {
if (!profileForm.name.trim()) {
setProfileError('El nombre completo no puede estar vacío.');
return;
}
if (profileForm.password !== profileForm.confirmPassword) {
setProfileError('Las contraseñas no coinciden.');
return;
}
let finalAvatar = profileForm.avatar;
if (!finalAvatar.trim()) {
finalAvatar = profileForm.name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2);
}
try {
const updated = await window.MOCK.updateUser(user.id, {
name: profileForm.name,
avatar: finalAvatar,
password: profileForm.password,
});
const updatedUserObj = { ...user, ...updated };
localStorage.setItem('vencidos_user', JSON.stringify(updatedUserObj));
window.MOCK.addAuditEntry('Editó perfil', `Actualizó sus datos de perfil (nombre/avatar)`, profileForm.name, user.role, finalAvatar);
setProfileSuccess(true);
setTimeout(() => { setProfileModalOpen(false); setProfileSuccess(false); }, 1000);
} catch (e) {
setProfileError('Error al guardar: ' + e.message);
}
};
React.useEffect(() => {
const onResize = () => setIsMobile(window.innerWidth < 769);
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
const ss = {
layout: {
display: 'flex', height: '100vh', width: '100vw', overflow: 'hidden'
},
sidebar: {
width: 'var(--sidebar-width)', height: '100vh', flexShrink: 0,
background: 'var(--sidebar-bg)', borderRight: '1px solid var(--border-color)',
display: isMobile ? 'none' : 'flex', flexDirection: 'column',
transition: 'all var(--transition-base)'
},
sidebarLogo: {
padding: '24px 20px 20px', display: 'flex', alignItems: 'center', gap: 12
},
logoMark: {
width: 36, height: 36, borderRadius: 10,
background: 'var(--accent)', display: 'flex',
alignItems: 'center', justifyContent: 'center', flexShrink: 0
},
logoText: { fontSize: 18, fontWeight: 800, letterSpacing: '-0.03em' },
logoSub: { fontSize: 11, color: 'var(--text-tertiary)', fontWeight: 500 },
nav: { flex: 1, padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: 2 },
navItem: (active) => ({
display: 'flex', alignItems: 'center', gap: 10,
padding: '10px 12px', borderRadius: 'var(--radius-md)',
background: active ? 'var(--sidebar-active-bg)' : 'transparent',
color: active ? 'var(--accent)' : 'var(--text-secondary)',
fontWeight: active ? 600 : 500, fontSize: 14,
cursor: 'pointer', transition: 'all var(--transition-fast)',
border: 'none', width: '100%', textAlign: 'left',
fontFamily: 'var(--font-body)'
}),
sidebarFooter: {
padding: '16px 12px', borderTop: '1px solid var(--border-color)',
display: 'flex', flexDirection: 'column', gap: 4
},
userCard: {
display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
cursor: 'pointer', borderRadius: 'var(--radius-md)',
transition: 'background var(--transition-fast)',
width: '100%', background: 'transparent', border: 'none',
textAlign: 'left', fontFamily: 'var(--font-body)', color: 'var(--text-primary)'
},
userAvatar: {
width: 32, height: 32, borderRadius: 8,
background: 'var(--accent)', display: 'flex',
alignItems: 'center', justifyContent: 'center',
fontSize: 12, fontWeight: 700, color: 'white',
overflow: 'hidden', flexShrink: 0
},
userName: { fontSize: 13, fontWeight: 600 },
userRole: { fontSize: 11, color: 'var(--text-tertiary)', textTransform: 'capitalize' },
main: {
flex: 1, display: 'flex', flexDirection: 'column',
overflow: 'hidden', position: 'relative'
},
header: {
height: 'var(--header-height)', flexShrink: 0,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '0 24px', borderBottom: '1px solid var(--border-color)',
background: 'var(--bg-glass)', backdropFilter: 'blur(16px)',
position: 'relative', zIndex: 10
},
headerLeft: { display: 'flex', alignItems: 'center', gap: 12 },
headerTitle: { fontSize: 17, fontWeight: 700 },
headerRight: { display: 'flex', alignItems: 'center', gap: 8 },
bellWrap: { position: 'relative' },
bellBadge: {
position: 'absolute', top: -2, right: -2, width: 16, height: 16,
borderRadius: '50%', background: 'var(--status-danger)',
color: 'white', fontSize: 9, fontWeight: 700,
display: 'flex', alignItems: 'center', justifyContent: 'center'
},
content: { flex: 1, overflow: 'auto', position: 'relative' },
mobileNav: {
display: isMobile ? 'flex' : 'none',
position: 'fixed', bottom: 0, left: 0, right: 0,
background: 'var(--bg-glass)', backdropFilter: 'blur(20px)',
borderTop: '1px solid var(--border-color)',
padding: '6px 8px calc(env(safe-area-inset-bottom, 0px) + 6px)',
justifyContent: 'space-around', zIndex: 100
},
mobileNavItem: (active) => ({
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
padding: '6px 12px', borderRadius: 'var(--radius-md)',
color: active ? 'var(--accent)' : 'var(--text-tertiary)',
background: 'transparent', border: 'none', cursor: 'pointer',
fontSize: 10, fontWeight: active ? 600 : 500,
fontFamily: 'var(--font-body)', transition: 'color var(--transition-fast)'
}),
hamburger: {
display: isMobile ? 'flex' : 'none',
background: 'none', border: 'none', color: 'var(--text-primary)',
cursor: 'pointer', padding: 4
}
};
const currentLabel = navItems.find((n) => n.id === currentView)?.label || 'Dashboard';
return (
{/* Sidebar - Desktop */}
{/* Main content */}
{children}
{/* Mobile bottom nav */}
{navItems.map((item) =>
)}
{notifOpen &&
setNotifOpen(false)}
onDelete={handleDeleteNotif}
onDeleteAll={handleDeleteAllNotifs}
canManage={canManageNotifications} />
}
{profileModalOpen && (
setProfileModalOpen(false)}>
e.stopPropagation()}>
Editar perfil
{profileError && (
{profileError}
)}
{profileSuccess && (
¡Perfil actualizado con éxito!
)}
{/* Avatar Selector */}
{profileForm.avatar && (profileForm.avatar.startsWith('data:') || profileForm.avatar.startsWith('http') || profileForm.avatar.startsWith('/')) ? (

) : (
{profileForm.avatar || profileForm.name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2)}
)}
{profileForm.avatar && (profileForm.avatar.startsWith('data:') || profileForm.avatar.startsWith('http') || profileForm.avatar.startsWith('/')) && (
)}
setProfileForm(prev => ({ ...prev, name: e.target.value }))} placeholder="Tu nombre..." />
setProfileForm(prev => ({ ...prev, password: e.target.value }))} placeholder="Nueva contraseña..." />
setProfileForm(prev => ({ ...prev, confirmPassword: e.target.value }))} placeholder="Confirmar contraseña..." />
)}
);
};
const profileModalStyles = {
overlay: {
position: 'fixed', inset: 0, zIndex: 1100, background: 'rgba(0, 0, 0, 0.5)',
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
backdropFilter: 'blur(4px)', animation: 'fadeIn 0.2s ease'
},
modal: {
background: 'var(--card-bg)', border: '1px solid var(--border-color)',
borderRadius: 'var(--radius-lg)', padding: '24px 28px', maxWidth: 360,
width: '100%', boxShadow: 'var(--shadow-xl)', display: 'flex',
flexDirection: 'column', gap: 14, animation: 'scaleIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) both'
},
title: { fontSize: 18, fontWeight: 800, letterSpacing: '-0.02em', display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
avatarSection: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10, margin: '4px 0' },
previewWrap: { position: 'relative', width: 72, height: 72, borderRadius: '50%', background: 'var(--accent)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '2.5px solid var(--border-color)', boxShadow: 'var(--shadow-md)', overflow: 'hidden' },
previewInitials: { fontSize: 24, fontWeight: 700, color: 'white' },
previewImg: { width: '100%', height: '100%', objectFit: 'cover' },
field: { display: 'flex', flexDirection: 'column', gap: 6 },
label: { fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)' },
footer: { display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 10 }
};
window.AppShell = AppShell;
/* ── Custom Select (global) ── */
const _OptionRow = ({ style, isSelected, onClick, label }) => {
const [hover, setHover] = React.useState(false);
return (
setHover(true)} onMouseLeave={() => setHover(false)}>
{label}
{isSelected && (
)}
);
};
const CustomSelect = ({ value, onChange, options, placeholder, style: wrapStyle = {}, active = false }) => {
const [open, setOpen] = React.useState(false);
const [query, setQuery] = React.useState('');
const ref = React.useRef(null);
React.useEffect(() => {
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, []);
const selected = options.find(o => (o.value !== undefined ? o.value : o.label) === value);
const displayLabel = selected ? selected.label : (placeholder || 'Seleccionar...');
const showSearch = options.length > 6;
const shownOptions = query
? options.filter(o => String(o.label).toLowerCase().includes(query.toLowerCase()))
: options;
const cs = {
wrap: { position: 'relative', ...wrapStyle },
trigger: {
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
width: '100%', padding: '10px 14px',
background: open ? 'var(--bg-tertiary)' : active ? 'var(--accent-subtle)' : 'var(--bg-secondary)',
border: `1px solid ${open || active ? 'var(--accent)' : 'var(--border-color)'}`,
borderRadius: open ? 'var(--radius-md) var(--radius-md) 0 0' : 'var(--radius-md)',
boxShadow: open ? '0 0 0 3px var(--accent-subtle)' : 'none',
color: active ? 'var(--accent)' : selected ? 'var(--text-primary)' : 'var(--text-tertiary)',
fontSize: 14, fontWeight: active ? 700 : 400, cursor: 'pointer', transition: 'all 0.15s',
fontFamily: 'var(--font-body)', outline: 'none',
boxSizing: 'border-box',
},
chevron: {
flexShrink: 0, marginLeft: 8, opacity: 0.5,
transition: 'transform 0.2s',
transform: open ? 'rotate(180deg)' : 'rotate(0deg)',
},
dropdown: {
position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 9999,
background: 'var(--bg-secondary)',
border: '1px solid var(--accent)',
borderTop: '1px solid var(--border-color)',
borderRadius: '0 0 var(--radius-md) var(--radius-md)',
boxShadow: '0 8px 24px rgba(0,0,0,0.35)',
maxHeight: 280, overflowY: 'auto',
animation: 'scaleIn 0.12s ease',
},
searchWrap: {
position: 'sticky', top: 0, zIndex: 1,
background: 'var(--bg-secondary)', padding: 8,
borderBottom: '1px solid var(--border-color)',
},
searchInput: {
width: '100%', padding: '7px 10px', fontSize: 13,
borderRadius: 'var(--radius-sm)', border: '1px solid var(--border-color)',
background: 'var(--bg-primary)', color: 'var(--text-primary)',
outline: 'none', boxSizing: 'border-box', fontFamily: 'var(--font-body)',
},
noResults: {
padding: '12px 14px', fontSize: 12,
color: 'var(--text-tertiary)', textAlign: 'center',
},
option: (isSelected, isHover) => ({
padding: '9px 14px', cursor: 'pointer', fontSize: 13,
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
background: isSelected ? 'var(--accent-subtle)' : isHover ? 'var(--bg-hover)' : 'transparent',
color: isSelected ? 'var(--accent)' : 'var(--text-primary)',
fontWeight: isSelected ? 600 : 400,
transition: 'background 0.1s',
borderLeft: isSelected ? '2px solid var(--accent)' : '2px solid transparent',
}),
};
return (
{open && (
{showSearch && (
setQuery(e.target.value)}
onClick={e => e.stopPropagation()} />
)}
{shownOptions.length === 0 && (
Sin resultados
)}
{shownOptions.map((o, i) => {
const val = o.value !== undefined ? o.value : o.label;
const isSelected = val === value;
return (
<_OptionRow key={i} style={cs.option} isSelected={isSelected}
onClick={() => { onChange(val); setOpen(false); setQuery(''); }}
label={o.label} />
);
})}
)}
);
};
window.SvgIcon = SvgIcon;