|
|
@@ -1,5 +1,5 @@
|
|
|
-import { Button } from '@/components/ui/button.js';
|
|
|
import { useComponentRegistry } from '@/framework/internal/component-registry/component-registry.js';
|
|
|
+import { DataTableColumnHeader } from '@/framework/internal/data-table/data-table-column-header.js';
|
|
|
import { DataTable } from '@/framework/internal/data-table/data-table.js';
|
|
|
import {
|
|
|
getListQueryFields,
|
|
|
@@ -10,10 +10,15 @@ import { api } from '@/graphql/api.js';
|
|
|
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
import { AnyRoute, AnyRouter, useNavigate } from '@tanstack/react-router';
|
|
|
-import { createColumnHelper, SortingState, Table } from '@tanstack/react-table';
|
|
|
+import {
|
|
|
+ ColumnFiltersState,
|
|
|
+ ColumnSort,
|
|
|
+ createColumnHelper,
|
|
|
+ SortingState,
|
|
|
+ Table,
|
|
|
+} from '@tanstack/react-table';
|
|
|
import { ColumnDef } from '@tanstack/table-core';
|
|
|
import { ResultOf } from 'gql.tada';
|
|
|
-import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';
|
|
|
import React from 'react';
|
|
|
|
|
|
type ListQueryFields<T extends TypedDocumentNode> = {
|
|
|
@@ -39,6 +44,7 @@ export interface ListPageProps<T extends TypedDocumentNode<U>, U extends ListQue
|
|
|
title: string;
|
|
|
listQuery: T;
|
|
|
customizeColumns?: CustomizeColumnConfig<T>;
|
|
|
+ // TODO: not yet implemented
|
|
|
defaultColumnOrder?: (keyof ListQueryFields<T>)[];
|
|
|
defaultVisibility?: Partial<Record<keyof ListQueryFields<T>, boolean>>;
|
|
|
route: AnyRoute;
|
|
|
@@ -58,16 +64,25 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
page: routeSearch.page ? parseInt(routeSearch.page) : 1,
|
|
|
itemsPerPage: routeSearch.perPage ? parseInt(routeSearch.perPage) : 10,
|
|
|
};
|
|
|
- const sorting = routeSearch.sort;
|
|
|
- const sort = (sorting?.split(',') || [])?.reduce((acc: any, sort: string) => {
|
|
|
- const direction = sort.startsWith('-') ? 'DESC' : 'ASC';
|
|
|
- const field = sort.replace(/^-/, '');
|
|
|
+ const sorting: SortingState = (routeSearch.sort ?? '').split(',').map((s: string) => {
|
|
|
+ return {
|
|
|
+ id: s.replace(/^-/, ''),
|
|
|
+ desc: s.startsWith('-'),
|
|
|
+ };
|
|
|
+ });
|
|
|
+ const sort = sorting?.reduce((acc: any, sort: ColumnSort) => {
|
|
|
+ const direction = sort.desc ? 'DESC' : 'ASC';
|
|
|
+ const field = sort.id;
|
|
|
|
|
|
if (!field || !direction) {
|
|
|
return acc;
|
|
|
}
|
|
|
return { ...acc, [field]: direction };
|
|
|
}, {});
|
|
|
+ const columnFilters = routeSearch.filters;
|
|
|
+ const filter = columnFilters?.length
|
|
|
+ ? { _and: (routeSearch.filters as ColumnFiltersState).map(f => ({ [f.id]: f.value })) }
|
|
|
+ : undefined;
|
|
|
const { data } = useQuery({
|
|
|
queryFn: () =>
|
|
|
api.query(listQuery, {
|
|
|
@@ -75,9 +90,10 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
take: pagination.itemsPerPage,
|
|
|
skip: (pagination.page - 1) * pagination.itemsPerPage,
|
|
|
sort,
|
|
|
+ filter,
|
|
|
},
|
|
|
}),
|
|
|
- queryKey: ['ListPage', route.id, pagination, sorting],
|
|
|
+ queryKey: ['ListPage', route.id, pagination, sorting, filter],
|
|
|
});
|
|
|
const fields = getListQueryFields(listQuery);
|
|
|
const queryName = getQueryName(listQuery);
|
|
|
@@ -87,7 +103,9 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
const customConfig = customizeColumns?.[field.name as keyof ListQueryFields<T>] ?? {};
|
|
|
const { header, ...customConfigRest } = customConfig;
|
|
|
return columnHelper.accessor(field.name as any, {
|
|
|
- meta: { type: field.type },
|
|
|
+ meta: { field },
|
|
|
+ enableColumnFilter: field.isScalar,
|
|
|
+ enableSorting: field.isScalar,
|
|
|
cell: ({ cell }) => {
|
|
|
const value = cell.getValue();
|
|
|
if (field.list && Array.isArray(value)) {
|
|
|
@@ -95,7 +113,7 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
}
|
|
|
let Cmp: React.ComponentType<{ value: any }> | undefined = undefined;
|
|
|
if ((field.type === 'DateTime' && typeof value === 'string') || value instanceof Date) {
|
|
|
- Cmp = getComponent('dateTime.display');
|
|
|
+ Cmp = getComponent('boolean.display');
|
|
|
}
|
|
|
if (field.type === 'Boolean') {
|
|
|
Cmp = getComponent('boolean.display');
|
|
|
@@ -110,37 +128,7 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
return value;
|
|
|
},
|
|
|
header: headerContext => {
|
|
|
- const column = headerContext.column;
|
|
|
- // By default, we only enable sorting on scalar type fields,
|
|
|
- // unless explicitly configured otherwise.
|
|
|
- const isSortable = customConfig.enableSorting || field.isScalar;
|
|
|
-
|
|
|
- const customHeader = customConfig.header;
|
|
|
- let display = field.name;
|
|
|
- if (typeof customHeader === 'function') {
|
|
|
- display = customHeader(headerContext);
|
|
|
- } else if (typeof customHeader === 'string') {
|
|
|
- display = customHeader;
|
|
|
- }
|
|
|
-
|
|
|
- const columSort = column.getIsSorted();
|
|
|
- const nextSort = columSort === 'asc' ? true : columSort === 'desc' ? undefined : false;
|
|
|
- return (
|
|
|
- <>
|
|
|
- {display}
|
|
|
- {isSortable && (
|
|
|
- <Button variant="ghost" onClick={() => column.toggleSorting(nextSort)}>
|
|
|
- {columSort === 'desc' ? (
|
|
|
- <ArrowUp />
|
|
|
- ) : columSort === 'asc' ? (
|
|
|
- <ArrowDown />
|
|
|
- ) : (
|
|
|
- <ArrowUpDown className="opacity-30" />
|
|
|
- )}
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- </>
|
|
|
- );
|
|
|
+ return <DataTableColumnHeader headerContext={headerContext} customConfig={customConfig} />;
|
|
|
},
|
|
|
...customConfigRest,
|
|
|
});
|
|
|
@@ -153,25 +141,26 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
...(defaultVisibility ?? {}),
|
|
|
};
|
|
|
|
|
|
+ function sortToString(sortingStates?: SortingState) {
|
|
|
+ return sortingStates?.map(s => `${s.desc ? '-' : ''}${s.id}`).join(',');
|
|
|
+ }
|
|
|
+
|
|
|
function persistListStateToUrl(
|
|
|
table: Table<any>,
|
|
|
listState: {
|
|
|
page?: number;
|
|
|
perPage?: number;
|
|
|
sort?: SortingState;
|
|
|
+ filters?: ColumnFiltersState;
|
|
|
},
|
|
|
) {
|
|
|
const tableState = table.getState();
|
|
|
const page = listState.page || tableState.pagination.pageIndex + 1;
|
|
|
const perPage = listState.perPage || tableState.pagination.pageSize;
|
|
|
-
|
|
|
- function sortToString(sortingStates?: SortingState) {
|
|
|
- return sortingStates?.map(s => `${s.desc ? '-' : ''}${s.id}`).join(',');
|
|
|
- }
|
|
|
-
|
|
|
const sort = sortToString(listState.sort ?? tableState.sorting);
|
|
|
+ const filters = listState.filters ?? tableState.columnFilters;
|
|
|
navigate({
|
|
|
- search: () => ({ sort, page, perPage }) as never,
|
|
|
+ search: () => ({ sort, page, perPage, filters: filters.length ? filters : undefined }) as never,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -183,6 +172,8 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
data={(data as any)?.[queryName]?.items ?? []}
|
|
|
page={pagination.page}
|
|
|
itemsPerPage={pagination.itemsPerPage}
|
|
|
+ sorting={sorting}
|
|
|
+ columnFilters={columnFilters}
|
|
|
totalItems={(data as any)?.[queryName]?.totalItems ?? 0}
|
|
|
onPageChange={(table, page, perPage) => {
|
|
|
persistListStateToUrl(table, { page, perPage });
|
|
|
@@ -190,6 +181,10 @@ export function ListPage<T extends TypedDocumentNode<U>, U extends Record<string
|
|
|
onSortChange={(table, sorting) => {
|
|
|
persistListStateToUrl(table, { sort: sorting });
|
|
|
}}
|
|
|
+ onFilterChange={(table, filters) => {
|
|
|
+ console.log('filters', filters);
|
|
|
+ persistListStateToUrl(table, { filters });
|
|
|
+ }}
|
|
|
defaultColumnVisibility={columnVisibility}
|
|
|
></DataTable>
|
|
|
</div>
|