Project: Account Management Dashboard
By the end of this lesson, you'll be able to:
- Combine sorting, row actions, inline editing, search, and pagination in one datatable
- Persist inline edits using updateRecord
- Apply the performance practices from this module
Prerequisites: "Custom Data Types and Performance Considerations"
What We're Building
An accountManagementDashboard component that:
- Lists Accounts with sortable Name, Industry, and Annual Revenue columns (Lessons 1-2).
- Offers a search box that filters the visible rows (Lesson 3).
- Supports inline editing of Industry, persisted via
updateRecord(Module 6). - Has a row action to delete an Account, backed by
deleteRecord(Module 6) and a confirmation modal (Module 9). - Follows the performance practices from Lesson 4 — a stable
columnsclass field andhide-checkbox-columnsince bulk selection isn't needed here.
The Component
import { LightningElement, wire } from 'lwc';
import { updateRecord, deleteRecord } from 'lightning/uiRecordApi';
import { refreshApex } from '@salesforce/apex';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import getAccounts from '@salesforce/apex/AccountSearchController.search';
import ConfirmDialog from 'c/confirmDialog';
const COLUMNS = [
{ label: 'Name', fieldName: 'accountName', type: 'text', sortable: true },
{ label: 'Industry', fieldName: 'industry', type: 'text', editable: true, sortable: true },
{ label: 'Annual Revenue', fieldName: 'revenue', type: 'currency', sortable: true },
{
type: 'action',
typeAttributes: { rowActions: [{ label: 'Delete', name: 'delete' }] },
},
];
export default class AccountManagementDashboard extends LightningElement {
columns = COLUMNS;
searchTerm = '';
sortedBy;
sortedDirection;
draftValues = [];
wiredAccounts;
@wire(getAccounts, { searchTerm: '' })
wiredResult(result) {
this.wiredAccounts = result;
}
get accounts() {
return this.wiredAccounts?.data ?? [];
}
get filteredAccounts() {
if (!this.searchTerm) {
return this.accounts;
}
const term = this.searchTerm.toLowerCase();
return this.accounts.filter(acc => acc.accountName.toLowerCase().includes(term));
}
handleSearchChange(event) {
this.searchTerm = event.target.value;
}
handleSort(event) {
const { fieldName, sortDirection } = event.detail;
this.sortedBy = fieldName;
this.sortedDirection = sortDirection;
}
async handleSave(event) {
const draftValues = event.detail.draftValues;
try {
await Promise.all(draftValues.map(draft => updateRecord({ fields: { Id: draft.accountId ?? draft.id, Industry: draft.industry } })));
this.draftValues = [];
await refreshApex(this.wiredAccounts);
this.dispatchEvent(new ShowToastEvent({ title: 'Accounts Updated', variant: 'success' }));
} catch (error) {
this.dispatchEvent(new ShowToastEvent({
title: 'Could Not Save Changes',
message: error.body?.message ?? 'An unexpected error occurred.',
variant: 'error',
}));
}
}
async handleRowAction(event) {
if (event.detail.action.name !== 'delete') {
return;
}
const result = await ConfirmDialog.open({ size: 'small', message: 'Delete this account?' });
if (result !== 'confirm') {
return;
}
try {
await deleteRecord(event.detail.row.accountId ?? event.detail.row.id);
await refreshApex(this.wiredAccounts);
this.dispatchEvent(new ShowToastEvent({ title: 'Account Deleted', variant: 'success' }));
} catch (error) {
this.dispatchEvent(new ShowToastEvent({
title: 'Could Not Delete Account',
message: error.body?.message ?? 'An unexpected error occurred.',
variant: 'error',
}));
}
}
}
<lightning-input label="Search Accounts" onchange={handleSearchChange}></lightning-input>
<lightning-datatable
key-field="accountId"
data={filteredAccounts}
columns={columns}
sorted-by={sortedBy}
sorted-direction={sortedDirection}
onsort={handleSort}
draft-values={draftValues}
onsave={handleSave}
onrowaction={handleRowAction}
hide-checkbox-column>
</lightning-datatable>
COLUMNS is defined once as a module-level constant, then assigned to a plain columns class field (Lesson 4's performance practice) rather than recomputed on every render. Promise.all (Module 4) persists every edited row concurrently rather than one at a time.
Exercise
Add hide-checkbox-column to the markup above if it were missing, and explain as a comment why it is appropriate here.
Show hint
This dashboard never uses bulk row selection.
Exercise
Challenge: explain, as a comment, why handleSave uses Promise.all instead of awaiting each updateRecord call one at a time in a loop.
Show hint
Recall Module 4's Promises lesson and Module 7's bulkification lesson.
Project: Account Management Dashboard Quiz
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 hands-on capstone assembling every datatable technique from this module into one realistic, production-quality Account management dashboard.