feat: Add Event Summary Page functionality

- Add event-summary page to required_pages array in main plugin file
- Update render_event_summary() method to handle event ID from URL
- Update template_include filter to load custom event summary template
- Update dashboard event links to point to new event summary page
- Create comprehensive event summary template with statistics and attendee info
- Add E2E tests for Event Summary Page
- Add documentation for Event Summary functionality

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
bengizmo 2025-05-20 08:23:40 -03:00
parent e59c2e5ccc
commit 1a563f3133
6 changed files with 732 additions and 5 deletions

View file

@ -0,0 +1,171 @@
import { test, expect } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';
import { DashboardPage } from './pages/DashboardPage';
test.describe('Event Summary Page', () => {
// Define test variables
const testTrainerUsername = 'test_trainer';
const testTrainerPassword = 'Test123!';
// We'll need to figure out a test event ID during the test
let testEventId: string;
test.beforeEach(async ({ page }) => {
// Log in before each test
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login(testTrainerUsername, testTrainerPassword);
// Navigate to dashboard to find an event to use for testing
const dashboardPage = new DashboardPage(page);
await dashboardPage.navigate();
// Find the first event in the dashboard and get its ID
// We'll extract the event ID from the "Summary" link's href
const summaryLink = page.locator('.column-actions a:has-text("Summary")').first();
// Check if there's at least one event
const linkCount = await summaryLink.count();
if (linkCount === 0) {
// No events available for testing, we'll need to skip the tests
test.skip(true, 'No events available for testing');
return;
}
// Get the href attribute from the summary link
const summaryUrl = await summaryLink.getAttribute('href');
// Extract the event ID from the URL
if (summaryUrl) {
const match = summaryUrl.match(/event_id=(\d+)/);
if (match && match[1]) {
testEventId = match[1];
} else {
test.skip(true, 'Could not extract event ID from summary link');
}
} else {
test.skip(true, 'Summary link has no href attribute');
}
});
test('should be accessible from dashboard', async ({ page }) => {
// Navigate to dashboard
const dashboardPage = new DashboardPage(page);
await dashboardPage.navigate();
// Find and click the first summary link
const summaryLink = page.locator('.column-actions a:has-text("Summary")').first();
await expect(summaryLink).toBeVisible();
await summaryLink.click();
// Verify we're on the event summary page
await expect(page).toHaveURL(/.*\/event-summary\/.*event_id=.*/);
await expect(page.locator('h1')).toContainText('Summary');
});
test('should display event overview information', async ({ page }) => {
// Navigate directly to the event summary page
await page.goto(`/event-summary/?event_id=${testEventId}`);
// Check for event overview section
await expect(page.locator('h2:has-text("Event Overview")')).toBeVisible();
// Check for basic event details
const detailsTable = page.locator('.hvac-details-table');
await expect(detailsTable).toBeVisible();
// Check for specific fields
const dateRow = detailsTable.locator('tr', { hasText: 'Date & Time' });
const statusRow = detailsTable.locator('tr', { hasText: 'Status' });
await expect(dateRow).toBeVisible();
await expect(statusRow).toBeVisible();
});
test('should display event statistics', async ({ page }) => {
// Navigate directly to the event summary page
await page.goto(`/event-summary/?event_id=${testEventId}`);
// Check for statistics section
await expect(page.locator('h2:has-text("Event Statistics")')).toBeVisible();
// Check for statistics cards
const statsRow = page.locator('.hvac-stats-row');
await expect(statsRow).toBeVisible();
// Look for specific metric cards
const totalTicketsCard = page.locator('.hvac-stat-card h3:has-text("Total Tickets")');
const totalRevenueCard = page.locator('.hvac-stat-card h3:has-text("Total Revenue")');
await expect(totalTicketsCard).toBeVisible();
await expect(totalRevenueCard).toBeVisible();
});
test('should display ticket sales and attendees information', async ({ page }) => {
// Navigate directly to the event summary page
await page.goto(`/event-summary/?event_id=${testEventId}`);
// Check for ticket sales section
await expect(page.locator('h2:has-text("Ticket Sales & Attendees")')).toBeVisible();
// The table might not be visible if there are no attendees, so check for either
// the table or the "No ticket sales" message
const attendeesTable = page.locator('.hvac-transactions-table');
const noAttendeesMessage = page.locator('text=No ticket sales or attendees found');
const hasTable = await attendeesTable.count() > 0;
if (hasTable) {
// Check table headers
const attendeeHeader = attendeesTable.locator('th', { hasText: 'Attendee' });
const emailHeader = attendeesTable.locator('th', { hasText: 'Email' });
const ticketTypeHeader = attendeesTable.locator('th', { hasText: 'Ticket Type' });
await expect(attendeeHeader).toBeVisible();
await expect(emailHeader).toBeVisible();
await expect(ticketTypeHeader).toBeVisible();
} else {
// Check for no attendees message
await expect(noAttendeesMessage).toBeVisible();
}
});
test('should display event description', async ({ page }) => {
// Navigate directly to the event summary page
await page.goto(`/event-summary/?event_id=${testEventId}`);
// Check for event description section
await expect(page.locator('h2:has-text("Event Description")')).toBeVisible();
// Check for description content
const descriptionContainer = page.locator('.hvac-event-description');
await expect(descriptionContainer).toBeVisible();
});
test('should have working navigation links', async ({ page }) => {
// Navigate directly to the event summary page
await page.goto(`/event-summary/?event_id=${testEventId}`);
// Check for dashboard navigation link
const dashboardLink = page.locator('a[href*="/hvac-dashboard/"]');
await expect(dashboardLink).toBeVisible();
// Check for edit event link (may not be visible if user doesn't have permission)
const editEventLink = page.locator('a[href*="/manage-event/"]');
// Check for view public page link
const viewPublicLink = page.locator('a[href*="/event/"]');
await expect(viewPublicLink).toBeVisible();
});
test('should redirect to login page when not logged in', async ({ page }) => {
// Log out first
await page.goto('/wp-login.php?action=logout');
await page.waitForURL(/.*\/community-login.*/);
// Try to access event summary page directly
await page.goto(`/event-summary/?event_id=${testEventId}`);
// Should redirect to login page
await expect(page).toHaveURL(/.*\/community-login.*/);
});
});

