lista de espera: salas consultoria/cabina (toggle + etiqueta) y scroll de WhatsApp
- skeen_citas: campo sala en skeen.lista.espera (consultoria/cabina, indexado) - frontend.py: sala en _lista_espera_to_dict, create y update (toggle desde la etiqueta; valor invalido vuelve a sin sala; cambio queda en historial) - ListaEspera.tsx: toggle Todas/Consultoria/Cabina con conteo, etiqueta clicable en cada tarjeta que alterna de sala, selector en el modal de alta - Agenda.tsx: cambiarSala() conectado al panel - WacrmMessages.tsx: el auto-refresco ya no empuja el scroll abajo si el usuario esta leyendo mensajes anteriores (solo baja al cambiar de conversacion, por deep-link o al enviar respuesta propia) Verificacion API: 7/7 tests OK (alta con sala, toggle, valor invalido, GET)
This commit is contained in:
@@ -939,8 +939,20 @@ const Agenda: FC = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const quitarDeListaEspera = async (entry: ListaEspera) => {
|
||||
if (!window.confirm(`¿Quitar a ${entry.patient} de la lista de espera?`)) return;
|
||||
// Toggle de sala en la lista de espera: Consultoría <-> Cabina
|
||||
const cambiarSala = async (entry: ListaEspera, sala: string) => {
|
||||
try {
|
||||
await odooApi.updateListaEspera(entry.id, { sala });
|
||||
const etiqueta = sala === 'consultoria' ? 'Consultoría' : sala === 'cabina' ? 'Cabina' : 'Sin sala';
|
||||
toast.success(`${entry.patient}: ${etiqueta}`);
|
||||
await recargarListaEspera();
|
||||
} catch (err) {
|
||||
const msg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
||||
toast.error(msg || 'No se pudo cambiar la sala');
|
||||
}
|
||||
};
|
||||
|
||||
const quitarDeListaEspera = async (entry: ListaEspera) => { if (!window.confirm(`¿Quitar a ${entry.patient} de la lista de espera?`)) return;
|
||||
try {
|
||||
await odooApi.updateListaEspera(entry.id, { state: 'descartado' });
|
||||
toast.success('Paciente descartado de la lista de espera');
|
||||
@@ -2032,6 +2044,7 @@ const Agenda: FC = () => {
|
||||
loading={loading}
|
||||
loadingAgendaId={listaAgendando}
|
||||
puedeGestionar={puedeAgendar}
|
||||
onChangeSala={(e, sala) => void cambiarSala(e, sala)}
|
||||
onAgendar={agendarDesdeLista}
|
||||
onQuitar={(e) => void quitarDeListaEspera(e)}
|
||||
onAdd={() => { setListaPrefill({ doctorId: doctorFilter || undefined }); setListaModalOpen(true); }}
|
||||
|
||||
@@ -26,6 +26,9 @@ const WacrmMessages: FC = () => {
|
||||
const [loadingOlder, setLoadingOlder] = useState(false);
|
||||
const threadRef = useRef<HTMLDivElement | null>(null);
|
||||
const convIdRef = useRef<string | null>(null);
|
||||
// ¿El usuario está viendo el final del hilo? Si subió a leer mensajes
|
||||
// antiguos, el auto-refresco NO debe regresarlo abajo.
|
||||
const cercaDelFondoRef = useRef(true);
|
||||
const [members, setMembers] = useState<Member[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
@@ -109,6 +112,14 @@ const WacrmMessages: FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Registra si el lector está al final del hilo (tolerancia de 150 px).
|
||||
// Es lo que decide si el auto-refresco puede hacer scroll o no.
|
||||
const handleThreadScroll = () => {
|
||||
const el = threadRef.current;
|
||||
if (!el) return;
|
||||
cercaDelFondoRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 150;
|
||||
};
|
||||
|
||||
// Historial de la conversación, leído del origen (Supabase) vía el proxy,
|
||||
// paginado: página 1 = últimos 50; con "Cargar mensajes anteriores" se
|
||||
// anteponen páginas más antiguas conservando la posición del scroll.
|
||||
@@ -118,6 +129,8 @@ const WacrmMessages: FC = () => {
|
||||
const el = threadRef.current;
|
||||
const prevHeight = keepScroll && el ? el.scrollHeight : 0;
|
||||
const prevTop = keepScroll && el ? el.scrollTop : 0;
|
||||
// ¿Es la primera carga de ESTA conversación? (cambio de conversación)
|
||||
const esCambio = convIdRef.current !== conversationId;
|
||||
const res = await odooApi.getWacrmConversationMessages(conversationId, page, 50);
|
||||
if (res.status === 'success') {
|
||||
convIdRef.current = conversationId;
|
||||
@@ -129,7 +142,11 @@ const WacrmMessages: FC = () => {
|
||||
const minTs = res.messages.length ? res.messages[0].created_at : '';
|
||||
const older = messages.filter((m) => !known.has(m.external_id) && (m.created_at ?? '') < (minTs ?? ''));
|
||||
setMessages([...older, ...res.messages]);
|
||||
if (convIdRef.current === conversationId) scrollThreadToBottom();
|
||||
// Solo baja al final si cambió de conversación o el usuario ya estaba
|
||||
// abajo; si estaba leyendo mensajes anteriores, se respeta su posición.
|
||||
if ((esCambio || cercaDelFondoRef.current) && convIdRef.current === conversationId) {
|
||||
scrollThreadToBottom();
|
||||
}
|
||||
} else {
|
||||
const prev = messages;
|
||||
const known = new Set(prev.map((m) => m.external_id));
|
||||
@@ -209,6 +226,7 @@ const WacrmMessages: FC = () => {
|
||||
const found = conversations.find((c) => c.external_id === convParam);
|
||||
if (found) {
|
||||
userSelectedRef.current = true;
|
||||
cercaDelFondoRef.current = true;
|
||||
setSelected(found);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -285,6 +303,8 @@ const WacrmMessages: FC = () => {
|
||||
|
||||
const handleSelect = useCallback((c: WacrmConversation) => {
|
||||
userSelectedRef.current = true;
|
||||
// Conversación nueva: arranca viendo el final (permite el auto-scroll)
|
||||
cercaDelFondoRef.current = true;
|
||||
setSelected(c);
|
||||
setDetalleTab('mensajes');
|
||||
}, []);
|
||||
@@ -312,6 +332,8 @@ const WacrmMessages: FC = () => {
|
||||
});
|
||||
toast.success('Mensaje enviado');
|
||||
setReplyText('');
|
||||
// Tu propio mensaje siempre se ve: baja al final aunque estuvieras arriba
|
||||
cercaDelFondoRef.current = true;
|
||||
await loadMessages(selected.external_id);
|
||||
await loadConversations();
|
||||
} catch (err) {
|
||||
@@ -636,7 +658,7 @@ const WacrmMessages: FC = () => {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div ref={threadRef} className="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<div ref={threadRef} onScroll={handleThreadScroll} className="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
{messages.length > 0 && msgHasMore && (
|
||||
<div className="text-center">
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user