feat: pre-aggregation workspace stats

This commit is contained in:
DarkSky
2026-01-01 05:01:52 +08:00
parent 97507e7043
commit f745f7b669
16 changed files with 722 additions and 245 deletions
@@ -17,10 +17,12 @@ import { useCallback, useTransition } from 'react';
interface DataTablePaginationProps<TData> {
table: Table<TData>;
disabled?: boolean;
}
export function DataTablePagination<TData>({
table,
disabled = false,
}: DataTablePaginationProps<TData>) {
const [, startTransition] = useTransition();
@@ -63,6 +65,7 @@ export function DataTablePagination<TData>({
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={onPageSizeChange}
disabled={disabled}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={table.getState().pagination.pageSize} />
@@ -86,7 +89,7 @@ export function DataTablePagination<TData>({
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={handleFirstPage}
disabled={!table.getCanPreviousPage()}
disabled={disabled || !table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<ChevronsLeftIcon className="h-4 w-4" />
@@ -95,7 +98,7 @@ export function DataTablePagination<TData>({
variant="outline"
className="h-8 w-8 p-0"
onClick={handlePreviousPage}
disabled={!table.getCanPreviousPage()}
disabled={disabled || !table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="h-4 w-4" />
@@ -104,7 +107,7 @@ export function DataTablePagination<TData>({
variant="outline"
className="h-8 w-8 p-0"
onClick={handleNextPage}
disabled={!table.getCanNextPage()}
disabled={disabled || !table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="h-4 w-4" />
@@ -113,7 +116,7 @@ export function DataTablePagination<TData>({
variant="outline"
className="h-8 w-8 p-0"
onClick={handleLastPage}
disabled={!table.getCanNextPage()}
disabled={disabled || !table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<ChevronsRightIcon className="h-4 w-4" />
@@ -27,6 +27,8 @@ interface DataTableProps<TData, TValue> {
totalCount: number;
pagination: PaginationState;
onPaginationChange: OnChangeFn<PaginationState>;
loading?: boolean;
disablePagination?: boolean;
// Row Selection
rowSelection?: RowSelectionState;
@@ -51,6 +53,8 @@ export function SharedDataTable<TData extends { id: string }, TValue>({
totalCount,
pagination,
onPaginationChange,
loading = false,
disablePagination = false,
rowSelection,
onRowSelectionChange,
renderToolbar,
@@ -83,9 +87,34 @@ export function SharedDataTable<TData extends { id: string }, TValue>({
});
return (
<div className="flex flex-col gap-4 py-5 px-6 h-full overflow-auto">
<div className="flex flex-col gap-4 py-5 px-6 h-full overflow-auto relative">
{renderToolbar?.(table)}
<div className="rounded-md border h-full flex flex-col overflow-auto">
<div className="rounded-md border h-full flex flex-col overflow-auto relative">
{loading ? (
<div className="absolute inset-0 z-10 bg-gray-50/70 backdrop-blur-[1px] flex flex-col items-center justify-center gap-2 text-sm text-gray-600">
<svg
className="h-5 w-5 animate-spin text-gray-500"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
/>
</svg>
<span>Loading...</span>
</div>
) : null}
<Table>
<TableHeader>
{table.getHeaderGroups().map(headerGroup => (
@@ -160,7 +189,10 @@ export function SharedDataTable<TData extends { id: string }, TValue>({
</Table>
</div>
</div>
<DataTablePagination table={table} />
<DataTablePagination
table={table}
disabled={disablePagination || loading}
/>
</div>
);
}
@@ -14,6 +14,7 @@ type FeatureFilterPopoverProps = {
onChange: (features: FeatureType[]) => void;
align?: 'start' | 'center' | 'end';
buttonLabel?: string;
disabled?: boolean;
};
export const FeatureFilterPopover = ({
@@ -22,29 +23,37 @@ export const FeatureFilterPopover = ({
onChange,
align = 'start',
buttonLabel = 'Features',
disabled = false,
}: FeatureFilterPopoverProps) => {
const handleFeatureToggle = useCallback(
(feature: FeatureType, checked: boolean) => {
if (disabled) {
return;
}
if (checked) {
onChange([...new Set([...selectedFeatures, feature])]);
} else {
onChange(selectedFeatures.filter(enabled => enabled !== feature));
}
},
[onChange, selectedFeatures]
[disabled, onChange, selectedFeatures]
);
const handleClearFeatures = useCallback(() => {
if (disabled) {
return;
}
onChange([]);
}, [onChange]);
}, [disabled, onChange]);
return (
<Popover>
<Popover open={disabled ? false : undefined}>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
className="h-8 px-2 lg:px-3 space-x-1"
disabled={disabled}
>
<span>{buttonLabel}</span>
{selectedFeatures.length > 0 ? (
@@ -70,6 +79,7 @@ export const FeatureFilterPopover = ({
onCheckedChange={checked =>
handleFeatureToggle(feature, !!checked)
}
disabled={disabled}
/>
<span className="text-sm truncate">{feature}</span>
</label>
@@ -80,7 +90,7 @@ export const FeatureFilterPopover = ({
variant="ghost"
size="sm"
onClick={handleClearFeatures}
disabled={selectedFeatures.length === 0}
disabled={disabled || selectedFeatures.length === 0}
>
Clear
</Button>