View file

@ -0,0 +1,102 @@
# Event Summary Functionality
## Overview
The Event Summary page provides trainers with detailed information about their events, including ticket sales, attendee data, and revenue tracking. This page is designed to be a central hub for monitoring event performance and managing attendees.
## Access & Navigation
- **Access Path**: The Event Summary page can be accessed from the Trainer Dashboard by clicking the "Summary" link next to any event in the events table.
- **URL Structure**: `/event-summary/?event_id={id}` where `{id}` is the event post ID.
- **Authentication**: Only logged-in users with appropriate permissions (event creators or administrators) can access this page.
## Page Components
### 1. Header & Navigation
The header section contains:
- The event title with "Summary" suffix
- Navigation links to:
- Dashboard
- Edit Event (if user has permission)
- View Public Page (opens in new tab)
- Email Attendees (Phase 2 feature, currently a placeholder)
### 2. Event Overview
This section provides basic event information including:
- Date & Time (start and end)
- Event Status (Published, Draft, etc.)
- Cost
- Venue information (if available)
- Organizer details (if available)
### 3. Event Statistics
A visual representation of key metrics:
- Total Tickets Sold
- Total Revenue
- Ticket Type Distribution (with count and revenue for each type)
Statistics are displayed in card format for easy scanning, with the same visual style as the dashboard stats.
### 4. Ticket Sales & Attendees
A comprehensive table showing all attendee information:
- Attendee name
- Email address
- Ticket type
- Price paid
- Order ID
- Check-in status
This table provides a complete overview of all registrations and can be used for attendee management.
### 5. Event Description
The full event description is displayed for reference.
## Technical Implementation
### Files & Classes
- **Template**: `templates/template-event-summary.php`
- **Data Handler**: `includes/community/class-event-summary-data.php`
- **Shortcode**: `[hvac_event_summary]` (registered in main plugin class)
- **Styling**: Inline CSS in the template (consistent with dashboard styling)
### Data Flow
1. The `render_event_summary()` method in the main plugin class:
- Retrieves the event ID from the URL parameter
- Verifies user permissions
- Includes the event summary template
2. The template:
- Initializes the `HVAC_Event_Summary_Data` class with the event ID
- Retrieves event details, venue info, organizer info, and transaction data
- Calculates statistics from the transaction data
- Renders the UI with the retrieved data
3. The `HVAC_Event_Summary_Data` class:
- Provides methods to retrieve all necessary event data
- Integrates with The Events Calendar API for event details
- Integrates with Event Tickets for transaction data
## Testing
The Event Summary page is verified by E2E tests in `tests/e2e/event-summary.spec.ts`. The tests cover:
- Page accessibility from the dashboard
- Display of event overview information
- Display of event statistics
- Display of ticket sales and attendees data
- Display of event description
- Proper navigation links
- Authentication requirements
## Future Enhancements (Planned)
- **Phase 2**: Integration with the Email Attendees feature
- **Phase 2**: CSV export of attendee data
- **Phase 3**: Enhanced visualization of event statistics
- **Phase 3**: Integration with certificate generation

