Skip to main content

Quickly Creating Cases from the Salesforce Service Console Using Utility Actions.

Call center agents often face the challenge of efficiently capturing information while on a call, especially when they need to quickly create a case for a customer. To streamline this process, I developed a Lightning Web Component (LWC) that is launched as a utility action within the Salesforce Service Console. This solution enables agents to efficiently handle customer inquiries without losing context or valuable time.

Use Case

In a typical scenario:

  • Dual-Monitor Setup: Call center agents often use two monitors—one for the primary console application and another for utility tools.
  • Customer Interaction: While on a call, an agent may need to search for a customer's contact record using personally identifiable information (PII) like Social Security Number (SSN), Date of Birth (DOB), or Name.
  • System Performance Delays: Contact records may take time to load due to backend processes or system latency. In the meantime, the agent still needs a place to capture customer details and case information as they gather it on the phone.
  • Seamless Case Creation: Once the contact record is loaded, the agent can associate the case with the correct contact and save it instantly.

This utility action allows agents to:

  1. Launch a standalone window for capturing case details.
  2. Associate the case with a contact once the record is retrieved.
  3. Handle errors gracefully when attempting to save a case for invalid or non-contact objects.
  4. Window can also be minimized if needed.

Solution Overview

To implement this functionality, I created:

  1. An Aura Component: Acts as a wrapper to ensure the contact record's ID is reliably passed to the LWC. Using Aura as a wrapper provides consistent access to the recordId, addressing scenarios where LWC alone may fail to retrieve it.
  2. A Lightning Web Component (LWC): Handles the UI and business logic for capturing case details and associating the case with the contact.
  3. Error Handling: Added robust error handling to prevent saving a case if the current context is invalid (e.g., if the agent is viewing a non-contact record).

Key Features

  1. Utility Action Launch:
    • The utility application can be opened in a separate window, allowing agents to multitask efficiently.
  2. Dynamic Contact Association:
    • Agents can search for contacts using PII like SSN, DOB, or Name while capturing case details.
    • Once the contact record is loaded, the agent can link the case to the appropriate contact.
  3. Error Handling:
    • If the save button is clicked while the agent is not viewing a contact record, the system displays a clear error message, preventing incorrect associations.
  4. Performance-Friendly Design:
    • Captures case details even while waiting for the contact record to load, ensuring no data is lost during customer interactions.

Benefits of the Utility Action

  1. Improved Agent Efficiency: Enables agents to multitask and capture data without waiting for the system to load.
  2. Accurate Data Entry: Prevents errors by ensuring cases are associated with valid contact records.
  3. Enhanced User Experience: Provides a seamless, intuitive interface that fits naturally into the agent's workflow.

Why Use Aura as a Wrapper?

While LWC is a powerful framework, there are known inconsistencies when accessing the recordId directly in some scenarios. By using an Aura component as a wrapper:

  • The recordId is consistently available and passed to the LWC.
  • It ensures seamless integration between the Service Console application and the utility action.

Technical Implementation

Here is a high-level summary of the components:

Aura Component

  • Captures the recordId from the current context.
  • Passes the recordId to the LWC.
<aura:component implements="force:hasRecordId,flexipage:availableForAllPageTypes,lightning:utilityItem">
    <aura:attribute name="recordId" type="String" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<c:createCaseUtility contactId="{!v.recordId}"/>
</aura:component>

({
    doInit: function (component, event, helper) {
        var recordId = component.get("v.recordId");
        if (!recordId) {
            console.warn("Record ID is undefined. Ensure the utility is launched from a record context.");
        } else {
            console.log("Record ID retrieved:", recordId);
        }
    }
});

LWC Component

  • Displays a user-friendly interface for capturing case details.
  • Handles case creation and error scenarios gracefully.
<template>
    <lightning-card>
        <div class="slds-m-left_small slds-m-right_small">
            <!-- First Line: Case Number and Reason Code -->
            <div class="slds-grid slds-gutters">
                <div class="slds-col slds-size_1-of-2">
                     <lightning-input type="text" label="Contact Name"
value={contactName} disabled></lightning-input>
                </div>
                <div class="slds-col slds-size_1-of-2">
                     <lightning-input type="text" name = "Subject" label="Subject"
