feat: implement coexistence strategy for Grid.js and React admin

Implement dual-mode rendering allowing classic Grid.js and new React
versions to run side-by-side during migration.

- Add coexistence mode checks to all admin page methods
- Check query param ?react=1 or option 'formipay_use_react_admin'
- Include classic PHP pages when React not active
- Add admin notice showing current version with toggle button
- Add footer toggle link to switch between versions

This ensures zero feature loss - old Grid.js pages continue working
(~20 features per page) while React versions are developed.

Files:
- Form.php, Coupon.php, Access.php, Order.php
- Customer.php, Product.php, License.php
- ReactAdmin.php (added version_notice, footer_toggle)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 16:55:56 +07:00
parent ab69d03f78
commit bd9cdac02e
12 changed files with 570 additions and 275 deletions

View File

@@ -0,0 +1,26 @@
.formipay-data-table-loading,
.formipay-data-table-empty {
padding: 40px;
text-align: center;
}
.formipay-data-table {
margin-top: 20px;
}
.formipay-data-table thead th {
padding: 12px 10px;
font-weight: 600;
}
.formipay-data-table tbody td {
padding: 10px;
}
.formipay-data-table tbody tr.is-clickable {
cursor: pointer;
}
.formipay-data-table tbody tr.is-clickable:hover {
background-color: #f0f0f1;
}

View File

@@ -0,0 +1,57 @@
/**
* Data Table - Simple table component for admin listings
*/
import { __ } from '@wordpress/i18n';
import './DataTable.css';
export default function DataTable({
columns,
data,
loading,
emptyMessage = __('No items found', 'formipay'),
onRowClick
}) {
if (loading) {
return (
<div className="formipay-data-table-loading">
<span className="spinner is-active" />
</div>
);
}
if (!data || data.length === 0) {
return (
<div className="formipay-data-table-empty">
<p>{ emptyMessage }</p>
</div>
);
}
return (
<table className="formipay-data-table wp-list-table widefat fixed striped">
<thead>
<tr>
{columns.map((column) => (
<th key={column.key}>{column.label}</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, rowIndex) => (
<tr
key={rowIndex}
onClick={onRowClick ? () => onRowClick(row) : undefined}
className={onRowClick ? 'is-clickable' : ''}
>
{columns.map((column) => (
<td key={column.key}>
{column.render ? column.render(row) : row[column.key]}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}

View File

@@ -0,0 +1,45 @@
.formipay-page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.formipay-page-header h1 {
margin: 0;
font-size: 23px;
font-weight: 400;
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.status-badge.status-publish,
.status-badge.status-active {
background-color: #edfaef;
color: #00a32a;
}
.status-badge.status-draft,
.status-badge.status-inactive {
background-color: #f0f0f1;
color: #646970;
}
.status-badge.status-expired {
background-color: #f6f7f7;
color: #d63638;
}
code {
background-color: #f0f0f1;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 13px;
}