Build a Sales Dashboard from Scratch
Odoo18 Dashboard OWL drives modern interface design. In this tutorial, we cover how to use Odoo18 Dashboard OWL to build a full-featured sales dashboard step by step. First, we set up the XML templates. Then, we integrate OWL components with JavaScript to fetch and render real data. Finally, we customize charts and interactivity for a seamless user experience. By the end, you will master Odoo18 Dashboard OWL for any reporting need.
source code : https://github.com/afjol77/odoo_sales_dashboard
Why Use Odoo18 Dashboard OWL?
First, Odoo18 Dashboard OWL leverages the lightweight OWL framework for reactive UI. Moreover, it integrates seamlessly with Odoo’s ORM and services. As a result, you get:
- Reactive components that update on data change.
- Modular templates in XML for clear separation of view logic.
- Chart.js integration to visualize metrics.
- Action services for navigation on user clicks.
Therefore, Odoo18 Dashboard OWL helps you build dashboards faster and maintain them easily.
Project Structure Overview
Next, let’s inspect the key files under odoo_sales_dashboard/static/src:
odoo_sales_dashboard/
├─ static/
│ ├─ src/
│ │ ├─ css/
│ │ │ └─ sales_dashboard.css
│ │ ├─ js/
│ │ │ └─ sales_dashboard.js
│ │ └─ xml/
│ │ └─ sales_dashboard_view.xml
- XML templates define the markup and CSS links.
- JavaScript implements the OWL component logic.
- CSS file styles cards, charts, and layout.
Defining the XML View Templates
In sales_dashboard_view.xml, we declare OWL templates:
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="sales_dashboard_template" xml:space="preserve">
<t t-name="sales_dashboard" owl="1">
<div class="parent-div" style="height:100% !important; overflow-y:scroll !important; font-family:roboto; background-color: #F7F7F7;">
<link rel="stylesheet" type="text/css" href="odoo_sales_dashboard/static/src/css/sales_dashboard.css"/>
<t t-call="sales-section"/>
</div>
</t>
...
</templates>
Parent Wrapper and Styles
First, the <t t-name="sales_dashboard"> template wraps all content. It links the CSS file and calls the main sales-section.
Sales Cards Section
Within <t t-name="sales-section"> we include:
<section id="sales_section" class="dashboard-container">
<h2 class="text-center fw-bold mb-4" style="font-size: 24px;">Sales Dashboard</h2>
<t t-call="sales_cards"/>
<t t-call="sales_charts"/>
<t t-call="top_sales_reps_section"/>
</section>
sales_cards
This template renders KPI cards:
<t t-name="sales_cards">
<div class="row mb-3">
<!-- Total Sales Card -->
<div class="col-lg-6 col-md-6 mb-3">
<div class="card sales-card light-blue">
<h3>Total Sales</h3>
<div class="card-data"><p><t t-esc="currency_symbol"/> <t t-esc="total_sales_amount"/></p></div>
</div>
</div>
<!-- More cards… -->
</div>
</t>
- We bind OWL expressions via
<t t-esc="..."/>. - We include click handlers with
t-on-click.
Sales Charts Section
Under <t t-name="sales_charts"> we define four chart containers:
<div class="row mb-3">
<div class="col-lg-6"><canvas t-ref="canvasMonthlySales"></canvas></div>
<div class="col-lg-6"><canvas t-ref="canvasTopSellingProducts"></canvas></div>
<!-- Additional charts... -->
</div>
- Each
<canvas>has at-reffor OWL to reference in JS.
Top Sales Reps Table
Finally, <t t-name="top_sales_reps_section"> shows a table:
<table class="table">
<thead>…</thead>
<tbody>
<t t-foreach="top_sales_reps" t-as="rep">
<tr><td><t t-esc="rep.sales_rep_name"/></td><td><t t-esc="rep.sales_volume"/></td></tr>
</t>
</tbody>
</table>
Implementing the OWL Component (JavaScript)
Now, let’s inspect sales_dashboard.js:
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { onWillStart, onMounted, useRef } from "@odoo/owl";
export class SalesDashboard extends Component {
setup() {
this.orm = useService("orm");
this.canvasMonthlySales = useRef('canvasMonthlySales');
// … other canvas refs …
onWillStart(async () => {
await loadBundle("web.chartjs_lib");
const data = await this.orm.call('sale.order','get_sales_dashboard_data');
Object.assign(this, data);
});
onMounted(() => {
this.renderMonthlySales();
// … other render calls …
});
}
// … click handlers …
renderMonthlySales() {
const ctx = this.canvasMonthlySales.el.getContext('2d');
new Chart(ctx, {/*… options …*/});
}
// … other render methods …
}
SalesDashboard.template = 'sales_dashboard';
registry.category("actions").add("sales_dashboard", SalesDashboard);
Setup and Data Fetching
- We use
useService("orm")to callsale.order.get_sales_dashboard_dataon the server. - We assign the returned metrics directly onto
this.
Interactivity: Click Handlers
We define methods like _onClickLeads() that call:
this.env.services.action.doAction({
name: _t("Leads"),
type: 'ir.actions.act_window',
res_model: 'crm.lead',
view_mode: 'tree',
domain: [['type','=','lead']],
});
This opens the standard Odoo list view.
Rendering Monthly Sales Chart
Inside renderMonthlySales():
const ctx = this.canvasMonthlySales.el.getContext('2d');
const gradient = ctx.createLinearGradient(0,0,0,400);
gradient.addColorStop(0,'rgba(89,50,234,0.7)');
gradient.addColorStop(1,'rgba(89,50,234,0.1)');
new Chart(ctx, {
type: 'line',
data: { labels: monthNames, datasets: [{ data: salesAmounts, backgroundColor: gradient, /*...*/ }] },
options: { responsive:true, plugins:{ legend:{position:'bottom'}}, scales:{ y:{ title:{text:`Sales (${this.currency_symbol})`} }} }
});
- We create a gradient fill for the line chart.
- We format tooltips to show currency.
Rendering Top Selling Products Chart
The renderTopSellingProducts() method sorts products, slices top 5, and displays a bar chart:
new Chart(this.canvasTopSellingProducts.el, {
type: 'bar',
data: { labels: productNames, datasets:[{ label:`Revenue (${this.currency_symbol})`, data:productRevenues }]},
options: { plugins:{ tooltip:{ callbacks:{ title: ctx=> this.top_selling_products[ctx[0].dataIndex].product_name }}}}
});
We use callbacks to show full product names in tooltips.
Rendering Fulfillment Efficiency Pie
In renderFulfillmentEfficiency():
new Chart(this.canvasFulfillmentEfficiency.el, {
type: 'pie',
data: { labels:['On-time','Delayed'], datasets:[{ data:[onTimeCount,delayedCount], backgroundColor:['#4CAF50','#FF6F61'] }]},
options: { plugins:{ title:{ text:`Efficiency: ${efficiencyPercentage.toFixed(1)}%` }}}
});
This pie chart displays shipment performance.
Rendering Sales by Customer Bar
Finally, renderSalesByCustomer() shows a horizontal bar chart of sales volume by customer:
new Chart(this.canvasSalesByCustomer.el, {
type: 'bar',
data: { labels: customers, datasets:[{ data:salesVolumes }]},
options: { indexAxis:'y', scales:{ x:{ beginAtZero:true, title:{ text:`Volume (${this.currency_symbol})`} } }}
});
Styling the Dashboard
In sales_dashboard.css, apply card colors and layout:
.sales-card { padding: 1rem; border-radius: 8px; transition: transform .2s; }
.sales-card.light-blue { background: #E0F2FF; }
.sales-card:hover { transform: scale(1.02); cursor: pointer; }
.dashboard-container { padding: 2rem; }
You can adjust fonts, spacing, and responsive breakpoints as needed.
Putting It All Together
- Enable the action in your module’s
__manifest__.pyunderir.actions.clientto register the dashboard. - Restart Odoo and update your module:
./odoo-bin -u odoo_sales_dashboard -d your_db - Access the dashboard via a menu item that references the
sales_dashboardaction.
You now have a dynamic Odoo18 Dashboard OWL that displays KPIs, charts, and detailed tables.
Next Steps & Advanced Tips
- Add Filtering Controls: Use OWL state hooks to filter data by date or salesperson.
- Cache Data: Implement server-side caching for heavy queries.
- Export to PDF: Integrate wkhtmltopdf to allow users to export dashboard snapshots.
- Mobile Optimizations: Adjust CSS grid and canvas sizes for small screens.
Resources & Further Reading
- OWL Framework: https://github.com/odoo/owl
- Chart.js Documentation: https://www.chartjs.org/docs/latest/
- Odoo OWL Integration Guide: https://www.odoo.com/documentation/15.0/developer/howtos/owl.html
With this guide, you can confidently build, customize, and extend Odoo18 Dashboard OWL components to fit any reporting requirement. Happy coding!
Discover more from teguhteja.id
Subscribe to get the latest posts sent to your email.

