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 { __ } from '@wordpress/i18n';
|
||||||
import { useState } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
import { Icon } from '@wordpress/icons';
|
import DataTable from '../components/shared/DataTable';
|
||||||
import plus from '@wordpress/icons/build/plus';
|
|
||||||
import VariationPricingTable from '../components/products/VariationPricingTable';
|
import VariationPricingTable from '../components/products/VariationPricingTable';
|
||||||
|
import './AdminPages.css';
|
||||||
|
|
||||||
export default function ProductsPage({ initialData }) {
|
export default function ProductsPage() {
|
||||||
const [isEditor, setIsEditor] = useState(false);
|
const [isEditor, setIsEditor] = useState(false);
|
||||||
const [selectedProductId, setSelectedProductId] = useState(null);
|
const [selectedProductId, setSelectedProductId] = useState(null);
|
||||||
|
|
||||||
// This would be the actual product data loaded from the server
|
// If in editor mode, show the variation pricing table
|
||||||
const productDetails = initialData?.productDetails || {};
|
|
||||||
|
|
||||||
if (isEditor && selectedProductId) {
|
if (isEditor && selectedProductId) {
|
||||||
return (
|
return (
|
||||||
<div className="formipay-page-products">
|
<div className="formipay-page-products">
|
||||||
<div className="formipay-products-header">
|
<div className="formipay-page-header">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="button button-secondary"
|
className="button button-secondary"
|
||||||
@@ -32,33 +30,139 @@ export default function ProductsPage({ initialData }) {
|
|||||||
<div className="formipay-product-editor">
|
<div className="formipay-product-editor">
|
||||||
<VariationPricingTable
|
<VariationPricingTable
|
||||||
productId={selectedProductId}
|
productId={selectedProductId}
|
||||||
productDetails={productDetails}
|
productDetails={{}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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 (
|
return (
|
||||||
<div className="formipay-page-products">
|
<div className="formipay-page-products">
|
||||||
<div className="formipay-products-list-header">
|
<div className="formipay-page-header">
|
||||||
<h1>{ __('Products', 'formipay') }</h1>
|
<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>
|
</div>
|
||||||
|
|
||||||
<p className="formipay-coming-soon">
|
<DataTable
|
||||||
{ __('Products list coming soon. Use the classic editor for now.', 'formipay') }
|
columns={columns}
|
||||||
</p>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user