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:
@@ -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
BIN
node_modules/.cache/babel-loader/56ff2da9eaca4044ea8bcd1b989a0c761c03747c09a08c265122869f0005264b.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/56ff2da9eaca4044ea8bcd1b989a0c761c03747c09a08c265122869f0005264b.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/7b4d58247fbd4610bdea98659070acb7cec36e726e38e29cbc1defd7d681f8fb.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/7b4d58247fbd4610bdea98659070acb7cec36e726e38e29cbc1defd7d681f8fb.json.gz
generated
vendored
Normal file
Binary file not shown.
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="formipay-page-products">
|
||||
<div className="formipay-products-list-header">
|
||||
<h1>{ __('Products', 'formipay') }</h1>
|
||||
const columns = [
|
||||
{
|
||||
key: 'ID',
|
||||
label: __('ID', 'formipay'),
|
||||
render: (row) => <strong>#{row.ID}</strong>
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
label: __('Title', 'formipay'),
|
||||
render: (row) => (
|
||||
<>
|
||||
<strong>
|
||||
<button
|
||||
type="button"
|
||||
className="button button-primary"
|
||||
className="button-link"
|
||||
onClick={() => {
|
||||
setSelectedProductId(null);
|
||||
setSelectedProductId(row.ID);
|
||||
setIsEditor(true);
|
||||
}}
|
||||
>
|
||||
<Icon icon={plus()} size={16} />
|
||||
{ __('Add New Product', 'formipay') }
|
||||
{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-page-header">
|
||||
<h1>{ __('Products', 'formipay') }</h1>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user