Skip to content

Commit f31960e

Browse files
committed
[IMP] awesome_dashboard: implement dashboard layout, memoized RPC, pie chart
- Added Customers button to open a kanban view with all the customers. - Added Leads button to open dynamic action on `crm.lead` model with list and form view. - Created a reusable DashboardItem card component with a configurable width based on its size prop, fetch statistics via RPC from `/awesome_dashboard/statistics`, and display multiple metric cards showing key monthly order stats. - Implement and register an `awesome_dashboard.statistics` service that memoizes RPC calls to `/awesome_dashboard/statistics` for consistent cached results, then integrate it into the Dashboard component to load and display the statistics. - Build a PieChart component that loads Chart.js in `onWillStart`, renders a canvas, and draws a pie chart of t-shirt sales per size from `/awesome_dashboard/statistics`, displaying it inside a DashboardItem.
1 parent 22e9d19 commit f31960e

File tree

7 files changed

+226
-3
lines changed

7 files changed

+226
-3
lines changed
Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,103 @@
11
/** @odoo-module **/
22

3-
import { Component } from "@odoo/owl";
3+
import { _t } from "@web/core/l10n/translation";
44
import { registry } from "@web/core/registry";
55
import { Layout } from "@web/search/layout";
6+
import { useService } from "@web/core/utils/hooks";
7+
import { DashboardItem } from "./dashboard_item/dashboard_item";
8+
import { PieChart } from "./pie_chart/pie_chart";
9+
import { Component, onWillStart, useState } from "@odoo/owl";
610

711
class AwesomeDashboard extends Component {
812
static template = "awesome_dashboard.AwesomeDashboard";
9-
static components = { Layout };
13+
static components = { Layout, DashboardItem, PieChart };
14+
15+
setup() {
16+
this.action = useService("action");
17+
this.statistics = useService("awesome_dashboard.statistics");
18+
this.cards = useState([
19+
{
20+
id: 1,
21+
size: 0,
22+
title: "Number of new orders this month",
23+
value: 0,
24+
},
25+
{
26+
id: 2,
27+
size: 0,
28+
title: "Total amount of new order this month",
29+
value: 0,
30+
},
31+
{
32+
id: 3,
33+
size: 0,
34+
title: "Average amount of t-shirt by order this month",
35+
value: 0,
36+
},
37+
{
38+
id: 4,
39+
size: 0,
40+
title: "Number of cancelled orders this month",
41+
value: 0,
42+
},
43+
{
44+
id: 5,
45+
size: 0,
46+
title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
47+
value: 0,
48+
}
49+
]);
50+
51+
onWillStart(async () => {
52+
this.result = await this.statistics.loadStatistics();
53+
this.cards = [
54+
{
55+
id: 1,
56+
size: 2,
57+
title: "Number of new orders this month",
58+
value: this.result.nb_new_orders,
59+
},
60+
{
61+
id: 2,
62+
size: 2,
63+
title: "Total amount of new order this month",
64+
value: this.result.total_amount,
65+
},
66+
{
67+
id: 3,
68+
size: 2,
69+
title: "Average amount of t-shirt by order this month",
70+
value: this.result.average_quantity,
71+
},
72+
{
73+
id: 4,
74+
size: 2,
75+
title: "Number of cancelled orders this month",
76+
value: this.result.nb_cancelled_orders,
77+
},
78+
{
79+
id: 5,
80+
size: 2,
81+
title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
82+
value: this.result.average_time,
83+
}
84+
]
85+
});
86+
}
87+
88+
openCustomers() {
89+
this.action.doAction("base.action_partner_form");
90+
}
91+
92+
openLeads() {
93+
this.action.doAction({
94+
type: 'ir.actions.act_window',
95+
name: _t('Leads'),
96+
target: 'current',
97+
res_model: 'crm.lead',
98+
views: [[false, 'kanban'], [false, 'list'], [false, 'form']], // [view_id, view_type]
99+
});
100+
}
10101
}
11102

12103
registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard);

awesome_dashboard/static/src/dashboard.xml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,29 @@
22
<templates xml:space="preserve">
33