value={subject} data-field="Subject"></lightning-input>
                </div>
               
            </div>
            <div class="slds-grid slds-gutters slds-m-top_small">
                <div class="slds-col slds-size_1-of-2">
                    <lightning-combobox
                        name="Status"
                        label="Status"
                        value={caseStatus}
                        placeholder="Select"
                        options={statusOptions}
                        onchange={handleInputChange}
                        data-field="CaseStatus">
                    </lightning-combobox>
                </div>
                <div class="slds-col slds-size_1-of-2">
                    <lightning-combobox
                        name="Case Origin"
                        label="Case Origin"
                        value={caseOrigin}
                        options={caseOriginOptions}
                        onchange={handleInputChange}
                        data-field="CaseOrigin"
                        >
                    </lightning-combobox>
                </div>
            </div>
            <!-- Second Line: Case Status and Due Date -->
            <div class="slds-grid slds-gutters slds-m-top_small">
               
                <div class="slds-col slds-size_1-of-2">
                    <lightning-combobox
                        name="Case Reason"
                        label="Case Reason"
                        value={caseReason}
                        options={caseReasonOption}
                        onchange={handleInputChange}
                        data-field="CaseReason"
                        >
                    </lightning-combobox>
                </div>
                <div class="slds-col slds-size_1-of-2">
                    <lightning-combobox
                        name="Case Type"
                        label="Case Type"
                        value={caseType}
                        options={caseTypeOptions}
                        onchange={handleInputChange}
                        data-field="CaseType"
                        >
                    </lightning-combobox>
                </div>
            </div>

            <!-- Third Line: Clinical Description -->
            <div class="slds-m-top_small">
                <lightning-textarea
                    label="Description"
                    value={description}
                    onchange={handleInputChange}
                    data-field="Description">
                </lightning-textarea>
            </div>

            <!-- Buttons aligned to the right -->
            <div class="slds-m-top_medium slds-text-align_right">
               <!-- <lightning-button
                    label="Cancel"
                    variant="neutral"
                    onclick={resetForm}>
                </lightning-button>-->
                <lightning-button class="slds-m-left_small"
                    label="Save"
                    variant="brand"
                    onclick={handleSave}>
                </lightning-button>
               
            </div>
        </div>
    </lightning-card>
</template>

JavaScript Controller
import { LightningElement,wire,api } from 'lwc';
import createCase from '@salesforce/apex/CreateCaseController.createCase';
import {ShowToastEvent} from "lightning/platformShowToastEvent";
import { minimize, EnclosingUtilityId } from 'lightning/platformUtilityBarApi';
import { getRecord } from 'lightning/uiRecordApi';
const CONTACT_FIELDS = ['Contact.Name','Contact.Phone','Contact.AccountId'];
export default class CreateCaseUtility extends LightningElement {
    contactName;
    contactPhone;
    accountId;
    caseOrigin;
    caseType;
    description;
    caseReason;
    caseStatus;
    subject;  

   @wire(EnclosingUtilityId) utilityId;
   
    currentRecordId;
    @api
    set contactId(value) {
        if (value !== this.currentRecordId) {
            this.currentRecordId = value;
            //this.validateCaseId();
        }
    }

    get contactId() {
        return this.currentRecordId;
    }
     get caseOriginOptions() {
      return [
          { label: 'Phone', value: 'Phone' },
          { label: 'Email', value: 'Email' },
          { label: 'Web', value: 'Web' }
         
      ];
   }
    get statusOptions() {
      return [
          { label: 'New', value: 'New' },
          { label: 'Working', value: 'Working' },
          { label: 'Escalated', value: 'Escalated' }
         
         
      ];
   }
   get caseReasonOption() {
      return [
          { label: 'Installation', value: 'Installation' },
          { label: 'Feedback', value: 'Feedback' },
          { label: 'Performance', value: 'Performance' }
         
         
      ];
   }
   get caseTypeOptions() {
      return [
          { label: 'Mechanical', value: 'Mechanical' },
          { label: 'Structural', value: 'Structural' },
          { label: 'Electronic', value: 'Electronic' }
         
         
      ];
   }

   @wire(getRecord, { recordId: '$currentRecordId', fields: CONTACT_FIELDS })
    wiredCase({ error, data }) {
        if (data) {
            this.contactName = data.fields.Name.value;
            this.contactPhone = data.fields.Phone.value;
            this.accountId = data.fields.AccountId.value;
           
        } else if (error) {
       
            console.error('Error fetching Case Number:', error);
        }
    }

     // add handleInputChange method to handle input changes.
    handleInputChange(event){
        const field = event.target.dataset.field;
        const value = event.target.value;
        console.log(field, value);
        switch(field){
            case 'CaseOrigin':
                this.caseOrigin = value;
                break;
            case 'CaseType':
                this.caseType = value;
                break;
            case 'Description':
                this.description = value;
                break;
            case 'CaseReason':
                this.caseReason = value;
                break;
            case 'CaseStatus':
                this.caseStatus = value;
                break;
            case 'Subject':
                this.subject =  value;
                break;
            default:
                break;}
    }

