Skip to content

CRM Analytics — UI Screens & Flows

Frontend documentation for the CRM Analytics dashboard: component hierarchy, state management, chart interactions, and the cross-filtering drilldown pattern.

Routes

PathComponentNotes
/crmindex.vueAnalyticsViewDefault landing view when the Analytics tab is active in the CRM page
/crm/forecastforecast/index.vueDeprecated — immediately redirects to /crm

The analytics view is mounted as one of the view-switcher tabs inside frontend/client/src/views/crm/index.vue. It is rendered with v-if (not keep-alive), so the component is fully destroyed and re-mounted on each tab switch.

Component Hierarchy

index.vue (CRM root)
└── AnalyticsView.vue          # Locale bridge + event bus listener
    └── ClientAnalyticsView.vue  # Full dashboard: filters, KPIs, charts, table
        ├── KpiCard.vue          # KPI card with trend badge
        ├── Card.vue             # Bootstrap wrapper card
        ├── Chart.vue            # Highcharts wrapper
        ├── Select2.vue          # Salesman + tier multi-select dropdowns
        └── VueDatePicker        # Date range picker with presets

AnalyticsView.vue

Responsibilities:

  • Reads the active locale from the _slocale cookie on mount.
  • Subscribes to the changeLocale event bus event and re-exposes the locale ID as a prop to ClientAnalyticsView.
  • Exposes a reload() method so the parent tab switcher can trigger a data refresh without unmounting.

Props passed to ClientAnalyticsView:

PropTypeDescription
currentLocaleIdNumber | nullActive locale ID from the cookie, or null for "all locales"

ClientAnalyticsView.vue

All dashboard logic lives here: filters, API calls, chart option builders, and drilldown interactions.

Injected: $sidepanel — the shared CRM sidepanel reference, used to open a lead detail from the active leads table.

Key reactive state:

RefTypeDescription
dateRange[Date, Date]Currently selected date range
selectedSalesmanstring | nullSelected executive ID (from by_executive list)
selectedTiersstring[]Active tier filters (array of numeric string values '0''5')
selectedStagestring | nullActive virtual stage or pipeline stage name
selectedProductstring | nullActive product translation tag
analyticsDataobjectFull API response, initialized to empty shape
loadingbooleanControls the spinner overlay

Dashboard Sections

1. Filter Bar

Located at the top of the dashboard. Filters are independent controls; any change immediately calls loadData().

ControlComponentBehavior
ExecutiveSelect2 (single-select, searchable)Filters by executive_id. Options populated from analyticsData.by_executive (returned by the API with locale filter only).
TierSelect2 (multi-select tags)Allows selecting multiple tiers (0–5). Values sent as comma-separated string to tier query param.
Date rangeVueDatePicker (multi-calendar range, auto-apply)YYYY-MM-DD format. Preset dates: Today, Last 30 Days, This Month, Last 3 Months, Last 6 Months.

The locale filter is not a user-visible dropdown — it is driven by the global locale cookie (_slocale) and passed in via the currentLocaleId prop.

2. Active Filters Pill Bar

Visible only when at least one interactive filter is active (selectedStage, selectedProduct, selectedSalesman, or selectedTiers). Appears between the filter bar and the KPI cards.

Each active filter renders as a dismissible pill. Clicking the × on a pill removes that specific filter and reloads. A "Clear all filters" button removes all interactive filters at once.

3. KPI Cards

Four KpiCard components arranged in a grid row.

CardValue sourceTrend badge
Total LeadsanalyticsData.kpis.total_leads.current(current - previous) / previous * 100 — green when positive, red when negative
Total ShipmentsanalyticsData.kpis.total_shipments.currentSame trend logic
Avg Shipment CostanalyticsData.kpis.avg_shipment_cost.current (formatted as USD)Same trend logic
Total LostanalyticsData.kpis.total_lost.current (shown in red)Inverted: positive delta = red, negative = green (fewer losses is better)

The "Total Lost" card is clickable — clicking it sets selectedStage = 'customer_lost' and reloads, acting as a shortcut to the lost-leads drilldown.

4. Sales Funnel + Outcome Charts

Two cards rendered side by side.

Left card — Sales Funnel table:

  • Renders analyticsData.sales_funnel as a sortable table with stage name, count, and an inline bar showing percentage.
  • Each row is clickable: toggles selectedStage (second click on the same stage clears it) and reloads.
  • The "active" row gets a table-active highlight class.
  • Stats footer below the table shows three extra KPIs inline:
    • avg_closing_days — average days to close
    • advancement_rate — percentage of leads that won or recharged (green ≥ 50%, yellow < 50%)
    • in_recharge — count of leads with a recharge but no shipment yet