44
<t t-name="awesome_dashboard.AwesomeDashboard">
5-
<Layout className="'o_dashboard h-100'"/>
5+
<Layout display="{ controlPanel: {} }" className="'o_dashboard h-100'">
6+
<t t-set-slot="layout-buttons">
7+
<button type="button" class="btn btn-primary" t-on-click="openCustomers">Customers</button>
8+
<button type="button" class="btn btn-primary" t-on-click="openLeads">Leads</button>
9+
</t>
10+
11+
<div class="d-flex flex-wrap gap-3 m-3">
12+
<div class="d-flex flex-wrap gap-3">
13+
<t t-foreach="cards" t-as="card" t-key="card.id">
14+
<DashboardItem size="card.size">
15+
<p class="mb-0" style="text-align: left;" t-esc="card.title"/>
16+
<t t-set-slot="value">
17+
<t t-esc="card.value"/>
18+
</t>
19+
</DashboardItem>
20+
</t>
21+
</div>
22+
<DashboardItem size="2">
23+
<p class="mb-0 text-center">T-Shirt orders by size</p>
24+
<PieChart tshirtSales="this.result.orders_by_size"/>
25+
</DashboardItem>
26+
</div>
27+
</Layout>
628
</t>
729

830
</templates>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/** @odoo-module **/
2+
3+
import { Component } from "@odoo/owl";
4+
5+
export class DashboardItem extends Component {
6+
static template = "awesome_dashboard.DashboardItem";
7+
static props = {
8+
size: {
9+
type: Number,
10+
optional: true,
11+
},
12+
slots: {
13+
type: Object,
14+
}
15+
}
16+
17+
setup() {
18+
const size = this.props.size || 1;
19+
this.width = 18*size;
20+
}
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
<t t-name="awesome_dashboard.DashboardItem">
4+
<div class="card align-items-center" t-att-style="`width: ${this.width}rem`">
5+
<div class="card-body">
6+
<t t-slot="default"/>
7+
<h1 class="text-center text-sucess" style="color: green;">
8+
<t t-slot="value"/>
9+
</h1>
10+
</div>
11+
</div>
12+
</t>
13+
</templates>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/** @odoo-module **/
2+
3+
import { Component, onWillStart, useRef, useEffect } from "@odoo/owl";
4+
import { loadJS } from '@web/core/assets';
5+
6+
export class PieChart extends Component {
7+
static template = "awesome_dashboard.PieChart";
8+
9+
static props = {
10+
tshirtSales: {
11+
type: Object,
12+
}
13+
}
14+
15+
setup() {
16+
this.canvasRef = useRef("canvas");
17+
onWillStart(async () => {
18+
await loadJS("/web/static/lib/Chart/Chart.js")
19+
});
20+
21+
useEffect(() => {
22+
this.createChart();
23+
return () => this.chart?.destroy();
24+
}, () => []);
25+
}
26+
27+
getChartConfig() {
28+
if (!this.props.tshirtSales) return {};
29+
return {
30+
type: 'pie',
31+
data: {
32+
labels: Object.keys(this.props.tshirtSales),
33+
datasets: [{
34+
data: Object.values(this.props.tshirtSales),
35+
}]
36+
},
37+
options: {
38+
aspectRatio: 2,
39+
}
40+
}
41+
}
42+
43+
createChart() {
44+
if (this.chart) {
45+
this.chart.destroy();
46+
}
47+
const config = this.getChartConfig();
48+
this.chart = new Chart(this.canvasRef.el, config);
49+
}
50+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
<t t-name="awesome_dashboard.PieChart">
4+
<canvas height="500" width="500" t-ref="canvas"/>
5+
</t>
6+
</templates>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/** @odoo-module **/
2+
3+
import { registry } from "@web/core/registry";
4+
import { rpc } from "@web/core/network/rpc";
5+
import { memoize } from "@web/core/utils/functions";
6+
7+
const loadStatistics = async() => {
8+
const result = await rpc("/awesome_dashboard/statistics");
9+
return result;
10+
}
11+
12+
export const loadStatService = {
13+
start () {
14+
return {
15+
loadStatistics: memoize(loadStatistics)
16+
}
17+
}
18+
}
19+
20+
registry.category("services").add("awesome_dashboard.statistics", loadStatService);

0 commit comments

Comments
 (0)