gb/CRM.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

14 KiB
Raw Blame History

CRM App Improvement Specification

Current State Analysis

The CRM app (botui/ui/suite/crm/crm.html) provides basic customer relationship management with:

  • Pipeline view (Lead → Qualified → Proposal → Negotiation → Won/Lost)
  • Deals, Accounts, Contacts, Campaigns tabs
  • Lead form with basic fields
  • Drag-and-drop pipeline cards
  • HTMX-based data loading

Critical Issues to Fix

1. Broken API Endpoints

Problem: Mixed endpoint patterns causing 404s

  • Pipeline uses /api/ui/crm/pipeline (correct)
  • Counts use /api/ui/crm/count AND /api/crm/count (inconsistent)
  • Deals use /api/ui/crm/deals (correct)
  • Stage updates use /api/crm/opportunity/{id}/stage (missing /ui/)

Fix:

<!-- BEFORE (inconsistent) -->
<span hx-get="/api/crm/count?stage=qualified">0</span>
<span hx-get="/api/ui/crm/count?stage=new">0</span>

<!-- AFTER (consistent) -->
<span hx-get="/api/ui/crm/count?stage=qualified">0</span>
<span hx-get="/api/ui/crm/count?stage=new">0</span>

Action: Standardize ALL endpoints to /api/ui/crm/* pattern.

2. Missing Deal Card Template

Problem: Pipeline loads empty - no HTML template for deal cards returned by API

Fix: Backend must return HTML fragments like:

<div class="pipeline-card" draggable="true" data-id="{{id}}">
    <div class="card-header">
        <h4>{{title}}</h4>
        <span class="card-value">${{value}}</span>
    </div>
    <div class="card-body">
        <p>{{company}}</p>
        <span class="card-contact">{{contact_name}}</span>
    </div>
    <div class="card-footer">
        <span class="card-probability">{{probability}}%</span>
        <span class="card-date">{{close_date}}</span>
    </div>
</div>

Action: Create botserver/src/ui/crm/templates/deal_card.html and render in API response.

3. Drag-and-Drop Not Working

Problem: Cards need draggable="true" and proper event handlers

Fix:

// Add to pipeline card template
<div class="pipeline-card" draggable="true" 
     ondragstart="event.dataTransfer.setData('text/plain', this.dataset.id)">

// Update drop handler
column.addEventListener('drop', async (e) => {
    e.preventDefault();
    const cardId = e.dataTransfer.getData('text/plain');
    const newStage = column.closest('.pipeline-column').dataset.stage;
    
    const token = localStorage.getItem('gb_token');
    await fetch(`/api/ui/crm/opportunity/${cardId}/stage`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + token
        },
        body: JSON.stringify({ stage: newStage })
    });
    
    // Refresh pipeline
    htmx.trigger('#crm-pipeline-view', 'refresh');
});

4. Form Validation Missing

Problem: Lead form submits incomplete data

Fix:

<!-- Add required fields -->
<input type="text" name="first_name" required>
<input type="text" name="last_name" required>
<input type="email" name="email" required>

<!-- Add client-side validation -->
<script>
window.submitLeadForm = async function(event) {
    const form = document.getElementById('leadForm');
    if (!form.checkValidity()) {
        form.reportValidity();
        return;
    }
    // ... rest of submission logic
};
</script>

Major Improvements Needed

5. Activity Timeline

What: Show interaction history for each deal/contact

Implementation:

<!-- Add to detail panel -->
<div class="activity-timeline">
    <div class="timeline-item">
        <div class="timeline-icon">📧</div>
        <div class="timeline-content">
            <strong>Email sent</strong>
            <p>Proposal sent to john@company.com</p>
            <span class="timeline-date">2 hours ago</span>
        </div>
    </div>
    <div class="timeline-item">
        <div class="timeline-icon">📞</div>
        <div class="timeline-content">
            <strong>Call completed</strong>
            <p>Discussed pricing and timeline</p>
            <span class="timeline-date">Yesterday</span>
        </div>
    </div>
</div>

Backend: Add GET /api/ui/crm/opportunity/{id}/activities endpoint.

6. Quick Actions Menu

What: Right-click context menu on pipeline cards

Implementation:

document.addEventListener('contextmenu', (e) => {
    const card = e.target.closest('.pipeline-card');
    if (!card) return;
    
    e.preventDefault();
    showContextMenu(e.clientX, e.clientY, [
        { label: 'Edit', action: () => editDeal(card.dataset.id) },
        { label: 'Send Email', action: () => sendEmail(card.dataset.id) },
        { label: 'Schedule Call', action: () => scheduleCall(card.dataset.id) },
        { label: 'Mark as Won', action: () => updateStage(card.dataset.id, 'won') },
        { label: 'Mark as Lost', action: () => updateStage(card.dataset.id, 'lost') },
        { label: 'Delete', action: () => deleteDeal(card.dataset.id), danger: true }
    ]);
});

7. Bulk Operations

What: Select multiple deals and perform batch actions

Implementation:

<!-- Add checkbox to cards -->
<div class="pipeline-card">
    <input type="checkbox" class="card-select" data-id="{{id}}">
    <!-- ... rest of card -->
</div>

<!-- Add bulk action bar -->
<div id="bulk-actions" class="bulk-actions" style="display: none;">
    <span class="bulk-count">0 selected</span>
    <button onclick="bulkUpdateStage()">Change Stage</button>
    <button onclick="bulkAssignOwner()">Assign Owner</button>
    <button onclick="bulkDelete()">Delete</button>
</div>

<script>
document.addEventListener('change', (e) => {
    if (e.target.classList.contains('card-select')) {
        const selected = document.querySelectorAll('.card-select:checked');
        const bulkBar = document.getElementById('bulk-actions');
        
        if (selected.length > 0) {
            bulkBar.style.display = 'flex';
            bulkBar.querySelector('.bulk-count').textContent = `${selected.length} selected`;
        } else {
            bulkBar.style.display = 'none';
        }
    }
});
</script>

8. Advanced Filters

What: Filter deals by owner, date range, value, probability

Implementation:

<div class="crm-filters">
    <select id="filter-owner" onchange="applyFilters()">
        <option value="">All Owners</option>
        <!-- Populated from API -->
    </select>
    
    <input type="date" id="filter-date-from" onchange="applyFilters()">
    <input type="date" id="filter-date-to" onchange="applyFilters()">
    
    <input type="number" id="filter-value-min" placeholder="Min Value" onchange="applyFilters()">
    <input type="number" id="filter-value-max" placeholder="Max Value" onchange="applyFilters()">
    
    <select id="filter-probability" onchange="applyFilters()">
        <option value="">All Probabilities</option>
        <option value="high">High (>70%)</option>
        <option value="medium">Medium (30-70%)</option>
        <option value="low">Low (<30%)</option>
    </select>
</div>

<script>
function applyFilters() {
    const params = new URLSearchParams({
        owner: document.getElementById('filter-owner').value,
        date_from: document.getElementById('filter-date-from').value,
        date_to: document.getElementById('filter-date-to').value,
        value_min: document.getElementById('filter-value-min').value,
        value_max: document.getElementById('filter-value-max').value,
        probability: document.getElementById('filter-probability').value
    });
    
    htmx.ajax('GET', `/api/ui/crm/pipeline?${params}`, '#crm-pipeline-view');
}
</script>

9. Deal Detail Panel

What: Slide-out panel with full deal information

Implementation:

<div id="deal-detail-panel" class="detail-panel">
    <div class="detail-header">
        <h2 id="detail-title"></h2>
        <button onclick="closeDetailPanel()">×</button>
    </div>
    
    <div class="detail-tabs">
        <button class="detail-tab active" data-tab="overview">Overview</button>
        <button class="detail-tab" data-tab="activities">Activities</button>
        <button class="detail-tab" data-tab="files">Files</button>
        <button class="detail-tab" data-tab="notes">Notes</button>
    </div>
    
    <div id="detail-content" class="detail-content">
        <!-- Content loaded via HTMX -->
    </div>
</div>

<script>
function showDealDetail(dealId) {
    const panel = document.getElementById('deal-detail-panel');
    panel.classList.add('open');
    
    htmx.ajax('GET', `/api/ui/crm/opportunity/${dealId}`, '#detail-content');
}
</script>

<style>
.detail-panel {
    position: fixed;
    right: -400px;
    top: 0;
    width: 400px;
    height: 100vh;
    background: var(--surface);
    border-left: 1px solid var(--border);
    transition: right 0.3s ease;
    z-index: 1000;
}

.detail-panel.open {
    right: 0;
}
</style>

10. Email Integration

What: Send emails directly from CRM

Implementation:

<div id="email-composer" class="modal">
    <form id="email-form">
        <input type="hidden" name="deal_id" id="email-deal-id">
        
        <div class="form-group">
            <label>To</label>
            <input type="email" name="to" required>
        </div>
        
        <div class="form-group">
            <label>Subject</label>
            <input type="text" name="subject" required>
        </div>
        
        <div class="form-group">
            <label>Template</label>
            <select id="email-template" onchange="loadTemplate(this.value)">
                <option value="">Blank</option>
                <option value="proposal">Proposal</option>
                <option value="followup">Follow-up</option>
                <option value="meeting">Meeting Request</option>
            </select>
        </div>
        
        <div class="form-group">
            <label>Message</label>
            <textarea name="body" rows="10" required></textarea>
        </div>
        
        <div class="form-actions">
            <button type="button" onclick="closeEmailComposer()">Cancel</button>
            <button type="submit">Send Email</button>
        </div>
    </form>
</div>

Backend: Add POST /api/ui/crm/opportunity/{id}/email endpoint.

Performance Optimizations

11. Lazy Loading

Problem: Loading all pipeline cards at once is slow

Fix:

<!-- Load only visible stages initially -->
<div class="pipeline-cards"
     hx-get="/api/ui/crm/pipeline?stage=new"
     hx-trigger="intersect once"
     hx-swap="innerHTML">
    <div class="loading-skeleton"></div>
</div>

12. Optimistic Updates

Problem: Drag-and-drop feels slow waiting for server response

Fix:

column.addEventListener('drop', async (e) => {
    e.preventDefault();
    const cardId = e.dataTransfer.getData('text/plain');
    const card = document.querySelector(`[data-id="${cardId}"]`);
    const newStage = column.closest('.pipeline-column').dataset.stage;
    
    // Optimistic update - move card immediately
    column.querySelector('.pipeline-cards').appendChild(card);
    
    // Update server in background
    try {
        await fetch(`/api/ui/crm/opportunity/${cardId}/stage`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ stage: newStage })
        });
    } catch (err) {
        // Revert on error
        console.error('Failed to update stage:', err);
        // Move card back to original column
    }
});

UI/UX Enhancements

13. Keyboard Shortcuts

document.addEventListener('keydown', (e) => {
    // Ctrl+N: New lead
    if (e.ctrlKey && e.key === 'n') {
        e.preventDefault();
        document.getElementById('crm-new-btn').click();
    }
    
    // Ctrl+F: Focus search
    if (e.ctrlKey && e.key === 'f') {
        e.preventDefault();
        document.querySelector('.crm-search input').focus();
    }
    
    // Escape: Close modal
    if (e.key === 'Escape') {
        closeCrmModal();
        closeDetailPanel();
    }
});

14. Empty States

<!-- When no deals in stage -->
<div class="empty-state">
    <svg class="empty-icon"><!-- icon --></svg>
    <h3>No deals in this stage</h3>
    <p>Drag deals here or create a new one</p>
    <button onclick="openLeadForm()">Create Lead</button>
</div>

15. Loading States

<!-- Replace "Loading..." text with skeleton -->
<div class="pipeline-card skeleton">
    <div class="skeleton-line" style="width: 70%;"></div>
    <div class="skeleton-line" style="width: 40%;"></div>
    <div class="skeleton-line" style="width: 90%;"></div>
</div>

Backend Requirements

API Endpoints Needed

GET  /api/ui/crm/pipeline?stage={stage}&owner={owner}&date_from={date}&date_to={date}
GET  /api/ui/crm/count?stage={stage}
GET  /api/ui/crm/opportunity/{id}
GET  /api/ui/crm/opportunity/{id}/activities
POST /api/ui/crm/opportunity/{id}/stage
POST /api/ui/crm/opportunity/{id}/email
POST /api/ui/crm/leads
GET  /api/ui/crm/deals?filter={filter}
GET  /api/ui/crm/accounts
GET  /api/ui/crm/contacts
GET  /api/ui/crm/search?q={query}
GET  /api/ui/crm/stats/pipeline-value
GET  /api/ui/crm/stats/conversion-rate
GET  /api/ui/crm/stats/avg-deal
GET  /api/ui/crm/stats/won-month

Database Schema Additions

-- Activity tracking
CREATE TABLE crm_activities (
    id UUID PRIMARY KEY,
    opportunity_id UUID REFERENCES crm_opportunities(id),
    activity_type VARCHAR(50), -- email, call, meeting, note
    subject TEXT,
    description TEXT,
    created_at TIMESTAMP,
    created_by UUID
);

-- Email templates
CREATE TABLE crm_email_templates (
    id UUID PRIMARY KEY,
    name VARCHAR(255),
    subject TEXT,
    body TEXT,
    created_at TIMESTAMP
);

Implementation Priority

  1. Critical (Do First):

    • Fix API endpoint inconsistencies
    • Add deal card HTML template
    • Fix drag-and-drop functionality
    • Add form validation
  2. High Priority:

    • Activity timeline
    • Deal detail panel
    • Advanced filters
    • Email integration
  3. Medium Priority:

    • Bulk operations
    • Quick actions menu
    • Keyboard shortcuts
    • Empty/loading states
  4. Nice to Have:

    • Lazy loading
    • Optimistic updates
    • Advanced analytics dashboard

Testing Checklist

  • Create new lead via form
  • Drag deal between pipeline stages
  • Search for deals/accounts
  • Filter deals by owner/date/value
  • View deal details in side panel
  • Send email from deal
  • Bulk update multiple deals
  • Keyboard shortcuts work
  • Mobile responsive layout
  • All API endpoints return correct data