mirror of
https://github.com/umami-software/umami.git
synced 2026-02-07 22:27:16 +01:00
Unified loading states.
This commit is contained in:
parent
7b5591a3ce
commit
da8c7e99c5
52 changed files with 506 additions and 364 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Loading, SearchField, Row, Column } from '@umami/react-zen';
|
||||
import { SearchField, Row, Column } from '@umami/react-zen';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { Pager } from '@/components/common/Pager';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { PagedQueryResult } from '@/lib/types';
|
||||
|
|
@ -24,16 +23,14 @@ export function DataGrid({
|
|||
allowSearch = true,
|
||||
allowPaging = true,
|
||||
autoFocus,
|
||||
renderEmpty,
|
||||
children,
|
||||
}: DataTableProps) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { result, params, setParams, query } = queryResult || {};
|
||||
const { error, isLoading, isFetched } = query || {};
|
||||
const { error, isLoading, isFetching } = query || {};
|
||||
const { page, pageSize, count, data } = result || {};
|
||||
const { search } = params || {};
|
||||
const hasData = Boolean(!isLoading && data?.length);
|
||||
const noResults = Boolean(search && !hasData);
|
||||
const { router, renderUrl } = useNavigation();
|
||||
|
||||
const handleSearch = (search: string) => {
|
||||
|
|
@ -46,7 +43,7 @@ export function DataGrid({
|
|||
};
|
||||
|
||||
return (
|
||||
<Column gap="4">
|
||||
<Column gap="4" minHeight="300px">
|
||||
{allowSearch && (hasData || search) && (
|
||||
<Row width="280px" alignItems="center">
|
||||
<SearchField
|
||||
|
|
@ -58,12 +55,9 @@ export function DataGrid({
|
|||
/>
|
||||
</Row>
|
||||
)}
|
||||
<LoadingPanel data={data} isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||
<LoadingPanel data={data} isLoading={isLoading} isFetching={isFetching} error={error}>
|
||||
<Column>
|
||||
{hasData ? (typeof children === 'function' ? children(result) : children) : null}
|
||||
{isLoading && <Loading position="page" />}
|
||||
{!isLoading && !hasData && !search && (renderEmpty ? renderEmpty() : <Empty />)}
|
||||
{!isLoading && noResults && <Empty message={formatMessage(messages.noResultsFound)} />}
|
||||
</Column>
|
||||
{allowPaging && hasData && (
|
||||
<Row marginTop="6">
|
||||
|
|
|
|||
|
|
@ -1,32 +1,59 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Spinner, Dots, Column, type ColumnProps } from '@umami/react-zen';
|
||||
import { Loading, Column, type ColumnProps } from '@umami/react-zen';
|
||||
import { ErrorMessage } from '@/components/common/ErrorMessage';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
|
||||
export interface LoadingPanelProps extends ColumnProps {
|
||||
data?: any;
|
||||
error?: Error;
|
||||
isEmpty?: boolean;
|
||||
isLoading?: boolean;
|
||||
isFetching?: boolean;
|
||||
loadingIcon?: 'dots' | 'spinner';
|
||||
renderEmpty?: () => ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function LoadingPanel({
|
||||
data,
|
||||
error,
|
||||
isEmpty,
|
||||
isFetched,
|
||||
isLoading,
|
||||
isFetching,
|
||||
loadingIcon = 'dots',
|
||||
renderEmpty = () => <Empty />,
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
error?: Error;
|
||||
isEmpty?: boolean;
|
||||
isFetched?: boolean;
|
||||
isLoading?: boolean;
|
||||
loadingIcon?: 'dots' | 'spinner';
|
||||
renderEmpty?: () => ReactNode;
|
||||
children: ReactNode;
|
||||
} & ColumnProps) {
|
||||
}: LoadingPanelProps) {
|
||||
const empty = isEmpty ?? checkEmpty(data);
|
||||
|
||||
return (
|
||||
<Column {...props}>
|
||||
{isLoading && !isFetched && (loadingIcon === 'dots' ? <Dots /> : <Spinner />)}
|
||||
<Column position="relative" flexGrow={1} {...props}>
|
||||
{/* Show loading spinner only if no data exists */}
|
||||
{(isLoading || isFetching) && !data && <Loading icon={loadingIcon} position="page" />}
|
||||
|
||||
{/* Show error */}
|
||||
{error && <ErrorMessage />}
|
||||
{!error && !isLoading && isEmpty && renderEmpty()}
|
||||
{!error && !isLoading && !isEmpty && children}
|
||||
|
||||
{/* Show empty state (once loaded) */}
|
||||
{!error && !isLoading && !isFetching && empty && renderEmpty()}
|
||||
|
||||
{/* Show main content when data exists */}
|
||||
{!error && !empty && children}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue