Skip to content

Commit 7d760e7

Browse files
committed
[ADD] awesome_owl: Build a customizable dashboard
- Redesigned layout with navigation buttons - Integrated RPC calls to fetch and display dashboard stats - Added data caching and real-time updating pie chart - Implemented lazy loading and modular architecture - Enabled add/remove functionality for dashboard widgets
1 parent 26ae352 commit 7d760e7

19 files changed

+397
-38
lines changed

awesome_dashboard/__manifest__.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
# -*- coding: utf-8 -*-
22
{
3-
'name': "Awesome Dashboard",
4-
5-
'summary': """
3+
"name": "Awesome Dashboard",
4+
"summary": """
65
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
76
""",
8-
9-
'description': """
7+
"description": """
108
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
119
""",
12-
13-
'author': "Odoo",
14-
'website': "https://www.odoo.com/",
15-
'category': 'Tutorials/AwesomeDashboard',
16-
'version': '0.1',
17-
'application': True,
18-
'installable': True,
19-
'depends': ['base', 'web', 'mail', 'crm'],
20-
21-
'data': [
22-
'views/views.xml',
10+
"author": "Odoo",
11+
"website": "https://www.odoo.com/",
12+
"category": "Tutorials/AwesomeDashboard",
13+
"version": "0.1",
14+
"application": True,
15+
"installable": True,
16+
"depends": ["base", "web", "mail", "crm"],
17+
"data": [
18+
"views/views.xml",
2319
],
24-
'assets': {
25-
'web.assets_backend': [
26-
'awesome_dashboard/static/src/**/*',
20+
"assets": {
21+
"web.assets_backend": [
22+
"awesome_dashboard/static/src/**/*",
23+
("remove", "awesome_dashboard/static/src/dashboard/**/*"),
24+
],
25+
"awesome_dashboard.dashboard": [
26+
"awesome_dashboard/static/src/dashboard/**/*",
2727
],
2828
},
29-
'license': 'AGPL-3'
29+
"license": "AGPL-3",
3030
}

awesome_dashboard/static/src/dashboard.js

Lines changed: 0 additions & 10 deletions
This file was deleted.

awesome_dashboard/static/src/dashboard.xml

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/** @odoo-module **/
2+
3+
import { Component, useState } from "@odoo/owl";
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 { Dialog } from "@web/core/dialog/dialog";
9+
import { CheckBox } from "@web/core/checkbox/checkbox";
10+
import { browser } from "@web/core/browser/browser";
11+
12+
13+
class AwesomeDashboard extends Component {
14+
static template = "awesome_dashboard.AwesomeDashboard";
15+
static components = { Layout, DashboardItem }
16+
17+
setup() {
18+
this.display = {
19+
controlPanel: {}
20+
}
21+
this.action = useService("action")
22+
this.dialog = useService("dialog")
23+
24+
this.statistics = useState(useService("awesome_dashboard.statistics"))
25+
this.items = registry.category("awesome_dashboard").getAll();
26+
this.state = useState({
27+
disabledItems: browser.localStorage.getItem("disabledDashboardItems")?.split(",") || []
28+
});
29+
}
30+
31+
openCustomers() {
32+
this.action.doAction("base.action_partner_form");
33+
}
34+
35+
openLeads() {
36+
this.action.doAction({
37+
type: 'ir.actions.act_window',
38+
name: 'Leads View',
39+
res_model: 'crm.lead',
40+
views: [[false, 'list'], [false, 'form'],],
41+
});
42+
}
43+
44+
openConfiguration() {
45+
this.dialog.add(ConfigurationDialog, {
46+
items: this.items,
47+
disabledItems: this.state.disabledItems,
48+
onUpdateConfiguration: this.updateConfiguration.bind(this),
49+
})
50+
}
51+
52+
53+
updateConfiguration(newDisabledItems) {
54+
this.state.disabledItems = newDisabledItems;
55+
}
56+
}
57+
58+
class ConfigurationDialog extends Component {
59+
static template = "awesome_dashboard.configuration_dialog"
60+
static components = { Dialog, CheckBox }
61+
static props = ["close", "items", "disabledItems", "onUpdateConfiguration"];
62+
63+
setup() {
64+
this.items = useState(this.props.items.map((item) => {
65+
return {
66+
...item,
67+
enabled: !this.props.disabledItems.includes(item.id),
68+
}
69+
}));
70+
}
71+
72+
onChange(checked, changedItem) {
73+
changedItem.enabled = checked;
74+
const newDisabledItems = Object.values(this.items).filter(
75+
(item) => !item.enabled
76+
).map((item) => item.id)
77+
78+
browser.localStorage.setItem(
79+
"disabledDashboardItems",
80+
newDisabledItems,
81+
);
82+
83+
this.props.onUpdateConfiguration(newDisabledItems);
84+
}
85+
86+
done() {
87+
this.props.close();
88+
}
89+
}
90+
91+
registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.o_dashboard {
2+
background-color: gray;
3+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<templates xml:space="preserve">
3+
4+
<t t-name="awesome_dashboard.AwesomeDashboard">
5+
6+
<Layout className="'o_dashboard h-100'" display="props.display">
7+
8+
<div class="d-flex gap-1 justify-content-start bg-white align-items-center py-3 px-4">
9+
<button class="btn btn-primary" t-on-click="openCustomers">Customers</button>
10+
<button class="btn btn-primary" t-on-click="openLeads">Leads</button>
11+
<h3>Dashbaord</h3>
12+
<i class="fa fa-gear" t-on-click="openConfiguration"/>
13+
</div>
14+
15+
<div t-if="statistics.isReady" class="d-flex flex-wrap justify-content-start align-item-center mt-3">
16+
17+
<t t-foreach="items" t-as="item" t-key="item.id">
18+
<DashboardItem t-if="!state.disabledItems.includes(item.id)" size="item.size || 1">
19+
<t t-set="itemProp" t-value="item.props ? item.props(statistics) : {'data': statistics}"/>
20+
<t t-component="item.Component" t-props="itemProp" />
21+
</DashboardItem>
22+
</t>
23+
24+
</div>
25+
26+
</Layout>
27+
</t>
28+
29+
<t t-name="awesome_dashboard.configuration_dialog">
30+
<Dialog title="'Dashboard items configuration'">
31+
Which cards do you whish to see ?
32+
33+
<t t-foreach="items" t-as="item" t-key="item.id">
34+
<CheckBox value="item.enabled" onChange="(ev) => this.onChange(ev, item)">
35+
<t t-esc="item.description" />
36+
</CheckBox>
37+
</t>
38+
39+
<t t-set-slot="footer">
40+
<button class="btn btn-primary" t-on-click="done">
41+
Done
42+
</button>
43+
</t>
44+
</Dialog>
45+
</t>
46+
47+
</templates>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { NumberCard } from "../number_card/number_card";
2+
import { PieChartCard } from "../pie_chart_card/pie_chart_card";
3+
import { registry } from "@web/core/registry"
4+
5+
const items = [
6+
{
7+
id: "average_quantity",
8+
description: "Average amount of t-shirt",
9+
Component: NumberCard,
10+
props: (data) => ({
11+
title: "Average amount of t-shirt by order this month",
12+
value: data.average_quantity,
13+
})
14+
},
15+
{
16+
id: "average_time",
17+
description: "Average time for an order",
18+
Component: NumberCard,
19+
props: (data) => ({
20+
title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
21+
value: data.average_time,
22+
})
23+
},
24+
{
25+
id: "number_new_orders",
26+
description: "New orders this month",
27+
Component: NumberCard,
28+
props: (data) => ({
29+
title: "Number of new orders this month",
30+
value: data.nb_new_orders,
31+
})
32+
},
33+
{
34+
id: "cancelled_orders",
35+
description: "Cancelled orders this month",
36+
Component: NumberCard,
37+
props: (data) => ({
38+
title: "Number of cancelled orders this month",
39+
value: data.nb_cancelled_orders,
40+
})
41+
},
42+
{
43+
id: "amount_new_orders",
44+
description: "amount orders this month",
45+
Component: NumberCard,
46+
props: (data) => ({
47+
title: "Total amount of new orders this month",
48+
value: data.total_amount,
49+
})
50+
},
51+
{
52+
id: "orders_by_size",
53+
description: "shirt orders by size",
54+
Component: PieChartCard,
55+
size: 2,
56+
props: (data) => ({
57+
title: "Shirt orders by size",
58+
value: data.orders_by_size,
59+
})
60+
},
61+
]
62+
63+
items.forEach((item) => {
64+
registry.category("awesome_dashboard").add(item.id, item)
65+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { reactive } from "@odoo/owl"
2+
import { registry } from "@web/core/registry"
3+
import { rpc } from "@web/core/network/rpc"
4+
5+
const statisticsService = {
6+
7+
start() {
8+
const statistics = reactive({ isReady: false });
9+
async function handleData() {
10+
const result = await rpc("/awesome_dashboard/statistics")
11+
Object.assign(statistics, result, { isReady: true })
12+
}
13+
14+
setInterval(handleData, 10 * 60 * 1000)
15+
handleData()
16+
17+
return statistics;
18+
}
19+
}
20+
21+
registry.category("services").add("awesome_dashboard.statistics", statisticsService)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { registry } from "@web/core/registry";
2+
import { LazyComponent } from "@web/core/assets";
3+
import { Component, xml } from "@odoo/owl";
4+
5+
export class AwesomeDashboardLoader extends Component {
6+
static components = { LazyComponent };
7+
static template = xml`
8+
<LazyComponent bundle="'awesome_dashboard.dashboard'" Component="'AwesomeDashboard'" />
9+
`;
10+
}
11+
12+
registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Component } from "@odoo/owl"
2+
3+
export class DashboardItem extends Component {
4+
static template = "awesome_dashbaord.dashboard_item"
5+
6+
static props = {
7+
slot: {
8+
type: Object,
9+
shape: {
10+
default: Object
11+
}
12+
},
13+
size: {
14+
type: Number,
15+
default: 1,
16+
optional: true
17+
}
18+
}
19+
20+
}

0 commit comments

Comments
 (0)