docs: Add dashboard improvements documentation

- Create detailed documentation for dashboard UI/UX improvements
- Document row layout for stats section
- Document dynamic event filtering functionality
- Add technical implementation details
- Add testing information

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
bengizmo 2025-05-19 19:43:23 -03:00
parent 5bcd8a48a8
commit 461304e9f6
3 changed files with 347 additions and 2 deletions

View file

@ -6,16 +6,35 @@ export class DashboardPage extends BasePage {
private readonly viewProfileButton = 'a:has-text("View Profile")';
private readonly logoutButton = 'a:has-text("Logout")';
private readonly eventsTable = 'table';
private readonly statsSection = '.hvac-stats-grid';
// Updated Stats row layout selectors
private readonly statsSection = '.hvac-stats-row';
private readonly statsColumns = '.hvac-stat-col';
private readonly totalEventsCard = '.hvac-stat-card:has-text("Total Events")';
private readonly upcomingEventsCard = '.hvac-stat-card:has-text("Upcoming Events")';
private readonly pastEventsCard = '.hvac-stat-card:has-text("Past Events")';
private readonly totalRevenueCard = '.hvac-stat-card:has-text("Total Revenue")';
// Event filters selectors
private readonly filterButtons = '.hvac-event-filters a';
private readonly allFilterButton = '.hvac-event-filters a:has-text("All")';
private readonly publishFilterButton = '.hvac-event-filters a:has-text("Publish")';
private readonly draftFilterButton = '.hvac-event-filters a:has-text("Draft")';
private readonly pendingFilterButton = '.hvac-event-filters a:has-text("Pending")';
private readonly privateFilterButton = '.hvac-event-filters a:has-text("Private")';
private readonly activeFilterClass = 'hvac-filter-active';
private readonly loadingIndicator = '.hvac-loading';
constructor(page: Page) {
super(page);
}
async navigate(): Promise<void> {
const STAGING_URL = 'https://wordpress-974670-5399585.cloudwaysapps.com';
await this.page.goto(`${STAGING_URL}/hvac-dashboard/`);
await this.page.waitForLoadState('networkidle');
}
async navigateToDashboard(): Promise<void> {
await this.navigate('/hvac-dashboard/');
}
@ -49,10 +68,166 @@ export class DashboardPage extends BasePage {
};
}
/**
* Get the number of stat columns in the row layout
*/
async getStatsColumnCount(): Promise<number> {
return await this.page.locator(this.statsColumns).count();
}
/**
* Check if the stats are displayed in a row layout
*/
async areStatsInRowLayout(): Promise<boolean> {
// First check if the row container exists
const statsRow = await this.isVisible(this.statsSection);
if (!statsRow) return false;
// Get columns
const columns = await this.page.locator(this.statsColumns);
const count = await columns.count();
// Need at least 2 columns to verify row layout
if (count < 2) return false;
// Get bounding boxes to verify horizontal layout
const box1 = await columns.nth(0).boundingBox();
const box2 = await columns.nth(1).boundingBox();
if (!box1 || !box2) return false;
// In a row layout, the second column should be to the right of the first
// (This may not be true on very narrow screens where they would wrap)
return box2.x > box1.x;
}
async isEventsTableVisible(): Promise<boolean> {
return await this.isVisible(this.eventsTable);
}
/**
* Get the count of filter buttons
*/
async getFilterButtonCount(): Promise<number> {
return await this.page.locator(this.filterButtons).count();
}
/**
* Get the currently active filter
*/
async getActiveFilter(): Promise<string> {
const activeFilter = await this.page.locator(`${this.filterButtons}.${this.activeFilterClass}`);
if (await activeFilter.count() === 0) {
return 'All'; // Default filter is All
}
return (await activeFilter.textContent() || '').trim();
}
/**
* Filter events by status
* @param status The filter status: 'All', 'Publish', 'Draft', 'Pending', or 'Private'
*/
async filterEvents(status: 'All' | 'Publish' | 'Draft' | 'Pending' | 'Private'): Promise<void> {
this.verbosity.log(`Filtering events by status: ${status}`);
// Get the appropriate selector based on status
let selector: string;
switch (status) {
case 'All':
selector = this.allFilterButton;
break;
case 'Publish':
selector = this.publishFilterButton;
break;
case 'Draft':
selector = this.draftFilterButton;
break;
case 'Pending':
selector = this.pendingFilterButton;
break;
case 'Private':
selector = this.privateFilterButton;
break;
default:
throw new Error(`Invalid filter status: ${status}`);
}
// Click the filter button
await this.click(selector);
// Wait for the loading indicator to disappear if it appears
const loadingElement = this.page.locator(this.loadingIndicator);
if (await loadingElement.isVisible()) {
await loadingElement.waitFor({ state: 'hidden', timeout: 5000 });
}
// Wait a moment for the AJAX content to update
await this.page.waitForTimeout(500);
}
/**
* Check if filter status appears in URL
*/
async doesUrlContainFilterStatus(status: string): Promise<boolean> {
const url = await this.getUrl();
// 'All' filter should not have event_status in URL
if (status.toLowerCase() === 'all') {
return !url.includes('event_status=');
}
// Other filters should have event_status=status in URL
return url.includes(`event_status=${status.toLowerCase()}`);
}
/**
* Check if table has been filtered by the given status
* If status is 'All', it checks if the table exists and has any events
* Otherwise it verifies that all visible events have the expected status
*/
async isTableFilteredByStatus(status: 'All' | 'Publish' | 'Draft' | 'Pending' | 'Private'): Promise<boolean> {
// First check if the table exists
if (!await this.isEventsTableVisible()) {
return false;
}
// If status is 'All', just check that the table exists
if (status === 'All') {
return true;
}
// Get all event rows
const rows = await this.page.locator(`${this.eventsTable} tbody tr`);
const count = await rows.count();
// If no events, can't verify status
if (count === 0) {
return false;
}
// Check for "No events found" message
if (count === 1) {
const firstRowText = await rows.first().textContent() || '';
if (firstRowText.includes('No events found')) {
// This is acceptable if we've filtered to a status with no events
return true;
}
}
// For each row, check if the status column matches the expected status
for (let i = 0; i < count; i++) {
const rowData = await this.getEventRowData(i);
// If the status doesn't match, the filter isn't working correctly
if (rowData.status.toLowerCase() !== status.toLowerCase()) {
return false;
}
}
// All rows have the expected status
return true;
}
async getEventRowData(index: number): Promise<{
status: string;
name: string;

View file

@ -13,8 +13,14 @@ export class LoginPage extends BasePage {
super(page);
}
async navigate(): Promise<void> {
const STAGING_URL = 'https://wordpress-974670-5399585.cloudwaysapps.com';
await this.page.goto(`${STAGING_URL}/community-login/`);
await this.page.waitForLoadState('networkidle');
}
async navigateToLogin(): Promise<void> {
await this.navigate('/community-login/');
await this.navigate();
}
async login(username: string, password: string, rememberMe: boolean = false): Promise<void> {

View file

@ -0,0 +1,164 @@
# Dashboard UI & UX Improvements
This document details the improvements made to the Trainer Dashboard UI and UX functionality.
## Summary of Improvements
1. **Stats Section Layout Enhancement**
- Changed from column layout to row layout
- Improved visual balance and space utilization
- Responsive design that adapts to different screen sizes
2. **Dynamic Event Filtering**
- Added AJAX-based filtering without page reload
- Improved user experience when filtering events
- Added loading indicators for better feedback
- Maintained URL parameters for direct linking to filtered views
## Technical Implementation
### Row Layout for Stats Section
The stats section previously used a column-based grid layout that did not effectively utilize horizontal space. The updated design:
- Uses flexbox row layout for better horizontal distribution
- Maintains consistent card height and spacing
- Scales appropriately on different screen sizes
- Wraps to multiple rows on mobile devices
```css
.hvac-stats-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: -10px;
justify-content: space-between;
align-items: stretch;
}
.hvac-stat-col {
flex: 1;
min-width: 160px;
padding: 10px;
}
```
### Dynamic Event Filtering
Events table filtering previously required a full page reload when changing filters. The new implementation:
1. Uses JavaScript to intercept filter button clicks
2. Makes AJAX requests to the server for filtered data
3. Updates the table DOM with the new data
4. Updates the URL using the History API for bookmarking
```javascript
// On filter button click
$('.hvac-event-filters a').on('click', function(e) {
e.preventDefault();
// Get filter status from data attribute
const status = $(this).data('status');
// Display loading indicator
$('.hvac-events-table-wrapper').append('<div class="hvac-loading">Filtering events...</div>');
// 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) {
// Update table with filtered data
$('.hvac-events-table-wrapper').html(response.data.html);
// Update URL for bookmarking
updateUrl(status);
}
});
});
```
### Server-Side Handler
A PHP handler was implemented to process AJAX requests:
```php
add_action('wp_ajax_hvac_filter_events', 'hvac_filter_events_handler');
function hvac_filter_events_handler() {
// Verify nonce and user permissions
// Get filtered events data
$events = get_filtered_events(get_current_user_id(), $_POST['status']);
// Generate HTML for response
$html = generate_events_table_html($events);
// Send JSON response
wp_send_json_success(['html' => $html]);
}
```
## Testing
### Automated Tests
The dashboard improvements are verified by Playwright E2E tests that confirm:
1. Stats are displayed in a row layout
2. Filter buttons update the event table dynamically
3. URL parameters are updated correctly
4. All filter statuses work as expected
```typescript
// Test stats row layout
test('Stats section should display in a row layout', async ({ page }) => {
await expect(page.locator('.hvac-stats-row')).toBeVisible();
const columnCount = await page.locator('.hvac-stat-col').count();
expect(columnCount).toBeGreaterThanOrEqual(4);
});
// Test dynamic filtering
test('Event filters should dynamically update events table without page reload', async ({ page }) => {
await page.click('a:has-text("Draft")');
// Check filter is working without reload
// ...
});
```
### Manual Testing
Manual testing verified:
- Visual appearance on multiple screen sizes
- Smooth interaction when filtering
- No flicker during table updates
- Proper loading indicators
- Browser back button functionality
## Benefits
1. **Improved User Experience**
- No page reloads when filtering events
- Better visual organization of statistics
- More responsive interface
2. **Performance Improvements**
- Reduced server load from fewer full page requests
- Faster filtering operations
- Only the necessary data is transferred
3. **Maintainability**
- Better separation of concerns (PHP/JS)
- More maintainable CSS using flexbox
- Unit tests to prevent regressions
## Future Improvements
1. Adding animation transitions for filter changes
2. Implementing server-side caching for filtered data
3. Adding sort functionality to the events table