View file

@ -59,6 +59,10 @@ function hvac_ce_create_required_pages() {
'title' => 'Trainer Profile', 'title' => 'Trainer Profile',
'content' => '<!-- wp:shortcode -->[hvac_trainer_profile]<!-- /wp:shortcode -->', 'content' => '<!-- wp:shortcode -->[hvac_trainer_profile]<!-- /wp:shortcode -->',
], ],
'event-summary' => [ // Add event summary page
'title' => 'Event Summary',
'content' => '<!-- wp:shortcode -->[hvac_event_summary]<!-- /wp:shortcode -->',
],
// REMOVED: 'submit-event' page creation. Will link to default TEC CE page. // REMOVED: 'submit-event' page creation. Will link to default TEC CE page.
// 'submit-event' => [ // 'submit-event' => [
// 'title' => 'Submit Event', // 'title' => 'Submit Event',

View file

@ -241,8 +241,34 @@ class HVAC_Community_Events {
* Render event summary content * Render event summary content
*/ */
public function render_event_summary() { public function render_event_summary() {
// This can be used to display custom event summary content // Check if user is logged in
return '<div class="hvac-event-summary">Event Summary Content Here</div>'; if (!is_user_logged_in()) {
return '<p>Please log in to view the event summary.</p>';
}
// Get event ID from URL parameter
$event_id = isset($_GET['event_id']) ? absint($_GET['event_id']) : 0;
if ($event_id <= 0) {
return '<div class="hvac-error">No event ID provided. Please access this page from your dashboard.</div>';
}
// Check if the event exists and user has permission to view it
$event = get_post($event_id);
if (!$event || get_post_type($event) !== Tribe__Events__Main::POSTTYPE) {
return '<div class="hvac-error">Event not found or invalid.</div>';
}
// Check if the current user has permission to view this event
// For now, we'll check if they're the post author or have edit_posts capability
if ($event->post_author != get_current_user_id() && !current_user_can('edit_posts')) {
return '<div class="hvac-error">You do not have permission to view this event summary.</div>';
}
// Include the event summary template
ob_start();
include HVAC_CE_PLUGIN_DIR . 'templates/event-summary/template-event-summary.php';
return ob_get_clean();
} }
/** /**
@ -295,6 +321,14 @@ class HVAC_Community_Events {
} }
} }
// Check for event-summary page
if (is_page('event-summary')) {
$custom_template = HVAC_CE_PLUGIN_DIR . 'templates/template-event-summary.php';
if (file_exists($custom_template)) {
return $custom_template;
}
}
// Check for single event view (temporary) // Check for single event view (temporary)
if ( is_singular( 'tribe_events' ) ) { if ( is_singular( 'tribe_events' ) ) {
$custom_template = HVAC_CE_PLUGIN_DIR . 'templates/single-tribe_events.php'; $custom_template = HVAC_CE_PLUGIN_DIR . 'templates/single-tribe_events.php';

View file

@ -0,0 +1,413 @@
<?php
/**
* Template Name: HVAC Event Summary
*
* This template handles the display of the HVAC Event Summary page.
* It shows detailed information about a specific event, including ticket sales,
* attendee information, and revenue tracking.
*
* @package HVAC Community Events
* @subpackage Templates
* @author HVAC Community Events
* @version 1.0.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Get the event ID from the URL parameter
$event_id = isset( $_GET['event_id'] ) ? absint( $_GET['event_id'] ) : 0;
// Ensure the data class is available
if ( ! class_exists( 'HVAC_Event_Summary_Data' ) ) {
// Attempt to include it if not loaded
$class_path = plugin_dir_path( __FILE__ ) . '../includes/community/class-event-summary-data.php';
if ( file_exists( $class_path ) ) {
require_once $class_path;
} else {
// Handle error: Class not found, cannot display summary
echo "<p>Error: Event Summary data handler not found.</p>";
return;
}
}
// Initialize the event summary data handler
$summary_data_handler = new HVAC_Event_Summary_Data( $event_id );
// Check if the event is valid
if ( ! $summary_data_handler->is_valid_event() ) {
// Redirect to dashboard if the event doesn't exist or user doesn't have permission
wp_safe_redirect( home_url( '/hvac-dashboard/' ) );
exit;
}
// Fetch all the required event data
$event_details = $summary_data_handler->get_event_details();
$venue_details = $summary_data_handler->get_event_venue_details();
$organizer_details = $summary_data_handler->get_event_organizer_details();
$transactions = $summary_data_handler->get_event_transactions();
// Calculate ticket sales summary data
$total_tickets = 0;
$total_revenue = 0;
$ticket_types = array();
// Process transactions data
if ( ! empty( $transactions ) ) {
foreach ( $transactions as $txn ) {
$total_tickets++;
if ( isset( $txn['price'] ) ) {
$total_revenue += floatval( $txn['price'] );
}
// Count ticket types
$ticket_type = $txn['ticket_type_name'] ?? 'Unknown';
if ( isset( $ticket_types[$ticket_type] ) ) {
$ticket_types[$ticket_type]['count']++;
if ( isset( $txn['price'] ) ) {
$ticket_types[$ticket_type]['revenue'] += floatval( $txn['price'] );
}
} else {
$ticket_types[$ticket_type] = array(
'count' => 1,
'revenue' => isset( $txn['price'] ) ? floatval( $txn['price'] ) : 0,
);
}
}
}
// Start the template
get_header();
?>
<div id="primary" class="content-area primary ast-container">
<main id="main" class="site-main">
<!-- Event Summary Header & Navigation -->
<div class="hvac-dashboard-header">
<h1 class="entry-title"><?php echo esc_html( $event_details['title'] ); ?> - Summary</h1>
<div class="hvac-dashboard-nav">
<a href="<?php echo esc_url( home_url( '/hvac-dashboard/' ) ); ?>" class="ast-button ast-button-primary">Dashboard</a>
<?php
// Edit event link (if user has permission)
if ( current_user_can( 'edit_post', $event_id ) ) {
$edit_url = add_query_arg( 'event_id', $event_id, home_url( '/manage-event/' ) );
echo '<a href="' . esc_url( $edit_url ) . '" class="ast-button ast-button-primary">Edit Event</a>';
}
// View public event page
echo '<a href="' . esc_url( $event_details['permalink'] ) . '" class="ast-button ast-button-secondary" target="_blank">View Public Page</a>';
// Email attendees link (future feature)
if ( current_user_can( 'edit_post', $event_id ) ) {
// TODO: Link to actual Email Attendees page when implemented (Phase 2)
$email_url = '#'; // Placeholder for now
echo '<a href="' . esc_url( $email_url ) . '" class="ast-button ast-button-secondary">Email Attendees</a>';
}
?>
</div>
</div>
<!-- Event Overview Section -->
<section class="hvac-event-summary-section">
<h2>Event Overview</h2>
<div class="hvac-event-summary-content">
<!-- Event Details -->
<div class="hvac-event-details">
<table class="hvac-details-table">
<tr>
<th>Date & Time:</th>
<td>
<?php
if ( function_exists( 'tribe_get_start_date' ) && function_exists( 'tribe_get_end_date' ) ) {
echo esc_html( tribe_get_start_date( $event_id, false ) );
if ( ! $event_details['is_all_day'] ) {
echo ' @ ' . esc_html( tribe_get_start_date( $event_id, false, 'g:i a' ) );
}
// Show end date/time if different from start date
$start_date = tribe_get_start_date( $event_id, false, 'Y-m-d' );
$end_date = tribe_get_end_date( $event_id, false, 'Y-m-d' );
if ( $start_date !== $end_date ) {
echo ' - ' . esc_html( tribe_get_end_date( $event_id, false ) );
if ( ! $event_details['is_all_day'] ) {
echo ' @ ' . esc_html( tribe_get_end_date( $event_id, false, 'g:i a' ) );
}
} elseif ( ! $event_details['is_all_day'] ) {
echo ' - ' . esc_html( tribe_get_end_date( $event_id, false, 'g:i a' ) );
}
} else {
echo esc_html( $event_details['start_date'] ?? 'N/A' );
echo ' - ';
echo esc_html( $event_details['end_date'] ?? 'N/A' );
}
?>
</td>
</tr>
<tr>
<th>Status:</th>
<td><?php echo esc_html( ucfirst( get_post_status( $event_id ) ) ); ?></td>
</tr>
<tr>
<th>Cost:</th>
<td><?php echo esc_html( $event_details['cost'] ?? 'N/A' ); ?></td>
</tr>
<?php if ( $venue_details && ! empty( $venue_details['name'] ) ) : ?>
<tr>
<th>Venue:</th>
<td>
<?php echo esc_html( $venue_details['name'] ); ?>
<?php if ( ! empty( $venue_details['address'] ) ) : ?>
<div class="hvac-detail-subtext"><?php echo esc_html( $venue_details['address'] ); ?></div>
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
<?php if ( $organizer_details && ! empty( $organizer_details['name'] ) ) : ?>
<tr>
<th>Organizer:</th>
<td>
<?php echo esc_html( $organizer_details['name'] ); ?>
<?php if ( ! empty( $organizer_details['email'] ) ) : ?>
<div class="hvac-detail-subtext"><?php echo esc_html( $organizer_details['email'] ); ?></div>
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
</table>
</div>
</div>
</section>
<!-- Event Statistics Section -->
<section class="hvac-event-summary-section">
<h2>Event Statistics</h2>
<div class="hvac-stats-row">
<!-- Total Tickets Stat Card -->
<div class="hvac-stat-col">
<div class="hvac-stat-card">
<h3>Total Tickets</h3>
<p class="metric-value"><?php echo esc_html( $total_tickets ); ?></p>
</div>
</div>
<!-- Total Revenue 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( $total_revenue, 2 ) ); ?></p>
</div>
</div>
<!-- Ticket Types / Distribution -->
<?php foreach ( $ticket_types as $type => $data ) : ?>
<div class="hvac-stat-col">
<div class="hvac-stat-card">
<h3><?php echo esc_html( $type ); ?></h3>
<p class="metric-value"><?php echo esc_html( $data['count'] ); ?></p>
<small>$<?php echo esc_html( number_format( $data['revenue'], 2 ) ); ?></small>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<!-- Ticket Sales / Attendees Section -->
<section class="hvac-event-summary-section">
<h2>Ticket Sales &amp; Attendees</h2>
<?php if ( ! empty( $transactions ) ) : ?>
<div class="hvac-event-summary-content">
<table class="hvac-transactions-table">
<thead>
<tr>
<th>Attendee</th>
<th>Email</th>
<th>Ticket Type</th>
<th>Price</th>
<th>Order ID</th>
<th>Checked In</th>
</tr>
</thead>
<tbody>
<?php foreach ( $transactions as $txn ) : ?>
<tr>
<td><?php echo esc_html( $txn['purchaser_name'] ?? 'N/A' ); ?></td>
<td><?php echo esc_html( $txn['purchaser_email'] ?? 'N/A' ); ?></td>
<td><?php echo esc_html( $txn['ticket_type_name'] ?? 'N/A' ); ?></td>
<td>$<?php echo esc_html( number_format( $txn['price'] ?? 0, 2 ) ); ?></td>
<td><?php echo esc_html( $txn['order_id'] ?? 'N/A' ); ?></td>
<td><?php echo $txn['checked_in'] ? 'Yes' : 'No'; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else : ?>
<p>No ticket sales or attendees found for this event.</p>
<?php endif; ?>
</section>
<!-- Event Description Section -->
<section class="hvac-event-summary-section">
<h2>Event Description</h2>
<div class="hvac-event-summary-content">
<div class="hvac-event-description">
<?php echo wp_kses_post( $event_details['description'] ); ?>
</div>
</div>
</section>
</main>
</div>
<!-- Include CSS for the Event Summary page -->
<style>
/* Event Summary Specific Styles */
.hvac-event-summary-section {
margin-bottom: 40px;
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
border: 1px solid #e9ecef;
}
.hvac-event-summary-section h2 {
margin-top: 0;
margin-bottom: 20px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.hvac-event-summary-content {
margin-top: 20px;
}
/* Details Table */
.hvac-details-table {
width: 100%;
border-collapse: collapse;
}
.hvac-details-table th,
.hvac-details-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #eee;
vertical-align: top;
}
.hvac-details-table th {
width: 150px;
font-weight: bold;
}
.hvac-detail-subtext {
font-size: 0.9em;
color: #666;
margin-top: 5px;
}
/* Transactions Table */
.hvac-transactions-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.hvac-transactions-table th,
.hvac-transactions-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #eee;
}
.hvac-transactions-table th {
background-color: #f1f1f1;
font-weight: bold;
}
.hvac-transactions-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.hvac-transactions-table tr:hover {
background-color: #f0f0f0;
}
/* Stats Row (reused from dashboard) */
.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;
}
.hvac-stat-card {
border: 1px solid #eee;
padding: 15px;
background: #fff;
text-align: center;
width: 100%;
flex-grow: 1;
height: 100%;
}
.hvac-stat-card h3 {
margin: 0 0 10px;
font-size: 16px;
font-weight: normal;
color: #666;
}
.hvac-stat-card .metric-value {
font-size: 32px;
font-weight: bold;
color: #E9AF28;
margin: 0;
}
.hvac-stat-card small {
display: block;
margin-top: 5px;
color: #666;
}
@media (max-width: 768px) {
.hvac-dashboard-header {
flex-direction: column;
align-items: flex-start;
}
.hvac-dashboard-nav {
margin-top: 15px;
display: flex;
flex-wrap: wrap;
}
.hvac-dashboard-nav a {
margin: 5px 5px 5px 0;
}
.hvac-details-table th {
width: 100px;
}
.hvac-transactions-table {
display: block;
overflow-x: auto;
}
}
</style>
<?php
get_footer();
?>

View file

@ -186,11 +186,14 @@ get_header(); // Use theme's header
<?php <?php
// Link to the new page containing the TEC CE submission form shortcode // Link to the new page containing the TEC CE submission form shortcode
$edit_url = add_query_arg( 'event_id', $event['id'], home_url( '/manage-event/' ) ); $edit_url = add_query_arg( 'event_id', $event['id'], home_url( '/manage-event/' ) );
// Link to the standard WP single event view (handled by our custom template) // Link to the custom event summary page
$summary_url = get_permalink( $event['id'] ); $summary_url = add_query_arg( 'event_id', $event['id'], home_url( '/event-summary/' ) );
// Link to the standard WP single event view
$view_url = get_permalink( $event['id'] );
?> ?>
<a href="<?php echo esc_url( $edit_url ); ?>">Edit</a> | <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> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>