SubscriptionsListTable.tsx
242 lines
| 1 | import {__} from '@wordpress/i18n'; |
| 2 | import {ListTablePage} from '@givewp/components'; |
| 3 | import ListTableApi from '@givewp/components/ListTable/api'; |
| 4 | import tableStyles from '@givewp/components/ListTable/ListTablePage/ListTablePage.module.scss'; |
| 5 | import {BulkActionsConfig, FilterConfig} from '@givewp/components/ListTable/ListTablePage'; |
| 6 | import {SubscriptionsRowActions} from './SubscriptionsRowActions'; |
| 7 | import {IdBadge} from '@givewp/components/ListTable/TableCell'; |
| 8 | import {Interweave} from 'interweave'; |
| 9 | import BlankSlate from '@givewp/components/ListTable/BlankSlate'; |
| 10 | import { StatConfig } from '@givewp/components/ListTable/ListTableStats/ListTableStats'; |
| 11 | import filterByOptions from '../constants/filterByOptions'; |
| 12 | |
| 13 | declare global { |
| 14 | interface Window { |
| 15 | GiveSubscriptions: { |
| 16 | apiNonce: string; |
| 17 | apiRoot: string; |
| 18 | table: {columns: Array<object>}; |
| 19 | forms: Array<{value: string; text: string}>; |
| 20 | paymentMode: boolean; |
| 21 | pluginUrl: string; |
| 22 | subscriptionStatuses: {[statusCode: string]: string}; |
| 23 | }; |
| 24 | GiveSubscriptionOptions?: { |
| 25 | currency: string; |
| 26 | }; |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | const API = new ListTableApi(window.GiveSubscriptions); |
| 31 | |
| 32 | const filters: Array<FilterConfig> = [ |
| 33 | { |
| 34 | name: 'campaignId', |
| 35 | type: 'campaignselect', |
| 36 | text: __('Select Campaign', 'give'), |
| 37 | ariaLabel: __('filter subscriptions by campaign', 'give'), |
| 38 | options: window.GiveSubscriptions.forms, |
| 39 | }, |
| 40 | { |
| 41 | name: 'search', |
| 42 | type: 'search', |
| 43 | inlineSize: '14rem', |
| 44 | text: __('Name, Email, or Subscription ID', 'give'), |
| 45 | ariaLabel: __('search subscriptions', 'give'), |
| 46 | }, |
| 47 | { |
| 48 | name: 'toggle', |
| 49 | type: 'checkbox', |
| 50 | text: __('Test', 'give'), |
| 51 | ariaLabel: __('View Test Subscriptions', 'give'), |
| 52 | }, |
| 53 | { |
| 54 | name: 'filterBy', |
| 55 | type: 'filterby', |
| 56 | groupedOptions: filterByOptions, |
| 57 | } |
| 58 | ]; |
| 59 | |
| 60 | const bulkActions: Array<BulkActionsConfig> = [ |
| 61 | { |
| 62 | label: __('Delete', 'give'), |
| 63 | value: 'delete', |
| 64 | type: 'danger', |
| 65 | isVisible: (data, parameters) => parameters?.status?.includes('trashed'), |
| 66 | action: async (selected) => { |
| 67 | const response = await API.fetchWithArgs('/delete', {ids: selected.join(',')}, 'DELETE'); |
| 68 | return response; |
| 69 | }, |
| 70 | confirm: (selected, names) => ( |
| 71 | <> |
| 72 | <p>{__('Really delete the following subscriptions?', 'give')}</p> |
| 73 | <ul role="document" tabIndex={0}> |
| 74 | {selected.map((subscriptionId, index) => ( |
| 75 | <li key={subscriptionId}> |
| 76 | <IdBadge id={subscriptionId} />{' '} |
| 77 | <span> |
| 78 | {__('from ', 'give')} <Interweave content={names[index]} /> |
| 79 | </span> |
| 80 | </li> |
| 81 | ))} |
| 82 | </ul> |
| 83 | </> |
| 84 | ), |
| 85 | }, |
| 86 | { |
| 87 | label: __('Trash', 'give'), |
| 88 | value: 'trash', |
| 89 | type: 'warning', |
| 90 | isVisible: (data, parameters) => !parameters?.status?.includes('trashed'), |
| 91 | action: async (selected) => { |
| 92 | const response = await API.fetchWithArgs('/trash', {ids: selected.join(',')}, 'DELETE'); |
| 93 | return response; |
| 94 | }, |
| 95 | confirm: (selected, names) => ( |
| 96 | <> |
| 97 | <p>{__('Are you sure you want add to trash the following subscriptions?', 'give')}</p> |
| 98 | <ul role="document" tabIndex={0}> |
| 99 | {selected.map((subscriptionId, index) => ( |
| 100 | <li key={subscriptionId}> |
| 101 | <IdBadge id={subscriptionId} />{' '} |
| 102 | <span> |
| 103 | {__('from ', 'give')} <Interweave content={names[index]} /> |
| 104 | </span> |
| 105 | </li> |
| 106 | ))} |
| 107 | </ul> |
| 108 | </> |
| 109 | ), |
| 110 | }, |
| 111 | { |
| 112 | label: __('Restore', 'give'), |
| 113 | value: 'restore', |
| 114 | type: 'normal', |
| 115 | isVisible: (data, parameters) => parameters?.status?.includes('trashed'), |
| 116 | action: async (selected) => { |
| 117 | const response = await API.fetchWithArgs('/untrash', {ids: selected.join(',')}, 'POST'); |
| 118 | return response; |
| 119 | }, |
| 120 | confirm: (selected, names) => ( |
| 121 | <> |
| 122 | <p>{__('Are you sure you want remove from trash the following subscriptions?', 'give')}</p> |
| 123 | <ul role="document" tabIndex={0}> |
| 124 | {selected.map((subscriptionId, index) => ( |
| 125 | <li key={subscriptionId}> |
| 126 | <IdBadge id={subscriptionId} />{' '} |
| 127 | <span> |
| 128 | {__('from ', 'give')} <Interweave content={names[index]} /> |
| 129 | </span> |
| 130 | </li> |
| 131 | ))} |
| 132 | </ul> |
| 133 | </> |
| 134 | ), |
| 135 | }, |
| 136 | ...(() => { |
| 137 | const subscriptionStatuses = { |
| 138 | active: __('Set To Active', 'give'), |
| 139 | expired: __('Set To Expired', 'give'), |
| 140 | completed: __('Set To Completed', 'give'), |
| 141 | cancelled: __('Set To Cancelled', 'give'), |
| 142 | pending: __('Set To Pending', 'give'), |
| 143 | failing: __('Set To Failing', 'give'), |
| 144 | suspended: __('Set To Suspended', 'give'), |
| 145 | abandoned: __('Set To Abandoned', 'give'), |
| 146 | }; |
| 147 | |
| 148 | return Object.entries(subscriptionStatuses).map(([value, label]) => { |
| 149 | return { |
| 150 | label, |
| 151 | value, |
| 152 | isVisible: (data, parameters) => !parameters?.status?.includes('trashed'), |
| 153 | action: async (selected) => |
| 154 | await API.fetchWithArgs( |
| 155 | '/setStatus', |
| 156 | { |
| 157 | ids: selected.join(','), |
| 158 | status: value, |
| 159 | }, |
| 160 | 'POST' |
| 161 | ), |
| 162 | confirm: (selected, names) => ( |
| 163 | <> |
| 164 | <p>{__('Set status for the following subscriptions?', 'give')}</p> |
| 165 | <ul role="document" tabIndex={0}> |
| 166 | {selected.map((subscriptionId, index) => ( |
| 167 | <li key={subscriptionId}> |
| 168 | <IdBadge id={subscriptionId} /> <span>{__('from', 'give')}</span> |
| 169 | <Interweave content={names[index]} /> |
| 170 | </li> |
| 171 | ))} |
| 172 | </ul> |
| 173 | </> |
| 174 | ), |
| 175 | }; |
| 176 | }); |
| 177 | })(), |
| 178 | ]; |
| 179 | |
| 180 | /** |
| 181 | * Displays a blank slate for the Subscriptions table. |
| 182 | * @since 2.27.0 |
| 183 | */ |
| 184 | const ListTableBlankSlate = ( |
| 185 | <BlankSlate |
| 186 | imagePath={`${window.GiveSubscriptions.pluginUrl}build/assets/dist/images/list-table/blank-slate-recurring-icon.svg`} |
| 187 | description={__('No subscriptions found', 'give')} |
| 188 | href={'https://docs.givewp.com/subscriptions'} |
| 189 | linkText={__('Recurring Donations.', 'give')} |
| 190 | /> |
| 191 | ); |
| 192 | |
| 193 | /** |
| 194 | * Configuration for the statistic tiles rendered above the ListTable. |
| 195 | * |
| 196 | * IMPORTANT: Object keys MUST MATCH the keys returned by the API's `stats` payload. |
| 197 | * For example, if the API returns: |
| 198 | * |
| 199 | * data.stats = { |
| 200 | * totalContributions: number; |
| 201 | * activeSubscriptions: number; |
| 202 | * } |
| 203 | * |
| 204 | * then this config must use those same keys: "totalContributions", "activeSubscriptions". |
| 205 | * Missing or mismatched keys will result in empty/undefined values in the UI. |
| 206 | * |
| 207 | * @since 4.12.0 |
| 208 | */ |
| 209 | const statsConfig: Record<string, StatConfig> = { |
| 210 | totalContributions: { |
| 211 | label: __('Total Contributions', 'give'), |
| 212 | currency: window.GiveSubscriptionOptions?.currency |
| 213 | }, |
| 214 | activeSubscriptions: { label: __('Active Subscriptions', 'give')}, |
| 215 | }; |
| 216 | |
| 217 | export default function SubscriptionsListTable() { |
| 218 | return ( |
| 219 | <ListTablePage |
| 220 | title={__('Subscriptions', 'give')} |
| 221 | singleName={__('subscription', 'give')} |
| 222 | pluralName={__('subscriptions', 'give')} |
| 223 | rowActions={SubscriptionsRowActions} |
| 224 | statsConfig={statsConfig} |
| 225 | bulkActions={bulkActions} |
| 226 | apiSettings={window.GiveSubscriptions} |
| 227 | filterSettings={filters} |
| 228 | paymentMode={!!window.GiveSubscriptions.paymentMode} |
| 229 | listTableBlankSlate={ListTableBlankSlate} |
| 230 | > |
| 231 | <button className={`button button-tertiary ${tableStyles.secondaryActionButton}`} onClick={showLegacyDonations}> |
| 232 | {__('Switch to Legacy View', 'give')} |
| 233 | </button> |
| 234 | </ListTablePage> |
| 235 | ); |
| 236 | } |
| 237 | |
| 238 | const showLegacyDonations = async (event) => { |
| 239 | await API.fetchWithArgs('/view', {isLegacy: 1}); |
| 240 | window.location.reload(); |
| 241 | }; |
| 242 |