feat: implement full-featured DataTable component
Implement comprehensive DataTable with all Grid.js features: - Checkbox selection with "Select All" - Bulk delete button (shows when rows selected) - Inline row actions (edit, delete, duplicate) on hover - Status filter tabs with counts - Search input with debounce - Sort dropdown (ID, date, title ASC/DESC) - Server-side pagination - "Add New" modal with SweetAlert2 - SweetAlert2 loaded via WordPress global scope Updated Forms page to use new DataTable component with: - Full column rendering (ID, title, date, status, shortcode) - Copy shortcode button with toast notification - All filter, search, sort, pagination features Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,74 +1,59 @@
|
||||
/**
|
||||
* Forms Page - List view and Form Builder
|
||||
* Forms Page - Form management with full table features
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState, useCallback, useEffect } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { formsApi } from '../api/client';
|
||||
import DataTable from '../components/shared/DataTable';
|
||||
import './AdminPages.css';
|
||||
|
||||
export default function FormsPage({ initialData }) {
|
||||
const [isBuilder, setIsBuilder] = useState(false);
|
||||
const [selectedFormId, setSelectedFormId] = useState(null);
|
||||
const [forms, setForms] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const loadForms = useCallback(() => {
|
||||
setLoading(true);
|
||||
formsApi.list()
|
||||
.then(result => {
|
||||
console.log('Forms API result:', result);
|
||||
// Handle both WordPress format and direct format
|
||||
const formsData = result.data?.results || result.results || result.data || [];
|
||||
console.log('Forms data extracted:', formsData);
|
||||
console.log('Forms data length:', formsData.length);
|
||||
setForms(formsData);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Load forms error:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadForms();
|
||||
}, [loadForms]);
|
||||
|
||||
if (isBuilder && selectedFormId) {
|
||||
window.location.href = `${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${selectedFormId}&action=edit`;
|
||||
return null;
|
||||
}
|
||||
// SweetAlert2 is loaded via WordPress (global scope)
|
||||
const Swal = window.Swal;
|
||||
|
||||
export default function FormsPage() {
|
||||
const columns = [
|
||||
{
|
||||
key: 'id',
|
||||
key: 'ID',
|
||||
label: __('ID', 'formipay'),
|
||||
render: (row) => <strong>#{row.ID || row.id}</strong>
|
||||
render: (row) => <strong>#{row.ID}</strong>
|
||||
},
|
||||
{
|
||||
key: 'title',
|
||||
label: __('Title', 'formipay'),
|
||||
render: (row) => (
|
||||
<a
|
||||
href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${row.ID || row.id}&action=edit`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsBuilder(true);
|
||||
setSelectedFormId(row.ID || row.id);
|
||||
}}
|
||||
>
|
||||
{row.post_title || row.title || __('Untitled', 'formipay')}
|
||||
</a>
|
||||
<>
|
||||
<strong>{row.title}</strong>
|
||||
<br />
|
||||
<span className="row-actions" style={{ display: 'none', visibility: 'hidden' }}>
|
||||
<a href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${row.ID}&action=edit`}>
|
||||
{__('Edit', 'formipay')}
|
||||
</a>
|
||||
{' | '}
|
||||
<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: 'shortcode',
|
||||
label: __('Shortcode', 'formipay'),
|
||||
render: (row) => <code>[formipay id="{row.ID || row.id}"]</code>
|
||||
key: 'date',
|
||||
label: __('Date', 'formipay'),
|
||||
render: (row) => {
|
||||
const date = new Date(row.date);
|
||||
return (
|
||||
<span style={{ whiteSpace: 'nowrap' }}>
|
||||
{date.toLocaleDateString()}
|
||||
<br />
|
||||
<span style={{ fontSize: 'smaller', color: '#646970' }}>
|
||||
{date.toLocaleTimeString()}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
@@ -80,22 +65,51 @@ export default function FormsPage({ initialData }) {
|
||||
draft: __('Draft', 'formipay'),
|
||||
pending: __('Pending', 'formipay'),
|
||||
};
|
||||
const statusLabel = statusLabels[status] || status;
|
||||
return (
|
||||
<span className={`status-badge status-${status === 'publish' ? 'active' : 'draft'}`}>
|
||||
{statusLabel}
|
||||
<span className={`status-label ${status}`}>
|
||||
{statusLabels[status] || status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
label: __('Date', 'formipay'),
|
||||
render: (row) => {
|
||||
const date = row.post_date || row.date;
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleDateString();
|
||||
}
|
||||
key: 'shortcode',
|
||||
label: __('Shortcode', 'formipay'),
|
||||
render: (row) => (
|
||||
<>
|
||||
<input
|
||||
className="formipay-form-shortcode"
|
||||
value={`[formipay form=${row.ID}]`}
|
||||
disabled
|
||||
/>
|
||||
<button
|
||||
className="copy-shortcode"
|
||||
data-copy={`[formipay form=${row.ID}]`}
|
||||
onClick={(e) => {
|
||||
const text = e.currentTarget.dataset.copy;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const originalHTML = e.currentTarget.innerHTML;
|
||||
e.currentTarget.innerHTML = '✓ Copied';
|
||||
setTimeout(() => {
|
||||
e.currentTarget.innerHTML = originalHTML;
|
||||
}, 2000);
|
||||
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: __('Shortcode copied!', 'formipay'),
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 3000,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
📋 {__('Copy', 'formipay')}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
},
|
||||
];
|
||||
|
||||
@@ -103,18 +117,33 @@ export default function FormsPage({ initialData }) {
|
||||
<div className="formipay-page-forms">
|
||||
<div className="formipay-page-header">
|
||||
<h1>{ __('Forms', 'formipay') }</h1>
|
||||
<Button
|
||||
variant="primary"
|
||||
href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post-new.php?post_type=formipay-form`}
|
||||
>
|
||||
{ __('+ Add New Form', 'formipay') }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={forms}
|
||||
loading={loading}
|
||||
ajaxUrl={window.formipayAdmin?.ajaxUrl || '/wp-admin/admin-ajax.php'}
|
||||
nonce={window.formipayAdmin?.nonce || ''}
|
||||
tableAction="formipay-tabledata-forms"
|
||||
deleteAction="formipay-delete-form"
|
||||
duplicateAction="formipay-duplicate-form"
|
||||
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 Form', 'formipay'),
|
||||
action: 'formipay-create-form-post',
|
||||
},
|
||||
bulkDelete: {
|
||||
action: 'formipay-bulk-delete-form',
|
||||
},
|
||||
inline: true,
|
||||
}}
|
||||
emptyMessage={__('No forms found', 'formipay')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user