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:
@@ -3,6 +3,6 @@
|
||||
.formipay-order-timeline h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.timeline-progress{justify-content:space-between;margin-bottom:24px}.timeline-progress,.timeline-step{align-items:center;display:flex;position:relative}.timeline-step{flex:1;flex-direction:column;gap:8px;z-index:1}.timeline-dot{background:#e0e0e0;border:2px solid #c3c4c7;border-radius:50%;height:24px;position:relative;width:24px}.timeline-step.completed .timeline-dot{background:#2271b1;border-color:#2271b1}.timeline-step.completed .timeline-dot:after{background:#fff;border-radius:50%;content:"";height:8px;right:50%;position:absolute;top:50%;transform:translate(50%,-50%);width:8px}.timeline-line{background:#e0e0e0;height:2px;right:50%;position:absolute;top:12px;width:100%;z-index:-1}.timeline-step.completed .timeline-line{background:#2271b1}.timeline-label{color:#646970;font-size:10px;font-weight:600;text-align:center;text-transform:uppercase}.timeline-events ul{list-style:none;margin:0;padding:0}.timeline-events li{border-bottom:1px solid #f0f0f1;display:grid;gap:8px;grid-template-columns:1fr auto;padding:12px 0}.timeline-events li:last-child{border-bottom:none}.event-status{color:#1e1e1e;font-size:13px;font-weight:600}.event-date{color:#646970;font-size:11px;text-align:left}.event-note{color:#646970;font-size:12px;grid-column:1/-1;margin-top:4px}.no-events{color:#646970;font-size:13px;padding:20px 0;text-align:center}
|
||||
.formipay-notification-log h3{align-items:center;color:#1e1e1e;display:flex;font-size:16px;font-weight:600;gap:8px;margin:0 0 16px}.formipay-notification-log svg{fill:#1e1e1e}.no-logs{color:#646970;font-size:13px;padding:20px 0;text-align:center}.notification-list{list-style:none;margin:0;padding:0}.notification-item{background:#f6f7f7;border:1px solid #e0e0e0;border-radius:4px;display:flex;gap:12px;margin-bottom:8px;padding:12px}.notification-icon{align-items:center;background:#fff;border-radius:50%;display:flex;flex-shrink:0;height:36px;justify-content:center;width:36px}.notification-icon svg{fill:#1e1e1e}.notification-content{flex:1;min-width:0}.notification-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.notification-type{color:#2271b1;font-size:10px;font-weight:700;text-transform:uppercase}.notification-status{border-radius:10px;font-size:10px;font-weight:600;padding:2px 6px}.notification-item.sent .notification-status{background:#e7f7ed;color:#28a745}.notification-item.failed .notification-status{background:#fbeaea;color:#dc3545}.notification-item.pending .notification-status{background:#fff8e5;color:#f0ad4e}.notification-details{display:flex;flex-direction:column;gap:2px}.notification-details strong{color:#1e1e1e;font-size:13px}.notification-date,.notification-recipient{color:#646970;font-size:11px}.notification-date{margin-top:4px}
|
||||
.formipay-order-detail{background:#f6f7f7;display:flex;flex-direction:column;height:100%}.formipay-detail-header{align-items:center;background:#fff;border-bottom:1px solid #e0e0e0;display:flex;gap:16px;justify-content:space-between;padding:16px 20px}.formipay-detail-header h1{flex:1;font-size:20px;font-weight:600;margin:0}.header-actions{display:flex;gap:8px}.formipay-detail-content{display:grid;gap:20px;grid-template-columns:2fr 1fr;overflow-y:auto;padding:20px}.formipay-detail-main,.formipay-detail-sidebar{display:flex;flex-direction:column;gap:20px}.formipay-detail-card{background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:20px}.formipay-detail-card h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.detail-list{display:grid;gap:16px;grid-template-columns:repeat(2,1fr)}.detail-list>div{display:flex;flex-direction:column}.detail-list dt{color:#646970;font-size:12px;font-weight:600;margin-bottom:4px}.detail-list dd{align-items:center;color:#1e1e1e;display:flex;font-size:14px;gap:8px}.detail-list dd .components-select-control{flex:1}.items-table{border-collapse:collapse;width:100%}.items-table td,.items-table th{border-bottom:1px solid #f0f0f1;padding:10px;text-align:right}.items-table th{color:#646970;font-size:12px;font-weight:600;text-transform:uppercase}.items-table td{font-size:13px}.items-table small{color:#646970;display:block;font-size:11px}.items-table tfoot td{border-bottom:none;border-top:2px solid #1e1e1e;padding-top:16px}.customer-info{display:grid;gap:12px;grid-template-columns:1fr}.customer-info>div{display:flex;flex-direction:column}.customer-info dt{color:#646970;font-size:11px;font-weight:600;margin-bottom:2px}.customer-info dd{color:#1e1e1e;font-size:13px}.no-data{color:#646970;font-size:13px}.formipay-error,.formipay-loading{align-items:center;display:flex;flex-direction:column;gap:16px;justify-content:center;padding:60px 20px}.formipay-error p{color:#646970;margin:0}
|
||||
.formipay-data-table-empty,.formipay-data-table-loading{padding:40px;text-align:center}.formipay-data-table{margin-top:20px}.formipay-data-table thead th{font-weight:600;padding:12px 10px}.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}
|
||||
.formipay-data-table-wrapper{margin:20px 0}.formipay-table-toolbar{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px}.formipay-table-search{flex-grow:1;max-width:300px}.formipay-table-toolbar .components-select-control{min-width:150px}.formipay-filter-tabs{border-bottom:1px solid #ddd;display:flex;gap:4px;margin-bottom:16px}.formipay-filter-tabs .filter-tab{background:transparent;border:none;border-bottom:3px solid transparent;color:#646970;cursor:pointer;font-size:13px;padding:8px 16px;transition:all .2s}.formipay-filter-tabs .filter-tab:hover{background:#f0f0f1;color:#135e96}.formipay-filter-tabs .filter-tab.active{border-bottom-color:#135e96;color:#135e96;font-weight:600}.formipay-filter-tabs .filter-tab .count{background:#dcdcde;border-radius:10px;display:inline-block;font-size:11px;line-height:1.4;margin-right:6px;min-width:18px;padding:2px 6px}.formipay-filter-tabs .filter-tab.active .count{background:#135e96;color:#fff}.formipay-table-container{background:#fff;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04)}.formipay-table-loading{padding:60px;text-align:center}.formipay-table-empty{color:#646970;padding:40px;text-align:center}.formipay-table{border-collapse:collapse;width:100%}.formipay-table thead th{background:#f6f7f7;border-bottom:1px solid #c3c4c7;font-weight:600;padding:12px 10px;text-align:right}.formipay-table tbody td{border-bottom:1px solid #c3c4c7;padding:10px}.formipay-table tbody tr:last-child td{border-bottom:none}.formipay-table tbody tr:hover{background-color:#f0f0f1}.formipay-table .column-select{text-align:center;width:40px}.formipay-table tbody td:first-child input[type=checkbox]{margin:0}.formipay-table .column-actions{width:200px}.formipay-table .row-actions{display:none;visibility:hidden}.formipay-table tbody tr:hover .row-actions{display:block;visibility:visible}.formipay-table .row-actions .button-link,.formipay-table .row-actions a{color:#a7aaad;cursor:pointer;text-decoration:none}.formipay-table .row-actions .button-link:hover,.formipay-table .row-actions a:hover{color:#135e96}.formipay-table .row-actions .delete{color:#b32d2e}.formipay-table .row-actions .delete:hover{color:#d63638}.formipay-table .status-label{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.formipay-table .status-label.publish{background:#edfaef;color:#007017}.formipay-table .status-label.draft{background:#f0f0f1;color:#646970}.formipay-table .status-label.pending{background:#fff8e5;color:#d63638}.formipay-table input.formipay-form-shortcode{background:#f6f7f7;border:1px solid #8c8f94;border-radius:4px;color:#646970;font-family:monospace;font-size:12px;min-width:150px;padding:4px 8px}.formipay-table button.copy-shortcode{align-items:center;background:#fff;border:1px solid #8c8f94;border-radius:4px;cursor:pointer;display:inline-flex;gap:4px;margin-right:4px;padding:4px 8px}.formipay-table button.copy-shortcode:hover{background:#f6f7f7}.formipay-table button.copy-shortcode svg{height:16px;width:16px}.formipay-table-pagination{align-items:center;background:#fff;border:1px solid #c3c4c7;border-top:none;display:flex;justify-content:space-between;padding:12px 16px}.formipay-table-pagination .pagination-info{color:#646970;font-size:13px}.formipay-table-pagination .pagination-controls{align-items:center;display:flex;gap:8px}.formipay-table-pagination .page-info{color:#646970;font-size:13px;padding:0 8px}.formipay-table-pagination .components-select-control{min-width:80px}.formipay-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.formipay-table thead th.sorted{padding-left:20px;position:relative}.formipay-table thead th .sort-indicator{color:#135e96;position:absolute;left:8px;top:50%;transform:translateY(-50%)}
|
||||
.formipay-page-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.formipay-page-header h1{font-size:23px;font-weight:400;margin:0}.status-badge{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.status-badge.status-active,.status-badge.status-publish{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;border-radius:3px;font-family:monospace;font-size:13px;padding:2px 6px}
|
||||
.formipay-variation-table{border-collapse:collapse;margin-top:20px;width:100%}.formipay-variation-table thead th{border-bottom:2px solid #1e1e1e;color:#1e1e1e;font-size:13px;font-weight:600;padding:12px;text-align:right}.formipay-variation-table tbody td{border-bottom:1px solid #f0f0f1;padding:12px}.variation-row{background:#fff}.variation-name{gap:8px}.toggle-expand,.variation-name{align-items:center;display:flex}.toggle-expand{background:transparent;border:none;border-radius:2px;cursor:pointer;height:24px;justify-content:center;padding:0;width:24px}.toggle-expand:hover{background:#f0f0f1}.toggle-expand svg{fill:#646970}.variation-name strong{color:#1e1e1e;font-size:13px}.price-cell,.variation-stock,.variation-weight{min-width:120px}.price-cell input,.variation-stock input,.variation-weight input{border:1px solid #8c8f94;border-radius:2px;font-size:13px;padding:6px 8px;width:100%}.variation-stock .components-base-control,.variation-weight .components-base-control{margin:0}.variation-actions{min-width:100px}.variation-details-row{background:#f9f9f9}.variation-details-row td{padding:0}.inner-table{border-collapse:collapse;margin:0;width:100%}.inner-table thead{display:none}.inner-table td{border-bottom:1px solid #f0f0f1;padding:8px 12px}.inner-table tr:last-child td{border-bottom:none}.inner-table input[type=number]{border:1px solid #8c8f94;border-radius:2px;font-size:12px;padding:6px 8px;width:100%}.currency-name{color:#1e1e1e;font-size:12px;font-weight:600}.required{color:#dc3545;margin-right:4px}
|
||||
|
||||
@@ -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' => 'a62306d2a4d673c06ceb');
|
||||
<?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' => '164b137f81dde0451242');
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
.formipay-order-timeline h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.timeline-progress{justify-content:space-between;margin-bottom:24px}.timeline-progress,.timeline-step{align-items:center;display:flex;position:relative}.timeline-step{flex:1;flex-direction:column;gap:8px;z-index:1}.timeline-dot{background:#e0e0e0;border:2px solid #c3c4c7;border-radius:50%;height:24px;position:relative;width:24px}.timeline-step.completed .timeline-dot{background:#2271b1;border-color:#2271b1}.timeline-step.completed .timeline-dot:after{background:#fff;border-radius:50%;content:"";height:8px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:8px}.timeline-line{background:#e0e0e0;height:2px;left:50%;position:absolute;top:12px;width:100%;z-index:-1}.timeline-step.completed .timeline-line{background:#2271b1}.timeline-label{color:#646970;font-size:10px;font-weight:600;text-align:center;text-transform:uppercase}.timeline-events ul{list-style:none;margin:0;padding:0}.timeline-events li{border-bottom:1px solid #f0f0f1;display:grid;gap:8px;grid-template-columns:1fr auto;padding:12px 0}.timeline-events li:last-child{border-bottom:none}.event-status{color:#1e1e1e;font-size:13px;font-weight:600}.event-date{color:#646970;font-size:11px;text-align:right}.event-note{color:#646970;font-size:12px;grid-column:1/-1;margin-top:4px}.no-events{color:#646970;font-size:13px;padding:20px 0;text-align:center}
|
||||
.formipay-notification-log h3{align-items:center;color:#1e1e1e;display:flex;font-size:16px;font-weight:600;gap:8px;margin:0 0 16px}.formipay-notification-log svg{fill:#1e1e1e}.no-logs{color:#646970;font-size:13px;padding:20px 0;text-align:center}.notification-list{list-style:none;margin:0;padding:0}.notification-item{background:#f6f7f7;border:1px solid #e0e0e0;border-radius:4px;display:flex;gap:12px;margin-bottom:8px;padding:12px}.notification-icon{align-items:center;background:#fff;border-radius:50%;display:flex;flex-shrink:0;height:36px;justify-content:center;width:36px}.notification-icon svg{fill:#1e1e1e}.notification-content{flex:1;min-width:0}.notification-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.notification-type{color:#2271b1;font-size:10px;font-weight:700;text-transform:uppercase}.notification-status{border-radius:10px;font-size:10px;font-weight:600;padding:2px 6px}.notification-item.sent .notification-status{background:#e7f7ed;color:#28a745}.notification-item.failed .notification-status{background:#fbeaea;color:#dc3545}.notification-item.pending .notification-status{background:#fff8e5;color:#f0ad4e}.notification-details{display:flex;flex-direction:column;gap:2px}.notification-details strong{color:#1e1e1e;font-size:13px}.notification-date,.notification-recipient{color:#646970;font-size:11px}.notification-date{margin-top:4px}
|
||||
.formipay-order-detail{background:#f6f7f7;display:flex;flex-direction:column;height:100%}.formipay-detail-header{align-items:center;background:#fff;border-bottom:1px solid #e0e0e0;display:flex;gap:16px;justify-content:space-between;padding:16px 20px}.formipay-detail-header h1{flex:1;font-size:20px;font-weight:600;margin:0}.header-actions{display:flex;gap:8px}.formipay-detail-content{display:grid;gap:20px;grid-template-columns:2fr 1fr;overflow-y:auto;padding:20px}.formipay-detail-main,.formipay-detail-sidebar{display:flex;flex-direction:column;gap:20px}.formipay-detail-card{background:#fff;border:1px solid #e0e0e0;border-radius:4px;padding:20px}.formipay-detail-card h3{color:#1e1e1e;font-size:16px;font-weight:600;margin:0 0 16px}.detail-list{display:grid;gap:16px;grid-template-columns:repeat(2,1fr)}.detail-list>div{display:flex;flex-direction:column}.detail-list dt{color:#646970;font-size:12px;font-weight:600;margin-bottom:4px}.detail-list dd{align-items:center;color:#1e1e1e;display:flex;font-size:14px;gap:8px}.detail-list dd .components-select-control{flex:1}.items-table{border-collapse:collapse;width:100%}.items-table td,.items-table th{border-bottom:1px solid #f0f0f1;padding:10px;text-align:left}.items-table th{color:#646970;font-size:12px;font-weight:600;text-transform:uppercase}.items-table td{font-size:13px}.items-table small{color:#646970;display:block;font-size:11px}.items-table tfoot td{border-bottom:none;border-top:2px solid #1e1e1e;padding-top:16px}.customer-info{display:grid;gap:12px;grid-template-columns:1fr}.customer-info>div{display:flex;flex-direction:column}.customer-info dt{color:#646970;font-size:11px;font-weight:600;margin-bottom:2px}.customer-info dd{color:#1e1e1e;font-size:13px}.no-data{color:#646970;font-size:13px}.formipay-error,.formipay-loading{align-items:center;display:flex;flex-direction:column;gap:16px;justify-content:center;padding:60px 20px}.formipay-error p{color:#646970;margin:0}
|
||||
.formipay-data-table-empty,.formipay-data-table-loading{padding:40px;text-align:center}.formipay-data-table{margin-top:20px}.formipay-data-table thead th{font-weight:600;padding:12px 10px}.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}
|
||||
.formipay-data-table-wrapper{margin:20px 0}.formipay-table-toolbar{align-items:center;display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px}.formipay-table-search{flex-grow:1;max-width:300px}.formipay-table-toolbar .components-select-control{min-width:150px}.formipay-filter-tabs{border-bottom:1px solid #ddd;display:flex;gap:4px;margin-bottom:16px}.formipay-filter-tabs .filter-tab{background:transparent;border:none;border-bottom:3px solid transparent;color:#646970;cursor:pointer;font-size:13px;padding:8px 16px;transition:all .2s}.formipay-filter-tabs .filter-tab:hover{background:#f0f0f1;color:#135e96}.formipay-filter-tabs .filter-tab.active{border-bottom-color:#135e96;color:#135e96;font-weight:600}.formipay-filter-tabs .filter-tab .count{background:#dcdcde;border-radius:10px;display:inline-block;font-size:11px;line-height:1.4;margin-left:6px;min-width:18px;padding:2px 6px}.formipay-filter-tabs .filter-tab.active .count{background:#135e96;color:#fff}.formipay-table-container{background:#fff;border:1px solid #c3c4c7;box-shadow:0 1px 1px rgba(0,0,0,.04)}.formipay-table-loading{padding:60px;text-align:center}.formipay-table-empty{color:#646970;padding:40px;text-align:center}.formipay-table{border-collapse:collapse;width:100%}.formipay-table thead th{background:#f6f7f7;border-bottom:1px solid #c3c4c7;font-weight:600;padding:12px 10px;text-align:left}.formipay-table tbody td{border-bottom:1px solid #c3c4c7;padding:10px}.formipay-table tbody tr:last-child td{border-bottom:none}.formipay-table tbody tr:hover{background-color:#f0f0f1}.formipay-table .column-select{text-align:center;width:40px}.formipay-table tbody td:first-child input[type=checkbox]{margin:0}.formipay-table .column-actions{width:200px}.formipay-table .row-actions{display:none;visibility:hidden}.formipay-table tbody tr:hover .row-actions{display:block;visibility:visible}.formipay-table .row-actions .button-link,.formipay-table .row-actions a{color:#a7aaad;cursor:pointer;text-decoration:none}.formipay-table .row-actions .button-link:hover,.formipay-table .row-actions a:hover{color:#135e96}.formipay-table .row-actions .delete{color:#b32d2e}.formipay-table .row-actions .delete:hover{color:#d63638}.formipay-table .status-label{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.formipay-table .status-label.publish{background:#edfaef;color:#007017}.formipay-table .status-label.draft{background:#f0f0f1;color:#646970}.formipay-table .status-label.pending{background:#fff8e5;color:#d63638}.formipay-table input.formipay-form-shortcode{background:#f6f7f7;border:1px solid #8c8f94;border-radius:4px;color:#646970;font-family:monospace;font-size:12px;min-width:150px;padding:4px 8px}.formipay-table button.copy-shortcode{align-items:center;background:#fff;border:1px solid #8c8f94;border-radius:4px;cursor:pointer;display:inline-flex;gap:4px;margin-left:4px;padding:4px 8px}.formipay-table button.copy-shortcode:hover{background:#f6f7f7}.formipay-table button.copy-shortcode svg{height:16px;width:16px}.formipay-table-pagination{align-items:center;background:#fff;border:1px solid #c3c4c7;border-top:none;display:flex;justify-content:space-between;padding:12px 16px}.formipay-table-pagination .pagination-info{color:#646970;font-size:13px}.formipay-table-pagination .pagination-controls{align-items:center;display:flex;gap:8px}.formipay-table-pagination .page-info{color:#646970;font-size:13px;padding:0 8px}.formipay-table-pagination .components-select-control{min-width:80px}.formipay-modal-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:20px}.formipay-table thead th.sorted{padding-right:20px;position:relative}.formipay-table thead th .sort-indicator{color:#135e96;position:absolute;right:8px;top:50%;transform:translateY(-50%)}
|
||||
.formipay-page-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:20px}.formipay-page-header h1{font-size:23px;font-weight:400;margin:0}.status-badge{border-radius:4px;display:inline-block;font-size:12px;font-weight:500;padding:4px 8px}.status-badge.status-active,.status-badge.status-publish{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;border-radius:3px;font-family:monospace;font-size:13px;padding:2px 6px}
|
||||
.formipay-variation-table{border-collapse:collapse;margin-top:20px;width:100%}.formipay-variation-table thead th{border-bottom:2px solid #1e1e1e;color:#1e1e1e;font-size:13px;font-weight:600;padding:12px;text-align:left}.formipay-variation-table tbody td{border-bottom:1px solid #f0f0f1;padding:12px}.variation-row{background:#fff}.variation-name{gap:8px}.toggle-expand,.variation-name{align-items:center;display:flex}.toggle-expand{background:transparent;border:none;border-radius:2px;cursor:pointer;height:24px;justify-content:center;padding:0;width:24px}.toggle-expand:hover{background:#f0f0f1}.toggle-expand svg{fill:#646970}.variation-name strong{color:#1e1e1e;font-size:13px}.price-cell,.variation-stock,.variation-weight{min-width:120px}.price-cell input,.variation-stock input,.variation-weight input{border:1px solid #8c8f94;border-radius:2px;font-size:13px;padding:6px 8px;width:100%}.variation-stock .components-base-control,.variation-weight .components-base-control{margin:0}.variation-actions{min-width:100px}.variation-details-row{background:#f9f9f9}.variation-details-row td{padding:0}.inner-table{border-collapse:collapse;margin:0;width:100%}.inner-table thead{display:none}.inner-table td{border-bottom:1px solid #f0f0f1;padding:8px 12px}.inner-table tr:last-child td{border-bottom:none}.inner-table input[type=number]{border:1px solid #8c8f94;border-radius:2px;font-size:12px;padding:6px 8px;width:100%}.currency-name{color:#1e1e1e;font-size:12px;font-weight:600}.required{color:#dc3545;margin-left:4px}
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
node_modules/.cache/babel-loader/14b79987dc3a84ec3a760fa9a4ef506e3ce0455b2ffc6fe20655310e48e3fcfa.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/14b79987dc3a84ec3a760fa9a4ef506e3ce0455b2ffc6fe20655310e48e3fcfa.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/3a08871aa808e620b9298452a2638f076758a922a7013dae8f82212b9abb6739.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/3a08871aa808e620b9298452a2638f076758a922a7013dae8f82212b9abb6739.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/3e0c80852595c04cdb34c154a893d8d91023a3de95e1efd6f29d107893ef8cde.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/3e0c80852595c04cdb34c154a893d8d91023a3de95e1efd6f29d107893ef8cde.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/565207e7fbd18187e191263c954119a26bbd7b51edc56eb9447ae968ea1f9725.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/565207e7fbd18187e191263c954119a26bbd7b51edc56eb9447ae968ea1f9725.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/59e6cb50a62c21cd73de0ec3a02847e236cca65f4149c9c42135fa79a1f8d24b.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/59e6cb50a62c21cd73de0ec3a02847e236cca65f4149c9c42135fa79a1f8d24b.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/5aab4366ac87b889d35d3e884b6b07308dacff02cd1b74d9d32eff007bcff386.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/5aab4366ac87b889d35d3e884b6b07308dacff02cd1b74d9d32eff007bcff386.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/7e14adc209a2f91edde190f1c99acfcf3d64a741cb2fb022cf781fac4171a8f0.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/7e14adc209a2f91edde190f1c99acfcf3d64a741cb2fb022cf781fac4171a8f0.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/8c0f4882f684e83867700f22af9cdb354be15632d4208fff53cb9d84f7309af6.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/8c0f4882f684e83867700f22af9cdb354be15632d4208fff53cb9d84f7309af6.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/c606103bba57e717885d000da0ada54cbeaf869a996fcecc39b58034960a9cdb.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/c606103bba57e717885d000da0ada54cbeaf869a996fcecc39b58034960a9cdb.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
node_modules/.cache/babel-loader/e45f6611ae3be43cc6b5aa48e32a8dca1bac0185e9a5467996f9400108d8b99c.json.gz
generated
vendored
Normal file
BIN
node_modules/.cache/babel-loader/e45f6611ae3be43cc6b5aa48e32a8dca1bac0185e9a5467996f9400108d8b99c.json.gz
generated
vendored
Normal file
Binary file not shown.
@@ -1,26 +1,269 @@
|
||||
.formipay-data-table-loading,
|
||||
.formipay-data-table-empty {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
/* DataTable Wrapper */
|
||||
.formipay-data-table-wrapper {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.formipay-data-table {
|
||||
margin-top: 20px;
|
||||
/* Toolbar */
|
||||
.formipay-table-toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.formipay-data-table thead th {
|
||||
padding: 12px 10px;
|
||||
.formipay-table-search {
|
||||
max-width: 300px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.formipay-table-toolbar .components-select-control {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* Filter Tabs */
|
||||
.formipay-filter-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.formipay-filter-tabs .filter-tab {
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #646970;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.formipay-filter-tabs .filter-tab:hover {
|
||||
color: #135e96;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.formipay-filter-tabs .filter-tab.active {
|
||||
color: #135e96;
|
||||
border-bottom-color: #135e96;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.formipay-data-table tbody td {
|
||||
padding: 10px;
|
||||
.formipay-filter-tabs .filter-tab .count {
|
||||
display: inline-block;
|
||||
min-width: 18px;
|
||||
padding: 2px 6px;
|
||||
margin-left: 6px;
|
||||
background: #dcdcde;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.formipay-data-table tbody tr.is-clickable {
|
||||
.formipay-filter-tabs .filter-tab.active .count {
|
||||
background: #135e96;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Table Container */
|
||||
.formipay-table-container {
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.04);
|
||||
}
|
||||
|
||||
.formipay-table-loading {
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.formipay-table-empty {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: #646970;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.formipay-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.formipay-table thead th {
|
||||
padding: 12px 10px;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
background: #f6f7f7;
|
||||
}
|
||||
|
||||
.formipay-table tbody td {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
.formipay-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.formipay-table tbody tr:hover {
|
||||
background-color: #f0f0f1;
|
||||
}
|
||||
|
||||
/* Checkbox Column */
|
||||
.formipay-table .column-select {
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.formipay-table tbody td:first-child input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Actions Column */
|
||||
.formipay-table .column-actions {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.formipay-table .row-actions {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.formipay-table tbody tr:hover .row-actions {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.formipay-table .row-actions a,
|
||||
.formipay-table .row-actions .button-link {
|
||||
text-decoration: none;
|
||||
color: #a7aaad;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.formipay-data-table tbody tr.is-clickable:hover {
|
||||
background-color: #f0f0f1;
|
||||
.formipay-table .row-actions a:hover,
|
||||
.formipay-table .row-actions .button-link:hover {
|
||||
color: #135e96;
|
||||
}
|
||||
|
||||
.formipay-table .row-actions .delete {
|
||||
color: #b32d2e;
|
||||
}
|
||||
|
||||
.formipay-table .row-actions .delete:hover {
|
||||
color: #d63638;
|
||||
}
|
||||
|
||||
/* Status Labels */
|
||||
.formipay-table .status-label {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.formipay-table .status-label.publish {
|
||||
background: #edfaef;
|
||||
color: #007017;
|
||||
}
|
||||
|
||||
.formipay-table .status-label.draft {
|
||||
background: #f0f0f1;
|
||||
color: #646970;
|
||||
}
|
||||
|
||||
.formipay-table .status-label.pending {
|
||||
background: #fff8e5;
|
||||
color: #d63638;
|
||||
}
|
||||
|
||||
/* Shortcode Input */
|
||||
.formipay-table input.formipay-form-shortcode {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #8c8f94;
|
||||
border-radius: 4px;
|
||||
background: #f6f7f7;
|
||||
color: #646970;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.formipay-table button.copy-shortcode {
|
||||
padding: 4px 8px;
|
||||
margin-left: 4px;
|
||||
border: 1px solid #8c8f94;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.formipay-table button.copy-shortcode:hover {
|
||||
background: #f6f7f7;
|
||||
}
|
||||
|
||||
.formipay-table button.copy-shortcode svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.formipay-table-pagination {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.formipay-table-pagination .pagination-info {
|
||||
color: #646970;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.formipay-table-pagination .pagination-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.formipay-table-pagination .page-info {
|
||||
padding: 0 8px;
|
||||
color: #646970;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.formipay-table-pagination .components-select-control {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
/* Modal Actions */
|
||||
.formipay-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Sort Indicator */
|
||||
.formipay-table thead th.sorted {
|
||||
position: relative;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.formipay-table thead th .sort-indicator {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #135e96;
|
||||
}
|
||||
|
||||
@@ -1,57 +1,554 @@
|
||||
/**
|
||||
* Data Table - Simple table component for admin listings
|
||||
* Full-featured DataTable component
|
||||
* Supports: selection, filtering, search, sort, pagination, actions
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState, useCallback, useEffect } from '@wordpress/element';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
TextControl,
|
||||
SelectControl,
|
||||
Spinner,
|
||||
} from '@wordpress/components';
|
||||
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>
|
||||
);
|
||||
}
|
||||
// SweetAlert2 is loaded via WordPress (global scope)
|
||||
const Swal = window.Swal;
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return (
|
||||
<div className="formipay-data-table-empty">
|
||||
<p>{ emptyMessage }</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default function DataTable({
|
||||
// Data fetching
|
||||
initialData = [],
|
||||
|
||||
// Columns definition
|
||||
columns,
|
||||
|
||||
// Filtering
|
||||
filterOptions = null, // { key: 'post_status', options: [{value, label}] }
|
||||
statusCounts = null, // { all: 10, publish: 5, draft: 5 }
|
||||
|
||||
// Search
|
||||
searchable = true,
|
||||
searchPlaceholder = __('Search...', 'formipay'),
|
||||
|
||||
// Sorting
|
||||
sortable = true,
|
||||
defaultSort = { id: 'ID', desc: true },
|
||||
|
||||
// Selection
|
||||
selectable = true,
|
||||
|
||||
// Pagination
|
||||
pagination = true,
|
||||
pageSize = 10,
|
||||
pageSizeOptions = [10, 20, 50, 100],
|
||||
|
||||
// Actions
|
||||
actions = {
|
||||
addNew: false, // { label, action: 'formipay-create-form-post' }
|
||||
bulkDelete: true, // { action: 'formipay-bulk-delete-form' }
|
||||
inline: true, // edit, delete, duplicate
|
||||
},
|
||||
|
||||
// Empty state
|
||||
emptyMessage = __('No items found', 'formipay'),
|
||||
|
||||
// AJAX config
|
||||
ajaxUrl,
|
||||
nonce,
|
||||
tableAction, // e.g., 'formipay-tabledata-forms'
|
||||
deleteAction,
|
||||
duplicateAction,
|
||||
}) {
|
||||
// State
|
||||
const [data, setData] = useState(initialData);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
// Filters
|
||||
const [activeFilter, setActiveFilter] = useState('all');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// Sorting
|
||||
const [sortBy, setSortBy] = useState(defaultSort.id || 'ID');
|
||||
const [sortOrder, setSortOrder] = useState(defaultSort.desc ? 'desc' : 'asc');
|
||||
|
||||
// Pagination
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [currentPageSize, setCurrentPageSize] = useState(pageSize);
|
||||
|
||||
// Selection
|
||||
const [selectedRows, setSelectedRows] = useState(new Set());
|
||||
const [selectAll, setSelectAll] = useState(false);
|
||||
|
||||
// Add New Modal
|
||||
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
||||
const [newItemTitle, setNewItemTitle] = useState('');
|
||||
|
||||
// Derive action names from tableAction
|
||||
const baseActionName = tableAction.replace('formipay-tabledata-', '');
|
||||
const bulkDeleteAction = actions.bulkDelete?.action || `formipay-bulk-delete-${baseActionName}`;
|
||||
const deleteActionName = deleteAction || `formipay-delete-${baseActionName}`;
|
||||
const duplicateActionName = duplicateAction || `formipay-duplicate-${baseActionName}`;
|
||||
|
||||
// Load data
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
action: tableAction,
|
||||
_wpnonce: nonce,
|
||||
limit: currentPageSize.toString(),
|
||||
offset: ((currentPage - 1) * currentPageSize).toString(),
|
||||
});
|
||||
|
||||
// Add filter
|
||||
if (filterOptions && activeFilter !== 'all') {
|
||||
params.append(filterOptions.key, activeFilter);
|
||||
}
|
||||
|
||||
// Add search
|
||||
if (searchQuery) {
|
||||
params.append('search', searchQuery);
|
||||
}
|
||||
|
||||
// Add sort
|
||||
params.append('orderby', sortBy);
|
||||
params.append('sort', sortOrder);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${ajaxUrl}?${params.toString()}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params,
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
const items = result.data?.results || result.results || result.data || [];
|
||||
setData(items);
|
||||
setTotal(result.total || items.length);
|
||||
} catch (error) {
|
||||
console.error('Load data error:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [ajaxUrl, nonce, tableAction, currentPageSize, currentPage, activeFilter, searchQuery, sortBy, sortOrder, filterOptions]);
|
||||
|
||||
// Initial load and refresh on filter/sort/page change
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
|
||||
// Handle filter change
|
||||
const handleFilterChange = (value) => {
|
||||
setActiveFilter(value);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
// Handle search (debounced)
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (searchQuery !== null) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, 500);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [searchQuery]);
|
||||
|
||||
// Handle selection
|
||||
const handleRowSelect = (id) => {
|
||||
const newSelected = new Set(selectedRows);
|
||||
if (newSelected.has(id)) {
|
||||
newSelected.delete(id);
|
||||
} else {
|
||||
newSelected.add(id);
|
||||
}
|
||||
setSelectedRows(newSelected);
|
||||
setSelectAll(false);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (selectAll) {
|
||||
setSelectedRows(new Set());
|
||||
} else {
|
||||
setSelectedRows(new Set(data.map(row => row.ID || row.id)));
|
||||
}
|
||||
setSelectAll(!selectAll);
|
||||
};
|
||||
|
||||
// Handle bulk delete
|
||||
const handleBulkDelete = async () => {
|
||||
if (selectedRows.size === 0) return;
|
||||
|
||||
const result = await Swal.fire({
|
||||
icon: 'info',
|
||||
html: __('Do you want to delete the selected item(s)?', 'formipay'),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: __('Confirm', 'formipay'),
|
||||
cancelButtonText: __('Cancel', 'formipay'),
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
await fetch(`${ajaxUrl}?action=${bulkDeleteAction}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
ids: Array.from(selectedRows),
|
||||
_wpnonce: nonce,
|
||||
}),
|
||||
});
|
||||
|
||||
setSelectedRows(new Set());
|
||||
setSelectAll(false);
|
||||
loadData();
|
||||
|
||||
Swal.fire({
|
||||
title: __('Done!', 'formipay'),
|
||||
html: __('Items deleted successfully.', 'formipay'),
|
||||
icon: 'success',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handle inline delete
|
||||
const handleDelete = async (id) => {
|
||||
const result = await Swal.fire({
|
||||
icon: 'info',
|
||||
html: __('Do you want to delete this item?', 'formipay'),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: __('Delete Permanently', 'formipay'),
|
||||
cancelButtonText: __('Cancel', 'formipay'),
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
await fetch(`${ajaxUrl}?action=${deleteActionName}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
id,
|
||||
_wpnonce: nonce,
|
||||
}),
|
||||
});
|
||||
|
||||
loadData();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle duplicate
|
||||
const handleDuplicate = async (id) => {
|
||||
const result = await Swal.fire({
|
||||
icon: 'info',
|
||||
html: __('Do you want to duplicate this item?', 'formipay'),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: __('Confirm', 'formipay'),
|
||||
cancelButtonText: __('Cancel', 'formipay'),
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
await fetch(`${ajaxUrl}?action=${duplicateActionName}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
id,
|
||||
_wpnonce: nonce,
|
||||
}),
|
||||
});
|
||||
|
||||
loadData();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle Add New
|
||||
const handleAddNew = async () => {
|
||||
if (!newItemTitle.trim()) {
|
||||
Swal.fire({
|
||||
html: __('Title is required.', 'formipay'),
|
||||
icon: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const createAction = actions.addNew.action;
|
||||
const result = await fetch(`${ajaxUrl}?action=${createAction}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
title: newItemTitle,
|
||||
_wpnonce: nonce,
|
||||
}),
|
||||
});
|
||||
|
||||
const response = await result.json();
|
||||
|
||||
if (response.success) {
|
||||
setIsAddModalOpen(false);
|
||||
setNewItemTitle('');
|
||||
if (response.data.edit_post_url) {
|
||||
window.location.href = response.data.edit_post_url;
|
||||
} else {
|
||||
loadData();
|
||||
}
|
||||
} else {
|
||||
Swal.fire({
|
||||
html: response.data.message || __('Error creating item.', 'formipay'),
|
||||
icon: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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' : ''}
|
||||
<div className="formipay-data-table-wrapper">
|
||||
{/* Toolbar */}
|
||||
<div className="formipay-table-toolbar">
|
||||
{/* Add New Button */}
|
||||
{actions.addNew && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setIsAddModalOpen(true)}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
<td key={column.key}>
|
||||
{column.render ? column.render(row) : row[column.key]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{actions.addNew.label || __('+ Add New', 'formipay')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Bulk Delete Button */}
|
||||
{actions.bulkDelete && selectable && selectedRows.size > 0 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
isDestructive
|
||||
onClick={handleBulkDelete}
|
||||
>
|
||||
{__('Delete Selected', 'formipay')} ({selectedRows.size})
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Search */}
|
||||
{searchable && (
|
||||
<TextControl
|
||||
placeholder={searchPlaceholder}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
className="formipay-table-search"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sort */}
|
||||
{sortable && (
|
||||
<SelectControl
|
||||
value={`${sortBy}-${sortOrder}`}
|
||||
options={[
|
||||
{ label: __('ID ↓', 'formipay'), value: 'ID-desc' },
|
||||
{ label: __('ID ↑', 'formipay'), value: 'ID-asc' },
|
||||
{ label: __('Date ↓', 'formipay'), value: 'date-desc' },
|
||||
{ label: __('Date ↑', 'formipay'), value: 'date-asc' },
|
||||
{ label: __('Title A-Z', 'formipay'), value: 'title-asc' },
|
||||
{ label: __('Title Z-A', 'formipay'), value: 'title-desc' },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
const [id, sort] = value.split('-');
|
||||
setSortBy(id);
|
||||
setSortOrder(sort);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filter Tabs */}
|
||||
{filterOptions && (
|
||||
<div className="formipay-filter-tabs">
|
||||
{filterOptions.options.map(option => (
|
||||
<button
|
||||
key={option.value}
|
||||
className={`filter-tab ${activeFilter === option.value ? 'active' : ''}`}
|
||||
onClick={() => handleFilterChange(option.value)}
|
||||
>
|
||||
{option.label}
|
||||
{statusCounts && (
|
||||
<span className="count">
|
||||
{statusCounts[option.value] || 0}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="formipay-table-container">
|
||||
{loading ? (
|
||||
<div className="formipay-table-loading">
|
||||
<Spinner />
|
||||
</div>
|
||||
) : data.length === 0 ? (
|
||||
<div className="formipay-table-empty">
|
||||
{emptyMessage}
|
||||
</div>
|
||||
) : (
|
||||
<table className="formipay-table wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{/* Checkbox column */}
|
||||
{selectable && (
|
||||
<th className="column-select">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectAll}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</th>
|
||||
)}
|
||||
{/* Data columns */}
|
||||
{columns.map((column) => (
|
||||
<th key={column.key} className={`column-${column.key}`}>
|
||||
{column.label}
|
||||
</th>
|
||||
))}
|
||||
{/* Actions column */}
|
||||
{actions.inline && (
|
||||
<th className="column-actions">{__('Actions', 'formipay')}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, rowIndex) => {
|
||||
const rowId = row.ID || row.id;
|
||||
return (
|
||||
<tr key={rowIndex} className="formipay-table-row">
|
||||
{/* Checkbox */}
|
||||
{selectable && (
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedRows.has(rowId)}
|
||||
onChange={() => handleRowSelect(rowId)}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
{/* Data columns */}
|
||||
{columns.map((column) => (
|
||||
<td key={column.key}>
|
||||
{column.render ? column.render(row) : row[column.key]}
|
||||
</td>
|
||||
))}
|
||||
{/* Actions */}
|
||||
{actions.inline && (
|
||||
<td className="column-actions">
|
||||
<div className="row-actions">
|
||||
<a href={`${window.formipayAdmin?.siteUrl || ''}/wp-admin/post.php?post=${rowId}&action=edit`}>
|
||||
{__('Edit', 'formipay')}
|
||||
</a>
|
||||
{' | '}
|
||||
<button
|
||||
className="button-link delete"
|
||||
onClick={() => handleDelete(rowId)}
|
||||
>
|
||||
{__('Delete', 'formipay')}
|
||||
</button>
|
||||
{' | '}
|
||||
<button
|
||||
className="button-link duplicate"
|
||||
onClick={() => handleDuplicate(rowId)}
|
||||
>
|
||||
{__('Duplicate', 'formipay')}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pagination */}
|
||||
{pagination && total > currentPageSize && (
|
||||
<div className="formipay-table-pagination">
|
||||
<div className="pagination-info">
|
||||
{__('Showing', 'formipay')} {((currentPage - 1) * currentPageSize) + 1} - {Math.min(currentPage * currentPageSize, total)} {__('of', 'formipay')} {total}
|
||||
</div>
|
||||
<div className="pagination-controls">
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => setCurrentPage(1)}
|
||||
>
|
||||
{'««'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => setCurrentPage(currentPage - 1)}
|
||||
>
|
||||
{'‹'}
|
||||
</Button>
|
||||
<span className="page-info">
|
||||
{__('Page', 'formipay')} {currentPage} {__('of', 'formipay')} {Math.ceil(total / currentPageSize)}
|
||||
</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage >= Math.ceil(total / currentPageSize)}
|
||||
onClick={() => setCurrentPage(currentPage + 1)}
|
||||
>
|
||||
{'›'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage >= Math.ceil(total / currentPageSize)}
|
||||
onClick={() => setCurrentPage(Math.ceil(total / currentPageSize))}
|
||||
>
|
||||
{'»'}
|
||||
</Button>
|
||||
<SelectControl
|
||||
value={currentPageSize.toString()}
|
||||
options={pageSizeOptions.map(size => ({
|
||||
label: size.toString(),
|
||||
value: size.toString(),
|
||||
}))}
|
||||
onChange={(value) => {
|
||||
setCurrentPageSize(parseInt(value));
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add New Modal */}
|
||||
{actions.addNew && (
|
||||
<Modal
|
||||
title={actions.addNew.label || __('Add New', 'formipay')}
|
||||
isOpen={isAddModalOpen}
|
||||
onClose={() => setIsAddModalOpen(false)}
|
||||
>
|
||||
<TextControl
|
||||
label={__('Title', 'formipay')}
|
||||
value={newItemTitle}
|
||||
onChange={setNewItemTitle}
|
||||
autoFocus
|
||||
/>
|
||||
<div className="formipay-modal-actions">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setIsAddModalOpen(false)}
|
||||
>
|
||||
{__('Cancel', 'formipay')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleAddNew}
|
||||
>
|
||||
{__('Create', 'formipay')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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