     resetForm() {
        //this.currentRecordId = '';
        this.caseStatus = '';
        this.caseReason = '';
        this.description = '';
        this.caseType = '';
        this.caseOrigin = '';
        this.handleMinimize();

    }
    async handleMinimize() {
        try {
            if (!this.utilityId) {
                return;
          }
        // Minimize the utility bar panel
        const isMinimized = await minimize(this.utilityId);
        console.log(`Minimize utility ${isMinimized ? 'successfully' : 'failed'}`);
        }
        catch (error) {
            // handle error
        }
    }
     showToast(title, message, variant) {
        const evt = new ShowToastEvent({
            title,
            message,
            variant,
        });
        this.dispatchEvent(evt);
    }
     validateCaseId() {
        if (!this.currentRecordId || !this.currentRecordId.startsWith('003')) {
            this.showToast('Case Create','Case should be created from Contact','error');
            return
        }
    }

    handleSave() {
        //this.validateCaseId()
        /*if (!this.currentRecordId) {
            this.showToast('Error', 'Case ID is missing in the URL!', 'error');
            return;
        }*/
        if (!this.currentRecordId || !this.currentRecordId.startsWith('003')) {
            this.showToast('Case Create','Case should be created from Contact','error');
            return
        }

    console.log('Handle save menthod caserecordid:', this.currentRecordId);
        const caseRecord = {

            Type: this.caseType,
            Status: this.caseStatus,
            Origin: this.caseOrigin,
            Description: this.description,
            ContactId: this.currentRecordId,
            Reason: this.caseReason,
            AccountId: this.accountId,
            ContactPhone: this.contactPhone,
            Subject: this.subject
             // Replace with the RecordTypeId for "Note"
        };
        console.log('Creating task with record type:',caseRecord);
        createCase({ cse: caseRecord })
            .then(() => {
                this.showToast('Success', 'Case created successfully!', 'success');
                this.resetForm();
               
            })
            .catch((error) => {
                this.showToast('Error', 'Error creating task: ' +
error.body.message, 'error');
            });
    }
}

Apex Class
public class CreateCaseController { @AuraEnabled public static void createCase(Case cse){ Insert cse; } }




Comments

Popular posts from this blog

Style in LWC

 Following are the ways we can apply in CSS in LWC. 1. Inline CCS Inline CSS is not recommended approaches, it is take highest priority among all CSS. style="color:green;font-size:10px;" is inline CSS added to div < template >     < lightning-card title = "Inline CSS" >         < div >             < div style = "color:green;font-size:10px;" > This is inline Style div </ div >         </ div >     </ lightning-card > </ template >  2. External CSS style can be applied to an elements such as h1, p,div span etc. It can applied to class using "." notation. for example .user{} It can also be applied to pseudo class.  for example .user:hover{} Id locator is not being used in LWC to apply style To apply external css, need to create separate CSS file, file name should be exactly matched with component name. for example - If component name is ...

How to Create/Delete file attachments(Content Document) through Apex ?

 There are 3 standard salesforce objects to store file attachments. Content Document, ContentDocumentVersion, ContentDocumentLink.  Here is the article to talk about these objects and relationship.  https://www.forcetalks.com/blog/contentdocument-and-contentversion-in-salesforce-an-overview/ ContentDocumentVersion ContentDocumentLink This post is all about how to create/delete content document though Apex. Here is code snippet // Insert Content Version record ContentVersion contentVersionRec = new ContentVersion(Title='filename',PathOnClient ='FileName.pdf',VersionData = bodyBlob,origin = 'H'); INSERT contentVersionRec; // this will insert one record in ContentDocument and ContentVersion , ContentDocument  is parent and  ContentVersion is child record // get contentdocument id contentVersionRec = [SELECT Id, Title, ContentDocumentId FROM ContentVersion WHERE Id = :contentVersionRec .Id LIMIT 1]; // Create Content Document Link record- This will attach ...

Lifecycle hooks in LWC

There are 3 phase of LWC component  1. Mounting  A. constructor, B. connnectedCallback C. render D. renderedCallback 2. UnMounting  A. disconnectedcallback 3. Error  A.errorcallback Note - render is not lifecycle hook, it is protected method of Lightning element class. Mounting Phase LWC Creation and Render Life cycle Constructor Method ·        This method called when component is instantiated and It flows from parent to child component. ·        Need to call Super() inside constructor method ·        Can’t access any component properties or child component because it’s not ready yet. ·        Host element can be accessed through “this. template” inside constructor method. ·        Don’t add any attributes to host inside constructor C   constructor (){          super (); //...