| 1 |
/** |
| 2 |
* Dashboard Preview Component |
| 3 |
* Renders the actual user dashboard matching layout-1.php structure |
| 4 |
* |
| 5 |
* @package Better_Payment |
| 6 |
*/ |
| 7 |
|
| 8 |
import { __ } from '@wordpress/i18n'; |
| 9 |
import { useState, useEffect, useCallback, useRef } from '@wordpress/element'; |
| 10 |
import AnalyticsReport from './AnalyticsReport'; |
| 11 |
import RecurringSubscriptionWidget from './RecurringSubscriptionWidget'; |
| 12 |
import SplitSubscriptionWidget from './SplitSubscriptionWidget'; |
| 13 |
import SubscriptionsTab from './SubscriptionsTab'; |
| 14 |
|
| 15 |
const DashboardSVGIcons = { |
| 16 |
dashboard: () => ( |
| 17 |
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 18 |
<g clipPath="url(#clip0_400_549)"> |
| 19 |
<path d="M4 5C4 4.73478 4.10536 4.48043 4.29289 4.29289C4.48043 4.10536 4.73478 4 5 4H9C9.26522 4 9.51957 4.10536 9.70711 4.29289C9.89464 4.48043 10 4.73478 10 5V9C10 9.26522 9.89464 9.51957 9.70711 9.70711C9.51957 9.89464 9.26522 10 9 10H5C4.73478 10 4.48043 9.89464 4.29289 9.70711C4.10536 9.51957 4 9.26522 4 9V5Z" stroke="#48506D" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 20 |
<path d="M4 15C4 14.7348 4.10536 14.4804 4.29289 14.2929C4.48043 14.1054 4.73478 14 5 14H9C9.26522 14 9.51957 14.1054 9.70711 14.2929C9.89464 14.4804 10 14.7348 10 15V19C10 19.2652 9.89464 19.5196 9.70711 19.7071C9.51957 19.8946 9.26522 20 9 20H5C4.73478 20 4.48043 19.8946 4.29289 19.7071C4.10536 19.5196 4 19.2652 4 19V15Z" stroke="#48506D" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 21 |
<path d="M14 15C14 14.7348 14.1054 14.4804 14.2929 14.2929C14.4804 14.1054 14.7348 14 15 14H19C19.2652 14 19.5196 14.1054 19.7071 14.2929C19.8946 14.4804 20 14.7348 20 15V19C20 19.2652 19.8946 19.5196 19.7071 19.7071C19.5196 19.8946 19.2652 20 19 20H15C14.7348 20 14.4804 19.8946 14.2929 19.7071C14.1054 19.5196 14 19.2652 14 19V15Z" stroke="#48506D" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 22 |
<path d="M14 7H20" stroke="#48506D" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 23 |
<path d="M17 4V10" stroke="#48506D" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 24 |
</g> |
| 25 |
<defs> |
| 26 |
<clipPath id="clip0_400_549"> |
| 27 |
<rect width="24" height="24" fill="white" /> |
| 28 |
</clipPath> |
| 29 |
</defs> |
| 30 |
</svg> |
| 31 |
), |
| 32 |
transactions: () => ( |
| 33 |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 34 |
<path fillRule="evenodd" clipRule="evenodd" d="M7.09878 1.24992C7.14683 1.24994 7.19559 1.24996 7.24508 1.24996H16.7551C16.8045 1.24996 16.8533 1.24994 16.9014 1.24992C17.9181 1.24947 18.6178 1.24917 19.2072 1.45422C20.3201 1.84149 21.1842 2.73714 21.5547 3.86546L20.8421 4.09942L21.5547 3.86546C21.7507 4.46258 21.7505 5.17242 21.7501 6.22642C21.7501 6.27359 21.7501 6.32145 21.7501 6.37002V20.3742C21.7501 21.8394 20.0231 22.7117 18.8857 21.6709C18.8062 21.5981 18.694 21.5981 18.6145 21.6709L18.1314 22.1129C17.2032 22.9623 15.7969 22.9623 14.8688 22.1129C14.5138 21.7881 13.9864 21.7881 13.6314 22.1129C12.7032 22.9623 11.2969 22.9623 10.3688 22.1129C10.0138 21.7881 9.48637 21.7881 9.13138 22.1129C8.20319 22.9623 6.79694 22.9623 5.86875 22.1129L5.38566 21.6709C5.30618 21.5981 5.19395 21.5981 5.11448 21.6709C3.97705 22.7117 2.25007 21.8394 2.25007 20.3742V6.37002C2.25007 6.32145 2.25005 6.2736 2.25003 6.22643C2.24965 5.17242 2.24939 4.46259 2.44545 3.86546C2.81591 2.73714 3.68002 1.84149 4.79298 1.45422C5.3823 1.24917 6.08203 1.24947 7.09878 1.24992ZM7.24508 2.74996C6.024 2.74996 5.6034 2.76045 5.28593 2.87091C4.62655 3.10035 4.09919 3.63716 3.8706 4.33338C3.75951 4.67171 3.75007 5.11784 3.75007 6.37002V20.3742C3.75007 20.4932 3.80999 20.566 3.88517 20.6007C3.92434 20.6189 3.96264 20.6235 3.99456 20.6193C4.0227 20.6155 4.05911 20.6034 4.10185 20.5643C4.75453 19.967 5.74561 19.967 6.39828 20.5643L6.88138 21.0063C7.23637 21.3312 7.76377 21.3312 8.11875 21.0063C9.04694 20.157 10.4532 20.157 11.3814 21.0063C11.7364 21.3312 12.2638 21.3312 12.6188 21.0063C13.5469 20.157 14.9532 20.157 15.8814 21.0063C16.2364 21.3312 16.7638 21.3312 17.1188 21.0063L17.6019 20.5643C18.2545 19.967 19.2456 19.967 19.8983 20.5643C19.941 20.6034 19.9774 20.6155 20.0056 20.6193C20.0375 20.6235 20.0758 20.6189 20.115 20.6007C20.1901 20.566 20.2501 20.4932 20.2501 20.3742V6.37002C20.2501 5.11784 20.2406 4.67171 20.1295 4.33338C19.9009 3.63716 19.3736 3.10035 18.7142 2.87091C18.3967 2.76045 17.9761 2.74996 16.7551 2.74996H7.24508ZM6.25007 7.49996C6.25007 7.08575 6.58585 6.74996 7.00007 6.74996H7.50007C7.91428 6.74996 8.25007 7.08575 8.25007 7.49996C8.25007 7.91417 7.91428 8.24996 7.50007 8.24996H7.00007C6.58585 8.24996 6.25007 7.91417 6.25007 7.49996ZM9.75007 7.49996C9.75007 7.08575 10.0859 6.74996 10.5001 6.74996H17.0001C17.4143 6.74996 17.7501 7.08575 17.7501 7.49996C17.7501 7.91417 17.4143 8.24996 17.0001 8.24996H10.5001C10.0859 8.24996 9.75007 7.91417 9.75007 7.49996ZM6.25007 11C6.25007 10.5857 6.58585 10.25 7.00007 10.25H7.50007C7.91428 10.25 8.25007 10.5857 8.25007 11C8.25007 11.4142 7.91428 11.75 7.50007 11.75H7.00007C6.58585 11.75 6.25007 11.4142 6.25007 11ZM9.75007 11C9.75007 10.5857 10.0859 10.25 10.5001 10.25H17.0001C17.4143 10.25 17.7501 10.5857 17.7501 11C17.7501 11.4142 17.4143 11.75 17.0001 11.75H10.5001C10.0859 11.75 9.75007 11.4142 9.75007 11ZM6.25007 14.5C6.25007 14.0857 6.58585 13.75 7.00007 13.75H7.50007C7.91428 13.75 8.25007 14.0857 8.25007 14.5C8.25007 14.9142 7.91428 15.25 7.50007 15.25H7.00007C6.58585 15.25 6.25007 14.9142 6.25007 14.5ZM9.75007 14.5C9.75007 14.0857 10.0859 13.75 10.5001 13.75H17.0001C17.4143 13.75 17.7501 14.0857 17.7501 14.5C17.7501 14.9142 17.4143 15.25 17.0001 15.25H10.5001C10.0859 15.25 9.75007 14.9142 9.75007 14.5Z" fill="#667085" /> |
| 35 |
</svg> |
| 36 |
), |
| 37 |
subscriptions: () => ( |
| 38 |
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 39 |
<g clipPath="url(#clip0_400_558)"> |
| 40 |
<path d="M3 8V12.172C3.00011 12.7024 3.2109 13.211 3.586 13.586L9.296 19.296C9.74795 19.7479 10.3609 20.0017 11 20.0017C11.6391 20.0017 12.252 19.7479 12.704 19.296L16.296 15.704C16.7479 15.252 17.0017 14.6391 17.0017 14C17.0017 13.3609 16.7479 12.748 16.296 12.296L10.586 6.586C10.211 6.2109 9.70239 6.00011 9.172 6H5C4.46957 6 3.96086 6.21071 3.58579 6.58579C3.21071 6.96086 3 7.46957 3 8Z" stroke="#2B2748" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 41 |
<path d="M18 19L19.592 17.408C20.4958 16.5041 21.0035 15.2782 21.0035 14C21.0035 12.7218 20.4958 11.4959 19.592 10.592L15 6" stroke="#2B2748" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 42 |
<path d="M6.99828 10H6.98828" stroke="#2B2748" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> |
| 43 |
</g> |
| 44 |
<defs> |
| 45 |
<clipPath id="clip0_400_558"> |
| 46 |
<rect width="24" height="24" fill="white" /> |
| 47 |
</clipPath> |
| 48 |
</defs> |
| 49 |
</svg> |
| 50 |
), |
| 51 |
}; |
| 52 |
|
| 53 |
/** |
| 54 |
* Map transaction status to display label and background color. |
| 55 |
* Mirrors PHP Helper::get_type_by_transaction_status($status, 'v2') and get_color_by_transaction_status($status, 'v2') |
| 56 |
*/ |
| 57 |
function getBpStatusInfo( status ) { |
| 58 |
const s = status || ''; |
| 59 |
if ( [ 'paid', 'Completed', 'completed', 'success' ].includes( s ) ) { |
| 60 |
return { label: 'Completed', color: '#0ECA86' }; |
| 61 |
} |
| 62 |
if ( s === 'refunded' ) { |
| 63 |
return { label: 'Refunded', color: '#FF0202' }; |
| 64 |
} |
| 65 |
// pending, Pending, unpaid, incomplete, Incomplete, empty → Incomplete |
| 66 |
return { label: 'Incomplete', color: '#FFDA15' }; |
| 67 |
} |
| 68 |
|
| 69 |
/** |
| 70 |
* Format date to match WordPress site date-only format: 'F j, Y' |
| 71 |
* Example: "April 9, 2026" |
| 72 |
*/ |
| 73 |
function formatBpDateOnly( dateStr ) { |
| 74 |
if ( ! dateStr ) return 'N/A'; |
| 75 |
const d = new Date( dateStr ); |
| 76 |
const month = d.toLocaleString( 'en-US', { month: 'long' } ); |
| 77 |
const day = d.getDate(); |
| 78 |
const year = d.getFullYear(); |
| 79 |
return `${ month } ${ day }, ${ year }`; |
| 80 |
} |
| 81 |
|
| 82 |
/** |
| 83 |
* Format date to match WordPress site date/time format: 'F j, Y g:i a' |
| 84 |
* Example: "April 9, 2026 2:25 pm" |
| 85 |
*/ |
| 86 |
function formatBpDate( dateStr ) { |
| 87 |
if ( ! dateStr ) return 'N/A'; |
| 88 |
const d = new Date( dateStr ); |
| 89 |
const month = d.toLocaleString( 'en-US', { month: 'long' } ); |
| 90 |
const day = d.getDate(); |
| 91 |
const year = d.getFullYear(); |
| 92 |
let hours = d.getHours(); |
| 93 |
const mins = String( d.getMinutes() ).padStart( 2, '0' ); |
| 94 |
const ampm = hours >= 12 ? 'pm' : 'am'; |
| 95 |
hours = hours % 12 || 12; |
| 96 |
return `${ month } ${ day }, ${ year } ${ hours }:${ mins } ${ ampm }`; |
| 97 |
} |
| 98 |
|
| 99 |
export default function DashboardPreview({ attributes }) { |
| 100 |
const { |
| 101 |
sidebarShow, |
| 102 |
avatarShow, |
| 103 |
usernameShow, |
| 104 |
dashboardShow, |
| 105 |
transactionsShow, |
| 106 |
subscriptionsShow, |
| 107 |
headerShow, |
| 108 |
dashboardLabel, |
| 109 |
transactionLabel, |
| 110 |
subscriptionLabel, |
| 111 |
refreshStatsLabel, |
| 112 |
noItemsLabel, |
| 113 |
dashboardTransactionSummaryShow, |
| 114 |
dashboardAnalyticsReportShow, |
| 115 |
dashboardRecentTransactionsShow, |
| 116 |
dashboardRecurringSubscriptionShow, |
| 117 |
dashboardSplitSubscriptionShow, |
| 118 |
transactionTableNameShow, |
| 119 |
transactionTableEmailAddressShow, |
| 120 |
transactionTableAmountShow, |
| 121 |
transactionTablePaymentTypeShow, |
| 122 |
transactionTableTransactionIdShow, |
| 123 |
transactionTableSourceShow, |
| 124 |
transactionTableStatusShow, |
| 125 |
transactionTableDateShow, |
| 126 |
dashboardTotalAmountLabel, |
| 127 |
dashboardCompletedAmountLabel, |
| 128 |
dashboardIncompleteAmountLabel, |
| 129 |
dashboardRefundedAmountLabel, |
| 130 |
dashboardViewAllLabel, |
| 131 |
dashboardAnalyticsReportsLabel, |
| 132 |
dashboardRecentTransactionsLabel, |
| 133 |
transactionTableNameLabel, |
| 134 |
transactionTableEmailAddressLabel, |
| 135 |
transactionTableAmountLabel, |
| 136 |
transactionTablePaymentTypeLabel, |
| 137 |
transactionTableTransactionIdLabel, |
| 138 |
transactionTableSourceLabel, |
| 139 |
transactionTableStatusLabel, |
| 140 |
transactionTableDateLabel, |
| 141 |
} = attributes; |
| 142 |
|
| 143 |
const blockData = window.betterPaymentBlockData || {}; |
| 144 |
const currentUser = blockData.currentUser || { user_email: '', user_login: '', user_avatar_url: '' }; |
| 145 |
const analytics = blockData.userAnalytics || {}; |
| 146 |
const assetUrl = blockData.assetUrl || blockData.assetsUrl || ''; |
| 147 |
const proAssets = blockData.proAssets || {}; |
| 148 |
const proEnabled = blockData.proEnabled === true; |
| 149 |
const analyticsChartData = blockData.analyticsChartData || null; |
| 150 |
const restUrl = blockData.restUrl || ''; |
| 151 |
const restNonce = blockData.restNonce || ''; |
| 152 |
const initMeta = blockData.userTransactionsMeta || { total: 0, pages: 1, perPage: 20 }; |
| 153 |
|
| 154 |
// Paginated transaction state — initialized from block data (page 1) |
| 155 |
const [txRows, setTxRows] = useState(blockData.userTransactions || []); |
| 156 |
const [txPage, setTxPage] = useState(1); |
| 157 |
const [txMeta, setTxMeta] = useState(initMeta); |
| 158 |
const [txLoading, setTxLoading] = useState(false); |
| 159 |
const txMounted = useRef(false); |
| 160 |
|
| 161 |
const fetchTxPage = useCallback((page) => { |
| 162 |
if ( ! restUrl ) return; |
| 163 |
setTxLoading(true); |
| 164 |
fetch(`${restUrl}?tab=transactions&page=${page}&per_page=${txMeta.perPage || 20}`, { |
| 165 |
headers: { 'X-WP-Nonce': restNonce }, |
| 166 |
}) |
| 167 |
.then(r => r.json()) |
| 168 |
.then(data => { |
| 169 |
setTxRows(data.transactions || []); |
| 170 |
setTxPage(data.page || page); |
| 171 |
setTxMeta({ total: data.total, pages: data.pages, perPage: data.per_page }); |
| 172 |
}) |
| 173 |
.finally(() => setTxLoading(false)); |
| 174 |
}, [restUrl, restNonce, txMeta.perPage]); |
| 175 |
|
| 176 |
useEffect(() => { |
| 177 |
if ( ! txMounted.current ) { |
| 178 |
txMounted.current = true; |
| 179 |
return; |
| 180 |
} |
| 181 |
fetchTxPage(txPage); |
| 182 |
}, [txPage]); // eslint-disable-line react-hooks/exhaustive-deps |
| 183 |
|
| 184 |
// Paginated subscription state — initialized from block data (page 1) |
| 185 |
const initSubMeta = blockData.userSubscriptionsMeta || { total: 0, pages: 1, perPage: 20 }; |
| 186 |
const [subRows, setSubRows] = useState(blockData.userSubscriptions || []); |
| 187 |
const [subPage, setSubPage] = useState(1); |
| 188 |
const [subMeta, setSubMeta] = useState(initSubMeta); |
| 189 |
const subMounted = useRef(false); |
| 190 |
|
| 191 |
const fetchSubPage = useCallback((page) => { |
| 192 |
if ( ! restUrl ) return; |
| 193 |
fetch(`${restUrl}?tab=subscriptions&page=${page}&per_page=${subMeta.perPage || 20}`, { |
| 194 |
headers: { 'X-WP-Nonce': restNonce }, |
| 195 |
}) |
| 196 |
.then(r => r.json()) |
| 197 |
.then(data => { |
| 198 |
setSubRows(data.transactions || []); |
| 199 |
setSubPage(data.page || page); |
| 200 |
setSubMeta({ total: data.total, pages: data.pages, perPage: data.per_page }); |
| 201 |
}); |
| 202 |
}, [restUrl, restNonce, subMeta.perPage]); |
| 203 |
|
| 204 |
useEffect(() => { |
| 205 |
if ( ! subMounted.current ) { |
| 206 |
subMounted.current = true; |
| 207 |
return; |
| 208 |
} |
| 209 |
fetchSubPage(subPage); |
| 210 |
}, [subPage]); // eslint-disable-line react-hooks/exhaustive-deps |
| 211 |
|
| 212 |
// Subscription data for the Dashboard tab summary widgets (uses first page from block data) |
| 213 |
const allSubscriptions = blockData.userSubscriptions || []; |
| 214 |
const recurringSubscriptions = allSubscriptions.filter(tx => !parseInt(tx.is_payment_split_payment || 0, 10)); |
| 215 |
const splitSubscriptions = allSubscriptions.filter(tx => !!parseInt(tx.is_payment_split_payment || 0, 10)); |
| 216 |
|
| 217 |
const [activeTab, setActiveTab] = useState( |
| 218 |
dashboardShow ? 'dashboard' : transactionsShow ? 'transactions' : subscriptionsShow ? 'subscriptions' : 'dashboard' |
| 219 |
); |
| 220 |
|
| 221 |
return ( |
| 222 |
<div className="better-payment"> |
| 223 |
<div className="better-payment-user-dashboard-container bp--section-wrapper flex gap-6 min-h-full"> |
| 224 |
<div className="bp-overlay"></div> |
| 225 |
|
| 226 |
{/* Sidebar */} |
| 227 |
{sidebarShow && ( |
| 228 |
<div className="better-payment-user-dashboard-sidebar user-dashboard-sidebar bp--sidebar-wrapper bp-hidden-xs"> |
| 229 |
<div className="bp--author"> |
| 230 |
{avatarShow && ( |
| 231 |
<div> |
| 232 |
<img |
| 233 |
src={currentUser.user_avatar_url || currentUser.avatar || 'https://www.gravatar.com/avatar/?d=mm'} |
| 234 |
alt={currentUser.user_login || currentUser.login || 'user'} |
| 235 |
style={{ width: '32px', height: '32px', minWidth: '32px', borderRadius: '50%', objectFit: 'cover', flexShrink: 0, display: 'block' }} |
| 236 |
/> |
| 237 |
</div> |
| 238 |
)} |
| 239 |
{usernameShow && ( |
| 240 |
<h5 className="user-name"> |
| 241 |
{currentUser.user_login || currentUser.login || 'Admin'} |
| 242 |
</h5> |
| 243 |
)} |
| 244 |
</div> |
| 245 |
|
| 246 |
<div className="bp--sidebar-nav-list"> |
| 247 |
{dashboardShow && ( |
| 248 |
<div |
| 249 |
className={`bp--sidebar-nav dashboard-tab ${activeTab === 'dashboard' ? 'active' : ''}`} |
| 250 |
onClick={() => setActiveTab('dashboard')} |
| 251 |
> |
| 252 |
<span className="bp--nav-icon"> |
| 253 |
<DashboardSVGIcons.dashboard /> |
| 254 |
</span> |
| 255 |
<span className="bp--nav-text">{dashboardLabel || __('Dashboard', 'better-payment')}</span> |
| 256 |
</div> |
| 257 |
)} |
| 258 |
|
| 259 |
{transactionsShow && ( |
| 260 |
<div |
| 261 |
className={`bp--sidebar-nav transactions-tab ${!dashboardShow && activeTab === 'transactions' ? 'active' : activeTab === 'transactions' ? 'active' : ''}`} |
| 262 |
onClick={() => setActiveTab('transactions')} |
| 263 |
> |
| 264 |
<span className="bp--nav-icon"> |
| 265 |
<DashboardSVGIcons.transactions /> |
| 266 |
</span> |
| 267 |
<span className="bp--nav-text">{transactionLabel || __('Transactions', 'better-payment')}</span> |
| 268 |
</div> |
| 269 |
)} |
| 270 |
|
| 271 |
{subscriptionsShow && ( |
| 272 |
<div |
| 273 |
className={`bp--sidebar-nav subscriptions-tab ${(!dashboardShow && !transactionsShow) || activeTab === 'subscriptions' ? 'active' : ''}`} |
| 274 |
onClick={() => setActiveTab('subscriptions')} |
| 275 |
> |
| 276 |
<span className="bp--nav-icon"> |
| 277 |
<DashboardSVGIcons.subscriptions /> |
| 278 |
</span> |
| 279 |
<span className="bp--nav-text">{subscriptionLabel || __('Subscriptions', 'better-payment')}</span> |
| 280 |
</div> |
| 281 |
)} |
| 282 |
</div> |
| 283 |
</div> |
| 284 |
)} |
| 285 |
|
| 286 |
{/* Main Content */} |
| 287 |
<div className="bp--db-main-wrapper"> |
| 288 |
{/* Dashboard Tab */} |
| 289 |
{dashboardShow && activeTab === 'dashboard' && ( |
| 290 |
<div className="bp--tab-conetnt-wrapper dashboard-tab-wrapper"> |
| 291 |
{headerShow && ( |
| 292 |
<div className="better-payment-user-dashboard-header bp--db-header bp-dashboard-header flex items-center justify-center"> |
| 293 |
{sidebarShow && ( |
| 294 |
<div className="bp-dashboard-hamburger padding-0 bp-visible-xs"> |
| 295 |
<svg fill="none" viewBox="0 0 24 24" width="25" xmlns="http://www.w3.org/2000/svg"> |
| 296 |
<path clipRule="evenodd" d="m3 8c0-.55228.44772-1 1-1h16c.5523 0 1 .44772 1 1s-.4477 1-1 1h-16c-.55228 0-1-.44772-1-1zm0 4c0-.5523.44772-1 1-1h16c.5523 0 1 .4477 1 1s-.4477 1-1 1h-16c-.55228 0-1-.4477-1-1zm0 4c0-.5523.44772-1 1-1h8c.5523 0 1 .4477 1 1s-.4477 1-1 1h-8c-.55228 0-1-.4477-1-1z" fill="rgb(0,0,0)" fillRule="evenodd" /> |
| 297 |
</svg> |
| 298 |
</div> |
| 299 |
)} |
| 300 |
<h2>{dashboardLabel || __('Dashboard', 'better-payment')}</h2> |
| 301 |
<button className="primary-btn bp-hidden-xs"> |
| 302 |
<a href="#" onClick={(e) => { e.preventDefault(); window.location.reload(); }}> |
| 303 |
{refreshStatsLabel || __('Refresh Stats', 'better-payment')} |
| 304 |
</a> |
| 305 |
</button> |
| 306 |
</div> |
| 307 |
)} |
| 308 |
|
| 309 |
<div className="bp-dashboard_wrapper"> |
| 310 |
{/* Transaction Summary */} |
| 311 |
{dashboardTransactionSummaryShow && ( |
| 312 |
<div className="bp-amount_wrapper"> |
| 313 |
<div className="bp-row"> |
| 314 |
<div className="bp-col_4 bp-col"> |
| 315 |
<div className="bp-amount"> |
| 316 |
<h4 className="mb-0 pb-0"> |
| 317 |
{dashboardTotalAmountLabel || __('Total Amount', 'better-payment')} |
| 318 |
</h4> |
| 319 |
<div className="bp-amount_price mb-0 pb-0"> |
| 320 |
<span>${parseFloat(analytics.total_transactions_amount || 0).toFixed(2)}</span> |
| 321 |
</div> |
| 322 |
<div className="bp-transaction"> |
| 323 |
<span className="bp-transaction_title">No of Transaction:</span> |
| 324 |
<span className="bp-transaction_number"> {analytics.total_transactions_count || 0}</span> |
| 325 |
</div> |
| 326 |
</div> |
| 327 |
</div> |
| 328 |
|
| 329 |
<div className="bp-col_4 bp-col"> |
| 330 |
<div className="bp-amount"> |
| 331 |
<h4 className="mb-0 pb-0"> |
| 332 |
{dashboardCompletedAmountLabel || __('Completed Amount', 'better-payment')} |
| 333 |
</h4> |
| 334 |
<div className="bp-amount_price mb-0 pb-0"> |
| 335 |
<span>${parseFloat(analytics.completed_transactions_amount || 0).toFixed(2)}</span> |
| 336 |
</div> |
| 337 |
<div className="bp-transaction"> |
| 338 |
<span className="bp-transaction_title">No of Transaction:</span> |
| 339 |
<span className="bp-transaction_number"> {analytics.completed_transactions_count || 0}</span> |
| 340 |
</div> |
| 341 |
</div> |
| 342 |
</div> |
| 343 |
|
| 344 |
<div className="bp-col_4 bp-col"> |
| 345 |
<div className="bp-amount"> |
| 346 |
<h4 className="mb-0 pb-0"> |
| 347 |
{dashboardIncompleteAmountLabel || __('Incomplete Amount', 'better-payment')} |
| 348 |
</h4> |
| 349 |
<div className="bp-amount_price mb-0 pb-0"> |
| 350 |
<span>${parseFloat(analytics.incomplete_transactions_amount || 0).toFixed(2)}</span> |
| 351 |
</div> |
| 352 |
<div className="bp-transaction"> |
| 353 |
<span className="bp-transaction_title">No of Transaction:</span> |
| 354 |
<span className="bp-transaction_number"> {analytics.incomplete_transactions_count || 0}</span> |
| 355 |
</div> |
| 356 |
</div> |
| 357 |
</div> |
| 358 |
|
| 359 |
<div className="bp-col_4 bp-col"> |
| 360 |
<div className="bp-amount"> |
| 361 |
<h4 className="mb-0 pb-0"> |
| 362 |
{dashboardRefundedAmountLabel || __('Refunded Amount', 'better-payment')} |
| 363 |
</h4> |
| 364 |
<div className="bp-amount_price mb-0 pb-0"> |
| 365 |
<span>${parseFloat(analytics.refunded_transactions_amount || 0).toFixed(2)}</span> |
| 366 |
</div> |
| 367 |
<div className="bp-transaction"> |
| 368 |
<span className="bp-transaction_title">No of Transaction:</span> |
| 369 |
<span className="bp-transaction_number"> {analytics.refunded_transactions_count || 0}</span> |
| 370 |
</div> |
| 371 |
</div> |
| 372 |
</div> |
| 373 |
</div> |
| 374 |
</div> |
| 375 |
)} |
| 376 |
|
| 377 |
{/* Analytics chart (75%) + Recent Transactions (25%) — siblings in one row, matching template-dashboard-tab.php */} |
| 378 |
{(dashboardAnalyticsReportShow || dashboardRecentTransactionsShow) && ( |
| 379 |
<div className="bp-analytics_chart-wrapper"> |
| 380 |
<div className="bp-row"> |
| 381 |
{dashboardAnalyticsReportShow && ( |
| 382 |
<div className="bp-col_10 bp-col"> |
| 383 |
<AnalyticsReport |
| 384 |
proEnabled={proEnabled} |
| 385 |
proAssets={proAssets} |
| 386 |
analyticsChartData={analyticsChartData} |
| 387 |
bp_settings={attributes} |
| 388 |
/> |
| 389 |
</div> |
| 390 |
)} |
| 391 |
|
| 392 |
{dashboardRecentTransactionsShow && ( |
| 393 |
<div className="bp-col_4 bp-col"> |
| 394 |
<div className="bp-recent_box"> |
| 395 |
<div className="bp-recent_header flex justify-between items-center"> |
| 396 |
<div className="flex gap-2 items-center"> |
| 397 |
<span> |
| 398 |
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 399 |
<path d="M13.9586 2.16663H6.03361C5.06788 2.16663 4.58502 2.16663 4.19558 2.30213C3.45711 2.55909 2.87733 3.15595 2.62772 3.91618C2.49609 4.31708 2.49609 4.81417 2.49609 5.80834V17.4785C2.49609 18.1937 3.31692 18.5731 3.83617 18.098C4.14124 17.8188 4.60095 17.8188 4.90601 18.098L5.30859 18.4664C5.84325 18.9556 6.64894 18.9556 7.18359 18.4664C7.71825 17.9771 8.52394 17.9771 9.05859 18.4664C9.59325 18.9556 10.3989 18.9556 10.9336 18.4664C11.4682 17.9771 12.2739 17.9771 12.8086 18.4664C13.3432 18.9556 14.1489 18.9556 14.6836 18.4664L15.0862 18.098C15.3912 17.8188 15.8509 17.8188 16.156 18.098C16.6753 18.5731 17.4961 18.1937 17.4961 17.4785V5.80834C17.4961 4.81417 17.4961 4.31708 17.3645 3.91618C17.1149 3.15595 16.5351 2.55909 15.7966 2.30213C15.4072 2.16663 14.9243 2.16663 13.9586 2.16663Z" stroke="#475467" strokeWidth="1.25" /> |
| 400 |
<path d="M8.74609 9.66663L14.1628 9.66663" stroke="#475467" strokeWidth="1.25" strokeLinecap="round" /> |
| 401 |
<path d="M5.8291 9.66663H6.24577" stroke="#475467" strokeWidth="1.25" strokeLinecap="round" /> |
| 402 |
<path d="M5.8291 6.75H6.24577" stroke="#475467" strokeWidth="1.25" strokeLinecap="round" /> |
| 403 |
<path d="M5.8291 12.5834H6.24577" stroke="#475467" strokeWidth="1.25" strokeLinecap="round" /> |
| 404 |
<path d="M8.74609 6.75H14.1628" stroke="#475467" strokeWidth="1.25" strokeLinecap="round" /> |
| 405 |
<path d="M8.74609 12.5834H14.1628" stroke="#475467" strokeWidth="1.25" strokeLinecap="round" /> |
| 406 |
</svg> |
| 407 |
</span> |
| 408 |
<h3 className="bp-recent_header-title"> |
| 409 |
{dashboardRecentTransactionsLabel || __('Recent Transactions', 'better-payment')} |
| 410 |
</h3> |
| 411 |
</div> |
| 412 |
<a href="#" className="bp-view_all-btn is-hidden"> |
| 413 |
{dashboardViewAllLabel || __('View All', 'better-payment')} |
| 414 |
</a> |
| 415 |
</div> |
| 416 |
|
| 417 |
<div className="bp-recent_body"> |
| 418 |
<div className="bp-th flex items-center"> |
| 419 |
<div className="w-195"> |
| 420 |
<h5>Transaction</h5> |
| 421 |
</div> |
| 422 |
<div className="w-80"> |
| 423 |
<h5>Amount</h5> |
| 424 |
</div> |
| 425 |
</div> |
| 426 |
|
| 427 |
<div className="bp-product_scroll"> |
| 428 |
{txRows && txRows.length > 0 ? ( |
| 429 |
txRows.slice(0, 5).map((txn, index) => ( |
| 430 |
<div key={index} className="bp-td flex items-center"> |
| 431 |
<div className="td-product flex items-start w-195 flex-col"> |
| 432 |
<div className="td-product_logo"> |
| 433 |
<img |
| 434 |
src={txn.source === 'paypal' ? assetUrl + '/img/paypal.png' : assetUrl + '/img/' + txn.source + '.svg'} |
| 435 |
title={txn.source.toUpperCase()} |
| 436 |
alt={txn.source} |
| 437 |
/> |
| 438 |
</div> |
| 439 |
<div className="td-product_info"> |
| 440 |
<h4 className="td-product_name"> |
| 441 |
{formatBpDateOnly( txn.payment_date )} |
| 442 |
</h4> |
| 443 |
</div> |
| 444 |
</div> |
| 445 |
<div className="td-product_price w-80"> |
| 446 |
<span>{txn.currency} {parseFloat(txn.amount).toFixed(2)}</span> |
| 447 |
</div> |
| 448 |
</div> |
| 449 |
)) |
| 450 |
) : ( |
| 451 |
<div> |
| 452 |
<p className="bp-no_subscription-text">{noItemsLabel || __('No records found!', 'better-payment')}</p> |
| 453 |
</div> |
| 454 |
)} |
| 455 |
</div> |
| 456 |
</div> |
| 457 |
</div> |
| 458 |
</div> |
| 459 |
)} |
| 460 |
</div> |
| 461 |
</div> |
| 462 |
)} |
| 463 |
|
| 464 |
{/* Subscriptions Section (Pro only) — both recurring and split in one wrapper, matching template-dashboard-tab.php */} |
| 465 |
{(dashboardRecurringSubscriptionShow || dashboardSplitSubscriptionShow) && ( |
| 466 |
<div className="bp-subscription_wrapper"> |
| 467 |
<div className="bp-row"> |
| 468 |
{dashboardRecurringSubscriptionShow && ( |
| 469 |
<div className="bp-col_7 bp-col"> |
| 470 |
<RecurringSubscriptionWidget |
| 471 |
proEnabled={proEnabled} |
| 472 |
proAssets={proAssets} |
| 473 |
subscriptions={recurringSubscriptions} |
| 474 |
title={attributes.dashboardRecurringSubscriptionsLabel || 'Recurring Subscriptions'} |
| 475 |
viewAllLabel={attributes.dashboardViewAllLabel} |
| 476 |
noItemsLabel={attributes.noItemsLabel} |
| 477 |
/> |
| 478 |
</div> |
| 479 |
)} |
| 480 |
|
| 481 |
{dashboardSplitSubscriptionShow && ( |
| 482 |
<div className="bp-col_7 bp-col"> |
| 483 |
<SplitSubscriptionWidget |
| 484 |
proEnabled={proEnabled} |
| 485 |
proAssets={proAssets} |
| 486 |
subscriptions={splitSubscriptions} |
| 487 |
title={attributes.dashboardSplitSubscriptionsLabel || 'Split Subscriptions'} |
| 488 |
viewAllLabel={attributes.dashboardViewAllLabel} |
| 489 |
noItemsLabel={attributes.noItemsLabel} |
| 490 |
/> |
| 491 |
</div> |
| 492 |
)} |
| 493 |
</div> |
| 494 |
</div> |
| 495 |
)} |
| 496 |
</div> |
| 497 |
</div> |
| 498 |
)} |
| 499 |
|
| 500 |
{/* Transactions Tab */} |
| 501 |
{transactionsShow && activeTab === 'transactions' && ( |
| 502 |
<div className="bp--tab-conetnt-wrapper transactions-tab-wrapper"> |
| 503 |
{headerShow && ( |
| 504 |
<div className="better-payment-user-dashboard-header bp--db-header bp-dashboard-header flex items-center justify-center"> |
| 505 |
{sidebarShow && ( |
| 506 |
<div className="bp-dashboard-hamburger padding-0 bp-visible-xs"> |
| 507 |
<svg fill="none" viewBox="0 0 24 24" width="25" xmlns="http://www.w3.org/2000/svg"> |
| 508 |
<path clipRule="evenodd" d="m3 8c0-.55228.44772-1 1-1h16c.5523 0 1 .44772 1 1s-.4477 1-1 1h-16c-.55228 0-1-.44772-1-1zm0 4c0-.5523.44772-1 1-1h16c.5523 0 1 .4477 1 1s-.4477 1-1 1h-16c-.55228 0-1-.4477-1-1zm0 4c0-.5523.44772-1 1-1h8c.5523 0 1 .4477 1 1s-.4477 1-1 1h-8c-.55228 0-1-.4477-1-1z" fill="rgb(0,0,0)" fillRule="evenodd" /> |
| 509 |
</svg> |
| 510 |
</div> |
| 511 |
)} |
| 512 |
<h2>{transactionLabel || __('Transactions', 'better-payment')}</h2> |
| 513 |
</div> |
| 514 |
)} |
| 515 |
|
| 516 |
<div className="bp--body-content"> |
| 517 |
<div className="bp--table-main-wrapper"> |
| 518 |
<div className="bp--table-wrapper transaction better-payment-user-dashboard-table"> |
| 519 |
<div className="better-payment-user-dashboard-table-header bp--table-header bp-min_width-1300 flex justify-between gap-1"> |
| 520 |
{transactionTableNameShow && ( |
| 521 |
<div className="th user-name details"> |
| 522 |
<h5>{transactionTableNameLabel || __('Name', 'better-payment')}</h5> |
| 523 |
</div> |
| 524 |
)} |
| 525 |
{transactionTableEmailAddressShow && ( |
| 526 |
<div className="th details email"> |
| 527 |
<h5>{transactionTableEmailAddressLabel || __('Email Address', 'better-payment')}</h5> |
| 528 |
</div> |
| 529 |
)} |
| 530 |
{transactionTableAmountShow && ( |
| 531 |
<div className="th details amount"> |
| 532 |
<h5>{transactionTableAmountLabel || __('Amount', 'better-payment')}</h5> |
| 533 |
</div> |
| 534 |
)} |
| 535 |
{transactionTablePaymentTypeShow && ( |
| 536 |
<div className="th details"> |
| 537 |
<h5>{transactionTablePaymentTypeLabel || __('Payment Type', 'better-payment')}</h5> |
| 538 |
</div> |
| 539 |
)} |
| 540 |
{transactionTableTransactionIdShow && ( |
| 541 |
<div className="th details transaction"> |
| 542 |
<h5>{transactionTableTransactionIdLabel || __('Transaction ID', 'better-payment')}</h5> |
| 543 |
</div> |
| 544 |
)} |
| 545 |
{transactionTableSourceShow && ( |
| 546 |
<div className="th details flex justify-center"> |
| 547 |
<h5>{transactionTableSourceLabel || __('Source', 'better-payment')}</h5> |
| 548 |
</div> |
| 549 |
)} |
| 550 |
{transactionTableStatusShow && ( |
| 551 |
<div className="th details flex justify-center"> |
| 552 |
<h5>{transactionTableStatusLabel || __('Status', 'better-payment')}</h5> |
| 553 |
</div> |
| 554 |
)} |
| 555 |
{transactionTableDateShow && ( |
| 556 |
<div className="th details flex justify-center"> |
| 557 |
<h5>{transactionTableDateLabel || __('Date', 'better-payment')}</h5> |
| 558 |
</div> |
| 559 |
)} |
| 560 |
</div> |
| 561 |
|
| 562 |
<div style={{ position: 'relative', opacity: txLoading ? 0.5 : 1 }}> |
| 563 |
{txRows && txRows.length > 0 ? ( |
| 564 |
txRows.map((txn, index) => { |
| 565 |
// customer_name and customer_email are pre-computed by PHP (BlockManager) |
| 566 |
// and by the REST API (UserAPI) — use them directly. |
| 567 |
const customerName = txn.customer_name || ''; |
| 568 |
const customerEmail = txn.customer_email || ''; |
| 569 |
const paymentType = txn.is_subscription || (() => { |
| 570 |
const ffi = txn.form_fields_info || {}; |
| 571 |
return ffi.subscription_id ? 'Subscription' : 'One Time'; |
| 572 |
})(); |
| 573 |
const { label: statusLabel, color: statusColor } = getBpStatusInfo( txn.status ); |
| 574 |
|
| 575 |
return ( |
| 576 |
<div key={index} className="better-payment-user-dashboard-table-body bp--table-body bp-min_width-1300 flex items-center justify-between gap-1"> |
| 577 |
{transactionTableNameShow && ( |
| 578 |
<div className="td details user-name flex items-center gap-3"> |
| 579 |
<p>{customerName || 'N/A'}</p> |
| 580 |
</div> |
| 581 |
)} |
| 582 |
{transactionTableEmailAddressShow && ( |
| 583 |
<div className="td details email flex items-center gap-3"> |
| 584 |
<p> |
| 585 |
<span id={`bp_email_copy_clipboard_input_${index + 1}`}> |
| 586 |
{customerEmail || 'N/A'} |
| 587 |
</span> |
| 588 |
<span |
| 589 |
id={`bp_email_copy_clipboard_${index + 1}`} |
| 590 |
className="bp-icon bp-copy-square bp-email-copy-clipboard" |
| 591 |
title="Copy" |
| 592 |
data-bp_txn_counter={index + 1} |
| 593 |
/> |
| 594 |
</p> |
| 595 |
</div> |
| 596 |
)} |
| 597 |
{transactionTableAmountShow && ( |
| 598 |
<div className="td details amount flex items-center gap-3"> |
| 599 |
<p>{txn.currency} {parseFloat(txn.amount).toFixed(2)}</p> |
| 600 |
</div> |
| 601 |
)} |
| 602 |
{transactionTablePaymentTypeShow && ( |
| 603 |
<div className="td details flex items-center gap-3"> |
| 604 |
<p>{paymentType}</p> |
| 605 |
</div> |
| 606 |
)} |
| 607 |
{transactionTableTransactionIdShow && ( |
| 608 |
<div className="td details transaction flex items-center gap-3"> |
| 609 |
{txn.transaction_id ? ( |
| 610 |
<p> |
| 611 |
<span id={`bp_copy_clipboard_input_${index + 1}`}> |
| 612 |
{txn.transaction_id} |
| 613 |
</span> |
| 614 |
<span |
| 615 |
id={`bp_copy_clipboard_${index + 1}`} |
| 616 |
className="bp-icon bp-copy-square bp-copy-clipboard" |
| 617 |
title="Copy" |
| 618 |
data-bp_txn_counter={index + 1} |
| 619 |
/> |
| 620 |
</p> |
| 621 |
) : null} |
| 622 |
</div> |
| 623 |
)} |
| 624 |
{transactionTableSourceShow && ( |
| 625 |
<div className="td details flex justify-center gap-3"> |
| 626 |
<img |
| 627 |
className="source--img" |
| 628 |
src={txn.source === 'paypal' ? assetUrl + '/img/paypal.png' : assetUrl + '/img/' + txn.source + '.svg'} |
| 629 |
title={txn.source ? txn.source.toUpperCase() : ''} |
| 630 |
alt={txn.source || ''} |
| 631 |
/> |
| 632 |
</div> |
| 633 |
)} |
| 634 |
{transactionTableStatusShow && ( |
| 635 |
<div className="td details flex justify-center gap-3"> |
| 636 |
<p data-id={txn.id}> |
| 637 |
<span style={{ color: '#fff', padding: '7px 15px', borderRadius: '20px', background: statusColor }}> |
| 638 |
{txn.status_label || statusLabel} |
| 639 |
</span> |
| 640 |
</p> |
| 641 |
</div> |
| 642 |
)} |
| 643 |
{transactionTableDateShow && ( |
| 644 |
<div className="td details flex justify-center gap-3"> |
| 645 |
<p>{formatBpDate( txn.payment_date )}</p> |
| 646 |
</div> |
| 647 |
)} |
| 648 |
</div> |
| 649 |
); |
| 650 |
}) |
| 651 |
) : ( |
| 652 |
<div className="flex justify-center m-5"> |
| 653 |
<p className="bp-no_subscription-text">{noItemsLabel || __('No records found!', 'better-payment')}</p> |
| 654 |
</div> |
| 655 |
)} |
| 656 |
</div> |
| 657 |
|
| 658 |
{txMeta.pages > 1 && ( |
| 659 |
<div className="bp--pagination pagination"> |
| 660 |
<ul> |
| 661 |
<li> |
| 662 |
<a |
| 663 |
href="#" |
| 664 |
className={`pagination-previous bp--prev${txPage <= 1 ? ' disabled' : ''}`} |
| 665 |
aria-disabled={txPage <= 1} |
| 666 |
onClick={(e) => { e.preventDefault(); if (txPage > 1) setTxPage(p => p - 1); }} |
| 667 |
> |
| 668 |
<i className="bp-icon bp-caret-left"></i> |
| 669 |
</a> |
| 670 |
</li> |
| 671 |
{(() => { |
| 672 |
const total = txMeta.pages; |
| 673 |
const items = []; |
| 674 |
const rendered = {}; |
| 675 |
let ellipsisLeft = false; |
| 676 |
let ellipsisRight = false; |
| 677 |
|
| 678 |
for (let p = 1; p <= total; p++) { |
| 679 |
const inWindow = p === 1 || p === total || (p >= txPage - 2 && p <= txPage + 2); |
| 680 |
if (total <= 7 || inWindow) { |
| 681 |
const isActive = p === txPage; |
| 682 |
items.push( |
| 683 |
<li key={p}> |
| 684 |
<a href="#" className={`pagination-link bp--page-num${isActive ? ' is-current active' : ''}`} aria-label={`Page ${p}`} |
| 685 |
onClick={(e) => { e.preventDefault(); if (!isActive) setTxPage(p); }}> {p} </a> |
| 686 |
</li> |
| 687 |
); |
| 688 |
rendered[p] = true; |
| 689 |
} else { |
| 690 |
const inLeftGap = p < txPage - 2 && !ellipsisLeft; |
| 691 |
const inRightGap = p > txPage + 2 && !ellipsisRight; |
| 692 |
if (inLeftGap) { |
| 693 |
const mid = Math.max(1, Math.round((2 + (txPage - 3)) / 2)); |
| 694 |
items.push(<li key={`el-l`}><a href="#" className="pagination-link bp--page-num bp--ellipsis" data-page={mid} title={`Jump to page ${mid}`} onClick={(e) => { e.preventDefault(); setTxPage(mid); }}>...</a></li>); |
| 695 |
ellipsisLeft = true; |
| 696 |
} else if (inRightGap) { |
| 697 |
const mid = Math.min(total, Math.round(((txPage + 3) + (total - 1)) / 2)); |
| 698 |
items.push(<li key={`el-r`}><a href="#" className="pagination-link bp--page-num bp--ellipsis" data-page={mid} title={`Jump to page ${mid}`} onClick={(e) => { e.preventDefault(); setTxPage(mid); }}>...</a></li>); |
| 699 |
ellipsisRight = true; |
| 700 |
} |
| 701 |
} |
| 702 |
} |
| 703 |
return items; |
| 704 |
})()} |
| 705 |
<li> |
| 706 |
<a |
| 707 |
href="#" |
| 708 |
className={`pagination-next bp--next${txPage >= txMeta.pages ? ' disabled' : ''}`} |
| 709 |
aria-disabled={txPage >= txMeta.pages} |
| 710 |
onClick={(e) => { e.preventDefault(); if (txPage < txMeta.pages) setTxPage(p => p + 1); }} |
| 711 |
> |
| 712 |
<i className="bp-icon bp-caret-right"></i> |
| 713 |
</a> |
| 714 |
</li> |
| 715 |
</ul> |
| 716 |
</div> |
| 717 |
)} |
| 718 |
</div> |
| 719 |
</div> |
| 720 |
</div> |
| 721 |
</div> |
| 722 |
)} |
| 723 |
|
| 724 |
{/* Subscriptions Tab (Pro only) */} |
| 725 |
{subscriptionsShow && activeTab === 'subscriptions' && ( |
| 726 |
<SubscriptionsTab |
| 727 |
proEnabled={proEnabled} |
| 728 |
proAssets={proAssets} |
| 729 |
subscriptions={subRows} |
| 730 |
subPage={subPage} |
| 731 |
subMeta={subMeta} |
| 732 |
setSubPage={setSubPage} |
| 733 |
attributes={attributes} |
| 734 |
noItemsLabel={attributes.noItemsLabel} |
| 735 |
/> |
| 736 |
)} |
| 737 |
</div> |
| 738 |
</div> |
| 739 |
</div> |
| 740 |
); |
| 741 |
} |
| 742 |
|