Skip to content

Commit 05c1a02

Browse files
committed
[IMP] awesome_dashboard: make dashboard extensible and implement persistent dashboard.
- Registered all the dashboard items in awesome_dashboard registry. - Used the newly registered dashboard item registry to import all dashboard items. - Adds a gear icon in the control panel to open a settings dialog. - The dialog lists dashboard items with checkboxes and an Apply button. - Unchecked items are stored in local storage and filtered out from the Dashboard view. - Removed trailing whitespaces and fixed linting issues.
1 parent 33668c7 commit 05c1a02

File tree

10 files changed

+129
-71
lines changed

10 files changed

+129
-71
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { loadJS } from '@web/core/assets';
55

66
export class PieChart extends Component {
77
static template = "awesome_dashboard.PieChart";
8-
8+
99
static props = {
1010
title: {
1111
type: String,

awesome_dashboard/static/src/dashboard/dashboard.js

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,65 @@ import { Layout } from "@web/search/layout";
66
import { useService } from "@web/core/utils/hooks";
77
import { DashboardItem } from "../dashboard_item/dashboard_item";
88
import { PieChart } from "./PieChartCard/pie_chart";
9-
import { dashboardCards } from "./dashboard_items";
109
import { Component, useState } from "@odoo/owl";
10+
import { DashboardDialog } from "../dashboard_dialog/dashboard_dialog";
11+
import { browser } from "@web/core/browser/browser";
1112

1213
class AwesomeDashboard extends Component {
1314
static template = "awesome_dashboard.AwesomeDashboard";
14-
static components = { Layout, DashboardItem, PieChart };
15+
static components = { Layout, DashboardItem, PieChart, DashboardDialog };
1516

1617
setup() {
1718
this.action = useService("action");
19+
this.dialog = useService("dialog");
1820
this.statistics = useService("awesome_dashboard.statistics");
1921
this.result = useState(this.statistics.stats);
20-
this.items = useState(dashboardCards);
22+
this.state = useState({ metricConfigs: {} });
23+
this.items = registry.category("awesome_dashboard_cards").get("awesome_dashboard.Cards");
24+
this.getBrowserCookie();
2125
}
2226

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-
]
27+
openDialog() {
28+
this.dialog.add(DashboardDialog, {
29+
metrics: this.items,
30+
metricConfigs: this.state.metricConfigs,
31+
closeDialog: this.closeDialog.bind(this),
32+
updateMetricConfigCallback: this.updateMetricConfig.bind(this)
33+
});
34+
}
35+
36+
closeDialog() {
37+
this.getBrowserCookie();
38+
}
39+
40+
updateMetricConfig(updated_metricConfig) {
41+
this.state.metricConfigs = updated_metricConfig;
42+
this.setBrowserCookie();
6343
}
6444

65-
get chart() {
66-
return this.result.orders_by_size;
45+
setBrowserCookie() {
46+
browser.localStorage.setItem(
47+
"awesome_dashboard.metric_configs", JSON.stringify(this.state.metricConfigs)
48+
);
49+
}
50+
51+
getBrowserCookie() {
52+
const metric_cookie_data = browser.localStorage.getItem("awesome_dashboard.metric_configs");
53+
if (metric_cookie_data) {
54+
this.state.metricConfigs = JSON.parse(metric_cookie_data);
55+
} else {
56+
const initialMetricState = {};
57+
for (const metric of this.items) {
58+
initialMetricState[metric.id] = true;
59+
}
60+
this.state.metricConfigs = initialMetricState;
61+
}
6762
}
6863

6964
openCustomers() {
7065
this.action.doAction("base.action_partner_form");
7166
}
72-
67+
7368
openLeads() {
7469
this.action.doAction({
7570
type: 'ir.actions.act_window',

awesome_dashboard/static/src/dashboard/dashboard.xml

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,23 @@
77
<button type="button" class="btn btn-primary" t-on-click="openCustomers">Customers</button>
88
<button type="button" class="btn btn-primary" t-on-click="openLeads">Leads</button>
99
</t>
10+
<t t-set-slot="control-panel-additional-actions">
11+
<button class="d-print-none btn p-0 border-0" data-hotkey="u" t-on-click="openDialog" data-tooltip="Actions">
12+
<i class="fa fa-fw fa-cog"/>
13+
</button>
14+
</t>
1015

1116
<div class="d-flex flex-wrap gap-3 m-3">
1217
<div class="d-flex flex-wrap gap-3">
1318
<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 t-if="state.metricConfigs[item.id]">
20+
<DashboardItem size="item.size || 1">
21+
<t t-set="itemProp" t-value="item.props ? item.props(result) : {'data': result}"/>
22+
<t t-component="item.Component" t-props="itemProp"/>
23+
</DashboardItem>
24+
</t>
1925
</t>
20-
<!-- <t t-foreach="cards" t-as="card" t-key="card.id">
21-
<DashboardItem size="card.size">
22-
<p class="mb-0" style="text-align: left;" t-esc="card.title"/>
23-
<t t-set-slot="value">
24-
<t t-esc="card.value"/>
25-
</t>
26-
</DashboardItem>
27-
</t> -->
2826
</div>
29-
<!-- <DashboardItem size="2">
30-
<p class="mb-0 text-center">T-Shirt orders by size</p>
31-
<PieChart tshirtSales="chart"/>
32-
</DashboardItem> -->
3327
</div>
3428
</Layout>
3529
</t>

awesome_dashboard/static/src/dashboard/dashboard_items.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
import { NumberCard } from "./NumberCard/number_card";
33
import { PieChart } from "./PieChartCard/pie_chart";
4+
import { registry } from "@web/core/registry";
45

56
export const dashboardCards = [
67
{
@@ -64,3 +65,5 @@ export const dashboardCards = [
6465
}),
6566
}
6667
]
68+
69+
registry.category("awesome_dashboard_cards").add("awesome_dashboard.Cards", dashboardCards);

awesome_dashboard/static/src/dashboard/statistics_service.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import { registry } from "@web/core/registry";
44
import { rpc } from "@web/core/network/rpc";
55
import { reactive } from "@odoo/owl";
66

7-
let statisticsObj = reactive({
7+
const statisticsObj = reactive({
88
nb_new_orders: 0,
99
total_amount: 0,
1010
average_quantity: 0,
1111
nb_cancelled_orders: 0,
1212
average_time: 0,
1313
orders_by_size: {}
14-
}, () => {
15-
console.log("Observed");
1614
});
1715

1816
const loadStatistics = async() => {
@@ -34,7 +32,7 @@ export const loadStatService = {
3432
return {
3533
get stats() {
3634
return statisticsObj;
37-
}
35+
},
3836
}
3937
}
4038
}

awesome_dashboard/static/src/dashboard_action.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,3 @@ export class LazyDashboardLoader extends Component {
1010
}
1111

1212
registry.category("actions").add("awesome_dashboard.dashboard", LazyDashboardLoader);
13-
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Component, useState } from "@odoo/owl";
2+
import { Dialog } from "@web/core/dialog/dialog";
3+
import { CheckBox } from "@web/core/checkbox/checkbox";
4+
5+
export class DashboardDialog extends Component {
6+
static template = "awesome_dashboard.DashboardDialog";
7+
static components = { Dialog, CheckBox }
8+
static props = {
9+
close: Function,
10+
updateMetricConfigCallback: Function,
11+
closeDialog: Function,
12+
metrics: {
13+
type: Object,
14+
},
15+
metricConfigs: {
16+
type: Object,
17+
}
18+
}
19+
20+
setup() {
21+
this.state = useState({
22+
metricConfigs: this.props.metricConfigs
23+
})
24+
}
25+
26+
applyChanges() {
27+
this.props.updateMetricConfigCallback(this.state.metricConfigs);
28+
this.props.close();
29+
}
30+
31+
toggleMetricConfig(itemID, checked) {
32+
this.state.metricConfigs[itemID] = checked;
33+
}
34+
35+
closeDialog() {
36+
this.props.closeDialog();
37+
this.props.close();
38+
}
39+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
<t t-name="awesome_dashboard.DashboardDialog">
4+
<Dialog>
5+
<t t-set-slot="header">
6+
<h3 class="modal-title text-break">
7+
Dashboard Item Configuration
8+
</h3>
9+
<button type="button" class="btn-close" aria-label="close" t-on-click="closeDialog"/>
10+
</t>
11+
<div>
12+
<p>
13+
Which metrics do you want to see?
14+
</p >
15+
<t t-foreach="props.metrics" t-as="metric" t-key="metric.id">
16+
<div class="d-flex gap-2 m-2">
17+
<CheckBox value="state.metricConfigs[metric.id]" onChange.bind="(checked) => this.toggleMetricConfig(metric.id, checked)">
18+
<t t-esc="metric.description"/>
19+
</CheckBox>
20+
</div>
21+
</t>
22+
</div>
23+
<t t-set-slot="footer">
24+
<button class="button btn btn-lg btn-primary" t-on-click="applyChanges">
25+
Apply
26+
</button>
27+
</t>
28+
</Dialog>
29+
</t>
30+
</templates>

estate/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
'installable': True,
1111
'license': 'LGPL-3',
1212
'data': [
13+
'security/ir.model.access.csv',
1314
'views/estate_property_offer_views.xml',
1415
'views/estate_property_type_views.xml',
1516
'views/estate_property_tags_views.xml',
1617
'views/res_users_views.xml',
1718
'views/estate_property_views.xml',
1819
'views/estate_menus_views.xml',
19-
'security/ir.model.access.csv',
2020
]
2121
}

estate/models/estate_property.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def _onchange_garden(self):
105105
# CRUD methods
106106
# -----------------------
107107
@api.ondelete(at_uninstall=False)
108-
def _unlink_except_new_or_cancelled(self):
108+
def _unlink_if_new_or_cancelled(self):
109109
for record in self:
110110
if record.state not in ['new', 'cancelled']:
111111
raise UserError('You can only delete properties that are in New or Cancelled state.')

0 commit comments

Comments
 (0)