feat: Improve dashboard UI and interactivity
- Change 'Your Stats' section to row layout for better visual display - Add dynamic event filtering without page reload - Create AJAX endpoint for event filtering - Add loading animation and UI feedback - Improve mobile responsiveness 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f5ccb9085c
commit
5bcd8a48a8
4 changed files with 310 additions and 31 deletions
|
|
@ -24,7 +24,23 @@
|
|||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
/* Target column padding specifically within the stats section */
|
||||
/* Row layout for stats */
|
||||
.hvac-stats-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: -10px; /* Counteract the padding on columns */
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.hvac-stat-col {
|
||||
flex: 1;
|
||||
min-width: 160px; /* Ensure minimum width for readability */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Target column padding specifically within the stats section - maintain compatibility */
|
||||
.hvac-dashboard-stats .ast-row > [class*="ast-col-"] {
|
||||
padding: 10px; /* Replicates removed inline style */
|
||||
display: flex; /* Ensure cards fill height if needed */
|
||||
|
|
@ -38,6 +54,7 @@
|
|||
text-align: center;
|
||||
width: 100%; /* Make card fill column */
|
||||
flex-grow: 1; /* Allow card to grow if needed */
|
||||
height: 100%; /* Ensure equal height */
|
||||
/* Add box-shadow or other theme-consistent styling if desired */
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +96,8 @@
|
|||
/* Events Table */
|
||||
.hvac-events-table-wrapper {
|
||||
overflow-x: auto; /* Add horizontal scroll for smaller screens */
|
||||
position: relative; /* For loading indicator positioning */
|
||||
min-height: 100px; /* Ensure space for loading indicator */
|
||||
}
|
||||
|
||||
/* Ensure table uses standard WP/Theme styling */
|
||||
|
|
@ -88,4 +107,36 @@
|
|||
|
||||
.events-table .column-actions a {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* Loading indicator */
|
||||
.hvac-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
z-index: 10;
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Error message */
|
||||
.hvac-error {
|
||||
color: #d63638;
|
||||
padding: 10px;
|
||||
border: 1px solid #ffb8bb;
|
||||
background-color: #ffebe9;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* HVAC Trainer Dashboard JavaScript
|
||||
*
|
||||
* Handles dynamic filtering of events table and other interactive features.
|
||||
*/
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
// Initialize the dashboard when DOM is ready
|
||||
$(document).ready(function() {
|
||||
initEventFilters();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize event filters to work without page reload
|
||||
*/
|
||||
function initEventFilters() {
|
||||
// Get the events table wrapper element
|
||||
const $eventsTableWrapper = $('.hvac-events-table-wrapper');
|
||||
|
||||
// Add click handler to filter links
|
||||
$('.hvac-event-filters a').on('click', function(e) {
|
||||
e.preventDefault(); // Prevent default link behavior (page reload)
|
||||
|
||||
// Get the status filter from the link URL
|
||||
const url = new URL($(this).attr('href'), window.location.origin);
|
||||
const status = url.searchParams.get('event_status') || 'all';
|
||||
|
||||
// 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');
|
||||
|
||||
// Show loading indicator
|
||||
$eventsTableWrapper.append('<div class="hvac-loading">Filtering events...</div>');
|
||||
|
||||
// Make AJAX request to get filtered events
|
||||
$.ajax({
|
||||
url: hvac_dashboard.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'hvac_filter_events',
|
||||
status: status,
|
||||
nonce: hvac_dashboard.nonce
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Replace the table HTML with the filtered results
|
||||
$eventsTableWrapper.html(response.data.html);
|
||||
|
||||
// Update the URL without reloading the page
|
||||
if (history.pushState) {
|
||||
const newUrl = status === 'all'
|
||||
? removeURLParameter(window.location.href, 'event_status')
|
||||
: addURLParameter(window.location.href, 'event_status', status);
|
||||
window.history.pushState({ path: newUrl }, '', newUrl);
|
||||
}
|
||||
} else {
|
||||
// Show error message
|
||||
$eventsTableWrapper.find('.hvac-loading').remove();
|
||||
$eventsTableWrapper.append('<div class="hvac-error">Error loading events: ' + response.data.message + '</div>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
// Show error message
|
||||
$eventsTableWrapper.find('.hvac-loading').remove();
|
||||
$eventsTableWrapper.append('<div class="hvac-error">Error communicating with server.</div>');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to add a URL parameter
|
||||
*/
|
||||
function addURLParameter(url, key, value) {
|
||||
const re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
|
||||
const separator = url.indexOf('?') !== -1 ? "&" : "?";
|
||||
|
||||
if (url.match(re)) {
|
||||
return url.replace(re, '$1' + key + "=" + value + '$2');
|
||||
} else {
|
||||
return url + separator + key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to remove a URL parameter
|
||||
*/
|
||||
function removeURLParameter(url, parameter) {
|
||||
const urlParts = url.split('?');
|
||||
if (urlParts.length < 2) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const urlBase = urlParts[0];
|
||||
const queryString = urlParts[1];
|
||||
const prefix = encodeURIComponent(parameter) + '=';
|
||||
const parts = queryString.split(/[&;]/g);
|
||||
|
||||
// Reverse iteration to safely remove items
|
||||
for (let i = parts.length; i-- > 0;) {
|
||||
if (parts[i].lastIndexOf(prefix, 0) !== -1) {
|
||||
parts.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const newQueryString = parts.join('&');
|
||||
return newQueryString.length > 0 ? urlBase + '?' + newQueryString : urlBase;
|
||||
}
|
||||
|
||||
})(jQuery);
|
||||
|
|
@ -21,7 +21,10 @@ class HVAC_Dashboard {
|
|||
add_action('init', array($this, 'register_shortcode'));
|
||||
// Use higher priority to run after shortcode processing
|
||||
add_filter('the_content', array($this, 'render_dashboard_content'), 99);
|
||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_dashboard_styles'));
|
||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_dashboard_assets'));
|
||||
|
||||
// AJAX handler for events filtering
|
||||
add_action('wp_ajax_hvac_filter_events', array($this, 'ajax_filter_events'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,39 +107,39 @@ class HVAC_Dashboard {
|
|||
<!-- Statistics Section -->
|
||||
<section class="hvac-dashboard-stats">
|
||||
<h2>Your Stats</h2>
|
||||
<div class="hvac-stats-grid">
|
||||
<div class="hvac-stats-row">
|
||||
<!-- Total Events -->
|
||||
<div class="hvac-stat-card">
|
||||
<div class="hvac-stat-col"><div class="hvac-stat-card">
|
||||
<h3>Total Events</h3>
|
||||
<p class="metric-value"><?php echo esc_html($data['total_events']); ?></p>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
<!-- Upcoming Events -->
|
||||
<div class="hvac-stat-card">
|
||||
<div class="hvac-stat-col"><div class="hvac-stat-card">
|
||||
<h3>Upcoming Events</h3>
|
||||
<p class="metric-value"><?php echo esc_html($data['upcoming_events']); ?></p>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
<!-- Past Events -->
|
||||
<div class="hvac-stat-card">
|
||||
<div class="hvac-stat-col"><div class="hvac-stat-card">
|
||||
<h3>Past Events</h3>
|
||||
<p class="metric-value"><?php echo esc_html($data['past_events']); ?></p>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
<!-- Total Tickets Sold -->
|
||||
<div class="hvac-stat-card">
|
||||
<div class="hvac-stat-col"><div class="hvac-stat-card">
|
||||
<h3>Tickets Sold</h3>
|
||||
<p class="metric-value"><?php echo esc_html($data['total_sold']); ?></p>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
<!-- Total Revenue -->
|
||||
<div class="hvac-stat-card">
|
||||
<div class="hvac-stat-col"><div class="hvac-stat-card">
|
||||
<h3>Total Revenue</h3>
|
||||
<p class="metric-value">$<?php echo esc_html(number_format($data['total_revenue'], 2)); ?></p>
|
||||
<?php if ($data['revenue_target']) : ?>
|
||||
<small>Target: $<?php echo esc_html(number_format($data['revenue_target'], 2)); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -152,9 +155,9 @@ class HVAC_Dashboard {
|
|||
$filter_statuses = array('all', 'publish', 'draft', 'pending', 'private');
|
||||
foreach ($filter_statuses as $status) :
|
||||
$url = ($status === 'all') ? remove_query_arg('event_status', $dashboard_url) : add_query_arg('event_status', $status, $dashboard_url);
|
||||
$class = ($status === $data['current_filter']) ? 'hvac-filter-active' : '';
|
||||
$class = ($status === $data['current_filter']) ? 'hvac-filter hvac-filter-active ast-button-primary' : 'hvac-filter ast-button-secondary';
|
||||
?>
|
||||
<a href="<?php echo esc_url($url); ?>" class="hvac-filter <?php echo esc_attr($class); ?>"><?php echo esc_html(ucfirst($status)); ?></a>
|
||||
<a href="<?php echo esc_url($url); ?>" class="ast-button <?php echo esc_attr($class); ?>" data-status="<?php echo esc_attr($status); ?>"><?php echo esc_html(ucfirst($status)); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
|
|
@ -215,13 +218,118 @@ class HVAC_Dashboard {
|
|||
}
|
||||
|
||||
/**
|
||||
* Enqueue dashboard styles
|
||||
* Handle AJAX request for filtered events table
|
||||
*/
|
||||
public function enqueue_dashboard_styles() {
|
||||
if (!is_page('hvac-dashboard')) {
|
||||
public function ajax_filter_events() {
|
||||
// Check nonce
|
||||
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_dashboard_nonce')) {
|
||||
wp_send_json_error(array('message' => 'Security check failed.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current user ID
|
||||
$user_id = get_current_user_id();
|
||||
if (!$user_id || !current_user_can('view_hvac_dashboard')) {
|
||||
wp_send_json_error(array('message' => 'Access denied.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get status filter
|
||||
$status = isset($_POST['status']) ? sanitize_key($_POST['status']) : 'all';
|
||||
|
||||
// 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);
|
||||
|
||||
// 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>
|
||||
<?php foreach ($events as $event) : ?>
|
||||
<tr>
|
||||
<td><?php echo esc_html(ucfirst($event['status'])); ?></td>
|
||||
<td>
|
||||
<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
|
||||
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>
|
||||
<?php
|
||||
$edit_url = add_query_arg('event_id', $event['id'], home_url('/manage-event/'));
|
||||
$summary_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>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php else : ?>
|
||||
<p>No events found.</p>
|
||||
<?php endif;
|
||||
|
||||
$html = ob_get_clean();
|
||||
|
||||
// Send JSON response
|
||||
wp_send_json_success(array(
|
||||
'html' => $html,
|
||||
'count' => count($events),
|
||||
'status' => $status
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue dashboard assets (CSS and JavaScript)
|
||||
*/
|
||||
public function enqueue_dashboard_assets() {
|
||||
// Check if we're on the dashboard page
|
||||
global $post;
|
||||
if (!is_a($post, 'WP_Post') || !has_shortcode($post->post_content, 'hvac_trainer_dashboard')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue dashboard JavaScript
|
||||
wp_enqueue_script(
|
||||
'hvac-dashboard-js',
|
||||
HVAC_CE_PLUGIN_URL . 'assets/js/hvac-dashboard.js',
|
||||
array('jquery'),
|
||||
HVAC_CE_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Localize script with AJAX URL and nonce
|
||||
wp_localize_script('hvac-dashboard-js', 'hvac_dashboard', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('hvac_dashboard_nonce')
|
||||
));
|
||||
|
||||
// Inline CSS for now - can be moved to external file later
|
||||
$css = '
|
||||
.hvac-dashboard-wrapper {
|
||||
|
|
@ -274,13 +382,22 @@ class HVAC_Dashboard {
|
|||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.hvac-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
.hvac-stats-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: -10px; /* Counteract the padding on columns */
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hvac-stat-col {
|
||||
flex: 1;
|
||||
min-width: 160px; /* Ensure minimum width for readability */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.hvac-stat-card {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ get_header(); // Use theme's header
|
|||
<!-- Statistics Section -->
|
||||
<section class="hvac-dashboard-stats">
|
||||
<h2>Your Stats</h2>
|
||||
<div class="ast-row"> <!-- Use Astra grid row -->
|
||||
<div class="hvac-stats-row"> <!-- Use flexbox row layout -->
|
||||
|
||||
<!-- Stat Card: Total Events -->
|
||||
<div class="ast-col-md-6 ast-col-lg-4"> <!-- Adjust columns as needed -->
|
||||
<div class="hvac-stat-col"> <!-- Equal width flexbox column -->
|
||||
<div class="hvac-stat-card">
|
||||
<h3>Total Events</h3>
|
||||
<p><?php echo esc_html( $total_events ); ?></p>
|
||||
|
|
@ -75,7 +75,7 @@ get_header(); // Use theme's header
|
|||
</div>
|
||||
|
||||
<!-- Stat Card: Upcoming Events -->
|
||||
<div class="ast-col-md-6 ast-col-lg-4">
|
||||
<div class="hvac-stat-col">
|
||||
<div class="hvac-stat-card">
|
||||
<h3>Upcoming Events</h3>
|
||||
<p><?php echo esc_html( $upcoming_events ); ?></p>
|
||||
|
|
@ -83,7 +83,7 @@ get_header(); // Use theme's header
|
|||
</div>
|
||||
|
||||
<!-- Stat Card: Past Events -->
|
||||
<div class="ast-col-md-6 ast-col-lg-4">
|
||||
<div class="hvac-stat-col">
|
||||
<div class="hvac-stat-card">
|
||||
<h3>Past Events</h3>
|
||||
<p><?php echo esc_html( $past_events ); ?></p>
|
||||
|
|
@ -91,7 +91,7 @@ get_header(); // Use theme's header
|
|||
</div>
|
||||
|
||||
<!-- Stat Card: Total Tickets Sold -->
|
||||
<div class="ast-col-md-6 ast-col-lg-4">
|
||||
<div class="hvac-stat-col">
|
||||
<div class="hvac-stat-card">
|
||||
<h3>Tickets Sold</h3>
|
||||
<p><?php echo esc_html( $total_sold ); ?></p>
|
||||
|
|
@ -99,7 +99,7 @@ get_header(); // Use theme's header
|
|||
</div>
|
||||
|
||||
<!-- Stat Card: Total Revenue -->
|
||||
<div class="ast-col-md-6 ast-col-lg-4">
|
||||
<div class="hvac-stat-col">
|
||||
<div class="hvac-stat-card">
|
||||
<h3>Total Revenue</h3>
|
||||
<p>$<?php echo esc_html( number_format( $total_revenue, 2 ) ); ?></p>
|
||||
|
|
@ -126,18 +126,18 @@ get_header(); // Use theme's header
|
|||
<span>Filter: </span>
|
||||
<?php foreach ($filter_statuses as $status) :
|
||||
$url = add_query_arg( 'event_status', $status, $dashboard_url );
|
||||
$class = 'ast-button ast-button-secondary';
|
||||
$class = 'ast-button ast-button-secondary hvac-filter';
|
||||
if ($status === $current_filter) {
|
||||
// Add a class or style for the active filter
|
||||
// Using primary button style for active state as an example
|
||||
$class = 'ast-button ast-button-primary';
|
||||
$class = 'ast-button ast-button-primary hvac-filter hvac-filter-active';
|
||||
}
|
||||
// Special case for 'all' filter link
|
||||
if ($status === 'all') {
|
||||
$url = remove_query_arg( 'event_status', $dashboard_url );
|
||||
}
|
||||
?>
|
||||
<a href="<?php echo esc_url( $url ); ?>" class="<?php echo esc_attr( $class ); ?>"><?php echo esc_html( ucfirst( $status ) ); ?></a>
|
||||
<a href="<?php echo esc_url( $url ); ?>" class="<?php echo esc_attr( $class ); ?>" data-status="<?php echo esc_attr( $status ); ?>"><?php echo esc_html( ucfirst( $status ) ); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue