feat: complete Phase 2B template system enhancements
✨ Enhanced Template Selector: - Grouped templates by category with descriptions - Template preview modal with field data display - Apply template functionality with AJAX loading - Enhanced UI with preview icons and better UX ✨ Save as Template Functionality: - Complete save template dialog with validation - Template name, description, and category fields - Public/private template sharing options - AJAX integration with error handling and success feedback ✨ Progressive Disclosure: - Advanced options toggle with smooth animations - Fields marked as advanced (capacity, cost, timezone) - Local storage for user preference persistence - Staggered reveal animations for better UX ✨ Enhanced Auto-save: - Intelligent auto-save with field-type specific delays - Draft recovery with age information and user confirmation - Error handling with fallback to essential fields only - Visual feedback with status indicator and animations - Auto-save on page visibility change ✨ AJAX Infrastructure: - Template preview handler (hvac_get_template_preview) - Template loading handler (hvac_load_template_data) - Template saving handler (hvac_save_template) - Comprehensive error handling and security validation 🎨 UI/UX Enhancements: - Modern modal dialogs with backdrop overlays - Responsive design for mobile devices - Smooth animations and transitions - Status indicators with rotating save icons - Comprehensive styling for all new components 🚀 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ac8c09a294
commit
09a15f874c
5 changed files with 1856 additions and 54 deletions
|
|
@ -21,7 +21,10 @@
|
|||
"WebFetch(domain:upskill-staging.measurequick.com)",
|
||||
"Bash(scripts/:*)",
|
||||
"Bash(./scripts/:*)",
|
||||
"Bash(php -l:*)"
|
||||
"Bash(php -l:*)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e scp -o StrictHostKeyChecking=no templates/page-native-event-test.php roodev@146.190.76.204:/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/templates/)",
|
||||
"Bash(SSHPASS=\"uSCO6f1y\" sshpass -e scp -o StrictHostKeyChecking=no templates/page-create-event.php roodev@146.190.76.204:/home/974670.cloudwaysapps.com/uberrxmprk/public_html/wp-content/plugins/hvac-community-events/templates/)",
|
||||
"mcp__playwright__browser_navigate"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": [],
|
||||
|
|
|
|||
492
docs/FEATURE_AI_EVENT_POPULATION.md
Normal file
492
docs/FEATURE_AI_EVENT_POPULATION.md
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
# AI-Assisted Event Submission Feature Specification
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document specifies an AI-powered feature to reduce friction for HVAC trainers submitting events to a WordPress calendar system. The solution uses a single Anthropic Claude API call to parse unstructured event information and auto-populate form fields, dramatically simplifying the submission process for non-technical users.
|
||||
|
||||
## Project Context
|
||||
|
||||
### Current State
|
||||
- **Platform**: WordPress site hosted on Cloudways
|
||||
- **Base Plugin**: The Events Calendar (TEC) by Modern Tribe
|
||||
- **Custom Plugin**: Existing custom plugin built on top of TEC for HVAC trainer event submissions
|
||||
- **User Base**: HVAC trainers who are often not tech-savvy and have limited time
|
||||
- **Problem**: High friction in manual form entry leading to incomplete or missing event submissions
|
||||
|
||||
### Technical Constraints
|
||||
- **Hosting**: Cloudways shared hosting environment
|
||||
- Limited server-side processing capabilities
|
||||
- No ability to install additional server software
|
||||
- Standard PHP/MySQL stack
|
||||
- HTTPS request capabilities
|
||||
- **Must maintain compatibility** with The Events Calendar plugin data structures
|
||||
- **Cannot add additional WordPress plugins** - solution must be implemented within existing custom plugin
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### Core Feature: AI-Assisted Event Form Population
|
||||
|
||||
#### User Flow
|
||||
1. User navigates to the create event page
|
||||
2. User sees an "Auto-populate with AI" button/option
|
||||
3. Upon clicking, a modal dialog appears with:
|
||||
- Clear instructions for the user
|
||||
- Single rich text input field
|
||||
- Submit button
|
||||
4. User provides one of three input types:
|
||||
- **Option A**: URL to a webpage containing event details
|
||||
- **Option B**: Copy/pasted text from email/document
|
||||
- **Option C**: Brief description with key details
|
||||
5. System processes input showing:
|
||||
- Loading spinner
|
||||
- Status messages during processing
|
||||
6. System populates form fields automatically
|
||||
7. System displays confidence/completeness matrix
|
||||
8. User reviews and adjusts populated fields before final submission
|
||||
|
||||
#### Input Acceptance Criteria
|
||||
|
||||
**URL Input**
|
||||
- Accept any valid HTTP/HTTPS URL
|
||||
- Handle various event page formats (EventBrite, custom sites, social media event pages)
|
||||
- Extract information even from non-standard page structures
|
||||
|
||||
**Pasted Text Input**
|
||||
- Accept unformatted text from emails, PDFs, documents
|
||||
- Handle various formatting styles and structures
|
||||
- Parse information regardless of order or format
|
||||
|
||||
**Brief Description Input**
|
||||
- Accept natural language descriptions
|
||||
- Extract partial information when complete details aren't provided
|
||||
- Make intelligent assumptions about missing data
|
||||
|
||||
#### Output Requirements
|
||||
|
||||
**Required Fields to Extract**
|
||||
- Event title
|
||||
- Date and time
|
||||
- Location/venue
|
||||
- Description
|
||||
- Organizer
|
||||
- Cost/pricing
|
||||
- Registration URL (if available)
|
||||
|
||||
**Data Validation Requirements**
|
||||
- Check against existing venues to avoid duplicates
|
||||
- Check against existing organizers to avoid duplicates
|
||||
- Validate date/time formats
|
||||
- Ensure required fields are populated
|
||||
|
||||
**Confidence Reporting**
|
||||
Display a matrix showing:
|
||||
- **Completeness**: Whether each field was found (High/Medium/Low/N/A)
|
||||
- **Confidence**: How confident the AI is in the extracted data (0.0-1.0)
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### Simplified Single-API Approach
|
||||
|
||||
#### Architecture Decision
|
||||
Use a single Anthropic Claude API call with web search capabilities enabled, eliminating the need for:
|
||||
- Separate web scraping services
|
||||
- Multiple NLP processing steps
|
||||
- Complex orchestration logic
|
||||
- Background job processing
|
||||
- Additional server infrastructure
|
||||
|
||||
#### Key Components
|
||||
|
||||
1. **Frontend (JavaScript)**
|
||||
- Modal interface for input collection
|
||||
- AJAX communication with WordPress backend
|
||||
- Form field population logic
|
||||
- Confidence matrix display
|
||||
|
||||
2. **Backend (PHP/WordPress)**
|
||||
- Single AJAX endpoint for AI processing
|
||||
- API key management
|
||||
- Request/response handling
|
||||
- Data sanitization and validation
|
||||
|
||||
3. **External Service**
|
||||
- Anthropic Claude API (claude-3-5-sonnet-20241022 or latest)
|
||||
- Web search/browsing capabilities enabled
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
#### Pattern 1: Stateless Request Processing
|
||||
- **Do**: Make each request completely self-contained
|
||||
- **Do**: Include all context needed in single prompt
|
||||
- **Do**: Return complete results in one response
|
||||
- **Don't**: Maintain conversation state between requests
|
||||
- **Don't**: Make multiple API calls for single submission
|
||||
|
||||
#### Pattern 2: Defensive Data Handling
|
||||
```php
|
||||
// Always validate and sanitize
|
||||
$user_input = sanitize_textarea_field($_POST['input'] ?? '');
|
||||
if (empty($user_input)) {
|
||||
wp_send_json_error(['message' => 'No input provided']);
|
||||
wp_die();
|
||||
}
|
||||
```
|
||||
|
||||
#### Pattern 3: Structured Output Enforcement
|
||||
- **Do**: Request JSON output explicitly in prompt
|
||||
- **Do**: Provide exact schema in prompt
|
||||
- **Do**: Include example of expected output
|
||||
- **Don't**: Parse free-text responses
|
||||
- **Don't**: Rely on consistent formatting without schema
|
||||
|
||||
#### Pattern 4: Graceful Degradation
|
||||
- **Do**: Provide fallback for API failures
|
||||
- **Do**: Allow manual form filling if AI fails
|
||||
- **Do**: Cache successful extractions
|
||||
- **Don't**: Block user progress on AI failure
|
||||
- **Don't**: Require AI for form submission
|
||||
|
||||
### Anti-Patterns to Avoid
|
||||
|
||||
#### Anti-Pattern 1: Client-Side API Keys
|
||||
**Never** expose API keys in JavaScript or make direct API calls from browser
|
||||
|
||||
#### Anti-Pattern 2: Synchronous Long-Running Requests
|
||||
**Avoid** blocking UI during API calls; always use async patterns with loading states
|
||||
|
||||
#### Anti-Pattern 3: Over-Engineering
|
||||
**Don't** build complex parsing systems when Claude can handle it in one call
|
||||
|
||||
#### Anti-Pattern 4: Rigid Field Matching
|
||||
**Don't** require exact field matches; use fuzzy matching for venues/organizers
|
||||
|
||||
## Prompt Engineering Strategy
|
||||
|
||||
### Core Prompt Structure
|
||||
|
||||
```text
|
||||
You are an AI assistant specialized in extracting event information from various sources.
|
||||
Your task is to extract structured event data for an HVAC training calendar.
|
||||
|
||||
[CONTEXT]
|
||||
- These are professional training events for HVAC technicians
|
||||
- Events may be in-person or virtual
|
||||
- Common organizers include: {list_of_existing_organizers}
|
||||
- Common venues include: {list_of_existing_venues}
|
||||
|
||||
[INPUT]
|
||||
{user_provides_one_of_these}
|
||||
- URL: Please retrieve and analyze the webpage at: {url}
|
||||
- Text: Parse the following event information: {pasted_text}
|
||||
- Description: Extract event details from: {brief_description}
|
||||
|
||||
[EXTRACTION RULES]
|
||||
1. Match venues/organizers to existing ones when similarity > 80%
|
||||
2. Infer missing information when reasonable (mark as low confidence)
|
||||
3. Handle relative dates (convert "next Tuesday" to actual date)
|
||||
4. Extract all URLs found in source material
|
||||
5. Standardize time to 24-hour format
|
||||
6. If multiple events found, extract only the first/primary one
|
||||
|
||||
[OUTPUT SCHEMA]
|
||||
Return ONLY a valid JSON object with this exact structure:
|
||||
{json_schema_here}
|
||||
|
||||
[IMPORTANT]
|
||||
- Return ONLY the JSON object, no explanatory text
|
||||
- Use null for missing fields rather than empty strings
|
||||
- Include confidence scores for each field
|
||||
```
|
||||
|
||||
### Prompt Optimization Techniques
|
||||
|
||||
#### 1. Few-Shot Examples
|
||||
Include 2-3 examples of successful extractions in the system prompt to improve consistency.
|
||||
|
||||
#### 2. Role Definition
|
||||
Clearly define Claude's role as an "event information extraction specialist" to improve focus.
|
||||
|
||||
#### 3. Explicit Constraints
|
||||
- Specify exact date format (ISO 8601)
|
||||
- Define confidence score ranges (0.0-1.0)
|
||||
- List acceptable values for enums
|
||||
|
||||
#### 4. Context Injection
|
||||
Always include:
|
||||
- Current date/time for relative date resolution
|
||||
- List of existing venues/organizers
|
||||
- Common patterns in your specific domain
|
||||
|
||||
### Handling Edge Cases
|
||||
|
||||
#### Edge Case 1: Multiple Events
|
||||
**Strategy**: Extract only the first/most prominent event
|
||||
**Prompt Addition**: "If multiple events are found, extract only the primary/first event"
|
||||
|
||||
#### Edge Case 2: Ambiguous Dates
|
||||
**Strategy**: Request current date context
|
||||
**Implementation**: Always include `Today is {current_date}` in prompt
|
||||
|
||||
#### Edge Case 3: Missing Critical Information
|
||||
**Strategy**: Return partial data with low confidence
|
||||
**Validation**: Check for minimum required fields before population
|
||||
|
||||
## Input/Output Validation
|
||||
|
||||
### Input Validation Strategy
|
||||
|
||||
#### Pre-Processing Validation
|
||||
```javascript
|
||||
function validateInput(input) {
|
||||
// Check for minimum length
|
||||
if (input.length < 10) {
|
||||
return { valid: false, error: 'Input too short' };
|
||||
}
|
||||
|
||||
// Detect input type
|
||||
const type = detectInputType(input);
|
||||
|
||||
// URL validation
|
||||
if (type === 'url') {
|
||||
try {
|
||||
new URL(input);
|
||||
} catch {
|
||||
return { valid: false, error: 'Invalid URL format' };
|
||||
}
|
||||
}
|
||||
|
||||
// Size limits (prevent token overflow)
|
||||
if (input.length > 50000) {
|
||||
return { valid: false, error: 'Input too large' };
|
||||
}
|
||||
|
||||
return { valid: true, type };
|
||||
}
|
||||
```
|
||||
|
||||
#### Input Sanitization
|
||||
- Strip potentially harmful HTML/scripts
|
||||
- Normalize whitespace and line breaks
|
||||
- Remove non-printable characters
|
||||
- Truncate to maximum acceptable length
|
||||
|
||||
### Output Validation Strategy
|
||||
|
||||
#### Schema Validation
|
||||
```php
|
||||
function validate_ai_response($response) {
|
||||
$required_fields = ['title', 'date'];
|
||||
$schema = [
|
||||
'title' => 'string',
|
||||
'date' => 'date',
|
||||
'venue' => 'string|null',
|
||||
'confidence' => 'array',
|
||||
'completeness' => 'array'
|
||||
];
|
||||
|
||||
// Check JSON structure
|
||||
if (!is_array($response)) {
|
||||
throw new ValidationException('Invalid JSON response');
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
foreach ($required_fields as $field) {
|
||||
if (empty($response[$field])) {
|
||||
throw new ValidationException("Missing required field: {$field}");
|
||||
}
|
||||
}
|
||||
|
||||
// Type checking and sanitization
|
||||
// ... implementation details
|
||||
|
||||
return $sanitized_response;
|
||||
}
|
||||
```
|
||||
|
||||
#### Confidence Threshold Handling
|
||||
- **High Confidence (>0.8)**: Auto-populate without warning
|
||||
- **Medium Confidence (0.5-0.8)**: Populate with visual indicator
|
||||
- **Low Confidence (<0.5)**: Populate but highlight for review
|
||||
- **No Confidence (null)**: Leave field empty
|
||||
|
||||
#### Duplicate Detection
|
||||
```php
|
||||
function check_duplicate_event($event_data) {
|
||||
// Check for same date + similar title
|
||||
$similar_events = get_posts([
|
||||
'post_type' => 'tribe_events',
|
||||
'meta_key' => '_EventStartDate',
|
||||
'meta_value' => $event_data['date'],
|
||||
'meta_compare' => '='
|
||||
]);
|
||||
|
||||
foreach ($similar_events as $existing) {
|
||||
$similarity = similar_text(
|
||||
strtolower($existing->post_title),
|
||||
strtolower($event_data['title']),
|
||||
$percent
|
||||
);
|
||||
|
||||
if ($percent > 75) {
|
||||
return [
|
||||
'is_duplicate' => true,
|
||||
'existing_id' => $existing->ID,
|
||||
'similarity' => $percent
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ['is_duplicate' => false];
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Recovery
|
||||
|
||||
### API Error Handling
|
||||
|
||||
#### Rate Limiting
|
||||
- Implement exponential backoff
|
||||
- Cache successful responses for 24 hours
|
||||
- Show user-friendly message when limit reached
|
||||
|
||||
#### Network Failures
|
||||
- Timeout after 30 seconds
|
||||
- Provide option to retry
|
||||
- Fall back to manual entry
|
||||
|
||||
#### Invalid Responses
|
||||
- Log malformed responses for debugging
|
||||
- Show generic error to user
|
||||
- Provide option to try different input
|
||||
|
||||
### User Communication
|
||||
|
||||
#### Status Messages
|
||||
1. "Analyzing your input..." (0-2 seconds)
|
||||
2. "Extracting event information..." (2-10 seconds)
|
||||
3. "Validating extracted data..." (10-15 seconds)
|
||||
4. "Populating form fields..." (15+ seconds)
|
||||
|
||||
#### Error Messages
|
||||
- Be specific but not technical
|
||||
- Provide actionable next steps
|
||||
- Never expose internal errors or API keys
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### API Key Management
|
||||
- Store in wp-config.php or environment variables
|
||||
- Never commit to version control
|
||||
- Rotate keys periodically
|
||||
- Use WordPress nonce for AJAX calls
|
||||
|
||||
### Input Security
|
||||
- Sanitize all user inputs
|
||||
- Prevent SQL injection via parameterized queries
|
||||
- Escape output when displaying
|
||||
- Limit request frequency per user
|
||||
|
||||
### Data Privacy
|
||||
- Don't log sensitive event information
|
||||
- Clear temporary data after processing
|
||||
- Comply with GDPR/privacy requirements
|
||||
- Allow users to opt-out of AI features
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Caching Strategy
|
||||
- Cache AI responses for identical inputs (24-hour TTL)
|
||||
- Cache venue/organizer lists (1-hour TTL)
|
||||
- Use WordPress transients for temporary data
|
||||
|
||||
### Request Optimization
|
||||
- Minimize prompt size while maintaining clarity
|
||||
- Pre-process input to remove redundant information
|
||||
- Batch similar requests when possible
|
||||
|
||||
### Frontend Optimization
|
||||
- Lazy-load AI modal components
|
||||
- Debounce input validation
|
||||
- Use optimistic UI updates
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Scenarios
|
||||
1. **URL Input**: Test with EventBrite, Facebook Events, custom sites
|
||||
2. **Text Input**: Test with various email formats, PDF copies
|
||||
3. **Description Input**: Test with minimal and detailed descriptions
|
||||
4. **Edge Cases**: Test with non-English content, past events, recurring events
|
||||
5. **Failure Cases**: Test with invalid URLs, gibberish input, timeout scenarios
|
||||
|
||||
### Validation Testing
|
||||
- Ensure all required fields are validated
|
||||
- Test confidence score accuracy
|
||||
- Verify duplicate detection works correctly
|
||||
- Test venue/organizer matching logic
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: Core Infrastructure
|
||||
- [ ] Set up Anthropic API integration
|
||||
- [ ] Create WordPress AJAX endpoint
|
||||
- [ ] Implement basic input validation
|
||||
- [ ] Build JSON response parser
|
||||
|
||||
### Phase 2: UI Implementation
|
||||
- [ ] Create modal interface
|
||||
- [ ] Implement form field population
|
||||
- [ ] Add loading states and progress indicators
|
||||
- [ ] Build confidence matrix display
|
||||
|
||||
### Phase 3: Data Processing
|
||||
- [ ] Implement venue/organizer matching
|
||||
- [ ] Add duplicate detection
|
||||
- [ ] Create validation pipeline
|
||||
- [ ] Add error handling and recovery
|
||||
|
||||
### Phase 4: Optimization
|
||||
- [ ] Implement caching layer
|
||||
- [ ] Add request rate limiting
|
||||
- [ ] Optimize prompt for accuracy
|
||||
- [ ] Performance testing and tuning
|
||||
|
||||
### Phase 5: Polish and Testing
|
||||
- [ ] Comprehensive error messages
|
||||
- [ ] User documentation
|
||||
- [ ] Edge case handling
|
||||
- [ ] Security audit
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Technical Metrics
|
||||
- API response time < 10 seconds (95th percentile)
|
||||
- Extraction accuracy > 85% for standard inputs
|
||||
- Zero security vulnerabilities
|
||||
- 99.9% uptime for feature availability
|
||||
|
||||
### User Metrics
|
||||
- 50% reduction in time to submit events
|
||||
- 75% of submissions use AI assistance
|
||||
- < 5% error rate requiring manual intervention
|
||||
- User satisfaction score > 4/5
|
||||
|
||||
## Appendix: The Events Calendar Integration
|
||||
|
||||
### Relevant Post Types
|
||||
- `tribe_events` - Main event post type
|
||||
- `tribe_venue` - Venue post type
|
||||
- `tribe_organizer` - Organizer post type
|
||||
|
||||
### Key Meta Fields
|
||||
- `_EventStartDate` - Event start date/time
|
||||
- `_EventEndDate` - Event end date/time
|
||||
- `_EventVenueID` - Reference to venue post
|
||||
- `_EventOrganizerID` - Reference to organizer post
|
||||
- `_EventCost` - Event cost
|
||||
- `_EventURL` - Registration/info URL
|
||||
|
||||
### Integration Points
|
||||
- Use TEC's built-in functions when available
|
||||
- Respect TEC's data validation rules
|
||||
- Maintain compatibility with TEC updates
|
||||
- Leverage TEC's duplicate detection if available
|
||||
|
|
@ -190,9 +190,19 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
|||
$this->add_cost_fields();
|
||||
}
|
||||
|
||||
// Add progressive disclosure toggle
|
||||
$this->add_progressive_disclosure();
|
||||
|
||||
// Mark certain fields as advanced
|
||||
$this->mark_field_as_advanced('event_capacity')
|
||||
->mark_field_as_advanced('event_cost')
|
||||
->mark_field_as_advanced('event_timezone');
|
||||
|
||||
// Template actions if enabled
|
||||
if ($this->template_mode_enabled) {
|
||||
$this->add_template_actions();
|
||||
// Mark template actions as advanced
|
||||
$this->mark_field_as_advanced('save_as_template');
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -220,23 +230,55 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
|||
$templates = $this->template_manager->get_templates();
|
||||
}
|
||||
|
||||
// Prepare template options
|
||||
$template_options = ['0' => '-- Select a Template --'];
|
||||
// Group templates by category for enhanced UI
|
||||
$templates_by_category = [];
|
||||
foreach ($templates as $template) {
|
||||
$template_options[$template['id']] = esc_html($template['name']) .
|
||||
' (' . ucfirst($template['category']) . ')';
|
||||
$category = $template['category'] ?? 'general';
|
||||
if (!isset($templates_by_category[$category])) {
|
||||
$templates_by_category[$category] = [];
|
||||
}
|
||||
$templates_by_category[$category][] = $template;
|
||||
}
|
||||
|
||||
// Prepare enhanced template options with categories
|
||||
$template_options = ['0' => '-- Select a Template --'];
|
||||
foreach ($templates_by_category as $category => $category_templates) {
|
||||
$template_options['optgroup_' . $category] = [
|
||||
'label' => ucfirst($category) . ' Templates',
|
||||
'options' => []
|
||||
];
|
||||
|
||||
foreach ($category_templates as $template) {
|
||||
$template_options['optgroup_' . $category]['options'][$template['id']] =
|
||||
esc_html($template['name']) .
|
||||
(!empty($template['description']) ? ' - ' . wp_trim_words($template['description'], 8) : '');
|
||||
}
|
||||
}
|
||||
|
||||
$template_field = array_merge($this->event_field_defaults['template-selector'], [
|
||||
'name' => 'event_template',
|
||||
'label' => 'Use Template',
|
||||
'options' => $template_options,
|
||||
'description' => 'Select a template to pre-fill form fields',
|
||||
'wrapper_class' => 'form-row template-selector-row',
|
||||
'description' => 'Select a template to pre-fill form fields. Templates are organized by category.',
|
||||
'wrapper_class' => 'form-row template-selector-row enhanced-selector',
|
||||
'data_attributes' => [
|
||||
'templates' => json_encode($templates),
|
||||
'enable_preview' => 'true'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->add_field($template_field);
|
||||
|
||||
// Add template preview area
|
||||
$preview_field = [
|
||||
'type' => 'custom',
|
||||
'name' => 'template_preview',
|
||||
'custom_html' => $this->render_template_preview_area(),
|
||||
'wrapper_class' => 'form-row template-preview-row',
|
||||
];
|
||||
|
||||
$this->add_field($preview_field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -401,14 +443,73 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
|||
'value' => 'Save as Template',
|
||||
'class' => 'button button-secondary hvac-save-template',
|
||||
'wrapper_class' => 'form-row template-actions',
|
||||
'onclick' => 'hvacSaveAsTemplate(event)',
|
||||
'onclick' => 'hvacShowSaveTemplateDialog(event)',
|
||||
];
|
||||
|
||||
$this->add_field($save_template_field);
|
||||
|
||||
// Add save template dialog
|
||||
$save_dialog_field = [
|
||||
'type' => 'custom',
|
||||
'name' => 'save_template_dialog',
|
||||
'custom_html' => $this->render_save_template_dialog(),
|
||||
'wrapper_class' => 'form-row template-dialog-row',
|
||||
];
|
||||
|
||||
$this->add_field($save_dialog_field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add progressive disclosure section
|
||||
*/
|
||||
public function add_progressive_disclosure(): self {
|
||||
// Advanced options toggle
|
||||
$advanced_toggle_field = [
|
||||
'type' => 'custom',
|
||||
'name' => 'advanced_options_toggle',
|
||||
'custom_html' => $this->render_advanced_options_toggle(),
|
||||
'wrapper_class' => 'form-row advanced-toggle-row',
|
||||
];
|
||||
|
||||
$this->add_field($advanced_toggle_field);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark field as advanced (for progressive disclosure)
|
||||
*/
|
||||
public function mark_field_as_advanced(string $field_name): self {
|
||||
foreach ($this->fields as &$field) {
|
||||
if ($field['name'] === $field_name) {
|
||||
$field['wrapper_class'] = ($field['wrapper_class'] ?? '') . ' advanced-field';
|
||||
$field['data_attributes'] = array_merge($field['data_attributes'] ?? [], [
|
||||
'advanced' => 'true'
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render advanced options toggle button
|
||||
*/
|
||||
private function render_advanced_options_toggle(): string {
|
||||
$html = '<div class="hvac-advanced-options-toggle">';
|
||||
$html .= '<button type="button" class="toggle-advanced-options" onclick="hvacToggleAdvancedOptions()">';
|
||||
$html .= '<span class="dashicons dashicons-arrow-down-alt2 toggle-icon"></span>';
|
||||
$html .= '<span class="toggle-text">Show Advanced Options</span>';
|
||||
$html .= '</button>';
|
||||
$html .= '<small class="toggle-description">Additional settings for power users</small>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load template data into form
|
||||
*
|
||||
|
|
@ -499,6 +600,97 @@ class HVAC_Event_Form_Builder extends HVAC_Form_Builder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template preview area
|
||||
*
|
||||
* @return string Preview HTML
|
||||
*/
|
||||
private function render_template_preview_area(): string {
|
||||
$html = '<div class="hvac-template-preview" id="hvac-template-preview" style="display: none;">';
|
||||
$html .= '<div class="preview-header">';
|
||||
$html .= '<h4><span class="dashicons dashicons-visibility"></span> Template Preview</h4>';
|
||||
$html .= '<button type="button" class="preview-close" onclick="hvacCloseTemplatePreview()">×</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="preview-content">';
|
||||
$html .= '<div class="preview-info">';
|
||||
$html .= '<p class="template-name"><strong></strong></p>';
|
||||
$html .= '<p class="template-description"></p>';
|
||||
$html .= '<p class="template-category">Category: <span></span></p>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="preview-fields">';
|
||||
$html .= '<h5>Pre-filled Fields:</h5>';
|
||||
$html .= '<ul class="field-list"></ul>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="preview-actions">';
|
||||
$html .= '<button type="button" class="button button-primary" onclick="hvacApplyTemplate()">Apply This Template</button>';
|
||||
$html .= '<button type="button" class="button" onclick="hvacCloseTemplatePreview()">Cancel</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render save template dialog
|
||||
*
|
||||
* @return string Dialog HTML
|
||||
*/
|
||||
private function render_save_template_dialog(): string {
|
||||
$html = '<div class="hvac-save-template-dialog" id="hvac-save-template-dialog" style="display: none;">';
|
||||
$html .= '<div class="dialog-header">';
|
||||
$html .= '<h4><span class="dashicons dashicons-saved"></span> Save as Template</h4>';
|
||||
$html .= '<button type="button" class="dialog-close" onclick="hvacCloseSaveDialog()">×</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="dialog-content">';
|
||||
|
||||
// Template name field
|
||||
$html .= '<div class="form-group">';
|
||||
$html .= '<label for="template-name"><strong>Template Name *</strong></label>';
|
||||
$html .= '<input type="text" id="template-name" name="template_name" required maxlength="100" ';
|
||||
$html .= 'placeholder="Enter template name..." class="template-input">';
|
||||
$html .= '</div>';
|
||||
|
||||
// Template description field
|
||||
$html .= '<div class="form-group">';
|
||||
$html .= '<label for="template-description"><strong>Description</strong></label>';
|
||||
$html .= '<textarea id="template-description" name="template_description" rows="3" maxlength="500" ';
|
||||
$html .= 'placeholder="Brief description of this template..." class="template-input"></textarea>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Template category field
|
||||
$html .= '<div class="form-group">';
|
||||
$html .= '<label for="template-category"><strong>Category *</strong></label>';
|
||||
$html .= '<select id="template-category" name="template_category" required class="template-input">';
|
||||
$html .= '<option value="">Select Category...</option>';
|
||||
$html .= '<option value="general">General</option>';
|
||||
$html .= '<option value="training">Training</option>';
|
||||
$html .= '<option value="workshop">Workshop</option>';
|
||||
$html .= '<option value="certification">Certification</option>';
|
||||
$html .= '<option value="conference">Conference</option>';
|
||||
$html .= '<option value="webinar">Webinar</option>';
|
||||
$html .= '</select>';
|
||||
$html .= '</div>';
|
||||
|
||||
// Public checkbox
|
||||
$html .= '<div class="form-group checkbox-group">';
|
||||
$html .= '<label for="template-public">';
|
||||
$html .= '<input type="checkbox" id="template-public" name="template_public" value="1">';
|
||||
$html .= ' Make this template available to other trainers';
|
||||
$html .= '</label>';
|
||||
$html .= '<small class="description">If unchecked, only you will be able to use this template</small>';
|
||||
$html .= '</div>';
|
||||
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="dialog-actions">';
|
||||
$html .= '<button type="button" class="button button-primary" onclick="hvacSaveAsTemplate()">Save Template</button>';
|
||||
$html .= '<button type="button" class="button" onclick="hvacCloseSaveDialog()">Cancel</button>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone options for select field
|
||||
*
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class HVAC_Shortcodes {
|
|||
private function __construct() {
|
||||
$this->define_shortcodes();
|
||||
$this->register_shortcodes();
|
||||
$this->register_ajax_handlers();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -860,4 +861,175 @@ class HVAC_Shortcodes {
|
|||
$profile_manager = HVAC_Trainer_Profile_Manager::get_instance();
|
||||
return $profile_manager->render_profile_edit($atts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register AJAX handlers
|
||||
*/
|
||||
private function register_ajax_handlers() {
|
||||
// Template preview AJAX handler
|
||||
add_action('wp_ajax_hvac_get_template_preview', array($this, 'ajax_get_template_preview'));
|
||||
add_action('wp_ajax_nopriv_hvac_get_template_preview', array($this, 'ajax_get_template_preview'));
|
||||
|
||||
// Template loading AJAX handler
|
||||
add_action('wp_ajax_hvac_load_template_data', array($this, 'ajax_load_template_data'));
|
||||
add_action('wp_ajax_nopriv_hvac_load_template_data', array($this, 'ajax_load_template_data'));
|
||||
|
||||
// Template saving AJAX handler
|
||||
add_action('wp_ajax_hvac_save_template', array($this, 'ajax_save_template'));
|
||||
add_action('wp_ajax_nopriv_hvac_save_template', array($this, 'ajax_save_template'));
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for getting template preview data
|
||||
*/
|
||||
public function ajax_get_template_preview() {
|
||||
// Verify nonce
|
||||
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_event_form_nonce')) {
|
||||
wp_send_json_error('Security check failed');
|
||||
return;
|
||||
}
|
||||
|
||||
$template_id = sanitize_text_field($_POST['template_id'] ?? '');
|
||||
if (empty($template_id)) {
|
||||
wp_send_json_error('No template ID provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get template manager instance
|
||||
if (!class_exists('HVAC_Event_Template_Manager')) {
|
||||
wp_send_json_error('Template manager not available');
|
||||
return;
|
||||
}
|
||||
|
||||
$template_manager = HVAC_Event_Template_Manager::instance();
|
||||
$template = $template_manager->get_template($template_id);
|
||||
|
||||
if (!$template) {
|
||||
wp_send_json_error('Template not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Return template data
|
||||
wp_send_json_success($template);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for loading template data into form
|
||||
*/
|
||||
public function ajax_load_template_data() {
|
||||
// Verify nonce
|
||||
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_event_form_nonce')) {
|
||||
wp_send_json_error('Security check failed');
|
||||
return;
|
||||
}
|
||||
|
||||
$template_id = sanitize_text_field($_POST['template_id'] ?? '');
|
||||
if (empty($template_id)) {
|
||||
wp_send_json_error('No template ID provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get template manager instance
|
||||
if (!class_exists('HVAC_Event_Template_Manager')) {
|
||||
wp_send_json_error('Template manager not available');
|
||||
return;
|
||||
}
|
||||
|
||||
$template_manager = HVAC_Event_Template_Manager::instance();
|
||||
$template = $template_manager->get_template($template_id);
|
||||
|
||||
if (!$template || empty($template['field_data'])) {
|
||||
wp_send_json_error('Template data not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Return field data
|
||||
wp_send_json_success($template['field_data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for saving new template
|
||||
*/
|
||||
public function ajax_save_template() {
|
||||
// Verify nonce
|
||||
if (!wp_verify_nonce($_POST['nonce'] ?? '', 'hvac_event_form_nonce')) {
|
||||
wp_send_json_error('Security check failed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check user permissions
|
||||
if (!is_user_logged_in()) {
|
||||
wp_send_json_error('Authentication required');
|
||||
return;
|
||||
}
|
||||
|
||||
$user = wp_get_current_user();
|
||||
if (!array_intersect(['hvac_trainer', 'hvac_master_trainer'], $user->roles)) {
|
||||
wp_send_json_error('Insufficient permissions');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate and sanitize input
|
||||
$template_name = sanitize_text_field($_POST['template_name'] ?? '');
|
||||
$template_description = sanitize_textarea_field($_POST['template_description'] ?? '');
|
||||
$template_category = sanitize_text_field($_POST['template_category'] ?? '');
|
||||
$is_public = (bool) ($_POST['is_public'] ?? false);
|
||||
$field_data = $_POST['field_data'] ?? [];
|
||||
|
||||
if (empty($template_name)) {
|
||||
wp_send_json_error('Template name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($template_category)) {
|
||||
wp_send_json_error('Template category is required');
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize field data
|
||||
$sanitized_field_data = [];
|
||||
foreach ($field_data as $key => $value) {
|
||||
$sanitized_key = sanitize_key($key);
|
||||
if (is_array($value)) {
|
||||
$sanitized_field_data[$sanitized_key] = array_map('sanitize_text_field', $value);
|
||||
} else {
|
||||
$sanitized_field_data[$sanitized_key] = sanitize_text_field($value);
|
||||
}
|
||||
}
|
||||
|
||||
// Get template manager instance
|
||||
if (!class_exists('HVAC_Event_Template_Manager')) {
|
||||
wp_send_json_error('Template manager not available');
|
||||
return;
|
||||
}
|
||||
|
||||
$template_manager = HVAC_Event_Template_Manager::instance();
|
||||
|
||||
// Prepare template data
|
||||
$template_data = [
|
||||
'name' => $template_name,
|
||||
'description' => $template_description,
|
||||
'category' => $template_category,
|
||||
'is_public' => $is_public,
|
||||
'field_data' => $sanitized_field_data,
|
||||
'created_by' => $user->ID,
|
||||
'meta_data' => [
|
||||
'source' => 'event_form',
|
||||
'created_at' => current_time('mysql'),
|
||||
'user_ip' => $_SERVER['REMOTE_ADDR'] ?? '',
|
||||
]
|
||||
];
|
||||
|
||||
// Create the template
|
||||
$result = $template_manager->create_template($template_data);
|
||||
|
||||
if ($result['success']) {
|
||||
wp_send_json_success([
|
||||
'message' => 'Template saved successfully',
|
||||
'template_id' => $result['template_id'] ?? null
|
||||
]);
|
||||
} else {
|
||||
wp_send_json_error($result['error'] ?? 'Failed to save template');
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue