feat: migrate Coupons, Access, Customers, Licenses pages to full DataTable

Update all admin listing pages to use the full-featured DataTable component:

Coupons page:
- All columns (ID, code, type, amount, usages, date limit, status)
- Status filter (All, Active, Inactive)
- Inline actions and bulk delete
- Add New modal

Access page:
- All columns (ID, title, product, status, date)
- Status filter (All, Published, Draft)
- Inline actions and bulk delete
- Add New modal

Customers page:
- All columns (ID, name, email, phone, total orders, date)
- Read-only (no selection or inline actions)
- Search and pagination

Licenses page:
- All columns (ID, license key, product, order, email, status)
- Status labels (Active, Inactive, Expired)
- Read-only (no selection or inline actions)
- Search and pagination

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:13:09 +07:00
parent 8529cfa2c0
commit 128e396040
15 changed files with 103 additions and 162 deletions

View File

@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/eye-closed', 'wp-icons/build/eye-opened', 'wp-icons/build/list', 'wp-icons/build/message', 'wp-icons/build/minus', 'wp-icons/build/plus', 'wp-icons/build/trash', 'wp-icons/build/visible'), 'version' => '164b137f81dde0451242');
<?php return array('dependencies' => array('react', 'wp-components', 'wp-element', 'wp-i18n', 'wp-icons/build/arrow-left', 'wp-icons/build/bell', 'wp-icons/build/eye-closed', 'wp-icons/build/eye-opened', 'wp-icons/build/list', 'wp-icons/build/message', 'wp-icons/build/minus', 'wp-icons/build/plus', 'wp-icons/build/trash', 'wp-icons/build/visible'), 'version' => 'ea8676d843297ad5e987');

File diff suppressed because one or more lines are too long

View File

