Select theme:
In this tutorial we will create a nice dashboard page for our CRM application that shows some performance metrics.
Before we start we would need more data in the CRM application. Run it from Radzen and add more opportunities, contacts and tasks. Or you can use this SQL script which comes with sample data (but will delete all existing data).
We will create a custom method which will calculate the monthly stats.
server\project.csproj
with Vusual Studio (or the server
directory with Visual Studio Code).server\Controllers\ServerMethodsController.cs
.using System;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using RadzenCrm.Data;
namespace RadzenCrm.Controllers
{
[Route("api/[controller]/[action]")]
public class ServerMethodsController : Controller
{
private readonly CrmContext context;
public ServerMethodsController(CrmContext context)
{
this.context = context;
}
}
}
This code injects the CrmContext
(Entity Framework context) which we will use to query the database.ServerMethodsController
class.public IActionResult MonthlyStats()
{
double wonOpportunities = context.Opportunities
.Include(opportunity => opportunity.OpportunityStatus)
.Where(opportunity => opportunity.OpportunityStatus.Name == "Won")
.Count();
var totalOpportunities = context.Opportunities.Count();
var ratio = wonOpportunities / totalOpportunities;
var stats = context.Opportunities
.Include(opportunity => opportunity.OpportunityStatus)
.Where(opportunity => opportunity.OpportunityStatus.Name == "Won")
.GroupBy(opportunity => new DateTime(opportunity.CloseDate.Year, opportunity.CloseDate.Month, 1))
.ToList()
.Select(group => new
{
Month = group.Key,
Revenue = group.Sum(opportunity => opportunity.Amount),
Opportunities = group.Count(),
AverageDealSize = group.Average(opportunity => opportunity.Amount),
Ratio = ratio
})
.OrderBy(deals => deals.Month)
.LastOrDefault();
return Json(stats, new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
});
}
Now that the custom method is done it is time to create the UI that will display the stats. We will use a Row component with four Columns. Each column will contain a Card that displays a metric from the monthly stats.
We will describe the process to create one card and then copy paste it.
16px
.
We have created the basic card layout. Now let's add some content that will display the data.
attach_money
.
Set the Width to 100%
and Height to 64px
.
Set the BackgroundColor to Info
(from the predefined colors). Set FontSize to 48px
.
Revenue
,
Size to H4
, TextAlign to Right
and Margin to 0px
.
LAST MONTH
, Color to Cool grey
and FontSize to 12px
.
Value
, Color to Info
, top Margin to 13px
to offset it from the other headings a bit, and FontSize to 24px
.
We have created the card design and it is time to display the Revenue
in the last heading component that we added.
Execute custom method
. Pick MonthlyStats
from the dropdown.monthlyStats
and Value to ${result}
.${montlyStats.Revenue | currency}
. This displays the Revenue
member of the monthlyStats
page property formatted as currency.If you now run the application you should see the following (assuming you have used the sample SQL data linked before).
To display the rest of the monthly stats we should duplicate the column, change the color, heading texts and the monthlyStats
member they display.
Here is how:
shopping_cart
. Set BackgroundColor to Danger
.Opportunities
.${monthlyStats.Opportunities}
and Color to Danger
.
account_valance_wallet
. Set BackgroundColor to Primary
.Average Deal Size
.${monthlyStats.AverageDealSize}
and Color to Primary
.
thumb up
. Set BackgroundColor to Secondary
.Win Rate
.${monthlyStats.Ratio | percent}
and Color to Secondary
.
The final result should look like this at runtime.
server\project.csproj
with Vusual Studio (or the server
directory with Visual Studio Code).server\Controllers\ServerMethodsController.cs
.public IActionResult RevenueByCompany()
{
var result = context.Opportunities
.Include(opportunity => opportunity.Contact)
.GroupBy(opportunity => opportunity.Contact.Company)
.Select(group => new {
Company = group.Key,
Revenue = group.Sum(opportunity => opportunity.Amount)
});
return Json(new { value = result }, new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
});
}
public IActionResult RevenueByEmployee()
{
var result = context.Opportunities
.Include(opportunity => opportunity.User)
.GroupBy(opportunity => $"{opportunity.User.FirstName} {opportunity.User.LastName}")
.Select(group => new {
Employee = group.Key,
Revenue = group.Sum(opportunity => opportunity.Amount)
});
return Json(new { value = result }, new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
});
}
public IActionResult RevenueByMonth()
{
var result = context.Opportunities
.Include(opportunity => opportunity.OpportunityStatus)
.Where(opportunity => opportunity.OpportunityStatus.Name == "Won")
.GroupBy(opportunity => new DateTime(opportunity.CloseDate.Year, opportunity.CloseDate.Month, 1))
.Select(group => new {
Revenue = group.Sum(opportunity => opportunity.Amount),
Month = group.Key
})
.OrderBy(deals => deals.Month);
return Json(new { value = result }, new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
});
}
As in the previous step we will start with the layout - row with three columns.
4
units (1/3th of the available space) and the LG size to 6
(1/2th of the available space).16px
.
Customer life time value
.RevenueByCompany
custom method in the Page Load event. Create a page property called revenueByCompany
with the ${result.value}
.
Column
, Data to ${revenueByCompany}
,
ValueProperty to Revenue
and CategoryProperty to Company
. Set the Width to 100%
.
Revenue
.RevenueByMonth
custom method in the Page Load event. Create a page property called revenueByMonth
with the ${result.value}
.Area
, Data to ${revenueByMonth}
, and CategoryProperty to Month
.Revenue by employee
.RevenueByEmployee
custom method in the Page Load event. Create a page property called revenueByEmployee
with the ${result.value}
.Bar
, Data to ${revenueByEmployee}
, and CategoryProperty to Employee
.Here is how the end result should look like.
As usual we start with the layout.
6
.First we need to get the data.
getOpportunities
data source method in the page Load event.OpportunityStatus,Contact
(we want to "join" those tables and request them in the response).10
- we want only 10
opportunities.CloseDate desc
because we want the 10 most recent opportunities.getOpportunitiesResult
with Value ${result.value}
.Then we can add a DataGrid.
${getOpportunitiesResult}
. Check AllowSorting.${data.Contact.FirstName} ${data.Contact.LastName}
. Set the Title to Contact
and SortProperty to Contact.FirstName
.Amount
. Set Template to ${data.Amount | currency}
.OpportunityStatus.Name
and Title to Status
.CloseDate
.getTasks
data source method in the page Load event.Opportunity,Opportunity($expand=User,Contact),TaskType
(yes that is a nested $expand as we need to expand the User
and Status
of the Opportunity
).DueDate desc
.getTasksResult
with Value ${result.value}
.Then duplicate the other column that has the Opportunities DataGrid.
Active Tasks
.${getTasksResult}
.
Employee
and SortProperty to Opportunity.User.FirstName
. Edit its Template.${data.Opportunity.User.Picture}
. Set Width and Height to 30px
and BorderRadius to 15px
.${data.Opportunity.User.FirstName} ${data.Opportunity.User.LastName}
Title
.Opportunity.Name
. Edit its Template.${data.Opportunity.Name}
. Go to Style and set Display to block
. This will make the next label appear on a new line.${data.Opportunity.Contact.FirstName} ${data.Opportunity.Contact.LastName}
and end template editing.
DueDate
.The Dashboard is now complete! Here is how it will look at runtime.
It is time for the finishing touches.
Radzen is free to use. You can also test the premium features for 15 days.
Download NowSelect theme: