21
common/resources/client/datatable/page/data-table-context.ts
Executable file
21
common/resources/client/datatable/page/data-table-context.ts
Executable file
@@ -0,0 +1,21 @@
|
||||
import React, {useContext} from 'react';
|
||||
import {GetDatatableDataParams} from '../requests/paginated-resources';
|
||||
import {UseQueryResult} from '@tanstack/react-query';
|
||||
import {PaginatedBackendResponse} from '../../http/backend-response/pagination-response';
|
||||
|
||||
export interface DataTableContextValue<T = unknown, A = unknown> {
|
||||
selectedRows: (string | number)[];
|
||||
setSelectedRows: (keys: (string | number)[]) => void;
|
||||
endpoint: string;
|
||||
params: GetDatatableDataParams;
|
||||
setParams: (value: GetDatatableDataParams) => void;
|
||||
query: UseQueryResult<PaginatedBackendResponse<T> & A, unknown>;
|
||||
}
|
||||
|
||||
export const DataTableContext = React.createContext<DataTableContextValue>(
|
||||
null!,
|
||||
);
|
||||
|
||||
export function useDataTable<T = unknown, A = unknown>() {
|
||||
return useContext(DataTableContext) as DataTableContextValue<T, A>;
|
||||
}
|
||||
42
common/resources/client/datatable/page/data-table-emty-state-message.tsx
Executable file
42
common/resources/client/datatable/page/data-table-emty-state-message.tsx
Executable file
@@ -0,0 +1,42 @@
|
||||
import React, {ReactNode} from 'react';
|
||||
import {IllustratedMessage} from '../../ui/images/illustrated-message';
|
||||
import {SvgImage} from '../../ui/images/svg-image/svg-image';
|
||||
import {Trans} from '../../i18n/trans';
|
||||
import {useIsMobileMediaQuery} from '../../utils/hooks/is-mobile-media-query';
|
||||
|
||||
export interface DataTableEmptyStateMessageProps {
|
||||
isFiltering?: boolean;
|
||||
title: ReactNode;
|
||||
filteringTitle?: ReactNode;
|
||||
image: string;
|
||||
size?: 'sm' | 'md';
|
||||
className?: string;
|
||||
}
|
||||
export function DataTableEmptyStateMessage({
|
||||
isFiltering,
|
||||
title,
|
||||
filteringTitle,
|
||||
image,
|
||||
size,
|
||||
className,
|
||||
}: DataTableEmptyStateMessageProps) {
|
||||
const isMobile = useIsMobileMediaQuery();
|
||||
if (!size) {
|
||||
size = isMobile ? 'sm' : 'md';
|
||||
}
|
||||
|
||||
// allow user to disable filtering message variation by not passing in "filteringTitle"
|
||||
return (
|
||||
<IllustratedMessage
|
||||
className={className}
|
||||
size={size}
|
||||
image={<SvgImage src={image} />}
|
||||
title={isFiltering && filteringTitle ? filteringTitle : title}
|
||||
description={
|
||||
isFiltering && filteringTitle ? (
|
||||
<Trans message="Try another search query or different filters" />
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
53
common/resources/client/datatable/page/data-table-page.tsx
Executable file
53
common/resources/client/datatable/page/data-table-page.tsx
Executable file
@@ -0,0 +1,53 @@
|
||||
import React, {ReactElement, ReactNode, useId} from 'react';
|
||||
import {TableDataItem} from '../../ui/tables/types/table-data-item';
|
||||
import {DataTable, DataTableProps} from '../data-table';
|
||||
import {TableProps} from '../../ui/tables/table';
|
||||
import {StaticPageTitle} from '../../seo/static-page-title';
|
||||
import {MessageDescriptor} from '../../i18n/message-descriptor';
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface Props<T extends TableDataItem> extends DataTableProps<T> {
|
||||
title?: ReactElement<MessageDescriptor>;
|
||||
headerContent?: ReactNode;
|
||||
headerItemsAlign?: string;
|
||||
enableSelection?: boolean;
|
||||
onRowAction?: TableProps<T>['onAction'];
|
||||
padding?: string;
|
||||
className?: string;
|
||||
}
|
||||
export function DataTablePage<T extends TableDataItem>({
|
||||
title,
|
||||
headerContent,
|
||||
headerItemsAlign = 'items-end',
|
||||
className,
|
||||
padding,
|
||||
...dataTableProps
|
||||
}: Props<T>) {
|
||||
const titleId = useId();
|
||||
|
||||
return (
|
||||
<div className={clsx(padding ?? 'p-12 md:p-24', className)}>
|
||||
{title && (
|
||||
<div
|
||||
className={clsx(
|
||||
'mb-16',
|
||||
headerContent && `flex ${headerItemsAlign} gap-4`,
|
||||
)}
|
||||
>
|
||||
<StaticPageTitle>{title}</StaticPageTitle>
|
||||
<h1 className="text-3xl font-light first:capitalize" id={titleId}>
|
||||
{title}
|
||||
</h1>
|
||||
{headerContent}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DataTable
|
||||
{...dataTableProps}
|
||||
tableDomProps={{
|
||||
'aria-labelledby': title ? titleId : undefined,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
53
common/resources/client/datatable/page/delete-selected-items-action.tsx
Executable file
53
common/resources/client/datatable/page/delete-selected-items-action.tsx
Executable file
@@ -0,0 +1,53 @@
|
||||
import {Button} from '../../ui/buttons/button';
|
||||
import {Trans} from '../../i18n/trans';
|
||||
import {ConfirmationDialog} from '../../ui/overlays/dialog/confirmation-dialog';
|
||||
import {DialogTrigger} from '../../ui/overlays/dialog/dialog-trigger';
|
||||
import React from 'react';
|
||||
import {useDeleteSelectedRows} from '../requests/delete-selected-rows';
|
||||
import {useDataTable} from './data-table-context';
|
||||
import {useDialogContext} from '@common/ui/overlays/dialog/dialog-context';
|
||||
import {errorStatusIs} from '@common/utils/http/error-status-is';
|
||||
|
||||
export function DeleteSelectedItemsAction() {
|
||||
return (
|
||||
<DialogTrigger type="modal">
|
||||
<Button variant="flat" color="danger" className="ml-auto">
|
||||
<Trans message="Delete" />
|
||||
</Button>
|
||||
<DeleteItemsDialog />
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
function DeleteItemsDialog() {
|
||||
const deleteSelectedRows = useDeleteSelectedRows();
|
||||
const {selectedRows, setSelectedRows} = useDataTable();
|
||||
const {close} = useDialogContext();
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
isLoading={deleteSelectedRows.isPending}
|
||||
title={
|
||||
<Trans
|
||||
message="Delete [one 1 item|other :count items]?"
|
||||
values={{count: selectedRows.length}}
|
||||
/>
|
||||
}
|
||||
body={
|
||||
<Trans message="This will permanently remove the items and cannot be undone." />
|
||||
}
|
||||
confirm={<Trans message="Delete" />}
|
||||
isDanger
|
||||
onConfirm={() => {
|
||||
deleteSelectedRows.mutate(undefined, {
|
||||
onSuccess: () => close(),
|
||||
onError: err => {
|
||||
if (errorStatusIs(err, 422)) {
|
||||
setSelectedRows([]);
|
||||
close();
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user