Advanced 40 min read

Project: Sales Pipeline Dashboard

By the end of this lesson, you'll be able to:

  • Aggregate Opportunity data by stage using an Apex aggregate query
  • Render grouped totals in a responsive SLDS grid layout
  • Recognize why this kind of aggregation genuinely requires Apex, not LDS

Prerequisites: "Project: Case Management Workspace"

What We're Building

A salesPipelineDashboard component showing each Opportunity stage as a column with its total value and count — Module 7 Lesson 1's "aggregation across many records" signal, now actually built.

The Apex Layer

public inherited sharing class PipelineSelector {
    public static List<AggregateResult> stageTotals() {
        return [
            SELECT StageName stage, COUNT(Id) opportunityCount, SUM(Amount) totalAmount
            FROM Opportunity
            WHERE IsClosed = false
            WITH SECURITY_ENFORCED
            GROUP BY StageName
        ];
    }
}

public inherited sharing class PipelineService {
    public static List<StageSummary> getStageSummaries() {
        List<StageSummary> summaries = new List<StageSummary>();
        for (AggregateResult ar : PipelineSelector.stageTotals()) {
            summaries.add(new StageSummary(
                (String) ar.get('stage'),
                (Integer) ar.get('opportunityCount'),
                (Decimal) ar.get('totalAmount')
            ));
        }
        return summaries;
    }
}

public with sharing class PipelineController {
    @AuraEnabled(cacheable=true)
    public static List<StageSummary> getStageSummaries() {
        return PipelineService.getStageSummaries();
    }
}

GROUP BY StageName with SUM/COUNT is precisely the aggregation LDS's simple record-read model can't express — the Selector (Module 16) isolates this specific aggregate query construction.

The Component

<div class="slds-grid slds-wrap slds-gutters">
    <template for:each={stages} for:item="stage">
        <div key={stage.stageName} class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3">
            <div class="slds-box">
                <h3>{stage.stageName}</h3>
                <p>{stage.formattedTotal} — {stage.opportunityCount} deals</p>
            </div>
        </div>
    </template>
</div>

The responsive grid (Module 9) stacks to one column on mobile and three on desktop; formattedTotal would apply the Intl.NumberFormat currency pattern from Module 11's internationalization lesson.

Exercise

Explain, as a comment, why GROUP BY StageName with SUM(Amount) cannot be expressed through Lightning Data Service.

Show hint

Recall Module 6/7's discussion of LDS's limits.

APEX

Exercise

Challenge: why is getStageSummaries marked cacheable=true rather than called imperatively?

Show hint

Recall Module 7's decision table.

APEX

Project: Sales Pipeline Dashboard Quiz

1. What SOQL feature aggregates Opportunity totals by stage?

2. What isolates the aggregate query construction in this project's Apex?

3. Why does this dashboard genuinely require Apex rather than LDS alone?

4. What makes the stage columns stack to one column on mobile and three on desktop?

5. Why is getStageSummaries called via @wire rather than imperatively?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A pipeline dashboard grouping Opportunities by stage with aggregate totals — exactly the kind of cross-record aggregation Module 7 identified as a genuine signal that Apex, not LDS, is required.