14 KiB
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/countAND/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
-
Critical (Do First):
- Fix API endpoint inconsistencies
- Add deal card HTML template
- Fix drag-and-drop functionality
- Add form validation
-
High Priority:
- Activity timeline
- Deal detail panel
- Advanced filters
- Email integration
-
Medium Priority:
- Bulk operations
- Quick actions menu
- Keyboard shortcuts
- Empty/loading states
-
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