@@ -1,43 +1,17 @@
/**
* Access Page - Access items management
* Access Page - Access items management with full table features
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { accessApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function AccessPage({ initialData }) {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const loadItems = useCallback(() => {
setLoading(true);
accessApi.list()
.then(result => {
// Handle both WordPress format and direct format
const items = result.data?.results || result.results || result.data || [];
setItems(items);
})
.catch(error => {
console.error('Load access items error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadItems();
}, [loadItems]);
export default function AccessPage() {
const columns = [
{
key: 'id',
key: 'ID',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.id}</strong>
render: (row) => <strong>#{row.ID}</strong>
},
{
key: 'title',
@@ -57,7 +31,7 @@ export default function AccessPage({ initialData }) {
draft: __('Draft', 'formipay'),
}[status] || status;
return (
<span className={`status-badge status-${status}`}>
<span className={`status-label ${status}`}>
{statusLabel}
</span>
);
@@ -78,15 +52,33 @@ export default function AccessPage({ initialData }) {
<div className="formipay-page-access">
<div className="formipay-page-header">
<h1>{ __('Access Items', 'formipay') }</h1>
<Button variant="primary" href={window.formipayAdmin?.siteUrl + '/wp-admin/post-new.php?post_type=formipay-access'}>
{ __('+ Add New Item', 'formipay') }
</Button>
</div>
<DataTable
columns={columns}
data={items}
loading={loading}
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}
nonce={window.formipayAdmin?.nonce || ''}
tableAction="formipay-tabledata-access-items"
deleteAction="formipay-delete-access-item"
duplicateAction="formipay-duplicate-access-item"
filterOptions={{
key: 'post_status',
options: [
{ value: 'all', label: __('All', 'formipay') },
{ value: 'publish', label: __('Published', 'formipay') },
{ value: 'draft', label: __('Draft', 'formipay') },
]
}}
actions={{
addNew: {
label: __('+ Add New Item', 'formipay'),
action: 'formipay-create-access-item-post',
},
bulkDelete: {
action: 'formipay-bulk-delete-access-item',
},
inline: true,
}}
emptyMessage={__('No access items found', 'formipay')}
/>
</div>

View File

@@ -1,43 +1,17 @@
/**
* Coupons Page - Coupon management
* Coupons Page - Coupon management with full table features
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { Button } from '@wordpress/components';
import { couponsApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function CouponsPage({ initialData }) {
const [coupons, setCoupons] = useState([]);
const [loading, setLoading] = useState(true);
const loadCoupons = useCallback(() => {
setLoading(true);
couponsApi.list()
.then(result => {
// Handle both WordPress format and direct format
const coupons = result.data?.results || result.results || result.data || [];
setCoupons(coupons);
})
.catch(error => {
console.error('Load coupons error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadCoupons();
}, [loadCoupons]);
export default function CouponsPage() {
const columns = [
{
key: 'id',
key: 'ID',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.id}</strong>
render: (row) => <strong>#{row.ID}</strong>
},
{
key: 'code',
@@ -55,18 +29,36 @@ export default function CouponsPage({ initialData }) {
{
key: 'amount',
label: __('Amount', 'formipay'),
render: (row) => {
// Multi-currency display would go here
return row.amount || '-';
}
},
{
key: 'usages',
label: __('Usages', 'formipay'),
render: (row) => row.usage_count || row.usages || 0
},
{
key: 'date_limit',
label: __('Date Limit', 'formipay'),
render: (row) => {
const dateLimit = row.date_limit;
if (!dateLimit || dateLimit === 'none') return __('Unlimited', 'formipay');
return new Date(dateLimit).toLocaleDateString();
}
},
{
key: 'status',
label: __('Status', 'formipay'),
render: (row) => {
const status = row.post_status || row.status || 'unknown';
return status === 'publish' ? __('Active', 'formipay') : __('Inactive', 'formipay');
const isActive = row.active === 'on' || status === 'publish';
return (
<span className={`status-label ${isActive ? 'publish' : 'draft'}`}>
{isActive ? __('Active', 'formipay') : __('Inactive', 'formipay')}
</span>
);
}
},
];
@@ -75,26 +67,33 @@ export default function CouponsPage({ initialData }) {
<div className="formipay-page-coupons">
<div className="formipay-page-header">
<h1>{ __('Coupons', 'formipay') }</h1>
<Button
variant="primary"
onClick={() => {
// Open create modal - for now just prompt
const code = prompt(__('Enter coupon code:', 'formipay'));
if (code) {
couponsApi.create({ code })
.then(() => loadCoupons())
.catch(console.error);
}
}}
>
{ __('+ Add New Coupon', 'formipay') }
</Button>
</div>
<DataTable
columns={columns}
data={coupons}
loading={loading}
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}
nonce={window.formipayAdmin?.nonce || ''}
tableAction="formipay-tabledata-coupons"
deleteAction="formipay-delete-coupon"
duplicateAction="formipay-duplicate-coupon"
filterOptions={{
key: 'status',
options: [
{ value: 'all', label: __('All', 'formipay') },
{ value: 'active', label: __('Active', 'formipay') },
{ value: 'inactive', label: __('Inactive', 'formipay') },
]
}}
actions={{
addNew: {
label: __('+ Add New Coupon', 'formipay'),
action: 'formipay-create-coupon-post',
},
bulkDelete: {
action: 'formipay-bulk-delete-coupon',
},
inline: true,
}}
emptyMessage={__('No coupons found', 'formipay')}
/>
</div>

View File

@@ -1,37 +1,12 @@
/**
* Customers Page - Customer list
* Customers Page - Customer list with full table features
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import { customersApi } from '../api/client';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function CustomersPage({ initialData }) {
const [customers, setCustomers] = useState([]);
const [loading, setLoading] = useState(true);
const loadCustomers = useCallback(() => {
setLoading(true);
customersApi.list()
.then(result => {
// Handle both WordPress format and direct format
const customers = result.data?.results || result.results || result.data || [];
setCustomers(customers);
})
.catch(error => {
console.error('Load customers error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadCustomers();
}, [loadCustomers]);
export default function CustomersPage() {
const columns = [
{
key: 'id',
@@ -76,8 +51,11 @@ export default function CustomersPage({ initialData }) {
<DataTable
columns={columns}
data={customers}
loading={loading}
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}
nonce={window.formipayAdmin?.nonce || ''}
tableAction="formipay-tabledata-customers"
selectable={false}
inline={false}
emptyMessage={__('No customers found', 'formipay')}
/>
</div>

View File

@@ -1,46 +1,12 @@
/**
* Licenses Page - License management
* Licenses Page - License management with full table features
*/
import { __ } from '@wordpress/i18n';
import { useState, useCallback, useEffect } from '@wordpress/element';
import DataTable from '../components/shared/DataTable';
import './AdminPages.css';
export default function LicensesPage({ initialData }) {
const [licenses, setLicenses] = useState([]);
const [loading, setLoading] = useState(true);
const loadLicenses = useCallback(() => {
setLoading(true);
// Licenses API might use a different endpoint
fetch(window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'formipay-tabledata-licenses',
_wpnonce: window.formipayAdmin?.nonce || '',
}),
})
.then(response => response.json())
.then(result => {
// Handle both WordPress format and direct format
const licenses = result.data?.results || result.results || result.data || [];
setLicenses(licenses);
})
.catch(error => {
console.error('Load licenses error:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
useEffect(() => {
loadLicenses();
}, [loadLicenses]);
export default function LicensesPage() {
const columns = [
{
key: 'id',
@@ -76,25 +42,31 @@ export default function LicensesPage({ initialData }) {
inactive: __('Inactive', 'formipay'),
expired: __('Expired', 'formipay'),
};
return statusLabels[status] || status;
const statusClass = status === 'active' ? 'publish' : status === 'expired' ? 'pending' : 'draft';
return (
<span className={`status-label ${statusClass}`}>
{statusLabels[status] || status}
</span>
);
}
},
];
return (
<div className="formipay-page-licenses">
<div className="formipay-page-licenses">
<div className="formipay-page-header">
<h1>{ __('Licenses', 'formipay') }</h1>
</div>
<DataTable
columns={columns}
data={licenses}
loading={loading}
emptyMessage={__('No licenses found', 'formipay')}
/>
<div className="formipay-page-header">
<h1>{ __('Licenses', 'formipay') }</h1>
</div>
<DataTable
columns={columns}
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}
nonce={window.formipayAdmin?.nonce || ''}
tableAction="formipay-tabledata-licenses"
selectable={false}
inline={false}
emptyMessage={__('No licenses found', 'formipay')}
/>
</div>
);
}