gb/CAMPAIGNS.md
Rodrigo Rodriguez (Pragmatismo) f3bad05e76
Some checks failed
BotServer CI / build (push) Failing after 13s
Fix LXD socket handling in container mode
2026-03-15 18:19:22 -03:00

16 KiB
Raw Blame History

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:

{
  "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:

<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:

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:

<!-- 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:

<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:

<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:

<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:

<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:

<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:

<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...