CRM UI
Frontend screens, components, and user flows for the CRM module.
Route Structure
/crm → Main CRM view (Kanban default)
/crm?type=prospect&id=42 → Opens sidepanel for prospect #42
/crm?type=client&id=100 → Opens sidepanel for client (company) #100The CRM is a single-page view at /crm with three switchable sub-views. Deep-linking to a specific record is supported via query parameters.
Main View (index.vue)
The entry point orchestrates all CRM sub-views and shared components.
Layout:
┌──────────────────────────────────────────────────────────────────┐
│ Section Title: "CRM Beta" │
│ [Kanban | List | Calendar] [+ Add] [Import] [Settings] │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Active View (Kanban / List / Calendar) │
│ │
└──────────────────────────────────────────────────────────────────┘Components:
| Element | Component | Description |
|---|---|---|
| Header | SectionTitle | Title with "Beta" badge |
| View Switch | Switch | Three-option toggle: Kanban, List, Calendar |
| Add Button | Button | Opens AddProspect modal |
| Import Button | Button | Opens CsvUpload modal |
| Settings Button | Button | Delegates to active view's showSettings() |
View Switching:
activeViewref controls which view is rendered viav-if/v-else-if- Default view:
kanban - Only one view is mounted at a time to minimize memory usage
Provide/Inject:
The index view provides four refs to all child components:
| Key | Ref | Purpose |
|---|---|---|
$kamban | kamban | Access Kanban reload and settings |
$sidepanel | sidepanel | Open sidepanel from any view |
$amodal | add | Open Add Prospect modal |
$excel | excel | Open CSV Import modal |
Kanban View (Kamban.vue)
Displays prospects organized into columns by follow-up status, with drag-and-drop to move cards between statuses.
Layout:
┌─────────────────────────────────────────────────────────────────────┐
│ [Filter Bar] search | follow | onboarding | ecommerce | type ... │
├──────────┬──────────┬──────────┬──────────┬──────────┬──────────────┤
│ New Lead │Contacted │Qualified │Negotiat. │ Won │ ... │
│ KPI: $5k │ KPI: $12k│ KPI: $8k │ KPI: $25k│ KPI: $3k│ │
├──────────┼──────────┼──────────┼──────────┼──────────┼──────────────┤
│ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ │ ┌──────┐ │ │
│ │Card 1│ │ │Card 3│ │ │Card 5│ │ │ │Card 7│ │ │
│ └──────┘ │ └──────┘ │ └──────┘ │ │ └──────┘ │ │
│ ┌──────┐ │ ┌──────┐ │ │ │ │ │
│ │Card 2│ │ │Card 4│ │ │ │ │ │
│ └──────┘ │ └──────┘ │ │ │ │ │
│ ... │ ... │ │ │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────────┘Data Source
- API:
GET /prospects/groupedviaapi.prospects.getOnGroup(filters) - KPIs:
GET /prospects/kpisviaapi.prospects.getKpis(filters) - Columns: Dynamically generated from
api.catalog.getFollowUpCategoriesGrouped()
Card Content
Each card displays:
| Element | Description |
|---|---|
| Company name | Primary identifier |
| Balance | Current account balance (formatted currency) |
| Account value | Estimated account value |
| Last contact | Date of most recent interaction |
| Badges | Verified status, tier, international flag, shops count, follow status, monthly shipments |
| Services | Requested service badges (national, international, FTL, LTL, fulfillment, etc.) |
Interactions
| Action | Behavior |
|---|---|
| Click card | Opens sidepanel with prospect/client details |
| Drag card to another column | Opens sidepanel on followup tab with target status pre-selected |
| Scroll to bottom of column | Infinite scroll loads more records (v-infinite-scroll) |
Drag-and-Drop
Implemented with vuedraggable. When a card is dropped into a new column:
- Sidepanel opens with the followup tab active
- The target follow-up status is pre-selected in the NoteForm
- User adds a note explaining the status change
- On save, the follow-up status is updated and the Kanban reloads
Filters
Uses useTablePreferences('crm-prospects') for filter visibility and ordering.
Default visible filters:
| Filter | Type | Description |
|---|---|---|
| Search | Text input | Free-text search across company/contact name |
| Follow | Select | Filter by follow-up status |
| Onboarding | Select | Filter by onboarding status |
| Ecommerce | Select | Filter by ecommerce platform |
| Type | Select | Filter by prospect type |
Additional filters (visible via Settings):
Campaign, Salesman, KAE, KAE LTL, CSR, CSR LTL, Fulfillment, WMS, Ecartpay, Parapaquetes
Permission-gated filters:
When all-prospects permission is active:
- Date range filter (startDate/endDate) — defaults to last 2 months
- Salesman filter
- Role-based filters (KAE, CSR, etc.) — each requires both
all-prospectsand the role's specific permission
Table/List View (ListView.vue)
A paginated data table with extensive column customization and server-side sorting.
Layout:
┌──────────────────────────────────────────────────────────────────┐
│ [Filter Bar] search | follow | onboarding | ecommerce | ... │
├──────────────────────────────────────────────────────────────────┤
│ Company │ Follow │ Status │ Balance │ Acct Val │ Last │ ... │
│───────────┼────────┼────────┼─────────┼──────────┼─────────┼─────│
│ Acme │ New │ Active │ $5,000 │ $10,000 │ 3d ago │ │
│ Beta Co │ Qual. │ Hot │ $12,000 │ $25,000 │ Today │ │
│ ... │ │ │ │ │ │ │
├──────────────────────────────────────────────────────────────────┤
│ Showing 1-25 of 1,250 [< 1 2 3 ... 50 >] │
└──────────────────────────────────────────────────────────────────┘Data Source
- API:
GET /prospects/listviaapi.prospects.getList(filters) - Pagination: Server-side with
startandlengthparameters - Sorting: Server-side by column name
Columns
All columns are configurable via useTablePreferences. Default configuration from CRM_TABLE_DEFAULTS:
| Column | Key | Default Visible | Description |
|---|---|---|---|
| Company | company_name | Yes | Company name with avatar/image |
| Follow-up | follow_name | Yes | Current follow-up status badge |
| Lead Status | lead_status | Yes | Lead qualification status |
| Salesman | salesman_name | No | Assigned sales executive |
| Balance | balance | Yes | Account balance (currency) |
| Account Value | account_value | Yes | Estimated account value |
| First Contact | first_contact | No | Date of first interaction |
| Last Contact | last_contact | Yes | Date of most recent interaction |
| Days UC | days_uc | No | Days since user creation |
| Days Without Contact | days_without_contact | Yes | Days since last interaction |
| Next Contact | next_contact | Yes | Scheduled next follow-up |
| Content | content_name | No | Package content type |
| Plan Type | plan_type_name | No | Service plan type |
| Monthly Shipments | monthly_shipments_desc | No | Estimated monthly volume |
| Weight Range | weight_range | No | Package weight category |
| Campaign | campaign | No | Acquisition campaign |
| First Meeting | first_meeting | No | First meeting date |
| Second Meeting | second_meeting | No | Second meeting date |
| Commercial Offer | commercial_offer_date | No | Date of commercial offer |
| Integration Type | integration_type | No | E-commerce integration |
| Est. Close Date | estimated_close_date | No | Expected deal close |
| Est. Close Month | estimated_close_month | No | Expected close month |
| Source | company_source | No | Lead acquisition source |
| Last Recharge | last_recharge_date | No | Last account recharge |
| Actions | (always visible) | Yes | "View info" button |
Custom Column Slots
Several columns use custom rendering:
- Company: Shows company image/avatar alongside name
- Follow-up: Colored badge based on status
- Balance / Account Value: Formatted currency with locale
- Dates: Relative formatting ("3 days ago", "Today")
- Days Without Contact: Color-coded (green < 7d, yellow < 14d, red > 14d)
Filters
All Kanban filters plus additional table-specific filters:
| Filter | Key | Default Visible | Description |
|---|---|---|---|
| Weight Range | weightRange | No | Package weight category |
| Content | contentId | No | Content type |
| Plan Type | planTypeId | No | Service plan |
| Min Shipments | minShipments | No | Minimum monthly shipments |
| Lead Status | leadStatus | No | Lead qualification status |
| Next Contact | nextContact | No | Date range for next contact |
| Commercial Offer | commercialOfferDate | No | Date range |
| Est. Close Date | estimatedCloseDate | No | Date range |
| Est. Close Month | estimatedCloseMonth | No | Month picker |
| Has Recharge | hasRecharge | No | Yes/No toggle |
Row Actions
| Action | Behavior |
|---|---|
| Click "View info" | Opens sidepanel with prospect/client details |
Calendar View (CalendarView.vue)
Displays follow-up activities as calendar events using FullCalendar.
Layout:
┌──────────────────────────────────────────────────────────────────┐
│ [Filter Bar] search | follow | onboarding | ecommerce | ... │
├──────────────────────────────────────────────────────────────────┤
│ [All] [Overdue] [Today] [This Month] [Upcoming] │
├──────────────────────────────────────────────────────────────────┤
│ < March 2026 > [Month] [Year] │
├──────────────────────────────────────────────────────────────────┤
│ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │ Sun │
│───────┼───────┼───────┼───────┼───────┼───────┼────── │
│ │ 📞 │ │ 📧 │ │ │ │
│ │ Acme │ │ Beta │ │ │ │
│ │ │ 🤝 │ │ 📱 │ │ │
│ │ │ Corp │ │ Delta │ │ │
└───────┴───────┴───────┴───────┴───────┴───────┴──────────────────┘Data Source
- API:
GET /prospects/activitiesviaapi.prospects.getActivities(params) - Required params:
startDate,endDate(calendar visible range)
Calendar Configuration
| Feature | Value |
|---|---|
| Library | FullCalendar (Vue 3) |
| Plugins | dayGrid, timeGrid, interaction, list |
| Views | dayGridMonth (default), listYear (for period filters) |
| Initial view | dayGridMonth |
Event Display
Events are color-coded by action type:
| Action | Icon | Color |
|---|---|---|
| Call | Phone | Blue |
| Message | Green | |
| Envelope | Orange | |
| Meeting | Handshake | Purple |
| SMS | Mobile | Teal |
| Other | Circle | Gray |
Overdue events (past scheduled date without completion) receive a distinct style.
Period Filters
Quick-access buttons that switch the calendar view:
| Period | Behavior |
|---|---|
| All | Shows all activities in the visible range |
| Overdue | Filters to past-due activities only |
| Today | Filters to today's activities |
| This Month | Filters to current month |
| Upcoming | Filters to future activities |
Interactions
| Action | Behavior |
|---|---|
| Click event | Opens sidepanel for the associated prospect or client |
| Navigate month | Fetches new activities for the visible date range |
Sidepanel (Sidepanel.vue)
An offcanvas panel that shows full details for a selected prospect or client. Opens from any view when a record is clicked.
Layout:
┌────────────────────────────────────────────────────────────────────────┐
│ Title: "Prospect Details" · Lead Status Badge │
├──────────────┬──────────────────────────┬──────────────────────────────┤
│ LEFT 30% │ CENTER 40% │ RIGHT 30% │
├──────────────┼──────────────────────────┼──────────────────────────────┤
│ │ │ │
│ ContactCard │ [Followup] [Contacts] │ Details Accordion │
│ ┌──────────┐ │ [Locations] [Mail] │ ├─ Ecommerce type │
│ │ Company │ │ [Activity] │ ├─ Channel │
│ │ #ID │ │ │ ├─ Monthly shipments │
│ │ Badge │ │ ┌──────────────────────┐ │ ├─ Account value │
│ │ Exec. │ │ │ Tab Content │ │ ├─ Weight range │
│ │ Email │ │ │ │ │ ├─ Content type │
│ │ Phone │ │ │ Notes timeline │ │ ├─ Account type │
│ │ Balance │ │ │ - or - │ │ ├─ Est. close date │
│ │ Follow │ │ │ Contacts list │ │ └─ Annotations │
│ │ date │ │ │ - or - │ │ │
│ └──────────┘ │ │ Locations list │ │ Metrics Accordion │
│ │ │ - or - │ │ ├─ Last contact │
│ [Reminder] │ │ Mail inbox │ │ ├─ Next contact │
│ │ │ │ │ ├─ Created date │
│ Onboarding │ └──────────────────────┘ │ ├─ Source │
│ Accordion │ │ └─ Campaign │
│ │ ┌──────────────────────┐ │ │
│ Lead Summary │ │ Form Area │ │ Integrations Accordion │
│ Accordion │ │ (NoteForm, │ │ └─ E-commerce integrations │
│ ├─ Origin │ │ ContactForm, │ │ │
│ ├─ Created │ │ LocationForm) │ │ Marketing Accordion │
│ ├─ 1st cont. │ │ │ │ └─ Marketing fields │
│ └─ Last cont.│ └──────────────────────┘ │ │
└──────────────┴──────────────────────────┴──────────────────────────────┘Data Loading
- Prospects:
GET /prospects/{id}viaapi.prospects.getById(id) - Clients:
GET /clients/{id}viaapi.clients.getById(companyId)
The sidepanel determines which API to call based on itemOptions:
{ has_prospect: true, id }— loads prospect data{ has_prospect: false, company }— loads client data
Left Column (30%)
| Section | Description |
|---|---|
| ContactCard | Company name, follow-up badge, assigned executive, email (clickable to mail tab), phone (WhatsApp link), balance |
| Follow-up date | Editable datetime input for the latest scheduled follow-up |
| Edit salesman | Button to reassign executive (requires crm-edit-salesman-prospect, only for prospects without company) |
| Add reminder | Opens TodoForm to create a task/reminder linked to this prospect |
| Onboarding accordion | Displays onboarding questionnaire answers (for clients) |
| Lead summary accordion | Origin, creation date, first contact, last contact |
Center Column (40%)
Tabs:
| Tab | Component | Description |
|---|---|---|
| Followup | NoteForm + Notes list | Chat-style timeline of notes. NoteForm at the bottom to add new notes with status, action, and optional scheduling |
| Contacts | ContactForm + list | List of extra contacts with add/edit/delete. Each contact has name, email, phone, position |
| Locations | LocationForm + list | List of locations with add/edit/delete. Each has name, type, and map link |
| MailingTab | Email inbox/outbox for crm_prospects or crm_clients module | |
| Activity | Placeholder | "Coming soon" placeholder |
Right Column (30%)
| Accordion | Fields |
|---|---|
| Details | Ecommerce type, channel, locale, monthly shipments, account value, weight range, content type, account type, estimated close date, annotations. Editable via DetailsForm |
| Metrics | Last contact, next contact, created date, source, campaign (read-only) |
| Integrations | E-commerce platform integrations (read-only) |
| Marketing | Marketing-related fields (read-only) |
Exposed API
sidepanel.open(item, config?)| Parameter | Type | Description |
|---|---|---|
item | Object | Record data with at minimum id and entity type info |
config.open | string | Tab to auto-open: 'followup' |
config.group | Object | Pre-selected follow-up group (for Kanban drag-and-drop) |
URL Synchronization
When the sidepanel opens, query parameters are updated:
?type=prospect&id=42or?type=client&id=100- On page load, if these params exist, the sidepanel auto-opens
Forms
NoteForm (Note.form.vue)
Creates follow-up notes with optional status change and scheduling.
| Field | Type | Validation | Description |
|---|---|---|---|
| Follow-up status | Grouped select | Optional | New status (grouped by category) |
| Action | Select | Optional | call, whatsapp, email, sms, meeting, other |
| Note | Textarea | Required | Note content |
| Scheduled date | Date input | Optional | Follow-up date (shown when status + action selected) |
| Scheduled time | Time input | Optional | Follow-up time (default: 09:00) |
ContactForm (Contact.form.vue)
Creates or updates contacts.
| Field | Type | Validation | Description |
|---|---|---|---|
| Name | Input | Required | Contact person name |
| Input | Required | Email address | |
| Phone code | Select | Required | Country phone code (from catalog) |
| Phone | Input | Required | Phone number |
| Position | Input | Optional | Job title (disabled for default contact) |
LocationForm (Location.form.vue)
Creates or updates locations.
| Field | Type | Validation | Description |
|---|---|---|---|
| Name | Input | Required, max 50 | Location name |
| Type | Select | Required | office, warehouse, branch, headquarters, distribution_center |
| Link | Input | Required, URL pattern | Map/address URL |
DetailsForm (Details.form.vue)
Edits follow-up metadata.
| Field | Type | Validation | Description |
|---|---|---|---|
| Ecommerce type | Select | Optional | E-commerce platform |
| Channel | Input | Optional | Acquisition channel |
| Locale | Select | Optional | Country (disabled for clients) |
| Monthly shipments | Input | Numeric | Shipment estimate |
| Account value | Input | Numeric, disabled | Auto-calculated from shipments x locale cost |
| Weight range | Select | Optional | Package weight category |
| Content type | Select | Optional | Package content |
| Account type | Select | Optional | individual, startup, sme, enterprise, corporate |
| Estimated close date | Date | Optional | Deal close target |
| Annotations | Textarea | Optional | Free-text notes (5 rows) |
ProspectForm (Prospect.form.vue)
Reassigns the executive/salesman for a prospect.
| Field | Type | Validation | Description |
|---|---|---|---|
| Assigned to | Select | Required | Admin users with SDR/MDR roles (roles 9, 21, 23, 10, 22, 24) |
Add Prospect Modal (AddProspect.modal.vue)
Modal form to create a single new prospect.
Form Fields:
| Field | Type | Validation | Description |
|---|---|---|---|
| Company | Input | Required | Company/organization name |
| Name | Input | Required | Contact person name |
| Input | Required, email | Email address | |
| Locale | Select | Required | Country (from catalog, filtered to active locales) |
| Phone code | Select | Required | Country phone code |
| Phone | Input | Required, numeric | Phone number |
| Monthly shipments | Number input | Optional | Estimated monthly volume |
| Follow-up status | Grouped select | Optional | Initial follow-up category |
Flow:
CSV Import (CsvUpload.modal.vue + ProcessCsv.offcanvas.vue)
Two-step import flow for bulk prospect creation from CSV files.
Step 1: Upload (CsvUpload.modal.vue)
- User selects a CSV file
- Client-side parsing validates the CSV structure
- Calls
POST /prospects/processto validate against the database (duplicate check) - Opens the ProcessCsv offcanvas with results
Step 2: Review & Confirm (ProcessCsv.offcanvas.vue)
Displays three categories:
| Category | Description |
|---|---|
| Inserts | New records ready to create |
| Updates | Existing inactive records that can be reactivated |
| Errors | Duplicates or invalid records with error codes |
User reviews the results and confirms the valid records, which are sent to POST /prospects for batch creation.
Table Preferences
All three views use the useTablePreferences composable with table ID crm-prospects.
Composable: useTablePreferences(tableId, options)
| Feature | Description |
|---|---|
| Column management | Toggle visibility, reorder columns via drag-and-drop |
| Filter management | Toggle filter visibility, reorder filters |
| Persistence | Saves to both localStorage (table-prefs-crm-prospects) and backend API |
| Reset | Restores defaults defined in CRM_TABLE_DEFAULTS |
Settings Panel
Accessed via the gear icon in the header. Opens a panel where users can:
- Toggle columns on/off (List view)
- Toggle filters on/off (all views)
- Drag to reorder columns and filters
- Reset to default configuration
Permissions Summary
| Permission | UI Effect |
|---|---|
all-prospects | Shows date range filter, salesman filter, role filters. Removes 2-month restriction |
crm-edit-salesman-prospect | Shows "Edit salesman" button in sidepanel (prospects only) |
crm-partners | Enables partner data editing in sidepanel |
crm-menu | Enables location CRUD in sidepanel |
Services (Prospect Card Badges)
Prospect cards in the Kanban view display service badges from the following catalog:
| Service | Icon |
|---|---|
| National shipments | Shipping truck |
| International shipments | Airplane |
| FTL - Full Truckload | Moving truck |
| LTL - Less than truckload | Boxes |
| Fulfillment | Warehouse |
| Para paquetes | Open box |
| Ecartpay | Credit card |
| Import services | Globe |
| Shipments from E-commerce | Shopping cart |
