Update
This commit is contained in:
528
client/App.js
528
client/App.js
@@ -1,286 +1,300 @@
|
||||
// App.js
|
||||
import { $, watch, h, when } from "sigpro";
|
||||
import {
|
||||
Navbar,
|
||||
Drawer,
|
||||
DrawerToggle,
|
||||
DrawerContent,
|
||||
DrawerSide,
|
||||
DrawerOverlay,
|
||||
Menu,
|
||||
Tabs,
|
||||
Swap,
|
||||
SwapToggle,
|
||||
SwapOn,
|
||||
SwapOff,
|
||||
Icon,
|
||||
Avatar,
|
||||
Dropdown,
|
||||
DropdownButton,
|
||||
DropdownContent,
|
||||
Modal,
|
||||
Fieldset,
|
||||
Input,
|
||||
Button
|
||||
Navbar,
|
||||
Drawer,
|
||||
DrawerToggle,
|
||||
DrawerContent,
|
||||
DrawerSide,
|
||||
DrawerOverlay,
|
||||
Menu,
|
||||
MenuTitle,
|
||||
MenuItem,
|
||||
Tabs,
|
||||
Tab,
|
||||
Swap,
|
||||
SwapToggle,
|
||||
SwapOn,
|
||||
SwapOff,
|
||||
Icon,
|
||||
Avatar,
|
||||
Dropdown,
|
||||
DropdownButton,
|
||||
DropdownContent,
|
||||
Modal,
|
||||
ModalBox,
|
||||
ModalClose,
|
||||
ModalAction,
|
||||
ModalBackdrop,
|
||||
Fieldset,
|
||||
Input,
|
||||
LabelFloating,
|
||||
Button,
|
||||
Loading
|
||||
} from "sigpro-ui";
|
||||
import 'sigpro-ui/css';
|
||||
|
||||
import { Desktop } from "./tabs/Desktop.js";
|
||||
import { ModalSearch } from "./components/ModalSearch.js";
|
||||
|
||||
export const isDark = $(false, "theme-mode");
|
||||
|
||||
|
||||
export const App = () => {
|
||||
// Tema oscuro/claro
|
||||
|
||||
const showSearchModal = $(false);
|
||||
|
||||
// Tema oscuro/claro
|
||||
watch(isDark, (dark) => {
|
||||
document.documentElement.setAttribute("data-theme", dark ? "dark" : "light");
|
||||
});
|
||||
document.documentElement.setAttribute("data-theme", isDark() ? "dark" : "light");
|
||||
|
||||
watch(isDark, (dark) => {
|
||||
document.documentElement.setAttribute("data-theme", dark ? "dark" : "light");
|
||||
});
|
||||
|
||||
// Activar tema inicial
|
||||
document.documentElement.setAttribute("data-theme", isDark() ? "dark" : "light");
|
||||
|
||||
// Estado de login persistente
|
||||
const logged = $(false, "logged");
|
||||
|
||||
// Estado para buscador
|
||||
const searchQuery = $("");
|
||||
|
||||
// Estado para modal de login
|
||||
const showLoginModal = $(false);
|
||||
const loginForm = {
|
||||
username: $(""),
|
||||
password: $("")
|
||||
};
|
||||
|
||||
// Pestañas: la primera (Escritorio) no es cerrable
|
||||
const tabs = $([
|
||||
{
|
||||
label: "Escritorio",
|
||||
content: () => Desktop,
|
||||
closable: false
|
||||
}
|
||||
]);
|
||||
const activeTab = $(0);
|
||||
const openDrawer = $(false);
|
||||
|
||||
// Elementos del menú en el drawer
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Clientes",
|
||||
children: [
|
||||
{ label: "Buscar Cliente", onclick: () => openTab("Clientes") },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Recibos",
|
||||
children: [
|
||||
{ label: "Buscar Recibo" },
|
||||
{ label: "Pendientes" },
|
||||
{ label: "Extornos" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Polizas",
|
||||
children: [
|
||||
{ label: "Buscar Póliza", onclick: () => openTab("Polizas") },
|
||||
{ label: "Nueva producción", onclick: () => openTab("Polizas") },
|
||||
{ label: "Anulaciones", onclick: () => openTab("Polizas") },
|
||||
{ label: "Renovación Cartera", onclick: () => openTab("Polizas") }
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
label: "Comercial",
|
||||
children: [
|
||||
{ label: "Oportunidades" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Siniestros",
|
||||
children: [
|
||||
{ label: "Nuevo Siniestro" },
|
||||
{ label: "Buscar Siniestro" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Soporte",
|
||||
children: [
|
||||
{ label: "Tickets" },
|
||||
{ label: "Reportes" },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Referencia al contenedor de pestañas para manejar el foco
|
||||
let tabsContainerRef = null;
|
||||
let drawerToggleRef = null;
|
||||
|
||||
// Abre o crea una pestaña, cierra el drawer
|
||||
const openTab = (label) => {
|
||||
const currentTabs = tabs();
|
||||
|
||||
if (currentTabs.length >= 15) return;
|
||||
|
||||
const newTab = {
|
||||
label,
|
||||
content: () => `¡Bienvenido al escritorio!`,
|
||||
closable: true
|
||||
// Estado de login persistente
|
||||
const logged = $(false, "logged");
|
||||
const showLoginModal = $(false);
|
||||
const loginForm = {
|
||||
username: $(""),
|
||||
password: $("")
|
||||
};
|
||||
tabs([...currentTabs, newTab]);
|
||||
activeTab(tabs().length - 1);
|
||||
|
||||
closeDrawer();
|
||||
};
|
||||
// Pestañas: la primera (Escritorio) no es cerrable
|
||||
const tabs = $([
|
||||
{
|
||||
label: "Escritorio",
|
||||
content: () => Desktop,
|
||||
closable: false
|
||||
}
|
||||
]);
|
||||
const activeTab = $(0);
|
||||
const openDrawer = $(false);
|
||||
|
||||
const closeDrawer = () => {
|
||||
openDrawer(false);
|
||||
if (drawerToggleRef) drawerToggleRef.checked = false;
|
||||
};
|
||||
// Elementos del menú en el drawer
|
||||
const menuItems = [
|
||||
{
|
||||
label: "Clientes",
|
||||
children: [
|
||||
{ label: "Buscar Cliente", onclick: () => openTab("Clientes") },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Recibos",
|
||||
children: [
|
||||
{ label: "Buscar Recibo" },
|
||||
{ label: "Pendientes" },
|
||||
{ label: "Extornos" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Polizas",
|
||||
children: [
|
||||
{ label: "Buscar Póliza", onclick: () => openTab("Polizas") },
|
||||
{ label: "Nueva producción", onclick: () => openTab("Polizas") },
|
||||
{ label: "Anulaciones", onclick: () => openTab("Polizas") },
|
||||
{ label: "Renovación Cartera", onclick: () => openTab("Polizas") }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Comercial",
|
||||
children: [
|
||||
{ label: "Oportunidades" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Siniestros",
|
||||
children: [
|
||||
{ label: "Nuevo Siniestro" },
|
||||
{ label: "Buscar Siniestro" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "Soporte",
|
||||
children: [
|
||||
{ label: "Tickets" },
|
||||
{ label: "Reportes" },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Manejo del login
|
||||
const handleLogin = () => {
|
||||
logged(true);
|
||||
showLoginModal(false);
|
||||
loginForm.username("");
|
||||
loginForm.password("");
|
||||
};
|
||||
let tabsContainerRef = null;
|
||||
let drawerToggleRef = null;
|
||||
|
||||
const handleLogout = () => {
|
||||
logged(false);
|
||||
};
|
||||
const openTab = (label) => {
|
||||
const currentTabs = tabs();
|
||||
if (currentTabs.length >= 15) return;
|
||||
const newTab = {
|
||||
label,
|
||||
content: () => `¡Bienvenido a ${label}!`,
|
||||
closable: true
|
||||
};
|
||||
tabs([...currentTabs, newTab]);
|
||||
activeTab(tabs().length - 1);
|
||||
closeDrawer();
|
||||
};
|
||||
|
||||
return [
|
||||
Drawer({}, [
|
||||
// Control oculto del drawer
|
||||
DrawerToggle({
|
||||
id: "app-drawer",
|
||||
ref: (el) => drawerToggleRef = el,
|
||||
checked: openDrawer,
|
||||
onchange: (e) => openDrawer(e.target.checked)
|
||||
}),
|
||||
const closeDrawer = () => {
|
||||
openDrawer(false);
|
||||
if (drawerToggleRef) drawerToggleRef.checked = false;
|
||||
};
|
||||
|
||||
// Contenido principal
|
||||
DrawerContent({}, [
|
||||
Navbar({ class: "bg-base-100 shadow-lg align-center" }, [
|
||||
// Botón hamburguesa
|
||||
div({ class: "flex-none" }, [
|
||||
label({ for: "app-drawer", class: "btn btn-ghost btn-square" }, [
|
||||
Icon({}, "icon-[lucide--menu]")
|
||||
])
|
||||
]),
|
||||
const handleLogin = () => {
|
||||
if (!loginForm.username() || !loginForm.password()) {
|
||||
alert('Por favor, complete todos los campos');
|
||||
return;
|
||||
}
|
||||
logged(true);
|
||||
showLoginModal(false);
|
||||
loginForm.username("");
|
||||
loginForm.password("");
|
||||
};
|
||||
|
||||
// Buscador
|
||||
div({ class: "flex-1 max-w-md mx-4" }, [
|
||||
Input({
|
||||
type: "search",
|
||||
placeholder: "Buscar...",
|
||||
value: searchQuery,
|
||||
left: span({ class: "icon-[lucide--search]" }),
|
||||
oninput: (e) => console.log(e.target.value)
|
||||
})
|
||||
]),
|
||||
const handleLogout = () => {
|
||||
logged(false);
|
||||
};
|
||||
|
||||
// Espaciador central
|
||||
div({ class: "flex-1" }, []),
|
||||
return [
|
||||
Drawer({}, [
|
||||
DrawerToggle({
|
||||
id: "app-drawer",
|
||||
ref: (el) => drawerToggleRef = el,
|
||||
checked: openDrawer,
|
||||
onchange: (e) => openDrawer(e.target.checked)
|
||||
}),
|
||||
|
||||
// Swap para tema claro/oscuro
|
||||
Swap({ class: "text-xl" }, [
|
||||
SwapToggle({ value: isDark, class: "swap-rotate" }),
|
||||
SwapOn({}, span({ class: "icon-[lucide--moon]" })),
|
||||
SwapOff({}, span({ class: "icon-[lucide--sun]" })),
|
||||
]),
|
||||
DrawerContent({}, [
|
||||
Navbar({ class: "bg-base-100 shadow-lg align-center" }, [
|
||||
div({ class: "flex-none" }, [
|
||||
label({ for: "app-drawer", class: "btn btn-ghost btn-square" }, [
|
||||
Icon({}, "icon-[lucide--menu]")
|
||||
])
|
||||
]),
|
||||
Button({ class: "icon-[lucide--search] btn-ghost", onclick: () => showSearchModal(true) }),
|
||||
div({ class: "flex-1" }),
|
||||
Swap({ class: "text-xl" }, [
|
||||
SwapToggle({ value: isDark, class: "swap-rotate" }),
|
||||
SwapOn({}, h('span', { class: "icon-[lucide--moon]" })),
|
||||
SwapOff({}, h('span', { class: "icon-[lucide--sun]" })),
|
||||
]),
|
||||
when(logged,
|
||||
() => Dropdown({ class: "flex-none ml-2 dropdown-bottom dropdown-end" }, [
|
||||
DropdownButton({ class: "btn-circle btn btn-ghost", tabindex: "0", role: "button" }, [
|
||||
Avatar({ class: "placeholder" }, [
|
||||
div({ class: "bg-neutral text-neutral-content w-10 rounded-full" }, [
|
||||
h('span', { class: "text-xl" }, "U")
|
||||
])
|
||||
])
|
||||
]),
|
||||
DropdownContent(
|
||||
{ class: "menu bg-base-100 rounded-box w-52 p-2 shadow" },
|
||||
[
|
||||
Menu({ class: "bg-base-100 max-w-xs w-full" },
|
||||
[
|
||||
MenuItem({ label: "Mi Perfil", onclick: () => openTab("Mi Perfil") }),
|
||||
MenuItem({ label: "Configuración", onclick: () => openTab("Configuración") }),
|
||||
MenuItem({ label: "Cerrar Sesión", onclick: handleLogout }),
|
||||
]
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
() => Button({
|
||||
class: "flex-none ml-2 btn btn-ghost btn-circle relative",
|
||||
onclick: () => showLoginModal(true)
|
||||
}, [
|
||||
Icon({}, "icon-[lucide--user] text-xl"),
|
||||
])
|
||||
)
|
||||
]),
|
||||
|
||||
// Avatar con dropdown o botón de login
|
||||
when(logged,
|
||||
() => Dropdown({ class: "flex-none ml-2 dropdown-bottom dropdown-end" }, [
|
||||
DropdownButton({ class: "btn-circle btn btn-ghost", tabindex: "0", role: "button" }, [
|
||||
div({ class: "w-10 rounded-full flex items-center justify-center" }, [
|
||||
Icon({}, "icon-[lucide--user] text-xl")
|
||||
// Área principal con pestañas
|
||||
div({
|
||||
class: "p-4",
|
||||
ref: (el) => tabsContainerRef = el
|
||||
}, [
|
||||
h('div', { class: 'tabs tabs-box' },
|
||||
() => tabs().map((item, idx) =>
|
||||
Tab({
|
||||
name: "app-tabs",
|
||||
"aria-label": item.label,
|
||||
checked: activeTab() === idx,
|
||||
onchange: () => activeTab(idx),
|
||||
content: item.content,
|
||||
closable: item.closable,
|
||||
tabs,
|
||||
index: idx
|
||||
})
|
||||
)
|
||||
)
|
||||
])
|
||||
]),
|
||||
DropdownContent(
|
||||
{ class: "menu bg-base-100 rounded-box w-52 p-2 shadow" },
|
||||
[
|
||||
Menu({
|
||||
class: "bg-base-100 max-w-xs w-full",
|
||||
items: [
|
||||
{ label: "Mis mensajes", onclick: () => hide() },
|
||||
{ label: "Delete", onclick: () => hide() },
|
||||
{ label: "Cerrar Sesión", onclick: handleLogout },
|
||||
],
|
||||
}),
|
||||
],
|
||||
),
|
||||
]),
|
||||
() => Button({
|
||||
class: "flex-none ml-2 btn btn-ghost btn-circle relative",
|
||||
onclick: () => showLoginModal(true)
|
||||
}, [
|
||||
Icon({}, "icon-[lucide--user] text-xl"),
|
||||
|
||||
DrawerSide({ class: "z-50" }, [
|
||||
DrawerOverlay({ for: "app-drawer" }),
|
||||
div({
|
||||
class: "menu bg-base-200 text-base-content min-h-full w-80 p-4"
|
||||
}, [
|
||||
h('h2', { class: "text-lg font-bold mb-4" }, "Menú"),
|
||||
Menu({ class: "bg-base-200 max-w-xs w-full" },
|
||||
menuItems.flatMap(item => [
|
||||
MenuTitle({}, item.label),
|
||||
...item.children.map(child =>
|
||||
MenuItem({ label: child.label, onclick: child.onclick })
|
||||
)
|
||||
])
|
||||
)
|
||||
])
|
||||
])
|
||||
)
|
||||
]),
|
||||
// Área principal con las pestañas
|
||||
div({
|
||||
class: "p-4",
|
||||
ref: (el) => tabsContainerRef = el
|
||||
}, [
|
||||
Tabs({
|
||||
class: 'tabs-box',
|
||||
items: tabs,
|
||||
activeIndex: activeTab
|
||||
})
|
||||
])
|
||||
]),
|
||||
|
||||
// Lateral del drawer
|
||||
DrawerSide({ class: "z-50" }, [
|
||||
DrawerOverlay({ for: "app-drawer" }),
|
||||
div({
|
||||
class: "menu bg-base-200 text-base-content min-h-full w-80 p-4"
|
||||
}, [
|
||||
h2({ class: "text-lg font-bold mb-4" }, ["Menú"]),
|
||||
Menu({ items: menuItems, class: "bg-base-200 max-w-xs w-full" })
|
||||
])
|
||||
])
|
||||
]),
|
||||
ModalSearch({
|
||||
open: showSearchModal,
|
||||
onSelect: (item) => {
|
||||
console.log("Item seleccionado:", item);
|
||||
showSearchModal(false);
|
||||
}
|
||||
}),
|
||||
|
||||
// Modal de login
|
||||
Modal({
|
||||
open: showLoginModal,
|
||||
actions: [
|
||||
Button({
|
||||
class: "btn btn-ghost",
|
||||
onclick: () => showLoginModal(false)
|
||||
}, "Cancelar"),
|
||||
Button({
|
||||
class: "btn btn-primary",
|
||||
onclick: handleLogin
|
||||
}, "Entrar")
|
||||
]
|
||||
}, [
|
||||
Fieldset({ label: "Iniciar sesión", class: "bg-base-200 border-base-300 rounded-box border gap-3 p-4", }, [
|
||||
Input({
|
||||
class: "w-full",
|
||||
type: "text",
|
||||
label: "Usuario",
|
||||
float: true,
|
||||
placeholder: "Nombre de usuario",
|
||||
value: loginForm.username
|
||||
})
|
||||
,
|
||||
|
||||
Input({
|
||||
class: "w-full",
|
||||
type: "password",
|
||||
label: "Contraseña",
|
||||
float: true,
|
||||
placeholder: "Contraseña",
|
||||
value: loginForm.password
|
||||
})
|
||||
])
|
||||
|
||||
])
|
||||
];
|
||||
// Modal de Login adaptado
|
||||
when(showLoginModal, () =>
|
||||
Modal({ open: true, class: '' }, [
|
||||
ModalBox({}, [
|
||||
ModalClose({ onclick: () => showLoginModal(false) }),
|
||||
h('h3', { class: "text-lg font-bold" }, "Iniciar Sesión"),
|
||||
Fieldset({
|
||||
class: "bg-base-200 border-base-300 rounded-box border gap-3 p-4"
|
||||
}, [
|
||||
LabelFloating({ class: "w-full" }, [
|
||||
Input({
|
||||
class: "w-full",
|
||||
type: "text",
|
||||
placeholder: "Nombre de usuario",
|
||||
value: loginForm.username,
|
||||
oninput: (e) => loginForm.username(e.target.value)
|
||||
}),
|
||||
h('span', {}, "Usuario")
|
||||
]),
|
||||
LabelFloating({ class: "w-full" }, [
|
||||
Input({
|
||||
class: "w-full",
|
||||
type: "password",
|
||||
placeholder: "Contraseña",
|
||||
value: loginForm.password,
|
||||
oninput: (e) => loginForm.password(e.target.value)
|
||||
}),
|
||||
h('span', {}, "Contraseña")
|
||||
])
|
||||
]),
|
||||
ModalAction({}, [
|
||||
Button({
|
||||
class: "btn btn-ghost",
|
||||
onclick: () => showLoginModal(false)
|
||||
}, "Cancelar"),
|
||||
Button({
|
||||
class: "btn btn-primary",
|
||||
onclick: handleLogin
|
||||
}, "Entrar")
|
||||
])
|
||||
]),
|
||||
ModalBackdrop({ onclick: () => showLoginModal(false) })
|
||||
])
|
||||
)
|
||||
];
|
||||
};
|
||||
175
client/components/ModalSearch.js
Normal file
175
client/components/ModalSearch.js
Normal file
@@ -0,0 +1,175 @@
|
||||
// components/ModalSearch.js
|
||||
import { $, watch, h, each } from "sigpro";
|
||||
import { db } from "sigpro/utils";
|
||||
import { Modal, Input, Button, Loading, Table } from "sigpro-ui";
|
||||
|
||||
export const ModalSearch = ({ open, onSelect }) => {
|
||||
const search = $('');
|
||||
const results = $([]);
|
||||
const loading = $(false);
|
||||
const error = $(null);
|
||||
const debounceTimer = $(null);
|
||||
|
||||
// Búsqueda con debounce de 500ms
|
||||
watch(search, (query) => {
|
||||
const currentTimer = debounceTimer();
|
||||
if (currentTimer) {
|
||||
clearTimeout(currentTimer);
|
||||
debounceTimer(null);
|
||||
}
|
||||
|
||||
if (!query || query.length < 3) {
|
||||
results([]);
|
||||
error(null);
|
||||
return;
|
||||
}
|
||||
|
||||
loading(true);
|
||||
error(null);
|
||||
|
||||
const timer = setTimeout(async () => {
|
||||
try {
|
||||
const data = await db('/proxy/3000/api/db/search', { q: query });
|
||||
console.log(data)
|
||||
results(data.results || []);
|
||||
} catch (err) {
|
||||
error(err.message);
|
||||
results([]);
|
||||
} finally {
|
||||
loading(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
debounceTimer(timer);
|
||||
});
|
||||
|
||||
// Columnas de la tabla
|
||||
const columns = [
|
||||
{ label: 'Código', key: 'CodigoPoliza', class: 'font-mono text-xs' },
|
||||
{ label: 'Mediador', key: 'CodigoMediador', class: 'text-xs' },
|
||||
{ label: 'Nombre', key: 'Nombre', class: 'font-medium text-xs' },
|
||||
{ label: 'Apellidos', key: 'Apellidos', class: 'text-xs' },
|
||||
{ label: 'Documento', key: 'Documento', class: 'text-xs' },
|
||||
{ label: 'Riesgo', key: 'Riesgo', class: 'text-xs' },
|
||||
{ label: 'Ramo', key: 'Ramo', class: 'text-xs' },
|
||||
{ label: 'Localidad', key: 'Localidad', class: 'text-xs' },
|
||||
{
|
||||
label: '',
|
||||
key: 'action',
|
||||
class: 'w-0 p-0',
|
||||
render: (item) => Button({
|
||||
class: 'btn btn-ghost btn-xs',
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
handleSelect(item);
|
||||
}
|
||||
}, 'Seleccionar')
|
||||
}
|
||||
];
|
||||
|
||||
const handleSelect = (item) => {
|
||||
onSelect?.(item);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
open(false);
|
||||
search('');
|
||||
results([]);
|
||||
error(null);
|
||||
};
|
||||
|
||||
return Modal({
|
||||
open,
|
||||
title: 'Buscar Pólizas',
|
||||
class: 'max-w-1024px max-h-612px',
|
||||
actions: [
|
||||
Button({
|
||||
class: 'btn btn-ghost',
|
||||
onclick: closeModal
|
||||
}, 'Cerrar')
|
||||
]
|
||||
}, [
|
||||
h('div', { class: 'space-y-4' }, [
|
||||
// Campo de búsqueda
|
||||
Input({
|
||||
type: "search",
|
||||
class: "w-full",
|
||||
placeholder: "Buscar por cualquier campo (mín. 3 caracteres)...",
|
||||
value: search,
|
||||
left: h('span', { class: "icon-[lucide--search]" }),
|
||||
right: () => loading() ?
|
||||
Loading({ class: "loading-sm" }) :
|
||||
(search() ? Button({
|
||||
class: "btn btn-ghost btn-xs btn-circle -mr-2",
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
search('');
|
||||
}
|
||||
}, h('span', { class: "icon-[lucide--x] opacity-50" })) : null),
|
||||
oninput: (e) => search(e.target.value),
|
||||
autofocus: true
|
||||
}),
|
||||
|
||||
// Estado de carga
|
||||
() => loading() ? h('div', { class: 'flex justify-center items-center py-8' }, [
|
||||
Loading({ class: 'loading-md' }),
|
||||
h('span', { class: 'ml-2 text-sm opacity-70' }, 'Buscando...')
|
||||
]) : null,
|
||||
|
||||
// Error
|
||||
() => !loading() && error() ? h('div', { class: 'alert alert-error py-2 px-3 text-sm' }, [
|
||||
h('span', { class: 'icon-[lucide--alert-circle]' }),
|
||||
h('span', {}, error())
|
||||
]) : null,
|
||||
|
||||
// Contador de resultados
|
||||
() => !loading() && !error() && results().length > 0 ?
|
||||
h('div', { class: 'text-xs opacity-60 px-1' },
|
||||
`${results().length} resultado${results().length !== 1 ? 's' : ''}`
|
||||
)
|
||||
: null,
|
||||
|
||||
// Tabla de resultados
|
||||
() => !loading() && !error() && results().length > 0 ?
|
||||
h('div', { class: 'overflow-x-auto max-h-96' }, [
|
||||
h('table', { class: 'table table-xs table-zebra table-pin-rows w-full' }, [
|
||||
h('thead', {}, [
|
||||
h('tr', {}, columns.map(col =>
|
||||
h('th', { class: col.class || '' }, col.label)
|
||||
))
|
||||
]),
|
||||
h('tbody', {},
|
||||
each(results, (item) =>
|
||||
h('tr', {
|
||||
class: 'hover cursor-pointer',
|
||||
onclick: () => handleSelect(item)
|
||||
}, columns.map(col =>
|
||||
h('td', { class: col.class || '' },
|
||||
col.render ? col.render(item) : (item[col.key] || '')
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
])
|
||||
])
|
||||
: null,
|
||||
|
||||
// Sin resultados
|
||||
() => !loading() && !error() && search().length >= 3 && results().length === 0 ?
|
||||
h('div', { class: 'text-center py-8 text-base-content/50' }, [
|
||||
h('span', { class: 'icon-[lucide--search-x] text-2xl mb-2 block' }),
|
||||
h('span', { class: 'text-sm' }, 'No se encontraron resultados')
|
||||
])
|
||||
: null,
|
||||
|
||||
// Estado inicial
|
||||
() => !loading() && !error() && search().length < 3 ?
|
||||
h('div', { class: 'text-center py-8 text-base-content/40 text-sm' },
|
||||
'Escribe al menos 3 caracteres para buscar'
|
||||
)
|
||||
: null
|
||||
])
|
||||
]);
|
||||
};
|
||||
89
client/components/SearchResults.js
Normal file
89
client/components/SearchResults.js
Normal file
@@ -0,0 +1,89 @@
|
||||
// components/SearchResults.js
|
||||
import { h, each } from "../sigpro.js";
|
||||
import { Button, Table, Loading } from "sigpro-ui";
|
||||
|
||||
export const SearchResults = ({ results, loading, error, onSelect }) => {
|
||||
const columns = [
|
||||
{ label: 'Código', key: 'CodigoPoliza', class: 'font-mono text-xs' },
|
||||
{ label: 'Mediador', key: 'CodigoMediador', class: 'text-xs' },
|
||||
{ label: 'Nombre', key: 'Nombre', class: 'font-medium text-xs' },
|
||||
{ label: 'Apellidos', key: 'Apellidos', class: 'text-xs' },
|
||||
{ label: 'Documento', key: 'Documento', class: 'text-xs' },
|
||||
{ label: 'Riesgo', key: 'Riesgo', class: 'text-xs' },
|
||||
{ label: 'Ramo', key: 'Ramo', class: 'text-xs' },
|
||||
{ label: 'Localidad', key: 'Localidad', class: 'text-xs' },
|
||||
{
|
||||
label: '',
|
||||
key: 'action',
|
||||
class: 'w-0 p-0',
|
||||
render: (item) => Button({
|
||||
class: 'btn btn-ghost btn-xs',
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
onSelect?.(item);
|
||||
}
|
||||
}, 'Seleccionar')
|
||||
}
|
||||
];
|
||||
|
||||
return h('div', { class: 'p-3 space-y-2' }, [
|
||||
// Estado de carga
|
||||
() => loading() ? h('div', { class: 'flex justify-center items-center py-6' }, [
|
||||
Loading({ class: 'loading-md' }),
|
||||
h('span', { class: 'ml-2 text-sm opacity-70' }, 'Buscando...')
|
||||
]) : null,
|
||||
|
||||
// Error
|
||||
() => !loading() && error() ? h('div', { class: 'alert alert-error py-2 px-3 text-sm' }, [
|
||||
h('span', { class: 'icon-[lucide--alert-circle]' }),
|
||||
h('span', {}, error())
|
||||
]) : null,
|
||||
|
||||
// Contador de resultados
|
||||
() => !loading() && !error() && results().length > 0 ?
|
||||
h('div', { class: 'text-xs opacity-60 px-1' },
|
||||
`${results().length} resultado${results().length !== 1 ? 's' : ''} encontrado${results().length !== 1 ? 's' : ''}`
|
||||
)
|
||||
: null,
|
||||
|
||||
// Tabla de resultados
|
||||
() => !loading() && !error() && results().length > 0 ?
|
||||
h('div', { class: 'overflow-x-auto w-full' }, [
|
||||
h('table', { class: 'table table-xs table-zebra table-pin-rows w-full' }, [
|
||||
h('thead', {}, [
|
||||
h('tr', {}, columns.map(col =>
|
||||
h('th', { class: col.class || '' }, col.label)
|
||||
))
|
||||
]),
|
||||
h('tbody', {},
|
||||
each(results, (item) =>
|
||||
h('tr', {
|
||||
class: 'hover cursor-pointer',
|
||||
onclick: () => onSelect?.(item)
|
||||
}, columns.map(col =>
|
||||
h('td', { class: col.class || '' },
|
||||
col.render ? col.render(item) : (item[col.key] || '')
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
])
|
||||
])
|
||||
: null,
|
||||
|
||||
// Sin resultados
|
||||
() => !loading() && !error() && results().length === 0 ?
|
||||
h('div', { class: 'text-center py-6 text-base-content/50' }, [
|
||||
h('span', { class: 'icon-[lucide--search-x] text-2xl mb-2 block' }),
|
||||
h('span', { class: 'text-sm' }, 'No se encontraron resultados')
|
||||
])
|
||||
: null,
|
||||
|
||||
// Estado inicial (menos de 3 caracteres)
|
||||
() => !loading() && !error() && results().length === 0 ?
|
||||
h('div', { class: 'text-center py-4 text-base-content/40 text-xs' },
|
||||
'Escribe al menos 3 caracteres para buscar'
|
||||
)
|
||||
: null
|
||||
]);
|
||||
};
|
||||
@@ -10,15 +10,13 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"sigpro": "git+http://gitea:3000/natxocc/sigpro",
|
||||
"sigpro-grid": "git+http://gitea:3000/natxocc/sigpro-grid",
|
||||
"sigpro-ui": "git+http://gitea:3000/natxocc/sigpro-ui"
|
||||
"sigpro": "git+http://gitea:3000/natxocc/sigpro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.470",
|
||||
"@iconify/json": "^2.2.473",
|
||||
"@iconify/tailwind4": "^1.2.3",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"vite": "^8.0.11"
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"vite": "^8.0.13"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,39 @@
|
||||
import { Alert } from "sigpro-ui";
|
||||
import { Grid } from "sigpro-grid";
|
||||
import { isDark} from "../App.js";
|
||||
import { isDark } from "../App.js";
|
||||
import { h, $ } from "sigpro";
|
||||
|
||||
export const Desktop = () => {
|
||||
const test = $(55);
|
||||
const rowData = [
|
||||
{ id: 1, nombre: "Juan Pérez", edad: 28, ciudad: "Madrid", activo: true, fecha: "2024-01-15" },
|
||||
{ id: 2, nombre: "María García", edad: 34, ciudad: "Barcelona", activo: true, fecha: "2024-02-20" },
|
||||
{ id: 3, nombre: "Carlos López", edad: 45, ciudad: "Valencia", activo: false, fecha: "2024-03-10" },
|
||||
{ id: 4, nombre: "Ana Martínez", edad: 23, ciudad: "Sevilla", activo: true, fecha: "2024-04-05" }
|
||||
];
|
||||
|
||||
const detailCellRendererParams = {
|
||||
// provide the Grid Options to use on the Detail Grid
|
||||
detailGridOptions: {
|
||||
columnDefs: [
|
||||
{ field: 'callId' },
|
||||
{ field: 'direction' },
|
||||
{ field: 'number' }
|
||||
]
|
||||
},
|
||||
// get the rows for each Detail Grid
|
||||
getDetailRowData: params => {
|
||||
params.successCallback(params.data.callRecords);
|
||||
}
|
||||
}
|
||||
const columnDefs = [
|
||||
{ field: "id", headerName: "ID", width: 80, filter: 'agMultiColumnFilter' },
|
||||
{ field: "id", headerName: "ID", width: 80, filter: 'agMultiColumnFilter', cellRenderer: "agGroupCellRenderer" },
|
||||
{ field: "nombre", headerName: "Nombre", width: 150, filter: 'agMultiColumnFilter', editable: true },
|
||||
{ field: "edad", headerName: "Edad", width: 100, filter: 'agMultiColumnFilter' },
|
||||
{ field: "ciudad", headerName: "Ciudad", width: 150, filter: 'agMultiColumnFilter' },
|
||||
{
|
||||
field: "activo",
|
||||
headerName: "Activo",
|
||||
width: 100,
|
||||
{
|
||||
field: "activo",
|
||||
headerName: "Activo",
|
||||
width: 100,
|
||||
filter: 'agMultiColumnFilter',
|
||||
cellRenderer: (params) => params.value ? "✅ Sí" : "❌ No"
|
||||
},
|
||||
@@ -28,6 +43,10 @@ export const Desktop = () => {
|
||||
// ✅ GridOptions completamente actualizado
|
||||
const gridOptions = {
|
||||
columnDefs: columnDefs,
|
||||
detailCellRendererParams: detailCellRendererParams,
|
||||
//aggrid enable context menu
|
||||
contextMenu: true,
|
||||
masterDetail: true,
|
||||
// 1. CORREGIDO: rowSelection con la sintaxis nueva
|
||||
rowSelection: {
|
||||
mode: "multiRow", // o "singleRow" para selección simple
|
||||
@@ -57,16 +76,19 @@ export const Desktop = () => {
|
||||
console.log("Filas seleccionadas:", selectedRows.length);
|
||||
};
|
||||
|
||||
return Grid({
|
||||
data: rowData,
|
||||
options: gridOptions,
|
||||
on: {
|
||||
onGridReady: onGridReady,
|
||||
onCellClicked: onCellClicked,
|
||||
onSelectionChanged: onSelectionChanged
|
||||
},
|
||||
class: "my-grid",
|
||||
style: "height: 800px; width: 100%;",
|
||||
dark: isDark()
|
||||
});
|
||||
return div([
|
||||
button({class: "btn", onclick: () => console.log(test(test()+1))}, ()=>test()),
|
||||
Grid({
|
||||
data: rowData,
|
||||
options: gridOptions,
|
||||
on: {
|
||||
onGridReady: onGridReady,
|
||||
onCellClicked: onCellClicked,
|
||||
onSelectionChanged: onSelectionChanged
|
||||
},
|
||||
class: "my-grid",
|
||||
style: "height: 800px; width: 100%;",
|
||||
dark: isDark()
|
||||
})
|
||||
])
|
||||
};
|
||||
Reference in New Issue
Block a user