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