feat: Add enhanced dashboard features with search, filters, pagination, and sorting
- Add search box for real-time event name filtering with 500ms debounce - Add date range filters (from/to) for filtering events by start date - Add pagination with customizable items per page (10, 25, 50, 100) - Add sortable table columns for status, name, date, capacity, sold, revenue - Update dashboard data handler to support all new query parameters - Create new JavaScript file for handling interactive features - Create new CSS file for enhanced dashboard styling - Maintain backward compatibility with existing status filters - Add URL state management for browser navigation - Add loading indicators and responsive design The dashboard now provides a comprehensive interface for trainers to efficiently manage and navigate their events with powerful filtering and sorting capabilities. Co-Authored-By: Ben Reed <ben@tealmaker.com>
This commit is contained in:
parent
057b0e8212
commit
1bd871cc7b
5 changed files with 926 additions and 530 deletions
|
|
@ -1,451 +1,358 @@
|
|||
/**
|
||||
* HVAC Dashboard - Enhanced Styling
|
||||
* HVAC Dashboard Enhanced Styles
|
||||
*
|
||||
* Updated dashboard styles using the harmonized framework
|
||||
* to integrate seamlessly with the Astra theme.
|
||||
*
|
||||
* @version 3.0.0
|
||||
* Styles for the enhanced dashboard with filters, search, and pagination
|
||||
*/
|
||||
|
||||
/* Dashboard page wrapper */
|
||||
.hvac-dashboard-page {
|
||||
background-color: var(--hvac-theme-background);
|
||||
min-height: 70vh;
|
||||
padding: var(--hvac-spacing-6) 0;
|
||||
}
|
||||
|
||||
/* Main dashboard container */
|
||||
.hvac-dashboard {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--hvac-spacing-4);
|
||||
}
|
||||
|
||||
/* Dashboard header */
|
||||
.hvac-dashboard-header {
|
||||
background: linear-gradient(135deg, var(--hvac-primary) 0%, var(--hvac-primary-dark) 100%);
|
||||
color: white;
|
||||
padding: var(--hvac-spacing-8) var(--hvac-spacing-6);
|
||||
border-radius: var(--hvac-radius-xl);
|
||||
margin-bottom: var(--hvac-spacing-8);
|
||||
box-shadow: var(--hvac-shadow-lg);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hvac-dashboard-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
transform: translate(30px, -30px);
|
||||
}
|
||||
|
||||
.hvac-dashboard-header h1 {
|
||||
font-size: var(--hvac-font-size-3xl);
|
||||
font-weight: var(--hvac-font-weight-bold);
|
||||
margin: 0 0 var(--hvac-spacing-2) 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hvac-dashboard-header p {
|
||||
font-size: var(--hvac-font-size-lg);
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.hvac-dashboard-nav {
|
||||
/* Table Controls Container */
|
||||
.hvac-table-controls {
|
||||
display: flex;
|
||||
gap: var(--hvac-spacing-3);
|
||||
flex-wrap: wrap;
|
||||
margin-top: var(--hvac-spacing-6);
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.hvac-dashboard-nav .hvac-btn {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
backdrop-filter: blur(10px);
|
||||
/* Search Box */
|
||||
.hvac-search-box {
|
||||
flex: 1;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.hvac-dashboard-nav .hvac-btn:hover {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-2px);
|
||||
.hvac-search-box input[type="search"] {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Stats grid */
|
||||
.hvac-dashboard-stats {
|
||||
margin-bottom: var(--hvac-spacing-8);
|
||||
.hvac-search-box input[type="search"]:focus {
|
||||
outline: none;
|
||||
border-color: #E9AF28;
|
||||
box-shadow: 0 0 0 2px rgba(233, 175, 40, 0.2);
|
||||
}
|
||||
|
||||
.hvac-dashboard-stats h2 {
|
||||
color: var(--hvac-theme-text-dark);
|
||||
margin-bottom: var(--hvac-spacing-6);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hvac-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: var(--hvac-spacing-6);
|
||||
}
|
||||
|
||||
.hvac-stat-card {
|
||||
background: linear-gradient(135deg, var(--hvac-background-white) 0%, var(--hvac-primary-subtle) 100%);
|
||||
border: 1px solid var(--hvac-border);
|
||||
border-radius: var(--hvac-radius-xl);
|
||||
padding: var(--hvac-spacing-6);
|
||||
text-align: center;
|
||||
box-shadow: var(--hvac-shadow-md);
|
||||
transition: all var(--hvac-transition-normal);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hvac-stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--hvac-shadow-xl);
|
||||
}
|
||||
|
||||
.hvac-stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--hvac-primary), var(--hvac-accent));
|
||||
}
|
||||
|
||||
.hvac-stat-number {
|
||||
font-size: var(--hvac-font-size-4xl);
|
||||
font-weight: var(--hvac-font-weight-bold);
|
||||
color: var(--hvac-primary);
|
||||
margin-bottom: var(--hvac-spacing-2);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.hvac-stat-label {
|
||||
font-size: var(--hvac-font-size-md);
|
||||
color: var(--hvac-theme-text);
|
||||
font-weight: var(--hvac-font-weight-medium);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Events section */
|
||||
.hvac-dashboard-events {
|
||||
margin-bottom: var(--hvac-spacing-8);
|
||||
}
|
||||
|
||||
.hvac-dashboard-events h2 {
|
||||
color: var(--hvac-theme-text-dark);
|
||||
margin-bottom: var(--hvac-spacing-6);
|
||||
/* Date Filters */
|
||||
.hvac-date-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--hvac-spacing-4);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hvac-events-header-actions {
|
||||
.hvac-date-filters label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hvac-date-input {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hvac-date-input:focus {
|
||||
outline: none;
|
||||
border-color: #E9AF28;
|
||||
}
|
||||
|
||||
/* Per Page Selector */
|
||||
.hvac-per-page {
|
||||
display: flex;
|
||||
gap: var(--hvac-spacing-3);
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Events table container */
|
||||
.hvac-events-table-container {
|
||||
background-color: var(--hvac-background-white);
|
||||
border-radius: var(--hvac-radius-xl);
|
||||
box-shadow: var(--hvac-shadow-lg);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--hvac-border);
|
||||
.hvac-per-page label {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hvac-events-table {
|
||||
.hvac-per-page-select {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.hvac-per-page-select:focus {
|
||||
outline: none;
|
||||
border-color: #E9AF28;
|
||||
}
|
||||
|
||||
/* Event Filters (Status Tabs) */
|
||||
.hvac-event-filters {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.hvac-event-filters span {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hvac-filter {
|
||||
padding: 6px 16px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
transition: all 0.3s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.hvac-filter:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #E9AF28;
|
||||
}
|
||||
|
||||
.hvac-filter-active,
|
||||
.hvac-filter.ast-button-primary {
|
||||
background-color: #E9AF28 !important;
|
||||
color: #000 !important;
|
||||
border-color: #E9AF28 !important;
|
||||
}
|
||||
|
||||
/* Events Table Wrapper */
|
||||
.hvac-events-table-wrapper {
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hvac-events-table-wrapper.loading {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.hvac-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 20px 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Enhanced Table Styles */
|
||||
.hvac-events-table,
|
||||
.wp-list-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hvac-events-table th,
|
||||
.hvac-events-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.hvac-events-table th {
|
||||
background: linear-gradient(135deg, var(--hvac-secondary-light) 0%, var(--hvac-background-gray) 100%);
|
||||
color: var(--hvac-theme-text-dark);
|
||||
font-weight: var(--hvac-font-weight-semibold);
|
||||
text-align: left;
|
||||
padding: var(--hvac-spacing-4) var(--hvac-spacing-5);
|
||||
border-bottom: 2px solid var(--hvac-border);
|
||||
font-size: var(--hvac-font-size-sm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hvac-events-table td {
|
||||
padding: var(--hvac-spacing-4) var(--hvac-spacing-5);
|
||||
border-bottom: 1px solid var(--hvac-border-light);
|
||||
vertical-align: middle;
|
||||
font-size: var(--hvac-font-size-sm);
|
||||
/* Sortable Column Headers */
|
||||
.hvac-events-table th.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hvac-events-table tbody tr {
|
||||
transition: background-color var(--hvac-transition-fast);
|
||||
.hvac-events-table th.sortable a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hvac-events-table th.sortable:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* Sorting Indicators */
|
||||
.sorting-indicators {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.sorting-indicator {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.sorting-indicator.asc {
|
||||
border-width: 0 4px 6px 4px;
|
||||
border-color: transparent transparent #ccc transparent;
|
||||
}
|
||||
|
||||
.sorting-indicator.desc {
|
||||
border-width: 6px 4px 0 4px;
|
||||
border-color: #ccc transparent transparent transparent;
|
||||
}
|
||||
|
||||
.sorted.asc .sorting-indicator.asc,
|
||||
.sorted.desc .sorting-indicator.desc {
|
||||
border-bottom-color: #E9AF28;
|
||||
border-top-color: #E9AF28;
|
||||
}
|
||||
|
||||
/* Table Rows */
|
||||
.hvac-events-table tbody tr:hover {
|
||||
background-color: var(--hvac-primary-subtle);
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.hvac-events-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
.hvac-events-table tbody tr:nth-child(even) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
/* Event status badges */
|
||||
.hvac-event-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: var(--hvac-spacing-1) var(--hvac-spacing-3);
|
||||
border-radius: var(--hvac-radius-lg);
|
||||
font-size: var(--hvac-font-size-xs);
|
||||
font-weight: var(--hvac-font-weight-medium);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
/* Column Specific Styles */
|
||||
.column-status {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.hvac-event-status--published {
|
||||
background-color: var(--hvac-success-light);
|
||||
color: var(--hvac-success);
|
||||
border: 1px solid var(--hvac-success);
|
||||
.column-title {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.hvac-event-status--draft {
|
||||
background-color: var(--hvac-warning-light);
|
||||
color: var(--hvac-warning);
|
||||
border: 1px solid var(--hvac-warning);
|
||||
.column-date {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.hvac-event-status--upcoming {
|
||||
background-color: var(--hvac-info-light);
|
||||
color: var(--hvac-accent);
|
||||
border: 1px solid var(--hvac-accent);
|
||||
}
|
||||
|
||||
/* Event actions */
|
||||
.hvac-event-actions {
|
||||
display: flex;
|
||||
gap: var(--hvac-spacing-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hvac-event-actions .hvac-btn {
|
||||
padding: var(--hvac-spacing-1) var(--hvac-spacing-3);
|
||||
font-size: var(--hvac-font-size-xs);
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.hvac-empty-state {
|
||||
.column-capacity,
|
||||
.column-sold {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
padding: var(--hvac-spacing-12) var(--hvac-spacing-6);
|
||||
background-color: var(--hvac-background-white);
|
||||
border-radius: var(--hvac-radius-xl);
|
||||
border: 2px dashed var(--hvac-border);
|
||||
margin: var(--hvac-spacing-6) 0;
|
||||
}
|
||||
|
||||
.hvac-empty-state-icon {
|
||||
font-size: 4rem;
|
||||
color: var(--hvac-border-dark);
|
||||
margin-bottom: var(--hvac-spacing-4);
|
||||
.column-revenue {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.hvac-empty-state h3 {
|
||||
color: var(--hvac-theme-text);
|
||||
margin-bottom: var(--hvac-spacing-3);
|
||||
.column-actions {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.hvac-empty-state p {
|
||||
color: var(--hvac-theme-text-light);
|
||||
margin-bottom: var(--hvac-spacing-6);
|
||||
max-width: 400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/* Pagination */
|
||||
.tablenav {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Quick actions panel */
|
||||
.hvac-quick-actions {
|
||||
background: linear-gradient(135deg, var(--hvac-background-white) 0%, var(--hvac-accent-light) 100%);
|
||||
border: 1px solid var(--hvac-border);
|
||||
border-radius: var(--hvac-radius-xl);
|
||||
padding: var(--hvac-spacing-6);
|
||||
box-shadow: var(--hvac-shadow-md);
|
||||
}
|
||||
|
||||
.hvac-quick-actions h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--hvac-spacing-4);
|
||||
color: var(--hvac-theme-text-dark);
|
||||
}
|
||||
|
||||
.hvac-quick-actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--hvac-spacing-4);
|
||||
}
|
||||
|
||||
.hvac-quick-action-item {
|
||||
.tablenav-pages {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--hvac-spacing-4);
|
||||
background-color: var(--hvac-background-white);
|
||||
border: 1px solid var(--hvac-border);
|
||||
border-radius: var(--hvac-radius-lg);
|
||||
text-decoration: none;
|
||||
color: var(--hvac-theme-text);
|
||||
transition: all var(--hvac-transition-fast);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.hvac-quick-action-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--hvac-shadow-md);
|
||||
text-decoration: none;
|
||||
color: var(--hvac-primary);
|
||||
.displaying-num {
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hvac-quick-action-icon {
|
||||
margin-right: var(--hvac-spacing-3);
|
||||
font-size: var(--hvac-font-size-xl);
|
||||
color: var(--hvac-primary);
|
||||
}
|
||||
|
||||
.hvac-quick-action-text {
|
||||
font-weight: var(--hvac-font-weight-medium);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.hvac-dashboard {
|
||||
padding: 0 var(--hvac-spacing-3);
|
||||
}
|
||||
|
||||
.hvac-dashboard-header {
|
||||
padding: var(--hvac-spacing-6) var(--hvac-spacing-4);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hvac-dashboard-header h1 {
|
||||
font-size: var(--hvac-font-size-2xl);
|
||||
}
|
||||
|
||||
.hvac-dashboard-nav {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hvac-stats-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--hvac-spacing-4);
|
||||
}
|
||||
|
||||
.hvac-stat-card {
|
||||
padding: var(--hvac-spacing-4);
|
||||
}
|
||||
|
||||
.hvac-stat-number {
|
||||
font-size: var(--hvac-font-size-3xl);
|
||||
}
|
||||
|
||||
.hvac-events-table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hvac-events-table th,
|
||||
.hvac-events-table td {
|
||||
padding: var(--hvac-spacing-2) var(--hvac-spacing-3);
|
||||
font-size: var(--hvac-font-size-xs);
|
||||
}
|
||||
|
||||
.hvac-quick-actions-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hvac-dashboard-header {
|
||||
padding: var(--hvac-spacing-4);
|
||||
}
|
||||
|
||||
.hvac-dashboard-header h1 {
|
||||
font-size: var(--hvac-font-size-xl);
|
||||
}
|
||||
|
||||
.hvac-dashboard-nav .hvac-btn {
|
||||
width: 100%;
|
||||
margin-bottom: var(--hvac-spacing-2);
|
||||
}
|
||||
|
||||
.hvac-stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hvac-events-table th,
|
||||
.hvac-events-table td {
|
||||
padding: var(--hvac-spacing-1) var(--hvac-spacing-2);
|
||||
}
|
||||
|
||||
.hvac-event-actions {
|
||||
flex-direction: column;
|
||||
gap: var(--hvac-spacing-1);
|
||||
}
|
||||
|
||||
.hvac-event-actions .hvac-btn {
|
||||
width: 100%;
|
||||
padding: var(--hvac-spacing-2);
|
||||
font-size: var(--hvac-font-size-xs);
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.hvac-loading {
|
||||
display: inline-flex;
|
||||
.pagination-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--hvac-spacing-2);
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.hvac-loading-spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--hvac-border);
|
||||
border-top: 2px solid var(--hvac-primary);
|
||||
border-radius: 50%;
|
||||
animation: hvac-spin 1s linear infinite;
|
||||
.pagination-links .button {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
min-width: 30px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes hvac-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
.pagination-links .button:hover:not(.disabled) {
|
||||
background: #f8f9fa;
|
||||
border-color: #E9AF28;
|
||||
}
|
||||
|
||||
/* Focus styles for better accessibility */
|
||||
.hvac-events-table tbody tr:focus-within {
|
||||
outline: 2px solid var(--hvac-primary);
|
||||
outline-offset: -2px;
|
||||
.pagination-links .button.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Dark mode support (if theme supports it) */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.hvac-dashboard-page {
|
||||
background-color: #1a202c;
|
||||
.paging-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.current-page {
|
||||
width: 50px;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Error Messages */
|
||||
.hvac-error {
|
||||
background: #fee;
|
||||
border: 1px solid #fcc;
|
||||
color: #c00;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.hvac-table-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.hvac-stat-card,
|
||||
.hvac-events-table-container,
|
||||
.hvac-quick-actions {
|
||||
background-color: #2d3748;
|
||||
border-color: #4a5568;
|
||||
.hvac-search-box {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.hvac-date-filters {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hvac-events-table {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hvac-events-table th,
|
||||
.hvac-events-table td {
|
||||
padding: 8px 5px;
|
||||
}
|
||||
|
||||
.column-organizer,
|
||||
.column-capacity {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tablenav {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
/**
|
||||
* HVAC Trainer Dashboard Enhanced JavaScript
|
||||
*
|
||||
* Handles dynamic filtering, sorting, pagination, and search for the events table.
|
||||
*/
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Store current state
|
||||
let currentState = {
|
||||
status: 'all',
|
||||
search: '',
|
||||
orderby: 'date',
|
||||
order: 'DESC',
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
date_from: '',
|
||||
date_to: ''
|
||||
};
|
||||
|
||||
// Debounce timer for search
|
||||
let searchTimer = null;
|
||||
|
||||
// Initialize the dashboard when DOM is ready
|
||||
$(document).ready(function() {
|
||||
initializeEnhancedDashboard();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize all enhanced dashboard features
|
||||
*/
|
||||
function initializeEnhancedDashboard() {
|
||||
// Initialize state from URL params
|
||||
initializeStateFromURL();
|
||||
|
||||
// Set up event handlers
|
||||
initEventFilters();
|
||||
initSearchBox();
|
||||
initDateFilters();
|
||||
initPerPageSelector();
|
||||
initSortableColumns();
|
||||
initPagination();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize state from URL parameters
|
||||
*/
|
||||
function initializeStateFromURL() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
currentState.status = urlParams.get('event_status') || 'all';
|
||||
currentState.search = urlParams.get('search') || '';
|
||||
currentState.orderby = urlParams.get('orderby') || 'date';
|
||||
currentState.order = urlParams.get('order') || 'DESC';
|
||||
currentState.page = parseInt(urlParams.get('paged')) || 1;
|
||||
currentState.per_page = parseInt(urlParams.get('per_page')) || 10;
|
||||
currentState.date_from = urlParams.get('date_from') || '';
|
||||
currentState.date_to = urlParams.get('date_to') || '';
|
||||
|
||||
// Update UI elements to match state
|
||||
$('#hvac-event-search').val(currentState.search);
|
||||
$('#hvac-date-from').val(currentState.date_from);
|
||||
$('#hvac-date-to').val(currentState.date_to);
|
||||
$('#hvac-per-page').val(currentState.per_page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize status filter tabs
|
||||
*/
|
||||
function initEventFilters() {
|
||||
$('.hvac-event-filters a').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const status = $(this).data('status');
|
||||
currentState.status = status;
|
||||
currentState.page = 1; // Reset to first page when filtering
|
||||
|
||||
// Update active class
|
||||
$('.hvac-event-filters a').removeClass('hvac-filter-active ast-button-primary').addClass('ast-button-secondary');
|
||||
$(this).addClass('hvac-filter-active ast-button-primary').removeClass('ast-button-secondary');
|
||||
|
||||
// Refresh table
|
||||
refreshEventsTable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize search box
|
||||
*/
|
||||
function initSearchBox() {
|
||||
$('#hvac-event-search').on('keyup', function() {
|
||||
const searchTerm = $(this).val();
|
||||
|
||||
// Clear existing timer
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer);
|
||||
}
|
||||
|
||||
// Set new timer to debounce search
|
||||
searchTimer = setTimeout(function() {
|
||||
currentState.search = searchTerm;
|
||||
currentState.page = 1; // Reset to first page when searching
|
||||
refreshEventsTable();
|
||||
}, 500); // 500ms delay
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize date filters
|
||||
*/
|
||||
function initDateFilters() {
|
||||
$('#hvac-date-from, #hvac-date-to').on('change', function() {
|
||||
currentState.date_from = $('#hvac-date-from').val();
|
||||
currentState.date_to = $('#hvac-date-to').val();
|
||||
currentState.page = 1; // Reset to first page when filtering
|
||||
refreshEventsTable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize per page selector
|
||||
*/
|
||||
function initPerPageSelector() {
|
||||
$('#hvac-per-page').on('change', function() {
|
||||
currentState.per_page = parseInt($(this).val());
|
||||
currentState.page = 1; // Reset to first page when changing per page
|
||||
refreshEventsTable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize sortable column headers
|
||||
*/
|
||||
function initSortableColumns() {
|
||||
$(document).on('click', '.hvac-events-table-wrapper th.sortable a', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const orderby = $(this).data('orderby');
|
||||
const order = $(this).data('order');
|
||||
|
||||
currentState.orderby = orderby;
|
||||
currentState.order = order;
|
||||
currentState.page = 1; // Reset to first page when sorting
|
||||
|
||||
refreshEventsTable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize pagination controls
|
||||
*/
|
||||
function initPagination() {
|
||||
// Pagination links
|
||||
$(document).on('click', '.pagination-links a:not(.disabled)', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const page = parseInt($(this).data('page'));
|
||||
if (page) {
|
||||
currentState.page = page;
|
||||
refreshEventsTable();
|
||||
}
|
||||
});
|
||||
|
||||
// Page input field
|
||||
$(document).on('keypress', '.current-page', function(e) {
|
||||
if (e.which === 13) { // Enter key
|
||||
e.preventDefault();
|
||||
const page = parseInt($(this).val());
|
||||
const totalPages = parseInt($('.total-pages').text());
|
||||
|
||||
if (page >= 1 && page <= totalPages) {
|
||||
currentState.page = page;
|
||||
refreshEventsTable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the events table via AJAX
|
||||
*/
|
||||
function refreshEventsTable() {
|
||||
const $eventsTableWrapper = $('.hvac-events-table-wrapper');
|
||||
|
||||
// Show loading indicator
|
||||
$eventsTableWrapper.addClass('loading').append('<div class="hvac-loading">Loading events...</div>');
|
||||
|
||||
// Make AJAX request
|
||||
$.ajax({
|
||||
url: hvac_dashboard.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'hvac_filter_events',
|
||||
status: currentState.status,
|
||||
search: currentState.search,
|
||||
orderby: currentState.orderby,
|
||||
order: currentState.order,
|
||||
page: currentState.page,
|
||||
per_page: currentState.per_page,
|
||||
date_from: currentState.date_from,
|
||||
date_to: currentState.date_to,
|
||||
nonce: hvac_dashboard.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Replace the table HTML
|
||||
$eventsTableWrapper.html(response.data.html);
|
||||
|
||||
// Update URL without reloading the page
|
||||
updateURL();
|
||||
|
||||
// Scroll to top of table
|
||||
$('html, body').animate({
|
||||
scrollTop: $('.hvac-dashboard-events').offset().top - 50
|
||||
}, 300);
|
||||
} else {
|
||||
showError('Error loading events: ' + response.data.message);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
showError('Error communicating with server.');
|
||||
},
|
||||
complete: function() {
|
||||
$eventsTableWrapper.removeClass('loading');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update URL parameters without reloading
|
||||
*/
|
||||
function updateURL() {
|
||||
if (!history.pushState) return;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
// Only add non-default values to URL
|
||||
if (currentState.status !== 'all') params.set('event_status', currentState.status);
|
||||
if (currentState.search) params.set('search', currentState.search);
|
||||
if (currentState.orderby !== 'date') params.set('orderby', currentState.orderby);
|
||||
if (currentState.order !== 'DESC') params.set('order', currentState.order);
|
||||
if (currentState.page > 1) params.set('paged', currentState.page);
|
||||
if (currentState.per_page !== 10) params.set('per_page', currentState.per_page);
|
||||
if (currentState.date_from) params.set('date_from', currentState.date_from);
|
||||
if (currentState.date_to) params.set('date_to', currentState.date_to);
|
||||
|
||||
const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
|
||||
window.history.pushState({ path: newUrl }, '', newUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error message
|
||||
*/
|
||||
function showError(message) {
|
||||
$('.hvac-events-table-wrapper')
|
||||
.html('<div class="hvac-error notice notice-error"><p>' + message + '</p></div>');
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -175,153 +175,179 @@ class HVAC_Dashboard_Data {
|
|||
/**
|
||||
* Get the data needed for the events table on the dashboard.
|
||||
*
|
||||
* @param string $filter_status The status to filter events by ('all', 'publish', 'future', 'draft', 'pending', 'private'). Defaults to 'all'.
|
||||
* @return array An array of event data arrays/objects, each containing keys like: id, status, name, link, date, organizer, capacity, sold, revenue.
|
||||
* @param array $args Query arguments including:
|
||||
* - 'status' (string): Event status filter ('all', 'publish', 'future', 'draft', 'pending', 'private')
|
||||
* - 'search' (string): Search term for event names
|
||||
* - 'orderby' (string): Column to sort by ('date', 'name', 'status', 'capacity', 'sold', 'revenue')
|
||||
* - 'order' (string): Sort order ('ASC' or 'DESC')
|
||||
* - 'page' (int): Current page number
|
||||
* - 'per_page' (int): Number of events per page
|
||||
* - 'date_from' (string): Start date filter (Y-m-d format)
|
||||
* - 'date_to' (string): End date filter (Y-m-d format)
|
||||
* @return array Contains 'events' array and 'pagination' data
|
||||
*/
|
||||
public function get_events_table_data( $filter_status = 'all' ) {
|
||||
public function get_events_table_data( $args = array() ) {
|
||||
// Default arguments
|
||||
$defaults = array(
|
||||
'status' => 'all',
|
||||
'search' => '',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'page' => 1,
|
||||
'per_page' => 10,
|
||||
'date_from' => '',
|
||||
'date_to' => ''
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
|
||||
// Use direct database approach since TEC interferes with WP_Query
|
||||
return $this->get_events_table_data_direct( $filter_status );
|
||||
return $this->get_events_table_data_direct( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events table data using direct database queries (bypassing TEC query interference)
|
||||
*/
|
||||
private function get_events_table_data_direct( $filter_status = 'all' ) {
|
||||
private function get_events_table_data_direct( $args ) {
|
||||
global $wpdb;
|
||||
|
||||
$events_data = [];
|
||||
$valid_statuses = array( 'publish', 'future', 'draft', 'pending', 'private' );
|
||||
|
||||
// Build status filter for SQL
|
||||
if ( 'all' === $filter_status || ! in_array( $filter_status, $valid_statuses, true ) ) {
|
||||
// Build WHERE clauses
|
||||
$where_clauses = array(
|
||||
'p.post_type = %s',
|
||||
'p.post_author = %d'
|
||||
);
|
||||
$where_values = array( 'tribe_events', $this->user_id );
|
||||
|
||||
// Status filter
|
||||
if ( 'all' === $args['status'] || ! in_array( $args['status'], $valid_statuses, true ) ) {
|
||||
$status_placeholders = implode( ',', array_fill( 0, count( $valid_statuses ), '%s' ) );
|
||||
$status_values = $valid_statuses;
|
||||
$where_clauses[] = "p.post_status IN ($status_placeholders)";
|
||||
$where_values = array_merge( $where_values, $valid_statuses );
|
||||
} else {
|
||||
$status_placeholders = '%s';
|
||||
$status_values = array( $filter_status );
|
||||
$where_clauses[] = 'p.post_status = %s';
|
||||
$where_values[] = $args['status'];
|
||||
}
|
||||
|
||||
// Use direct database query to get events (bypassing TEC query modifications)
|
||||
$sql = "SELECT ID, post_title, post_status, post_date
|
||||
FROM {$wpdb->posts}
|
||||
WHERE post_type = %s
|
||||
AND post_author = %d
|
||||
AND post_status IN ($status_placeholders)
|
||||
ORDER BY post_date DESC";
|
||||
// Search filter
|
||||
if ( ! empty( $args['search'] ) ) {
|
||||
$where_clauses[] = 'p.post_title LIKE %s';
|
||||
$where_values[] = '%' . $wpdb->esc_like( $args['search'] ) . '%';
|
||||
}
|
||||
|
||||
$query_params = array_merge(
|
||||
array( 'tribe_events', $this->user_id ),
|
||||
$status_values
|
||||
);
|
||||
// Date range filters
|
||||
if ( ! empty( $args['date_from'] ) ) {
|
||||
$where_clauses[] = "pm_start.meta_value >= %s";
|
||||
$where_values[] = $args['date_from'] . ' 00:00:00';
|
||||
}
|
||||
|
||||
$events = $wpdb->get_results( $wpdb->prepare( $sql, $query_params ) );
|
||||
if ( ! empty( $args['date_to'] ) ) {
|
||||
$where_clauses[] = "pm_start.meta_value <= %s";
|
||||
$where_values[] = $args['date_to'] . ' 23:59:59';
|
||||
}
|
||||
|
||||
// Build ORDER BY clause
|
||||
$order_column = 'p.post_date';
|
||||
switch ( $args['orderby'] ) {
|
||||
case 'name':
|
||||
$order_column = 'p.post_title';
|
||||
break;
|
||||
case 'status':
|
||||
$order_column = 'p.post_status';
|
||||
break;
|
||||
case 'date':
|
||||
$order_column = 'COALESCE(pm_start.meta_value, p.post_date)';
|
||||
break;
|
||||
case 'capacity':
|
||||
$order_column = 'capacity';
|
||||
break;
|
||||
case 'sold':
|
||||
$order_column = 'sold';
|
||||
break;
|
||||
case 'revenue':
|
||||
$order_column = 'revenue';
|
||||
break;
|
||||
}
|
||||
$order_dir = ( strtoupper( $args['order'] ) === 'ASC' ) ? 'ASC' : 'DESC';
|
||||
|
||||
// Calculate offset for pagination
|
||||
$offset = ( $args['page'] - 1 ) * $args['per_page'];
|
||||
|
||||
// Build the complete SQL query
|
||||
$where_sql = implode( ' AND ', $where_clauses );
|
||||
|
||||
// First, get total count for pagination
|
||||
$count_sql = "SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
LEFT JOIN {$wpdb->postmeta} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate'
|
||||
WHERE $where_sql";
|
||||
|
||||
$total_items = $wpdb->get_var( $wpdb->prepare( $count_sql, $where_values ) );
|
||||
|
||||
// Main query with joins for all needed data
|
||||
$sql = "SELECT
|
||||
p.ID,
|
||||
p.post_title,
|
||||
p.post_status,
|
||||
p.post_date,
|
||||
COALESCE(pm_start.meta_value, p.post_date) as event_date,
|
||||
COALESCE(
|
||||
(SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'tribe_tpp_attendees' AND post_parent = p.ID),
|
||||
0
|
||||
) as sold,
|
||||
COALESCE(
|
||||
(SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2)))
|
||||
FROM {$wpdb->posts} attendees
|
||||
LEFT JOIN {$wpdb->postmeta} pm_price ON attendees.ID = pm_price.post_id AND pm_price.meta_key = '_tribe_tpp_ticket_price'
|
||||
WHERE attendees.post_type = 'tribe_tpp_attendees' AND attendees.post_parent = p.ID),
|
||||
0
|
||||
) as revenue,
|
||||
50 as capacity
|
||||
FROM {$wpdb->posts} p
|
||||
LEFT JOIN {$wpdb->postmeta} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate'
|
||||
WHERE $where_sql
|
||||
ORDER BY $order_column $order_dir
|
||||
LIMIT %d OFFSET %d";
|
||||
|
||||
$query_values = array_merge( $where_values, array( $args['per_page'], $offset ) );
|
||||
$events = $wpdb->get_results( $wpdb->prepare( $sql, $query_values ) );
|
||||
|
||||
if ( ! empty( $events ) ) {
|
||||
foreach ( $events as $event ) {
|
||||
$event_id = $event->ID;
|
||||
|
||||
// Get event start date
|
||||
$start_date = get_post_meta( $event_id, '_EventStartDate', true );
|
||||
$start_date_ts = $start_date ? strtotime( $start_date ) : time();
|
||||
|
||||
// Get sold count from attendees
|
||||
$sold = $wpdb->get_var( $wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->posts}
|
||||
WHERE post_type = 'tribe_tpp_attendees'
|
||||
AND post_parent = %d",
|
||||
$event_id
|
||||
) );
|
||||
|
||||
// Get revenue from attendees
|
||||
$revenue = $wpdb->get_var( $wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm.meta_value AS DECIMAL(10,2)))
|
||||
FROM {$wpdb->posts} p
|
||||
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_ticket_price'
|
||||
WHERE p.post_type = 'tribe_tpp_attendees'
|
||||
AND p.post_parent = %d",
|
||||
$event_id
|
||||
) );
|
||||
$start_date_ts = $event->event_date ? strtotime( $event->event_date ) : strtotime( $event->post_date );
|
||||
|
||||
// Build event data array (matching template expectations)
|
||||
$events_data[] = array(
|
||||
'id' => $event_id,
|
||||
'name' => $event->post_title, // Template expects 'name'
|
||||
'name' => $event->post_title,
|
||||
'status' => $event->post_status,
|
||||
'start_date_ts' => $start_date_ts, // Template expects this
|
||||
'start_date_ts' => $start_date_ts,
|
||||
'link' => get_permalink( $event_id ),
|
||||
'organizer_id' => $this->user_id, // Template expects this
|
||||
'capacity' => 50, // Default capacity
|
||||
'sold' => (int) ($sold ?: 0),
|
||||
'revenue' => (float) ($revenue ?: 0.00),
|
||||
'organizer_id' => $this->user_id,
|
||||
'capacity' => (int) $event->capacity,
|
||||
'sold' => (int) $event->sold,
|
||||
'revenue' => (float) $event->revenue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $events_data;
|
||||
|
||||
if ( ! empty( $events ) ) {
|
||||
foreach ( $events as $event ) {
|
||||
$event_id = $event->ID;
|
||||
$event_id = get_the_ID();
|
||||
|
||||
// Get Capacity - Sum capacity of all tickets for this event
|
||||
$total_capacity = 0;
|
||||
if ( function_exists( 'tribe_get_tickets' ) ) {
|
||||
$tickets = tribe_get_tickets( $event_id );
|
||||
if ( $tickets ) {
|
||||
foreach ( $tickets as $ticket ) {
|
||||
$capacity = $ticket->capacity();
|
||||
// -1 often means unlimited capacity for Tribe Tickets
|
||||
if ( $capacity === -1 ) {
|
||||
$total_capacity = -1; // Mark as unlimited
|
||||
break; // No need to sum further if one is unlimited
|
||||
}
|
||||
if ( is_numeric( $capacity ) ) {
|
||||
$total_capacity += $capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get sold and revenue counts, checking for both standard and alternative meta fields
|
||||
$sold = get_post_meta( $event_id, '_tribe_tickets_sold', true );
|
||||
if (!is_numeric($sold)) {
|
||||
$sold = get_post_meta( $event_id, '_tribe_ticket_sold_count', true );
|
||||
|
||||
// If still no valid count, calculate from attendees
|
||||
if (!is_numeric($sold)) {
|
||||
$sold = $this->count_event_attendees($event_id);
|
||||
if ($sold > 0) {
|
||||
update_post_meta($event_id, '_tribe_tickets_sold', $sold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$revenue = get_post_meta( $event_id, '_tribe_revenue_total', true );
|
||||
if (!is_numeric($revenue)) {
|
||||
$revenue = $this->calculate_event_revenue($event_id);
|
||||
if ($revenue > 0) {
|
||||
update_post_meta($event_id, '_tribe_revenue_total', $revenue);
|
||||
}
|
||||
}
|
||||
|
||||
$events_data[] = array(
|
||||
'id' => $event_id,
|
||||
'status' => get_post_status( $event_id ),
|
||||
'name' => get_the_title(),
|
||||
// Return raw data instead of calling TEC functions here
|
||||
'link' => get_permalink( $event_id ), // Use standard WP permalink
|
||||
'start_date_ts' => strtotime( get_post_meta( $event_id, '_EventStartDate', true ) ), // Return timestamp
|
||||
'organizer_id' => (int) get_post_meta( $event_id, '_EventOrganizerID', true ), // Return organizer ID
|
||||
'capacity' => ( $total_capacity === -1 ) ? 'Unlimited' : (int) $total_capacity,
|
||||
'sold' => is_numeric( $sold ) ? (int) $sold : 0,
|
||||
'revenue' => is_numeric( $revenue ) ? (float) $revenue : 0.0,
|
||||
);
|
||||
}
|
||||
wp_reset_postdata(); // Restore original Post Data
|
||||
}
|
||||
|
||||
return $events_data;
|
||||
// Calculate pagination data
|
||||
$total_pages = ceil( $total_items / $args['per_page'] );
|
||||
|
||||
return array(
|
||||
'events' => $events_data,
|
||||
'pagination' => array(
|
||||
'total_items' => $total_items,
|
||||
'total_pages' => $total_pages,
|
||||
'current_page' => $args['page'],
|
||||
'per_page' => $args['per_page'],
|
||||
'has_prev' => $args['page'] > 1,
|
||||
'has_next' => $args['page'] < $total_pages
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -234,65 +234,172 @@ class HVAC_Dashboard {
|
|||
return;
|
||||
}
|
||||
|
||||
// Get status filter
|
||||
$status = isset($_POST['status']) ? sanitize_key($_POST['status']) : 'all';
|
||||
// Get all filters and parameters
|
||||
$args = array(
|
||||
'status' => isset($_POST['status']) ? sanitize_key($_POST['status']) : 'all',
|
||||
'search' => isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '',
|
||||
'orderby' => isset($_POST['orderby']) ? sanitize_key($_POST['orderby']) : 'date',
|
||||
'order' => isset($_POST['order']) ? sanitize_key($_POST['order']) : 'DESC',
|
||||
'page' => isset($_POST['page']) ? absint($_POST['page']) : 1,
|
||||
'per_page' => isset($_POST['per_page']) ? absint($_POST['per_page']) : 10,
|
||||
'date_from' => isset($_POST['date_from']) ? sanitize_text_field($_POST['date_from']) : '',
|
||||
'date_to' => isset($_POST['date_to']) ? sanitize_text_field($_POST['date_to']) : '',
|
||||
);
|
||||
|
||||
// Include dashboard data class
|
||||
require_once HVAC_CE_PLUGIN_DIR . 'includes/class-hvac-dashboard-data.php';
|
||||
$dashboard_data = new HVAC_Dashboard_Data($user_id);
|
||||
|
||||
// Get filtered events data
|
||||
$events = $dashboard_data->get_events_table_data($status);
|
||||
$result = $dashboard_data->get_events_table_data($args);
|
||||
$events = $result['events'];
|
||||
$pagination = $result['pagination'];
|
||||
|
||||
// Build HTML for events table
|
||||
ob_start();
|
||||
|
||||
if (!empty($events)) : ?>
|
||||
<table class="hvac-events-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Event Name</th>
|
||||
<th>Date</th>
|
||||
<th>Organizer</th>
|
||||
<th>Capacity</th>
|
||||
<th>Sold</th>
|
||||
<th>Revenue</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
?>
|
||||
<table class="hvac-events-table wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="manage-column column-status sortable <?php echo ($args['orderby'] === 'status') ? 'sorted ' . strtolower($args['order']) : ''; ?>">
|
||||
<a href="#" data-orderby="status" data-order="<?php echo ($args['orderby'] === 'status' && $args['order'] === 'ASC') ? 'DESC' : 'ASC'; ?>">
|
||||
<span>Status</span>
|
||||
<span class="sorting-indicators">
|
||||
<span class="sorting-indicator asc" aria-hidden="true"></span>
|
||||
<span class="sorting-indicator desc" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="manage-column column-title sortable <?php echo ($args['orderby'] === 'name') ? 'sorted ' . strtolower($args['order']) : ''; ?>">
|
||||
<a href="#" data-orderby="name" data-order="<?php echo ($args['orderby'] === 'name' && $args['order'] === 'ASC') ? 'DESC' : 'ASC'; ?>">
|
||||
<span>Event Name</span>
|
||||
<span class="sorting-indicators">
|
||||
<span class="sorting-indicator asc" aria-hidden="true"></span>
|
||||
<span class="sorting-indicator desc" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="manage-column column-date sortable <?php echo ($args['orderby'] === 'date') ? 'sorted ' . strtolower($args['order']) : ''; ?>">
|
||||
<a href="#" data-orderby="date" data-order="<?php echo ($args['orderby'] === 'date' && $args['order'] === 'ASC') ? 'DESC' : 'ASC'; ?>">
|
||||
<span>Date</span>
|
||||
<span class="sorting-indicators">
|
||||
<span class="sorting-indicator asc" aria-hidden="true"></span>
|
||||
<span class="sorting-indicator desc" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="manage-column column-organizer">Organizer</th>
|
||||
<th scope="col" class="manage-column column-capacity sortable <?php echo ($args['orderby'] === 'capacity') ? 'sorted ' . strtolower($args['order']) : ''; ?>">
|
||||
<a href="#" data-orderby="capacity" data-order="<?php echo ($args['orderby'] === 'capacity' && $args['order'] === 'ASC') ? 'DESC' : 'ASC'; ?>">
|
||||
<span>Capacity</span>
|
||||
<span class="sorting-indicators">
|
||||
<span class="sorting-indicator asc" aria-hidden="true"></span>
|
||||
<span class="sorting-indicator desc" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="manage-column column-sold sortable <?php echo ($args['orderby'] === 'sold') ? 'sorted ' . strtolower($args['order']) : ''; ?>">
|
||||
<a href="#" data-orderby="sold" data-order="<?php echo ($args['orderby'] === 'sold' && $args['order'] === 'ASC') ? 'DESC' : 'ASC'; ?>">
|
||||
<span>Sold</span>
|
||||
<span class="sorting-indicators">
|
||||
<span class="sorting-indicator asc" aria-hidden="true"></span>
|
||||
<span class="sorting-indicator desc" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="manage-column column-revenue sortable <?php echo ($args['orderby'] === 'revenue') ? 'sorted ' . strtolower($args['order']) : ''; ?>">
|
||||
<a href="#" data-orderby="revenue" data-order="<?php echo ($args['orderby'] === 'revenue' && $args['order'] === 'ASC') ? 'DESC' : 'ASC'; ?>">
|
||||
<span>Revenue</span>
|
||||
<span class="sorting-indicators">
|
||||
<span class="sorting-indicator asc" aria-hidden="true"></span>
|
||||
<span class="sorting-indicator desc" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="manage-column column-actions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="the-list">
|
||||
<?php if (!empty($events)) : ?>
|
||||
<?php foreach ($events as $event) : ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html(ucfirst($event['status'])); ?></td>
|
||||
<td>
|
||||
<td class="column-status"><?php echo esc_html(ucfirst($event['status'])); ?></td>
|
||||
<td class="column-title">
|
||||
<strong><a href="<?php echo esc_url($event['link']); ?>" target="_blank"><?php echo esc_html($event['name']); ?></a></strong>
|
||||
</td>
|
||||
<td><?php echo esc_html(date('Y-m-d H:i', $event['start_date_ts'])); ?></td>
|
||||
<td><?php
|
||||
<td class="column-date"><?php echo esc_html(date('Y-m-d H:i', $event['start_date_ts'])); ?></td>
|
||||
<td class="column-organizer"><?php
|
||||
if (function_exists('tribe_get_organizer')) {
|
||||
echo esc_html(tribe_get_organizer($event['organizer_id']));
|
||||
} else {
|
||||
echo 'Organizer ID: ' . esc_html($event['organizer_id']);
|
||||
}
|
||||
?></td>
|
||||
<td><?php echo esc_html($event['capacity']); ?></td>
|
||||
<td><?php echo esc_html($event['sold']); ?></td>
|
||||
<td>$<?php echo esc_html(number_format($event['revenue'], 2)); ?></td>
|
||||
<td>
|
||||
<td class="column-capacity"><?php echo esc_html($event['capacity']); ?></td>
|
||||
<td class="column-sold"><?php echo esc_html($event['sold']); ?></td>
|
||||
<td class="column-revenue">$<?php echo esc_html(number_format($event['revenue'], 2)); ?></td>
|
||||
<td class="column-actions">
|
||||
<?php
|
||||
$edit_url = add_query_arg('event_id', $event['id'], home_url('/manage-event/'));
|
||||
$summary_url = get_permalink($event['id']);
|
||||
$summary_url = add_query_arg('event_id', $event['id'], home_url('/event-summary/'));
|
||||
$view_url = get_permalink($event['id']);
|
||||
?>
|
||||
<a href="<?php echo esc_url($edit_url); ?>">Edit</a> |
|
||||
<a href="<?php echo esc_url($summary_url); ?>">Summary</a>
|
||||
<a href="<?php echo esc_url($summary_url); ?>">Summary</a> |
|
||||
<a href="<?php echo esc_url($view_url); ?>" target="_blank">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else : ?>
|
||||
<p>No events found.</p>
|
||||
<?php else : ?>
|
||||
<tr>
|
||||
<td colspan="8">No events found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ($pagination['total_pages'] > 1) : ?>
|
||||
<div class="tablenav bottom">
|
||||
<div class="tablenav-pages">
|
||||
<span class="displaying-num"><?php echo esc_html($pagination['total_items']); ?> items</span>
|
||||
<span class="pagination-links">
|
||||
<?php if ($pagination['has_prev']) : ?>
|
||||
<a class="prev-page button" href="#" data-page="1">
|
||||
<span class="screen-reader-text">First page</span>
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
<a class="prev-page button" href="#" data-page="<?php echo esc_attr($pagination['current_page'] - 1); ?>">
|
||||
<span class="screen-reader-text">Previous page</span>
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">‹</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<span class="paging-input">
|
||||
<label for="current-page-selector" class="screen-reader-text">Current Page</label>
|
||||
<input class="current-page" id="current-page-selector" type="text" name="paged" value="<?php echo esc_attr($pagination['current_page']); ?>" size="1" aria-describedby="table-paging">
|
||||
<span class="tablenav-paging-text"> of <span class="total-pages"><?php echo esc_html($pagination['total_pages']); ?></span></span>
|
||||
</span>
|
||||
|
||||
<?php if ($pagination['has_next']) : ?>
|
||||
<a class="next-page button" href="#" data-page="<?php echo esc_attr($pagination['current_page'] + 1); ?>">
|
||||
<span class="screen-reader-text">Next page</span>
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
<a class="next-page button" href="#" data-page="<?php echo esc_attr($pagination['total_pages']); ?>">
|
||||
<span class="screen-reader-text">Last page</span>
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">›</span>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">»</span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif;
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
|
@ -301,7 +408,8 @@ class HVAC_Dashboard {
|
|||
wp_send_json_success(array(
|
||||
'html' => $html,
|
||||
'count' => count($events),
|
||||
'status' => $status
|
||||
'pagination' => $pagination,
|
||||
'args' => $args
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -331,17 +439,25 @@ class HVAC_Dashboard {
|
|||
true
|
||||
);
|
||||
|
||||
// Enqueue dashboard JavaScript
|
||||
// Enqueue enhanced dashboard CSS
|
||||
wp_enqueue_style(
|
||||
'hvac-dashboard-enhanced-css',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/css/hvac-dashboard-enhanced.css',
|
||||
array('hvac-ux-enhancements-css'),
|
||||
HVAC_CE_VERSION
|
||||
);
|
||||
|
||||
// Enqueue enhanced dashboard JavaScript
|
||||
wp_enqueue_script(
|
||||
'hvac-dashboard-js',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/js/hvac-dashboard.js',
|
||||
'hvac-dashboard-enhanced-js',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/js/hvac-dashboard-enhanced.js',
|
||||
array('jquery', 'hvac-ux-enhancements-js'),
|
||||
HVAC_CE_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Localize script with AJAX URL and nonce
|
||||
wp_localize_script('hvac-dashboard-js', 'hvac_dashboard', array(
|
||||
wp_localize_script('hvac-dashboard-enhanced-js', 'hvac_dashboard', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('hvac_dashboard_nonce')
|
||||
));
|
||||
|
|
|
|||
|
|
@ -206,6 +206,37 @@ get_header(); // Use theme's header
|
|||
'bottom'
|
||||
); ?>
|
||||
|
||||
<!-- Enhanced Filters and Controls -->
|
||||
<div class="hvac-table-controls">
|
||||
<!-- Search Box -->
|
||||
<div class="hvac-search-box">
|
||||
<?php echo HVAC_Help_System::add_tooltip(
|
||||
'<input type="search" id="hvac-event-search" placeholder="Search events..." class="regular-text">',
|
||||
'Search events by name'
|
||||
); ?>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Filters -->
|
||||
<div class="hvac-date-filters">
|
||||
<label for="hvac-date-from">From:</label>
|
||||
<input type="date" id="hvac-date-from" class="hvac-date-input">
|
||||
<label for="hvac-date-to">To:</label>
|
||||
<input type="date" id="hvac-date-to" class="hvac-date-input">
|
||||
</div>
|
||||
|
||||
<!-- Per Page Selector -->
|
||||
<div class="hvac-per-page">
|
||||
<label for="hvac-per-page">Show:</label>
|
||||
<select id="hvac-per-page" class="hvac-per-page-select">
|
||||
<option value="10" selected>10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<span>per page</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Filters -->
|
||||
<?php
|
||||
$dashboard_url = get_permalink(); // Get the current page URL
|
||||
|
|
@ -237,7 +268,21 @@ get_header(); // Use theme's header
|
|||
<!-- Events Table -->
|
||||
<?php
|
||||
// $current_filter is already defined above
|
||||
$events = $dashboard_data->get_events_table_data( $current_filter );
|
||||
// Get events with new parameters
|
||||
$args = array(
|
||||
'status' => $current_filter,
|
||||
'search' => isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '',
|
||||
'orderby' => isset($_GET['orderby']) ? sanitize_key($_GET['orderby']) : 'date',
|
||||
'order' => isset($_GET['order']) ? sanitize_key($_GET['order']) : 'DESC',
|
||||
'page' => isset($_GET['paged']) ? absint($_GET['paged']) : 1,
|
||||
'per_page' => isset($_GET['per_page']) ? absint($_GET['per_page']) : 10,
|
||||
'date_from' => isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : '',
|
||||
'date_to' => isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : ''
|
||||
);
|
||||
|
||||
$result = $dashboard_data->get_events_table_data( $args );
|
||||
$events = $result['events'];
|
||||
$pagination = $result['pagination'];
|
||||
?>
|
||||
|
||||
<div class="hvac-events-table-wrapper">
|
||||
|
|
@ -299,6 +344,49 @@ get_header(); // Use theme's header
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<?php if ($pagination['total_pages'] > 1) : ?>
|
||||
<div class="tablenav bottom">
|
||||
<div class="tablenav-pages">
|
||||
<span class="displaying-num"><?php echo esc_html($pagination['total_items']); ?> items</span>
|
||||
<span class="pagination-links">
|
||||
<?php if ($pagination['has_prev']) : ?>
|
||||
<a class="first-page button" href="#" data-page="1">
|
||||
<span class="screen-reader-text">First page</span>
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
<a class="prev-page button" href="#" data-page="<?php echo esc_attr($pagination['current_page'] - 1); ?>">
|
||||
<span class="screen-reader-text">Previous page</span>
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">«</span>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">‹</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<span class="paging-input">
|
||||
<label for="current-page-selector" class="screen-reader-text">Current Page</label>
|
||||
<input class="current-page" id="current-page-selector" type="text" name="paged" value="<?php echo esc_attr($pagination['current_page']); ?>" size="1" aria-describedby="table-paging">
|
||||
<span class="tablenav-paging-text"> of <span class="total-pages"><?php echo esc_html($pagination['total_pages']); ?></span></span>
|
||||
</span>
|
||||
|
||||
<?php if ($pagination['has_next']) : ?>
|
||||
<a class="next-page button" href="#" data-page="<?php echo esc_attr($pagination['current_page'] + 1); ?>">
|
||||
<span class="screen-reader-text">Next page</span>
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
<a class="last-page button" href="#" data-page="<?php echo esc_attr($pagination['total_pages']); ?>">
|
||||
<span class="screen-reader-text">Last page</span>
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">›</span>
|
||||
<span class="tablenav-pages-navspan button disabled" aria-hidden="true">»</span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</section>
|
||||
|
||||
</main> <!-- #main -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue