feat: migrate Products page to full DataTable

Update Products page with full-featured DataTable:
- All columns (ID, title, price with multi-currency, type, stock, status)
- Status filter (All, Published, Draft)
- Inline actions (edit variations, delete, duplicate)
- Bulk delete and Add New modal
- VariationPricingTable editor preserved for editing individual products

All 7 admin listing pages now use the full-featured DataTable component:
✓ Forms - with shortcode copy button
✓ Coupons - with type and amount display
✓ Access - with product relation
✓ Orders - specialized with filters and date ranges
✓ Products - with multi-currency pricing
✓ Customers - read-only with order counts
✓ Licenses - read-only with status labels

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:20:19 +07:00
parent 128e396040
commit bb74df4d6b
5 changed files with 130 additions and 26 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' => 'ea8676d843297ad5e987');
<?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/trash', 'wp-icons/build/visible'), 'version' => '42bdafafef51db2063cf');

File diff suppressed because one or more lines are too long

View File

@@ -1,24 +1,22 @@
/**
* Products Page - Product list and editor
* Products Page - Product management with full table features
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { Icon } from '@wordpress/icons';
import plus from '@wordpress/icons/build/plus';
import DataTable from '../components/shared/DataTable';
import VariationPricingTable from '../components/products/VariationPricingTable';
import './AdminPages.css';
export default function ProductsPage({ initialData }) {
export default function ProductsPage() {
const [isEditor, setIsEditor] = useState(false);
const [selectedProductId, setSelectedProductId] = useState(null);
// This would be the actual product data loaded from the server
const productDetails = initialData?.productDetails || {};
// If in editor mode, show the variation pricing table
if (isEditor && selectedProductId) {
return (
<div className="formipay-page-products">
<div className="formipay-products-header">
<div className="formipay-page-header">
<button
type="button"
className="button button-secondary"
@@ -32,33 +30,139 @@ export default function ProductsPage({ initialData }) {
<div className="formipay-product-editor">
<VariationPricingTable
productId={selectedProductId}
productDetails={productDetails}
productDetails={{}}
/>
</div>
</div>
);
}
const columns = [
{
key: 'ID',
label: __('ID', 'formipay'),
render: (row) => <strong>#{row.ID}</strong>
},
{
key: 'title',
label: __('Title', 'formipay'),
render: (row) => (
<>
<strong>
<button
className="button-link"
onClick={() => {
setSelectedProductId(row.ID);
setIsEditor(true);
}}
>
{row.post_title || row.title || __('Untitled', 'formipay')}
</button>
</strong>
<br />
<span className="row-actions" style={{ display: 'none', visibility: 'hidden' }}>
<button className="button-link" onClick={() => {
setSelectedProductId(row.ID);
setIsEditor(true);
}}>
{__('Edit Variations', 'formipay')}
</button>
{' | '}
<button className="button-link delete" data-id={row.ID}>
{__('Delete', 'formipay')}
</button>
{' | '}
<button className="button-link duplicate" data-id={row.ID}>
{__('Duplicate', 'formipay')}
</button>
</span>
</>
)
},
{
key: 'price',
label: __('Price', 'formipay'),
render: (row) => {
// Multi-currency price display
const prices = row.prices || row.price;
if (typeof prices === 'object' && prices !== null) {
return Object.entries(prices).map(([currency, price]) => (
<div key={currency}>
{currency}: {price}
</div>
));
}
return prices || '-';
}
},
{
key: 'type',
label: __('Type', 'formipay'),
render: (row) => {
const type = row.product_type || row.type || 'digital';
return type === 'physical' ? __('Physical', 'formipay') : __('Digital', 'formipay');
}
},
{
key: 'stock',
label: __('Stock', 'formipay'),
render: (row) => {
const stock = row.stock;
if (stock === null || stock === '') return __('Unlimited', 'formipay');
return stock;
}
},
{
key: 'status',
label: __('Status', 'formipay'),
render: (row) => {
const status = row.post_status || row.status || 'unknown';
const statusLabels = {
publish: __('Published', 'formipay'),
draft: __('Draft', 'formipay'),
};
return (
<span className={`status-label ${status}`}>
{statusLabels[status] || status}
</span>
);
}
},
];
return (
<div className="formipay-page-products">
<div className="formipay-products-list-header">
<div className="formipay-page-header">
<h1>{ __('Products', 'formipay') }</h1>
<button
type="button"
className="button button-primary"
onClick={() => {
setSelectedProductId(null);
setIsEditor(true);
}}
>
<Icon icon={plus()} size={16} />
{ __('Add New Product', 'formipay') }
</button>
</div>
<p className="formipay-coming-soon">
{ __('Products list coming soon. Use the classic editor for now.', 'formipay') }
</p>
<DataTable
columns={columns}
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}
nonce={window.formipayAdmin?.nonce || ''}
tableAction="formipay-tabledata-products"
deleteAction="formipay-delete-product"
duplicateAction="formipay-duplicate-product"
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 Product', 'formipay'),
action: 'formipay-create-product-post',
},
bulkDelete: {
action: 'formipay-bulk-delete-product',
},
inline: true,
}}
emptyMessage={__('No products found', 'formipay')}
/>
</div>
);
}