Files
playzer/apps/frontend/src/features/home/home-page.tsx

179 lines
5.0 KiB
TypeScript

import { Booking } from '@/features/booking/booking';
import { ApiClientError, apiClient } from '@/lib/api-client';
import { useCurrentComplexStore } from '@/lib/stores/current-complex-store';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from '@tanstack/react-router';
import { useEffect } from 'react';
function extractMessage(error: unknown, fallback: string) {
if (error instanceof ApiClientError) {
return error.message || fallback;
}
return fallback;
}
export function HomePage() {
const queryClient = useQueryClient();
const navigate = useNavigate();
const currentComplexQuery = useQuery({
queryKey: ['current-complex'],
queryFn: () => apiClient.complexes.getCurrent(),
});
const { setCurrentComplex } = useCurrentComplexStore();
useEffect(() => {
if (currentComplexQuery.data) {
setCurrentComplex(currentComplexQuery.data.complexSlug);
}
}, [currentComplexQuery.data, setCurrentComplex]);
useEffect(() => {
const unsubscribe = useCurrentComplexStore.subscribe((state, prevState) => {
if (state.currentComplexSlug !== prevState.currentComplexSlug) {
queryClient.invalidateQueries({ queryKey: ['current-complex'] });
queryClient.invalidateQueries({ queryKey: ['admin-bookings'] });
}
});
return unsubscribe;
}, [queryClient]);
const myComplexesQuery = useQuery({
queryKey: ['my-complexes'],
queryFn: () => apiClient.complexes.listMine(),
});
useEffect(() => {
async function autoSelect() {
if (myComplexesQuery.data && myComplexesQuery.data.length === 1) {
try {
const result = await apiClient.complexes.select({
complexId: myComplexesQuery.data[0].id,
});
queryClient.setQueryData(['current-complex'], result);
} catch (error) {
console.error('Error al auto-seleccionar complejo:', error);
}
} else if (
myComplexesQuery.data &&
myComplexesQuery.data.length > 1 &&
!currentComplexQuery.data
) {
navigate({ to: '/select-complex' });
}
}
if (
!currentComplexQuery.isLoading &&
!currentComplexQuery.data &&
myComplexesQuery.data &&
!myComplexesQuery.isLoading
) {
if (myComplexesQuery.data.length === 0) {
navigate({ to: '/onboard/setup' });
return;
}
autoSelect();
}
}, [
currentComplexQuery.isLoading,
currentComplexQuery.data,
myComplexesQuery.data,
myComplexesQuery.isLoading,
navigate,
queryClient,
]);
const selectedComplex = currentComplexQuery.data ?? null;
useEffect(() => {
if (!selectedComplex?.id) return;
const channel = `complex-${selectedComplex.id}`;
let eventSource: EventSource | null = null;
let reconnectTimeout: ReturnType<typeof setTimeout> | null = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 3;
let lastEventId: string | null = null;
let pollInterval: ReturnType<typeof setInterval> | null = null;
const startPolling = () => {
if (pollInterval) return;
pollInterval = setInterval(() => {
void queryClient.invalidateQueries({ queryKey: ['admin-bookings', selectedComplex.id] });
}, 10000);
};
const connect = () => {
if (eventSource) {
eventSource.close();
}
if (reconnectAttempts >= maxReconnectAttempts) {
startPolling();
return;
}
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
const url = `${baseUrl}/api/events/${encodeURIComponent(channel)}`;
eventSource = new EventSource(url);
eventSource.onerror = () => {
if (eventSource?.readyState === EventSource.CLOSED) {
reconnectAttempts++;
reconnectTimeout = setTimeout(() => {
connect();
}, 2000);
}
};
eventSource.addEventListener('message', (event) => {
if (event.data && event.data !== 'connected' && event.data !== 'ping') {
if (event.data !== lastEventId) {
lastEventId = event.data;
reconnectAttempts = 0;
void queryClient.invalidateQueries({
queryKey: ['admin-bookings', selectedComplex.id],
});
}
}
});
};
connect();
return () => {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
if (pollInterval) {
clearInterval(pollInterval);
}
eventSource?.close();
};
}, [selectedComplex?.id, queryClient]);
if (myComplexesQuery.isLoading) {
return <p className="text-sm text-muted-foreground">Cargando panel...</p>;
}
if (myComplexesQuery.isError) {
return (
<p className="text-sm text-destructive">
{extractMessage(myComplexesQuery.error, 'No pudimos cargar tus complejos.')}
</p>
);
}
if (!selectedComplex) {
return <p className="text-sm text-muted-foreground">No tienes complejos asignados todavía.</p>;
}
return <Booking complex={selectedComplex} />;
}