Right card — Outcome Pie chart (Highcharts pie):

  • Four slices: Won (green #22C55E), Lost (red #EF4444), In Recharge (amber #F59E0B), Remaining (grey #6B7280).
  • Clicking a slice toggles the corresponding selectedStage filter (clicking "Remaining" clears the stage filter instead).

5. Salesman By Stage Chart

A Highcharts stacked horizontal bar chart (type: 'bar'). Height scales dynamically: max(250, executives * 40 + 80).

  • Three stacked series: Won (green), Lost (red), In Progress (grey).
  • Sorted by total (won + lost + remaining) descending.
  • Clicking any bar segment filters by the salesman name (onSalesmanClick).

6. Active Tiers & Product Breakdown

Two charts in a 4:8 column layout.

Tier chart (Highcharts column, left):

  • X-axis: tiers in canonical order blank, T0, T1, T2, T3, T4, T5 (active leads only — excludes won/lost).
  • Clicking a column toggles that tier in selectedTiers (multi-select supported).
  • When a tier is selected, unselected columns become light grey (#CBD5E1), the selected column retains blue with a border.

Product chart (Highcharts column, right, dual-axis):

  • Top 10 products by total_value.
  • Left Y-axis: total account value in USD (formatted as $Xk).
  • Right Y-axis: lead count.
  • Two series per product: total value (purple #8B5CF6) and lead count (green #10B981).
  • Clicking a column toggles selectedProduct.
  • When a product is selected, other products fade to 30% opacity.

7. Active Leads Quick View

A table showing the top 100 oldest open leads (not won, not lost), sorted by age_days descending (sortedActiveLeads computed).

ColumnDrilldown behavior
CompanyDisplay only
ExecutiveClickable — filters by that salesman (onSalesmanClickByName)
TierClickable — toggles tier in selectedTiers
StageClickable badge — toggles selectedStage
AgeDisplay only (days since creation)
Eye iconOpens the CRM Sidepanel for that lead

Cross-Chart Drilldown Pattern

Every interactive element on the dashboard shares the same state machine. This is the core UX mechanic of the analytics view.

Click handlers:

HandlerTriggered byAction
onStageClick(stageName)Funnel table row, outcome pie slice, lost KPI card, active leads stage badgeToggle selectedStage
onSalesmanClick(name)Executive bar chart, active leads executive cellResolve name → ID, toggle selectedSalesman
onTierClick(tierKey)Tier column chart, active leads tier cellAdd/remove tierKey from selectedTiers array
onProductClick(productName)Product chart columnsToggle selectedProduct

Stale filter auto-clear: after each loadData(), if selectedSalesman is no longer in by_executive, or selectedStage is not in sales_funnel, or selectedProduct is not in by_product, the corresponding filter is automatically cleared. This prevents the dashboard from being stuck in an impossible state when the new dataset is smaller.

Important: any active interactive filter (selectedSalesman, selectedTiers, selectedStage, selectedProduct) bypasses the snapshot cache. The API always calls computeCrmAnalytics() live.

Data Loading

loadData() is the single function responsible for all API calls. It runs on:

  • Component mount (onMounted)
  • Any filter change
  • Locale change (via changeLocale event propagated from AnalyticsView)
  • External call via the exposed reload() method
javascript
const loadData = async () => {
  loading.value = true;
  const params = {
    startDate, endDate,          // from dateRange
    localeIds,                   // from currentLocaleId prop
    salesman, tier, stage, product  // from selected* refs (omitted when null/empty)
  };
  analyticsData.value = await api.prospects.getClientAnalytics(params);
  // auto-clear stale filters
};

Lead Score Badge

The lead score badge is not rendered inside ClientAnalyticsView itself, but it is used across the CRM module (Kanban cards, list view rows) using the shared helper from frontend/client/src/views/crm/constants/leadScore.constants.js.

javascript
import { getScoreBadgeClass } from '@/views/crm/constants/leadScore.constants';

// Usage
<span class="badge" :class="`bg-${getScoreBadgeClass(lead.score)}`">
  {{ lead.score }}
</span>
Return valueCSS classDisplayed as
'success'bg-successGreen
'warning'bg-warningYellow
'info'bg-infoBlue
'secondary'bg-secondaryGrey

Thresholds: score >= 8 → success, score >= 5 → warning, score >= 3 → info, else secondary.

  • Module Overview — Materialized table ETL, query formulas, and key decisions
  • API EndpointsGET /prospects/client-analytics parameter and response reference
  • CRM UI — Other CRM views: Kanban, List, Calendar, Sidepanel
  • User Guide — End-user instructions for using the dashboard

Envia Admin