PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 3.16.3
GiveWP – Donation Plugin and Fundraising Platform v3.16.3
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 1 year ago index.tsx 2 years ago
index.tsx
296 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 listTableBlankSlate: JSX.Element;
32 productRecommendation?: JSX.Element;
33 columnFilters?: Array<ColumnFilterConfig>;
34 banner?: () => JSX.Element;
35 }
36
37 export interface FilterConfig {
38 // required
39 name: string;
40 type: 'select' | 'formselect' | 'search' | 'checkbox';
41
42 // optional
43 ariaLabel?: string;
44 inlineSize?: string;
45 text?: string;
46 options?: Array<{text: string; value: string}>;
47 }
48
49 export interface ColumnFilterConfig {
50 column: string;
51 filter: Function
52 }
53
54 export interface BulkActionsConfig {
55 //required
56 label: string;
57 value: string | number;
58 action: (selected: Array<string | number>) => Promise<{errors: string | number; successes: string | number}>;
59 confirm: (selected: Array<string | number>, names?: Array<string>) => JSX.Element | JSX.Element[] | string;
60
61 //optional
62 isVisible?: (data, parameters) => Boolean;
63 type?: 'normal' | 'warning' | 'danger';
64 }
65
66 export const ShowConfirmModalContext = createContext((label, confirm, action, type = null) => {});
67 export const CheckboxContext = createContext(null);
68
69 export default function ListTablePage({
70 title,
71 apiSettings,
72 bulkActions = null,
73 filterSettings = [],
74 singleName = __('item', 'give'),
75 pluralName = __('items', 'give'),
76 rowActions = null,
77 children = null,
78 align = 'start',
79 paymentMode,
80 listTableBlankSlate,
81 productRecommendation,
82 columnFilters = [],
83 banner
84 }: ListTablePageProps) {
85 const [page, setPage] = useState<number>(1);
86 const [perPage, setPerPage] = useState<number>(30);
87 const [filters, setFilters] = useState(getInitialFilterState(filterSettings));
88 const [modalContent, setModalContent] = useState<{confirm; action; label; type?: 'normal' | 'warning' | 'danger'}>({
89 confirm: (selected) => {},
90 action: (selected) => {},
91 label: '',
92 });
93 const [selectedAction, setSelectedAction] = useState<string>('');
94 const [selectedIds, setSelectedIds] = useState([]);
95 const [selectedNames, setSelectedNames] = useState([]);
96 const dialog = useRef() as {current: A11yDialogInstance};
97 const checkboxRefs = useRef([]);
98 const [sortField, setSortField] = useState<{sortColumn: string; sortDirection: string}>({
99 sortColumn: 'id',
100 sortDirection: 'desc',
101 });
102 const [testMode, setTestMode] = useState(paymentMode);
103
104 const {sortColumn, sortDirection} = sortField;
105 const locale = navigator.language || navigator.languages[0];
106 const testModeFilter = filterSettings.find((filter) => filter.name === 'toggle');
107
108 const parameters = {
109 page,
110 perPage,
111 sortColumn,
112 sortDirection,
113 locale,
114 testMode,
115 ...filters,
116 };
117
118 const archiveApi = useRef(new ListTableApi(apiSettings)).current;
119
120 const {data, error, isValidating, mutate} = archiveApi.useListTable(parameters);
121
122 useResetPage(data, page, setPage, filters);
123
124 const handleFilterChange = (name, value) => {
125 setFilters((prevState) => ({...prevState, [name]: value}));
126 };
127
128 const handleDebouncedFilterChange = useDebounce(handleFilterChange);
129
130 const showConfirmActionModal = (label, confirm, action, type: 'normal' | 'warning' | 'danger' | null = null) => {
131 setModalContent({confirm, action, label, type});
132 dialog.current.show();
133 };
134
135 const openBulkActionModal = (event) => {
136 event.preventDefault();
137
138 if (window.GiveDonations && window.GiveDonations.addonsBulkActions) {
139 bulkActions = [...bulkActions, ...window.GiveDonations.addonsBulkActions];
140 }
141
142 const actionIndex = bulkActions.findIndex((config) => selectedAction === config.value);
143
144 if (actionIndex < 0) return;
145
146 const selected = [];
147 const names = [];
148 checkboxRefs.current.forEach((checkbox) => {
149 if (checkbox.checked) {
150 selected.push(checkbox.dataset.id);
151 names.push(checkbox.dataset.name);
152 }
153 });
154 setSelectedIds(selected);
155 setSelectedNames(names);
156 if (selected.length) {
157 setModalContent({...bulkActions[actionIndex]});
158 dialog.current.show();
159 }
160 };
161
162 const setSortDirectionForColumn = (column, direction) => {
163 setSortField((previousState) => {
164 return {
165 ...previousState,
166 sortColumn: column,
167 sortDirection: direction,
168 };
169 });
170 };
171
172 const showPagination = () => (
173 <Pagination
174 currentPage={page}
175 totalPages={data ? data.totalPages : 1}
176 disabled={!data}
177 totalItems={data ? parseInt(data.totalItems) : -1}
178 setPage={setPage}
179 singleName={singleName}
180 pluralName={pluralName}
181 />
182 );
183
184 const PageActions = ({PageActionsTop}: {PageActionsTop?: boolean}) => {
185 return (
186 <div className={cx(styles.pageActions, {[styles.alignEnd]: !bulkActions})}>
187 <BulkActionSelect
188 selectedState={[selectedAction, setSelectedAction]}
189 parameters={parameters}
190 data={data}
191 bulkActions={bulkActions}
192 showModal={openBulkActionModal}
193 />
194 {PageActionsTop && testModeFilter && <TestModeFilter />}
195 {page && setPage && showPagination()}
196 </div>
197 );
198 };
199
200 const TestModeFilter = () => (
201 <ToggleSwitch ariaLabel={testModeFilter?.ariaLabel} onChange={setTestMode} checked={testMode} />
202 );
203
204 const TestModeBadge = () => <span>{testModeFilter?.text}</span>;
205
206 return (
207 <>
208 <article className={styles.page}>
209 <header className={styles.pageHeader}>
210 <div className={styles.flexRow}>
211 <GiveIcon size={'1.875rem'} />
212 <h1 className={styles.pageTitle}>{title}</h1>
213 {testModeFilter && testMode && <TestModeBadge />}
214 </div>
215 {children && <div className={styles.flexRow}>{children}</div>}
216 </header>
217 {banner && (
218 <section role="banner">
219 {banner()}
220 </section>
221 )}
222 <section role="search" id={styles.searchContainer}>
223 {filterSettings.map((filter) => (
224 <Filter
225 key={filter.name}
226 value={filters[filter.name]}
227 filter={filter}
228 onChange={handleFilterChange}
229 debouncedOnChange={handleDebouncedFilterChange}
230 />
231 ))}
232 </section>
233 <div className={cx('wp-header-end', 'hidden')} />
234 <div className={styles.pageContent}>
235 <PageActions PageActionsTop />
236 <CheckboxContext.Provider value={checkboxRefs}>
237 <ShowConfirmModalContext.Provider value={showConfirmActionModal}>
238 <ListTable
239 apiSettings={apiSettings}
240 sortField={sortField}
241 setSortDirectionForColumn={setSortDirectionForColumn}
242 singleName={singleName}
243 pluralName={pluralName}
244 title={title}
245 rowActions={rowActions}
246 parameters={parameters}
247 data={data}
248 error={error}
249 isLoading={isValidating}
250 align={align}
251 testMode={testMode}
252 listTableBlankSlate={listTableBlankSlate}
253 productRecommendation={productRecommendation}
254 columnFilters={columnFilters}
255 />
256 </ShowConfirmModalContext.Provider>
257 </CheckboxContext.Provider>
258 <PageActions />
259 </div>
260 </article>
261 <A11yDialog
262 id="giveListTableModal"
263 dialogRef={(instance) => (dialog.current = instance)}
264 title={modalContent.label}
265 titleId={styles.modalTitle}
266 classNames={{
267 container: styles.container,
268 overlay: styles.overlay,
269 dialog: cx(styles.dialog, {
270 [styles.warning]: modalContent?.type === 'warning',
271 [styles.danger]: modalContent?.type === 'danger',
272 }),
273 closeButton: 'hidden',
274 }}
275 >
276 <div className={styles.modalContent}>{modalContent?.confirm(selectedIds, selectedNames) || null}</div>
277 <div className={styles.gutter}>
278 <button id={styles.cancel} onClick={(event) => dialog.current?.hide()}>
279 {__('Cancel', 'give')}
280 </button>
281 <button
282 id={styles.confirm}
283 onClick={async (event) => {
284 dialog.current?.hide();
285 await modalContent.action(selectedIds);
286 await mutate();
287 }}
288 >
289 {__('Confirm', 'give')}
290 </button>
291 </div>
292 </A11yDialog>
293 </>
294 );
295 }
296