Skip to content

Commit 7b79e29

Browse files
committed
[IMP]: awesome_dashboard: Implement real life update, lazy loading and generic dashboard.
- Convert statistics into a shared reactive object in the service, reload it every 10 minutes, and let any displayed dashboard update instantly via useState. - Bundle all dashboard files together and load them on demand using LazyComponent so the code only loads when the user actually views the dashboard. - Replace hard-coded dashboard content with a configurable items list, using dynamic components and props to support different types of cards like NumberCard and PieChartCard.
1 parent f31960e commit 7b79e29

File tree

14 files changed

+255
-135
lines changed

14 files changed

+255
-135
lines changed

awesome_dashboard/__manifest__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
'web.assets_backend': [
2626
'awesome_dashboard/static/src/**/*',
2727
],
28+
'awesome_dashboard.dashboard:': [
29+
'awesome_dashboard/static/src/dashboard/**/*'
30+
],
2831
},
2932
'license': 'AGPL-3'
3033
}

awesome_dashboard/static/src/dashboard.js

Lines changed: 0 additions & 103 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Component } from '@odoo/owl'
2+
3+
export class NumberCard extends Component {
4+
static template = "awesome_dashboard.numberCard"
5+
static components = {}
6+
static props = {
7+
title: {
8+
type: String
9+
},
10+
value: {
11+
type: Number | String
12+
}
13+
}
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates>
3+
<t t-name="awesome_dashboard.numberCard">
4+
<h2 class="text-lg-center">
5+
<t t-esc="props.title"/>
6+
</h2>
7+
<div class="text-success text-bold text-lg-center" style="font-size: 38px; font-weight: bold">
8+
<t t-esc="props.value"/>
9+
</div>
10+
</t>
11+
</templates>

awesome_dashboard/static/src/pie_chart/pie_chart.js renamed to awesome_dashboard/static/src/dashboard/PieChartCard/pie_chart.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ export class PieChart extends Component {
77
static template = "awesome_dashboard.PieChart";
88

99
static props = {
10-
tshirtSales: {
11-
type: Object,
10+
title: {
11+
type: String,
12+
},
13+
value: {
14+
type: String | Number,
1215
}
1316
}
1417

@@ -21,17 +24,17 @@ export class PieChart extends Component {
2124
useEffect(() => {
2225
this.createChart();
2326
return () => this.chart?.destroy();
24-
}, () => []);
27+
});
2528
}
2629

2730
getChartConfig() {
28-
if (!this.props.tshirtSales) return {};
31+
if (!this.props.value) return {};
2932
return {
3033
type: 'pie',
3134
data: {
32-
labels: Object.keys(this.props.tshirtSales),
35+
labels: Object.keys(this.props.value),
3336
datasets: [{
34-
data: Object.values(this.props.tshirtSales),
37+
data: Object.values(this.props.value),
3538
}]
3639
},
3740
options: {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/** @odoo-module **/
2+
3+
import { _t } from "@web/core/l10n/translation";
4+
import { registry } from "@web/core/registry";
5+
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 "./PieChartCard/pie_chart";
9+
import { dashboardCards } from "./dashboard_items";
10+
import { Component, useState } from "@odoo/owl";
11+
12+
class AwesomeDashboard extends Component {
13+
static template = "awesome_dashboard.AwesomeDashboard";
14+
static components = { Layout, DashboardItem, PieChart };
15+
16+
setup() {
17+
this.action = useService("action");
18+
this.statistics = useService("awesome_dashboard.statistics");
19+
this.result = useState(this.statistics.stats);
20+
this.items = useState(dashboardCards);
21+
}
22+
23+
get cards() {
24+
return [
25+
{
26+
id: "nb_new_orders",
27+
description: "New t-shirt orders this month.",
28+
size: 2,
29+
props: (data) => ({
30+
title: "Number of new orders this month",
31+
value: this.result.nb_new_orders,
32+
}),
33+
},
34+
{
35+
id: "total_amount",
36+
description: "New orders this month.",
37+
size: 2,
38+
title: "Total amount of new order this month",
39+
value: this.result.total_amount,
40+
},
41+
{
42+
id: "average_quantity",
43+
description: "Average amount of t-shirt.",
44+
size: 2,
45+
title: "Average amount of t-shirt by order this month",
46+
value: this.result.average_quantity,
47+
},
48+
{
49+
id: "nb_cancelled_orders",
50+
description: "Cancelled orders this month.",
51+
size: 2,
52+
title: "Number of cancelled orders this month",
53+
value: this.result.nb_cancelled_orders,
54+
},
55+
{
56+
id: "average_time",
57+
description: "Average time for an order to reach conclusion (sent or cancelled).",
58+
size: 2,
59+
title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
60+
value: this.result.average_time,
61+
}
62+
]
63+
}
64+
65+
get chart() {
66+
return this.result.orders_by_size;
67+
}
68+
69+
openCustomers() {
70+
this.action.doAction("base.action_partner_form");
71+
}
72+
73+
openLeads() {
74+
this.action.doAction({
75+
type: 'ir.actions.act_window',
76+
name: _t('Leads'),
77+
target: 'current',
78+
res_model: 'crm.lead',
79+
views: [[false, 'kanban'], [false, 'list'], [false, 'form']], // [view_id, view_type]
80+
});
81+
}
82+
}
83+
84+
registry.category("lazy_components").add("awesome_dashboard.LazyComponent", AwesomeDashboard);

awesome_dashboard/static/src/dashboard.xml renamed to awesome_dashboard/static/src/dashboard/dashboard.xml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,26 @@
1010

1111
<div class="d-flex flex-wrap gap-3 m-3">
1212
<div class="d-flex flex-wrap gap-3">
13-
<t t-foreach="cards" t-as="card" t-key="card.id">
13+
<t t-foreach="items" t-as="item" t-key="item.id">
14+
<DashboardItem size="item.size || 1">
15+
<t t-set="itemProp" t-value="item.props ? item.props(result) : {'data': result}"/>
16+
<t t-log="itemProp"/>
17+
<t t-component="item.Component" t-props="itemProp"/>
18+
</DashboardItem>
19+
</t>
20+
<!-- <t t-foreach="cards" t-as="card" t-key="card.id">
1421
<DashboardItem size="card.size">
1522
<p class="mb-0" style="text-align: left;" t-esc="card.title"/>
1623
<t t-set-slot="value">
1724
<t t-esc="card.value"/>
1825
</t>
1926
</DashboardItem>
20-
</t>
27+
</t> -->
2128
</div>
22-
<DashboardItem size="2">
29+
<!-- <DashboardItem size="2">
2330
<p class="mb-0 text-center">T-Shirt orders by size</p>
24-
<PieChart tshirtSales="this.result.orders_by_size"/>
25-
</DashboardItem>
31+
<PieChart tshirtSales="chart"/>
32+
</DashboardItem> -->
2633
</div>
2734
</Layout>
2835
</t>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
import { NumberCard } from "./NumberCard/number_card";
3+
import { PieChart } from "./PieChartCard/pie_chart";
4+
5+
export const dashboardCards = [
6+
{
7+
id: "nb_new_orders",
8+
description: "New t-shirt orders this month.",
9+
Component: NumberCard,
10+
size: 2,
11+
props: (data) => ({
12+
title: "Number of new orders this month",
13+
value: data.nb_new_orders,
14+
}),
15+
},
16+
{
17+
id: "total_amount",
18+
description: "New orders this month.",
19+
Component: NumberCard,
20+
size: 2,
21+
props: (data) => ({
22+
title: "Total amount of new order this month",
23+
value: data.total_amount,
24+
}),
25+
},
26+
{
27+
id: "average_quantity",
28+
description: "Average amount of t-shirt.",
29+
Component: NumberCard,
30+
size: 2,
31+
props: (data) => ({
32+
title: "Average amount of t-shirt by order this month",
33+
value: data.average_quantity,
34+
}),
35+
},
36+
{
37+
id: "nb_cancelled_orders",
38+
description: "Cancelled orders this month.",
39+
Component: NumberCard,
40+
size: 2,
41+
props: (data) => ({
42+
title: "Number of cancelled orders this month",
43+
value: data.nb_cancelled_orders,
44+
}),
45+
},
46+
{
47+
id: "average_time",
48+
description: "Average time for an order to reach conclusion (sent or cancelled).",
49+
Component: NumberCard,
50+
size: 2,
51+
props: (data) => ({
52+
title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
53+
value: data.average_time,
54+
}),
55+
},
56+
{
57+
id: "orders_by_size",
58+
description: "T-shirt orders grouped by their size.",
59+
Component: PieChart,
60+
size: 2,
61+
props: (data) => ({
62+
title: "T-shirt order by size",
63+
value: data.orders_by_size,
64+
}),
65+
}
66+
]

0 commit comments

Comments
 (0)