implement datatable sorting on non-analytics tables. (website/board/link/pixel/team/admin)
This commit is contained in:
1 parent
b5c4dbfa56
commit
5ccfb6d0a5
39 files changed
+322
-100
No files matched your search
@@ -41,7 +41,7 @@ export function DataGrid({
|
||||
const { data, error, isLoading, isFetching } = query;
|
||||
const { router, updateParams, query: queryParams } = useNavigation();
|
||||
const [search, setSearch] = useState(queryParams?.search || data?.search || '');
|
||||
const showPager = allowPaging && data && (data.count > data.pageSize || data.isCapped);
|
||||
const showPager = allowPaging && data && data.count > 0;
|
||||
const { isMobile } = useMobile();
|
||||
const displayMode = isMobile ? 'cards' : undefined;
|
||||
|
||||
|
||||
@@ -11,11 +11,18 @@ export interface PagerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Pager({ page, pageSize, count, isCapped, onPageChange }: PagerProps) {
|
||||
export function Pager({
|
||||
page,
|
||||
pageSize,
|
||||
count,
|
||||
isCapped,
|
||||
onPageChange,
|
||||
}: PagerProps) {
|
||||
const { t, labels } = useMessages();
|
||||
const maxPage = pageSize && count ? Math.ceil(+count / +pageSize) : 0;
|
||||
const lastPage = page === maxPage;
|
||||
const firstPage = page === 1;
|
||||
const showNavigation = maxPage > 1 || isCapped;
|
||||
|
||||
if (count === 0 || !maxPage) {
|
||||
return null;
|
||||
@@ -29,34 +36,40 @@ export function Pager({ page, pageSize, count, isCapped, onPageChange }: PagerPr
|
||||
}
|
||||
};
|
||||
|
||||
if (maxPage === 1 && !isCapped) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayCount = isCapped ? `10,000+` : (+count).toLocaleString();
|
||||
|
||||
return (
|
||||
<Row alignItems="center" justifyContent="space-between" gap="3" flexGrow={1}>
|
||||
<Text>{t(labels.numberOfRecords, { x: displayCount })}</Text>
|
||||
<Row alignItems="center" justifyContent="flex-end" gap="3">
|
||||
<Text>
|
||||
{t(labels.pageOf, {
|
||||
current: page.toLocaleString(),
|
||||
total: maxPage.toLocaleString(),
|
||||
})}
|
||||
</Text>
|
||||
<Row gap="1">
|
||||
<Button variant="outline" onPress={() => handlePageChange(-1)} isDisabled={firstPage}>
|
||||
<Icon size="sm" rotate={180}>
|
||||
<ChevronRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button variant="outline" onPress={() => handlePageChange(1)} isDisabled={lastPage}>
|
||||
<Icon size="sm">
|
||||
<ChevronRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
<Row alignItems="center" justifyContent="space-between" gap="3" flexGrow={1} wrap="wrap">
|
||||
<Text color="muted">{t(labels.numberOfRecords, { x: displayCount })}</Text>
|
||||
<Row
|
||||
alignItems="center"
|
||||
justifyContent="flex-end"
|
||||
gap="3"
|
||||
wrap="nowrap"
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
>
|
||||
{showNavigation && (
|
||||
<>
|
||||
<Text color="muted">
|
||||
{t(labels.pageOf, {
|
||||
current: page.toLocaleString(),
|
||||
total: maxPage.toLocaleString(),
|
||||
})}
|
||||
</Text>
|
||||
<Row gap="1">
|
||||
<Button variant="outline" onPress={() => handlePageChange(-1)} isDisabled={firstPage}>
|
||||
<Icon size="sm" rotate={180}>
|
||||
<ChevronRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Button variant="outline" onPress={() => handlePageChange(1)} isDisabled={lastPage}>
|
||||
<Icon size="sm">
|
||||
<ChevronRight />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { Icon } from '@umami/react-zen';
|
||||
import { ChevronDown, ChevronUp } from '@/components/icons';
|
||||
import { useNavigation } from '@/components/hooks';
|
||||
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export interface SortableLabelProps {
|
||||
label: ReactNode;
|
||||
sortKey: string;
|
||||
defaultDirection?: SortDirection;
|
||||
}
|
||||
|
||||
export function SortableLabel({
|
||||
label,
|
||||
sortKey,
|
||||
defaultDirection = 'asc',
|
||||
}: SortableLabelProps) {
|
||||
const { router, query, updateParams } = useNavigation();
|
||||
const isActive = query.orderBy === sortKey;
|
||||
const isDescending = query.sortDescending === 'true';
|
||||
const direction = isActive ? (isDescending ? 'desc' : 'asc') : undefined;
|
||||
const activeColor = 'var(--text-primary)';
|
||||
|
||||
const getNextDirection = (): SortDirection => {
|
||||
if (!isActive) {
|
||||
return defaultDirection;
|
||||
}
|
||||
|
||||
return direction === 'desc' ? 'asc' : 'desc';
|
||||
};
|
||||
|
||||
const handleSort = () => {
|
||||
const nextDirection = getNextDirection();
|
||||
|
||||
router.push(
|
||||
updateParams({
|
||||
orderBy: sortKey,
|
||||
sortDescending: nextDirection === 'desc' ? 'true' : undefined,
|
||||
page: 1,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSort}
|
||||
className="inline-flex appearance-none items-center gap-1 border-0 bg-transparent p-0 text-inherit"
|
||||
aria-pressed={isActive}
|
||||
>
|
||||
<span>{label}</span>
|
||||
<span
|
||||
aria-hidden
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'column',
|
||||
gap: 0,
|
||||
lineHeight: 0,
|
||||
color: 'var(--text-muted)',
|
||||
opacity: 0.8,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: direction === 'asc' ? activeColor : undefined,
|
||||
opacity: direction === 'asc' ? 1 : 0.55,
|
||||
transform: 'scale(0.9)',
|
||||
transformOrigin: 'center',
|
||||
}}
|
||||
>
|
||||
<Icon size="sm" color={direction === 'asc' ? undefined : 'muted'}>
|
||||
<ChevronUp />
|
||||
</Icon>
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
color: direction === 'desc' ? activeColor : undefined,
|
||||
opacity: direction === 'desc' ? 1 : 0.55,
|
||||
marginTop: '-6px',
|
||||
transform: 'scale(0.9)',
|
||||
transformOrigin: 'center',
|
||||
}}
|
||||
>
|
||||
<Icon size="sm" color={direction === 'desc' ? undefined : 'muted'}>
|
||||
<ChevronDown />
|
||||
</Icon>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user