import {createContext, useRef, useState, forwardRef, useImperativeHandle} from 'react'; import {__} from '@wordpress/i18n'; import {A11yDialog} from 'react-a11y-dialog'; import A11yDialogInstance from 'a11y-dialog'; import {GiveIcon} from '@givewp/components'; import {ListTable} from '../ListTable'; import Pagination from '../Pagination'; import {Filter, getInitialFilterState} from '../Filters'; import useDebounce from '../hooks/useDebounce'; import {useResetPage} from '../hooks/useResetPage'; import ListTableApi from '../api'; import styles from './ListTablePage.module.scss'; import cx from 'classnames'; import {BulkActionSelect} from '@givewp/components/ListTable/BulkActions/BulkActionSelect'; import ToggleSwitch from '@givewp/components/ListTable/ToggleSwitch'; import DeleteIcon from '@givewp/components/ListTable/ListTablePage/DeleteIcon'; import ListTableStats, { StatConfig } from '../ListTableStats/ListTableStats'; import FilterBy from '../FilterBy'; export interface ListTablePageProps { //required title: string; apiSettings: {apiRoot; apiNonce; table}; //optional bulkActions?: Array | null; pluralName?: string; singleName?: string; children?: JSX.Element | JSX.Element[] | null; rowActions?: JSX.Element | JSX.Element[] | Function | null; filterSettings?; align?: 'start' | 'center' | 'end'; paymentMode?: boolean; listTableBlankSlate: JSX.Element; productRecommendation?: JSX.Element; columnFilters?: Array; banner?: () => JSX.Element; contentMode?: boolean; perPage?: number; statsConfig?: Record; } interface FilterConfigBase { // required name: string; // optional ariaLabel?: string; inlineSize?: string; text?: string; } interface FilterConfigWithSimpleOptions extends FilterConfigBase { type: 'select' | 'campaignselect' | 'search' | 'checkbox' | 'hidden'; options?: Array<{text: string; value: string}>; } export interface FilterByGroupedOptions { id: string; apiParam: string; name: string; type: 'checkbox' | 'radio' | 'toggle'; options: Array<{text: string; value: string}>; defaultValue?: string | string[]; isVisible?: (values: Record) => boolean; showTitle?: boolean; } interface FilterConfigWithGroupedOptions extends FilterConfigBase { type: 'filterby'; groupedOptions?: Array; } export type FilterConfig = FilterConfigWithSimpleOptions | FilterConfigWithGroupedOptions; export interface ColumnFilterConfig { column: string; filter: Function; } interface BulkActionsConfigBase { //required label: string; value: string | number; confirm: ( selected: Array, names?: Array, isOpen?: boolean, setOpen?: (isOpen?: boolean) => void ) => JSX.Element | JSX.Element[] | string; //optional isVisible?: (data: any, parameters: any) => boolean; isIdSelectable?: (id: string, data: any) => boolean; type?: 'normal' | 'warning' | 'danger' | 'custom'; } // Makes the "action" property required for the standard types interface BulkActionsConfigWithAction extends BulkActionsConfigBase { type: 'normal' | 'warning' | 'danger'; action: (selected: Array) => Promise<{errors: string | number; successes: string | number}>; } // Makes the "action" property required for the undefined type interface BulkActionsConfigWithoutType extends BulkActionsConfigBase { type?: undefined; action: (selected: Array) => Promise<{errors: string | number; successes: string | number}>; } // Makes the "action" property forbidden for the custom type export interface BulkActionsConfigWithoutAction extends BulkActionsConfigBase { type: 'custom'; } export type BulkActionsConfig = | BulkActionsConfigWithAction | BulkActionsConfigWithoutType | BulkActionsConfigWithoutAction; export const ShowConfirmModalContext = createContext( (label, confirm, action, type = null, confirmButtonText = __('Confirm', 'give')) => {} ); export const CheckboxContext = createContext(null); export interface ListTablePageRef { refresh: () => Promise; } const ListTablePage = forwardRef(({ title, apiSettings, bulkActions = null, filterSettings = [], singleName = __('item', 'give'), pluralName = __('items', 'give'), rowActions = null, children = null, align = 'start', paymentMode, listTableBlankSlate, productRecommendation, columnFilters = [], banner, contentMode, perPage = 30, statsConfig, }: ListTablePageProps, ref) => { const [page, setPage] = useState(1); const [filters, setFilters] = useState(getInitialFilterState(filterSettings)); const [isOpen, setOpen] = useState(false); const [modalContent, setModalContent] = useState<{ confirm; action?; label; confirmButtonText?: string; type?: 'normal' | 'warning' | 'danger' | 'custom'; }>({ confirm: (selected) => {}, action: (selected) => {}, label: '', confirmButtonText: '', }); const [selectedAction, setSelectedAction] = useState(''); const [selectedIds, setSelectedIds] = useState([]); const [selectedNames, setSelectedNames] = useState([]); const dialog = useRef() as {current: A11yDialogInstance}; const checkboxRefs = useRef([]); const [sortField, setSortField] = useState<{sortColumn: string; sortDirection: string}>({ sortColumn: 'id', sortDirection: 'desc', }); const [testMode, setTestMode] = useState(paymentMode); const {sortColumn, sortDirection} = sortField; const locale = navigator.language || navigator.languages[0]; const testModeFilter = filterSettings.find((filter) => filter.name === 'toggle'); const parameters = { page, perPage, sortColumn, sortDirection, locale, testMode, ...filters, }; const archiveApi = useRef(new ListTableApi(apiSettings)).current; const {data, error, isValidating, mutate} = archiveApi.useListTable(parameters); const {data: statsData, error: statsError, isValidating: statsIsValidating, mutate: mutateStats} = statsConfig ? archiveApi.useStats(testMode) : {data: null, error: null, isValidating: false, mutate: async () => {}}; useResetPage(data, page, setPage, filters); useImperativeHandle(ref, () => ({ refresh: async () => { await mutate(); statsConfig && await mutateStats(); } }), [mutate, mutateStats, statsConfig]); const handleFilterChange = (name, value) => { setFilters((prevState) => { if (!value || (Array.isArray(value) && value.length === 0)) { const {[name]: _, ...rest} = prevState as Record; return rest; } return {...prevState, [name]: value}; }); }; const handleDebouncedFilterChange = useDebounce(handleFilterChange); const showConfirmActionModal = ( label, confirm, action, type?: 'normal' | 'warning' | 'danger' | 'custom' | null, confirmButtonText?: string ) => { setModalContent({label, confirm, action, type, confirmButtonText}); dialog.current.show(); }; const openBulkActionModal = (event) => { event.preventDefault(); if (window.GiveDonations && window.GiveDonations.addonsBulkActions) { bulkActions = [...bulkActions, ...window.GiveDonations.addonsBulkActions]; } const bulkAction = bulkActions.find((config) => selectedAction === config.value); if (!bulkAction) return; const selected = []; const names = []; const selectedRefs = checkboxRefs.current.filter((checkbox) => { const isSelectable = bulkAction?.isIdSelectable?.(checkbox.dataset.id, data) ?? true; return checkbox.checked && isSelectable; }); selectedRefs.forEach((checkbox) => { selected.push(checkbox.dataset.id); names.push(checkbox.dataset.name); }); setSelectedIds(selected); setSelectedNames(names); if (selected.length) { setModalContent({...bulkAction}); if ('custom' === bulkAction.type) { setOpen(true); bulkAction?.confirm(selected, names, isOpen, setOpen); } else { dialog.current.show(); } } }; const setSortDirectionForColumn = (column, direction) => { setSortField((previousState) => { return { ...previousState, sortColumn: column, sortDirection: direction, }; }); }; const showPagination = () => ( ); const PageActions = ({PageActionsTop}: {PageActionsTop?: boolean}) => { return (
{PageActionsTop ? ( ) : ( <> {page && setPage && showPagination()} )}
); }; const TestModeFilter = () => ( ); const TestModeBadge = () => {testModeFilter?.text}; const SearchSection = () => (
{filterSettings.map((filter) => ( filter.type === 'filterby' ? ( ) : ( ) ))}
); return ( <>
{!contentMode && ( <>

{title}

{testModeFilter && testMode && }
{children &&
{children}
}
{banner &&
{banner()}
} {testModeFilter && (
)} {statsConfig && !statsIsValidating && } )}
{contentMode && children ? <>{children} : <>} 0} />
(dialog.current = instance)} title={ <> {modalContent?.type === 'danger' && } {modalContent?.label} } titleId={styles.modalTitle} classNames={{ container: styles.container, overlay: styles.overlay, dialog: cx(styles.dialog, { [styles.warning]: modalContent?.type === 'warning', [styles.danger]: modalContent?.type === 'danger', }), closeButton: 'hidden', }} >
{modalContent?.confirm(selectedIds, selectedNames, isOpen, setOpen) || null}
); }); ListTablePage.displayName = 'ListTablePage'; export default ListTablePage;