Some checks failed
BotServer CI / build (push) Failing after 13s
511 lines
16 KiB
Markdown
511 lines
16 KiB
Markdown
# Campaigns App Improvement Specification
|
||
|
||
## Current State Analysis
|
||
|
||
The Campaigns app (`botui/ui/suite/campaigns/campaigns.html`) provides multi-channel marketing with:
|
||
- Campaign grid view with status badges
|
||
- Channel filters (Email, WhatsApp, Social)
|
||
- Lists and Templates tabs
|
||
- Basic campaign creation modal
|
||
- Metrics display (sent, opened, clicked)
|
||
|
||
## Critical Issues to Fix
|
||
|
||
### 1. Missing Backend Integration
|
||
**Problem:** All data loading endpoints return empty or placeholder data
|
||
|
||
**Fix Required:**
|
||
```
|
||
GET /api/crm/campaigns - Returns campaign list
|
||
GET /api/crm/lists - Returns contact lists
|
||
GET /api/crm/templates - Returns message templates
|
||
POST /api/crm/campaigns - Creates new campaign
|
||
```
|
||
|
||
**Expected Response Format:**
|
||
```json
|
||
{
|
||
"campaigns": [
|
||
{
|
||
"id": "uuid",
|
||
"name": "Welcome Series",
|
||
"status": "running",
|
||
"channels": ["email", "whatsapp"],
|
||
"metrics": {
|
||
"sent": 1250,
|
||
"opened": 890,
|
||
"clicked": 234
|
||
},
|
||
"budget": 500.00,
|
||
"scheduled_at": "2026-03-20T10:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 2. Campaign Card Template Missing
|
||
**Problem:** Backend returns JSON but frontend expects HTML
|
||
|
||
**Fix:** Backend must return HTML fragments:
|
||
```html
|
||
<div class="campaign-card" data-id="{{id}}">
|
||
<div class="campaign-card-header">
|
||
<h3 class="campaign-card-title">{{name}}</h3>
|
||
<span class="campaign-status {{status}}">{{status}}</span>
|
||
</div>
|
||
|
||
<div class="campaign-channels">
|
||
{{#each channels}}
|
||
<span class="campaign-channel-tag">
|
||
{{icon}} {{name}}
|
||
</span>
|
||
{{/each}}
|
||
</div>
|
||
|
||
<div class="campaign-metrics">
|
||
<div class="campaign-metric">
|
||
<span class="campaign-metric-value">{{metrics.sent}}</span>
|
||
<span class="campaign-metric-label">Sent</span>
|
||
</div>
|
||
<div class="campaign-metric">
|
||
<span class="campaign-metric-value">{{metrics.opened}}</span>
|
||
<span class="campaign-metric-label">Opened</span>
|
||
</div>
|
||
<div class="campaign-metric">
|
||
<span class="campaign-metric-value">{{metrics.clicked}}</span>
|
||
<span class="campaign-metric-label">Clicked</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="campaign-actions">
|
||
<button class="campaign-action-btn" onclick="viewCampaign('{{id}}')">View</button>
|
||
<button class="campaign-action-btn" onclick="editCampaign('{{id}}')">Edit</button>
|
||
<button class="campaign-action-btn primary" onclick="launchCampaign('{{id}}')">Launch</button>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### 3. Form Submission Not Working
|
||
**Problem:** Modal form uses HTMX but doesn't refresh list after creation
|
||
|
||
**Fix:**
|
||
```javascript
|
||
document.getElementById('campaign-form').addEventListener('htmx:afterRequest', function(e) {
|
||
if (e.detail.successful) {
|
||
hideCampaignModal();
|
||
// Refresh campaigns list
|
||
htmx.ajax('GET', '/api/crm/campaigns', {
|
||
target: '#campaignsList',
|
||
swap: 'innerHTML'
|
||
});
|
||
} else {
|
||
// Show error message
|
||
const error = e.detail.xhr.responseText;
|
||
showError(error);
|
||
}
|
||
});
|
||
```
|
||
|
||
### 4. Channel Filter Not Working
|
||
**Problem:** Filter function checks text content instead of data attributes
|
||
|
||
**Fix:**
|
||
```html
|
||
<!-- Add data attribute to cards -->
|
||
<div class="campaign-card" data-channels="email,whatsapp">
|
||
|
||
<!-- Update filter function -->
|
||
<script>
|
||
function filterCampaigns(channel) {
|
||
const cards = document.querySelectorAll('.campaign-card');
|
||
cards.forEach(card => {
|
||
if (channel === 'all') {
|
||
card.style.display = '';
|
||
} else {
|
||
const channels = card.dataset.channels.split(',');
|
||
card.style.display = channels.includes(channel) ? '' : 'none';
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
```
|
||
|
||
## Major Improvements Needed
|
||
|
||
### 5. Campaign Builder Wizard
|
||
**What:** Multi-step campaign creation with preview
|
||
|
||
**Implementation:**
|
||
```html
|
||
<div id="campaign-wizard" class="wizard">
|
||
<!-- Step 1: Basic Info -->
|
||
<div class="wizard-step active" data-step="1">
|
||
<h3>Campaign Details</h3>
|
||
<input type="text" name="name" placeholder="Campaign Name" required>
|
||
<select name="channel" required>
|
||
<option value="email">Email</option>
|
||
<option value="whatsapp">WhatsApp</option>
|
||
<option value="multi">Multi-Channel</option>
|
||
</select>
|
||
<button onclick="nextStep()">Next</button>
|
||
</div>
|
||
|
||
<!-- Step 2: Audience -->
|
||
<div class="wizard-step" data-step="2">
|
||
<h3>Select Audience</h3>
|
||
<div class="list-selector">
|
||
<!-- Load contact lists -->
|
||
<div hx-get="/api/crm/lists" hx-trigger="load"></div>
|
||
</div>
|
||
<button onclick="prevStep()">Back</button>
|
||
<button onclick="nextStep()">Next</button>
|
||
</div>
|
||
|
||
<!-- Step 3: Content -->
|
||
<div class="wizard-step" data-step="3">
|
||
<h3>Create Message</h3>
|
||
<select name="template" onchange="loadTemplate(this.value)">
|
||
<option value="">Start from scratch</option>
|
||
<!-- Load templates -->
|
||
</select>
|
||
<textarea name="message" rows="10"></textarea>
|
||
<button onclick="prevStep()">Back</button>
|
||
<button onclick="nextStep()">Next</button>
|
||
</div>
|
||
|
||
<!-- Step 4: Schedule -->
|
||
<div class="wizard-step" data-step="4">
|
||
<h3>Schedule Campaign</h3>
|
||
<label>
|
||
<input type="radio" name="schedule_type" value="now" checked>
|
||
Send immediately
|
||
</label>
|
||
<label>
|
||
<input type="radio" name="schedule_type" value="later">
|
||
Schedule for later
|
||
</label>
|
||
<input type="datetime-local" name="scheduled_at" id="schedule-datetime">
|
||
<button onclick="prevStep()">Back</button>
|
||
<button onclick="nextStep()">Next</button>
|
||
</div>
|
||
|
||
<!-- Step 5: Review -->
|
||
<div class="wizard-step" data-step="5">
|
||
<h3>Review & Launch</h3>
|
||
<div class="campaign-preview">
|
||
<!-- Show summary of all settings -->
|
||
</div>
|
||
<button onclick="prevStep()">Back</button>
|
||
<button onclick="launchCampaign()" class="btn-primary">Launch Campaign</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let currentStep = 1;
|
||
|
||
function nextStep() {
|
||
if (validateStep(currentStep)) {
|
||
document.querySelector(`[data-step="${currentStep}"]`).classList.remove('active');
|
||
currentStep++;
|
||
document.querySelector(`[data-step="${currentStep}"]`).classList.add('active');
|
||
}
|
||
}
|
||
|
||
function prevStep() {
|
||
document.querySelector(`[data-step="${currentStep}"]`).classList.remove('active');
|
||
currentStep--;
|
||
document.querySelector(`[data-step="${currentStep}"]`).classList.add('active');
|
||
}
|
||
|
||
function validateStep(step) {
|
||
const stepEl = document.querySelector(`[data-step="${step}"]`);
|
||
const inputs = stepEl.querySelectorAll('[required]');
|
||
|
||
for (let input of inputs) {
|
||
if (!input.value) {
|
||
input.focus();
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
</script>
|
||
```
|
||
|
||
### 6. Contact List Management
|
||
**What:** Create and manage segmented contact lists
|
||
|
||
**Implementation:**
|
||
```html
|
||
<div id="list-modal" class="modal">
|
||
<form id="list-form">
|
||
<h2>Create Contact List</h2>
|
||
|
||
<div class="form-group">
|
||
<label>List Name</label>
|
||
<input type="text" name="name" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Description</label>
|
||
<textarea name="description" rows="3"></textarea>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Import Contacts</label>
|
||
<div class="import-options">
|
||
<button type="button" onclick="showCsvImport()">
|
||
📄 Upload CSV
|
||
</button>
|
||
<button type="button" onclick="showCrmImport()">
|
||
👥 Import from CRM
|
||
</button>
|
||
<button type="button" onclick="showManualAdd()">
|
||
➕ Add Manually
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="import-area" class="import-area">
|
||
<!-- Dynamic import UI -->
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="button" onclick="hideListModal()">Cancel</button>
|
||
<button type="submit">Create List</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- CSV Import -->
|
||
<div id="csv-import" style="display: none;">
|
||
<input type="file" accept=".csv" onchange="handleCsvUpload(this)">
|
||
<div class="csv-preview">
|
||
<table id="csv-preview-table"></table>
|
||
</div>
|
||
<div class="csv-mapping">
|
||
<label>Map columns:</label>
|
||
<select name="name_column"></select>
|
||
<select name="email_column"></select>
|
||
<select name="phone_column"></select>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### 7. Message Template Editor
|
||
**What:** Rich template editor with variables and preview
|
||
|
||
**Implementation:**
|
||
```html
|
||
<div id="template-editor" class="template-editor">
|
||
<div class="editor-toolbar">
|
||
<button onclick="insertVariable('{{name}}')">Name</button>
|
||
<button onclick="insertVariable('{{company}}')">Company</button>
|
||
<button onclick="insertVariable('{{custom_field}}')">Custom Field</button>
|
||
<button onclick="toggleBold()"><strong>B</strong></button>
|
||
<button onclick="toggleItalic()"><em>I</em></button>
|
||
<button onclick="insertLink()">🔗</button>
|
||
<button onclick="insertImage()">🖼️</button>
|
||
</div>
|
||
|
||
<div class="editor-content">
|
||
<textarea id="template-content" name="content"></textarea>
|
||
</div>
|
||
|
||
<div class="editor-preview">
|
||
<h4>Preview</h4>
|
||
<div id="template-preview"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
function insertVariable(variable) {
|
||
const textarea = document.getElementById('template-content');
|
||
const pos = textarea.selectionStart;
|
||
const text = textarea.value;
|
||
|
||
textarea.value = text.slice(0, pos) + variable + text.slice(pos);
|
||
textarea.focus();
|
||
textarea.selectionStart = textarea.selectionEnd = pos + variable.length;
|
||
|
||
updatePreview();
|
||
}
|
||
|
||
function updatePreview() {
|
||
const content = document.getElementById('template-content').value;
|
||
const preview = document.getElementById('template-preview');
|
||
|
||
// Replace variables with sample data
|
||
const sampleData = {
|
||
name: 'John Doe',
|
||
company: 'Acme Corp',
|
||
custom_field: 'Sample Value'
|
||
};
|
||
|
||
let rendered = content;
|
||
for (let [key, value] of Object.entries(sampleData)) {
|
||
rendered = rendered.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
||
}
|
||
|
||
preview.innerHTML = rendered;
|
||
}
|
||
|
||
document.getElementById('template-content').addEventListener('input', updatePreview);
|
||
</script>
|
||
```
|
||
|
||
### 8. Campaign Analytics Dashboard
|
||
**What:** Detailed metrics and performance tracking
|
||
|
||
**Implementation:**
|
||
```html
|
||
<div id="campaign-analytics" class="analytics-dashboard">
|
||
<div class="analytics-header">
|
||
<h2>Campaign Performance</h2>
|
||
<select id="analytics-timeframe" onchange="loadAnalytics()">
|
||
<option value="7d">Last 7 days</option>
|
||
<option value="30d">Last 30 days</option>
|
||
<option value="90d">Last 90 days</option>
|
||
<option value="all">All time</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="analytics-grid">
|
||
<!-- Key Metrics -->
|
||
<div class="metric-card">
|
||
<span class="metric-label">Total Sent</span>
|
||
<span class="metric-value" hx-get="/api/crm/analytics/sent" hx-trigger="load">0</span>
|
||
<span class="metric-change positive">+12%</span>
|
||
</div>
|
||
|
||
<div class="metric-card">
|
||
<span class="metric-label">Open Rate</span>
|
||
<span class="metric-value" hx-get="/api/crm/analytics/open-rate" hx-trigger="load">0%</span>
|
||
<span class="metric-change positive">+5%</span>
|
||
</div>
|
||
|
||
<div class="metric-card">
|
||
<span class="metric-label">Click Rate</span>
|
||
<span class="metric-value" hx-get="/api/crm/analytics/click-rate" hx-trigger="load">0%</span>
|
||
<span class="metric-change negative">-2%</span>
|
||
</div>
|
||
|
||
<div class="metric-card">
|
||
<span class="metric-label">Conversion Rate</span>
|
||
<span class="metric-value" hx-get="/api/crm/analytics/conversion-rate" hx-trigger="load">0%</span>
|
||
<span class="metric-change positive">+8%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Charts -->
|
||
<div class="analytics-charts">
|
||
<div class="chart-container">
|
||
<h3>Campaign Performance Over Time</h3>
|
||
<canvas id="performance-chart"></canvas>
|
||
</div>
|
||
|
||
<div class="chart-container">
|
||
<h3>Channel Breakdown</h3>
|
||
<canvas id="channel-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Top Campaigns -->
|
||
<div class="top-campaigns">
|
||
<h3>Top Performing Campaigns</h3>
|
||
<table class="analytics-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Campaign</th>
|
||
<th>Channel</th>
|
||
<th>Sent</th>
|
||
<th>Opened</th>
|
||
<th>Clicked</th>
|
||
<th>Converted</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody hx-get="/api/crm/analytics/top-campaigns" hx-trigger="load">
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### 9. A/B Testing
|
||
**What:** Test different message variants
|
||
|
||
**Implementation:**
|
||
```html
|
||
<div class="ab-test-setup">
|
||
<h3>A/B Test Setup</h3>
|
||
|
||
<div class="variant-container">
|
||
<div class="variant">
|
||
<h4>Variant A (50%)</h4>
|
||
<textarea name="variant_a_content"></textarea>
|
||
<input type="range" name="variant_a_percentage" min="0" max="100" value="50">
|
||
</div>
|
||
|
||
<div class="variant">
|
||
<h4>Variant B (50%)</h4>
|
||
<textarea name="variant_b_content"></textarea>
|
||
<input type="range" name="variant_b_percentage" min="0" max="100" value="50">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="test-settings">
|
||
<label>Test Duration</label>
|
||
<input type="number" name="test_duration" value="24"> hours
|
||
|
||
<label>Success Metric</label>
|
||
<select name="success_metric">
|
||
<option value="open_rate">Open Rate</option>
|
||
<option value="click_rate">Click Rate</option>
|
||
<option value="conversion_rate">Conversion Rate</option>
|
||
</select>
|
||
|
||
<label>
|
||
<input type="checkbox" name="auto_select_winner">
|
||
Automatically send winning variant to remaining contacts
|
||
</label>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### 10. WhatsApp Integration
|
||
**What:** Send WhatsApp campaigns with media support
|
||
|
||
**Implementation:**
|
||
```html
|
||
<div class="whatsapp-composer">
|
||
<div class="message-preview">
|
||
<div class="whatsapp-bubble">
|
||
<div class="message-content" id="whatsapp-preview">
|
||
<!-- Preview of message -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="message-editor">
|
||
<textarea name="message" placeholder="Type your message..."
|
||
oninput="updateWhatsAppPreview()"></textarea>
|
||
|
||
<div class="media-attachments">
|
||
<button onclick="attachImage()">📷 Image</button>
|
||
<button onclick="attachVideo()">🎥 Video</button>
|
||
<button onclick="attachDocument()">📄 Document</button>
|
||
</div>
|
||
|
||
<div class="quick-replies">
|
||
<h4>Quick Reply Buttons</h4>
|
||
<div id="quick-replies-list">
|
||
<input type="text" placeholder="Button 1" name="quick_reply_1">
|
||
<input type="text" placeholder="Button 2" name="quick_reply_2">
|
||
<input type="text" placeholder="Button 3" name="quick_reply_3">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
## Continued in CAMPAIGNS-PART2.md...
|