PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 2.24.0
GiveWP – Donation Plugin and Fundraising Platform v2.24.0
4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Views / Components / ListTable / ListTablePage / index.tsx
give / src / Views / Components / ListTable / ListTablePage Last commit date
ListTablePage.module.scss 3 years ago index.tsx 3 years ago
index.tsx
266 lines
1 import {createContext, useRef, useState} from 'react';
2 import {__} from '@wordpress/i18n';
3 import {A11yDialog} from 'react-a11y-dialog';
4 import A11yDialogInstance from 'a11y-dialog';
5 import {GiveIcon} from '@givewp/components';
6 import {ListTable} from '../ListTable';
7 import Pagination from '../Pagination';
8 import {Filter, getInitialFilterState} from '../Filters';
9 import useDebounce from '../hooks/useDebounce';
10 import {useResetPage} from '../hooks/useResetPage';
11 import ListTableApi from '../api';
12 import styles from './ListTablePage.module.scss';
13 import cx from 'classnames';
14 import {BulkActionSelect} from '@givewp/components/ListTable/BulkActions/BulkActionSelect';
15 import ToggleSwitch from '@givewp/components/ListTable/ToggleSwitch';
16
17 export interface ListTablePageProps {
18 //required
19 title: string;
20 apiSettings: {apiRoot; apiNonce; table};
21
22 //optional
23 bulkActions?: Array<BulkActionsConfig> | null;
24 pluralName?: string;
25 singleName?: string;
26 children?: JSX.Element | JSX.Element[] | null;
27 rowActions?: JSX.Element | JSX.Element[] | Function | null;
28 filterSettings?;
29 align?: 'start' | 'center' | 'end';
30 paymentMode?: boolean;
31 }
32
33 export interface FilterConfig {
34 // required
35 name: string;
36 type: 'select' | 'formselect' | 'search' | 'checkbox';
37
38 // optional
39 ariaLabel?: string;
40 inlineSize?: string;
41 text?: string;
42 options?: Array<{text: string; value: string}>;
43 }
44
45 export interface BulkActionsConfig {
46 //required
47 label: string;
48 value: string | number;
49 action: (selected: Array<string | number>) => Promise<{errors: string | number; successes: string | number}>;
50 confirm: (selected: Array<string | number>, names?: Array<string>) => JSX.Element | JSX.Element[] | string;
51
52 //optional
53 isVisible?: (data, parameters) => Boolean;
54 type?: 'normal' | 'warning' | 'danger';
55 }
56
57 export const ShowConfirmModalContext = createContext((label, confirm, action, type = null) => {});
58 export const CheckboxContext = createContext(null);
59
60 export default function ListTablePage({
61 title,
62 apiSettings,
63 bulkActions = null,
64 filterSettings = [],
65 singleName = __('item', 'give'),
66 pluralName = __('items', 'give'),
67 rowActions = null,
68 children = null,
69 align = 'start',
70 paymentMode,
71 }: ListTablePageProps) {
72 const [page, setPage] = useState<number>(1);
73 const [perPage, setPerPage] = useState<number>(30);
74 const [filters, setFilters] = useState(getInitialFilterState(filterSettings));
75 const [modalContent, setModalContent] = useState<{confirm; action; label; type?: 'normal' | 'warning' | 'danger'}>({
76 confirm: (selected) => {},
77 action: (selected) => {},
78 label: '',
79 });
80 const [selectedIds, setSelectedIds] = useState([]);
81 const [selectedNames, setSelectedNames] = useState([]);
82 const dialog = useRef() as {current: A11yDialogInstance};
83 const checkboxRefs = useRef([]);
84 const [sortField, setSortField] = useState<{sortColumn: string; sortDirection: string}>({
85 sortColumn: 'id',
86 sortDirection: 'desc',
87 });
88 const [testMode, setTestMode] = useState(paymentMode);
89
90 const {sortColumn, sortDirection} = sortField;
91 const locale = navigator.language || navigator.languages[0];
92 const testModeFilter = filterSettings.find((filter) => filter.name === 'toggle');
93
94 const parameters = {
95 page,
96 perPage,
97 sortColumn,
98 sortDirection,
99 locale,
100 testMode,
101 ...filters,
102 };
103
104 const archiveApi = useRef(new ListTableApi(apiSettings)).current;
105
106 const {data, error, isValidating, mutate} = archiveApi.useListTable(parameters);
107
108 useResetPage(data, page, setPage, filters);
109
110 const handleFilterChange = (name, value) => {
111 setFilters((prevState) => ({...prevState, [name]: value}));
112 };
113
114 const handleDebouncedFilterChange = useDebounce(handleFilterChange);
115
116 const showConfirmActionModal = (label, confirm, action, type: 'normal' | 'warning' | 'danger' | null = null) => {
117 setModalContent({confirm, action, label, type});
118 dialog.current.show();
119 };
120
121 const openBulkActionModal = (event) => {
122 event.preventDefault();
123 const formData = new FormData(event.target);
124 const action = formData.get('giveListTableBulkActions');
125 const actionIndex = bulkActions.findIndex((config) => action == config.value);
126 if (actionIndex < 0) return;
127 const selected = [];
128 const names = [];
129 checkboxRefs.current.forEach((checkbox) => {
130 if (checkbox.checked) {
131 selected.push(checkbox.dataset.id);
132 names.push(checkbox.dataset.name);
133 }
134 });
135 setSelectedIds(selected);
136 setSelectedNames(names);
137 if (selected.length) {
138 setModalContent({...bulkActions[actionIndex]});
139 dialog.current.show();
140 }
141 };
142
143 const setSortDirectionForColumn = (column, direction) => {
144 setSortField((previousState) => {
145 return {
146 ...previousState,
147 sortColumn: column,
148 sortDirection: direction,
149 };
150 });
151 };
152
153 const showPagination = () => (
154 <Pagination
155 currentPage={page}
156 totalPages={data ? data.totalPages : 1}
157 disabled={!data}
158 totalItems={data ? parseInt(data.totalItems) : -1}
159 setPage={setPage}
160 singleName={singleName}
161 pluralName={pluralName}
162 />
163 );
164
165 const PageActions = ({PageActionsTop}: {PageActionsTop?: boolean}) => (
166 <div className={cx(styles.pageActions, {[styles.alignEnd]: !bulkActions})}>
167 <BulkActionSelect
168 parameters={parameters}
169 data={data}
170 bulkActions={bulkActions}
171 showModal={openBulkActionModal}
172 />
173 {PageActionsTop && testModeFilter && <TestModeFilter />}
174 {page && setPage && showPagination()}
175 </div>
176 );
177
178 const TestModeFilter = () => (
179 <ToggleSwitch ariaLabel={testModeFilter?.ariaLabel} onChange={setTestMode} checked={testMode} />
180 );
181
182 const TestModeBadge = () => <span>{testModeFilter?.text}</span>;
183
184 return (
185 <>
186 <article className={styles.page}>
187 <header className={styles.pageHeader}>
188 <div className={styles.flexRow}>
189 <GiveIcon size={'1.875rem'} />
190 <h1 className={styles.pageTitle}>{title}</h1>
191 {testModeFilter && testMode && <TestModeBadge />}
192 </div>
193 {children && <div className={styles.flexRow}>{children}</div>}
194 </header>
195 <section role="search" id={styles.searchContainer}>
196 {filterSettings.map((filter) => (
197 <Filter
198 key={filter.name}
199 value={filters[filter.name]}
200 filter={filter}
201 onChange={handleFilterChange}
202 debouncedOnChange={handleDebouncedFilterChange}
203 />
204 ))}
205 </section>
206 <div className={cx('wp-header-end', 'hidden')} />
207 <div className={styles.pageContent}>
208 <PageActions PageActionsTop />
209 <CheckboxContext.Provider value={checkboxRefs}>
210 <ShowConfirmModalContext.Provider value={showConfirmActionModal}>
211 <ListTable
212 apiSettings={apiSettings}
213 sortField={sortField}
214 setSortDirectionForColumn={setSortDirectionForColumn}
215 singleName={singleName}
216 pluralName={pluralName}
217 title={title}
218 rowActions={rowActions}
219 parameters={parameters}
220 data={data}
221 error={error}
222 isLoading={isValidating}
223 align={align}
224 testMode={testMode}
225 />
226 </ShowConfirmModalContext.Provider>
227 </CheckboxContext.Provider>
228 <PageActions />
229 </div>
230 </article>
231 <A11yDialog
232 id="giveListTableModal"
233 dialogRef={(instance) => (dialog.current = instance)}
234 title={modalContent.label}
235 titleId={styles.modalTitle}
236 classNames={{
237 container: styles.container,
238 overlay: styles.overlay,
239 dialog: cx(styles.dialog, {
240 [styles.warning]: modalContent?.type === 'warning',
241 [styles.danger]: modalContent?.type === 'danger',
242 }),
243 closeButton: 'hidden',
244 }}
245 >
246 <div className={styles.modalContent}>{modalContent?.confirm(selectedIds, selectedNames) || null}</div>
247 <div className={styles.gutter}>
248 <button id={styles.cancel} onClick={(event) => dialog.current?.hide()}>
249 {__('Cancel', 'give')}
250 </button>
251 <button
252 id={styles.confirm}
253 onClick={async (event) => {
254 dialog.current?.hide();
255 await modalContent.action(selectedIds);
256 await mutate();
257 }}
258 >
259 {__('Confirm', 'give')}
260 </button>
261 </div>
262 </A11yDialog>
263 </>
264 );
265 }
266