umami/src/components/common/LoadingPanel.tsx
Arthur Sepiol ef9a382cdd Add real-time session updates via Server-Sent Events
Implements push-based real-time updates for the Sessions page. New sessions now appear instantly without manual reload or polling.

Changes:
- Add SSE event emitter for session creation notifications
- Create SSE stream endpoint at /api/websites/[id]/sessions/stream
- Emit session events in tracking endpoint when sessions are created
- Add useSessionStream hook to connect to SSE and invalidate queries
- Fix LoadingPanel to prevent flicker during background refetches
2025-12-10 16:02:44 +03:00

73 lines
1.7 KiB
TypeScript

import { Column, type ColumnProps, Loading } from '@umami/react-zen';
import type { ReactNode } from 'react';
import { Empty } from '@/components/common/Empty';
import { ErrorMessage } from '@/components/common/ErrorMessage';
export interface LoadingPanelProps extends ColumnProps {
data?: any;
error?: unknown;
isEmpty?: boolean;
isLoading?: boolean;
isFetching?: boolean;
loadingIcon?: 'dots' | 'spinner';
loadingPlacement?: 'center' | 'absolute' | 'inline';
renderEmpty?: () => ReactNode;
children: ReactNode;
}
export function LoadingPanel({
data,
error,
isEmpty,
isLoading,
isFetching,
loadingIcon = 'dots',
loadingPlacement = 'absolute',
renderEmpty = () => <Empty />,
children,
...props
}: LoadingPanelProps): ReactNode {
const empty = isEmpty ?? checkEmpty(data);
const hasData = data && !empty;
// Show loading only on initial load when no data exists yet
// Don't show loading during background refetches when we already have data
if ((isLoading || isFetching) && !hasData) {
return (
<Column position="relative" height="100%" width="100%" {...props}>
<Loading icon={loadingIcon} placement={loadingPlacement} />
</Column>
);
}
// Show error
if (error) {
return <ErrorMessage />;
}
// Show empty state (once loaded)
if (!error && !isLoading && !isFetching && empty) {
return renderEmpty();
}
// Show content when we have data (even during background refetch)
if (hasData) {
return children;
}
return null;
}
function checkEmpty(data: any) {
if (!data) return false;
if (Array.isArray(data)) {
return data.length <= 0;
}
if (typeof data === 'object') {
return Object.keys(data).length <= 0;
}
return !!data;
}