Some checks failed
BotServer CI / build (push) Failing after 13s
504 lines
14 KiB
Markdown
504 lines
14 KiB
Markdown
# 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:**
|
||
```html
|
||
<!-- 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:
|
||
```html
|
||
<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:**
|
||
```javascript
|
||
// 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:**
|
||
```html
|
||
<!-- 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:**
|
||
```html
|
||
<!-- 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:**
|
||
```javascript
|
||
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:**
|
||
```html
|
||
<!-- 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:**
|
||
```html
|
||
<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:**
|
||
```html
|
||
<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:**
|
||
```html
|
||
<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:**
|
||
```html
|
||
<!-- 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:**
|
||
```javascript
|
||
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
|
||
```javascript
|
||
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
|
||
```html
|
||
<!-- 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
|
||
```html
|
||
<!-- 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
|
||
```sql
|
||
-- 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
|