Skip to content

Commit 23664f6

Browse files
committed
[IMP] awesome_dashboard: add core dashboard features, dynamic widget framework
- Implement main dashboard layout with widget container and registry - Add base widget class and example widget following tutorial pattern - Integrate RPC calls for dynamic data fetching in widgets - Setup dashboard service to manage widget lifecycle and events - Configure QWeb templates and assets for widget rendering - Enable mounting and updating of widgets via Owl framework - Implemented real-time updates with reactive state for auto-refreshing dashboard. - Added lazy loading for the dashboard to optimize asset loading and performance. - Refactored dashboard to be generic and dynamic using configurable items. - Implemented adding new dashboard items via dialog - Added functionality to remove dashboard items - Updated UI components to handle dynamic item changes - Ensured state synchronization and user interaction flows
1 parent b2cbb43 commit 23664f6

File tree

23 files changed

+419
-24
lines changed

23 files changed

+419
-24
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
}
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>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use strict";
2+
import { Component, onWillStart, useRef, useEffect } from '@odoo/owl'
3+
import { AssetsLoadingError, loadJS } from '@web/core/assets';
4+
5+
export class PieChartCard extends Component {
6+
7+
static template = "awesome_dashboard.pieChartCard";
8+
static components = {}
9+
static props = {
10+
title: {
11+
type: String
12+
},
13+
value: {
14+
type: Object
15+
}
16+
}
17+
18+
setup() {
19+
this.canvasRef = useRef("canvas");
20+
21+
onWillStart(async () => {
22+
try {
23+
await loadJS(["/web/static/lib/Chart/Chart.js"]);
24+
}
25+
catch (error) {
26+
if (!(error instanceof AssetsLoadingError)) {
27+
throw error;
28+
}
29+
}
30+
})
31+
32+
useEffect(() => this.renderPieChart())
33+
}
34+
35+
renderPieChart() {
36+
const ctx = this.canvasRef.el.getContext("2d");
37+
if (this.chart) {
38+
this.chart.destroy();
39+
}
40+
this.chart = new Chart(ctx, {
41+
type: "pie",
42+
data: {
43+
labels: Object.keys(this.props.value || []), //["S", "M", "L", "XL", "XXL"],
44+
datasets: [{
45+
data: Object.values(this.props.value || []), // [10, 20, 15, 5, 2]
46+
}],
47+
},
48+
options: {
49+
responsive: true,
50+
},
51+
});
52+
}
53+
}
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>
3+
<t t-name="awesome_dashboard.pieChartCard">
4+
<canvas t-ref="canvas" width="300" height="300"></canvas>
5+
</t>
6+
</templates>

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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/** @odoo-module **/
2+
3+
import { Component, useState } from "@odoo/owl";
4+
import { DashboardItem } from "../dashboarditem/dashboarditem";
5+
import { Layout } from '@web/search/layout'
6+
import { PieChartCard } from "../cards/piechartcard/piechart";
7+
import { NumberCard } from "../cards/numbercard/numbercard";
8+
import { ItemConfigurationPopup } from "./item_configuration_popup/item_configuration_popup";
9+
import { _t } from "@web/core/l10n/translation";
10+
import { browser } from "@web/core/browser/browser";
11+
import { registry } from "@web/core/registry";
12+
import { useService } from "@web/core/utils/hooks";
13+
14+
const dashboardRegistry = registry.category("awesome_dashboard.dashboard");
15+
16+
export class AwesomeDashboard extends Component {
17+
static template = "awesome_dashboard.AwesomeDashboard";
18+
static components = { Layout, DashboardItem, PieChartCard, NumberCard, ItemConfigurationPopup }
19+
20+
setup() {
21+
this.action = useService("action");
22+
this.state = useState({ items: [], chart: {}, itemConfigs: {} });
23+
this.statisticsService = useService('statistics');
24+
this.statsResult = useState(this.statisticsService);
25+
this.dialogService = useService("dialog");
26+
this.getBrowserLocalStorageData();
27+
}
28+
29+
get items() {
30+
return dashboardRegistry.get("awesome_dashboard.items");
31+
}
32+
33+
get chart() {
34+
return this.statsResult.orders_by_size;
35+
}
36+
37+
showDialog() {
38+
this.updateItemConfig = this.updateItemConfig.bind(this);
39+
this.closeWrapper = this.closeWrapper.bind(this);
40+
this.dialogService.add(ItemConfigurationPopup, {
41+
items: this.items,
42+
itemConfigs: this.state.itemConfigs,
43+
closeWrapper: this.closeWrapper,
44+
updateItemConfigCallback: this.updateItemConfig
45+
});
46+
}
47+
48+
openCustomers() {
49+
this.action.doAction("contacts.action_contacts");
50+
}
51+
52+
openLeads() {
53+
this.action.doAction({
54+
type: 'ir.actions.act_window',
55+
name: _t('Leads'),
56+
target: 'current',
57+
res_model: 'crm.lead',
58+
views: [[false, 'kanban'], [false, 'list'], [false, 'form']], // [view_id, view_type]
59+
});
60+
}
61+
62+
closeWrapper() {
63+
this.getBrowserLocalStorageData();
64+
}
65+
66+
updateItemConfig(updated_itemconfigs) {
67+
this.state.itemConfigs = updated_itemconfigs;
68+
}
69+
70+
getBrowserLocalStorageData() {
71+
let item_configuration_localdata = browser.localStorage.getItem("awesome_dashboard.item_configuration");
72+
if (item_configuration_localdata) {
73+
this.state.itemConfigs = JSON.parse(item_configuration_localdata);
74+
}
75+
else {
76+
let initialToggleState = {};
77+
for (const item of this.items) {
78+
initialToggleState[item.id] = true;
79+
}
80+
this.state.itemConfigs = initialToggleState;
81+
}
82+
}
83+
}
84+
85+
registry.category("lazy_components").add("awesome_dashboard.dashboard", AwesomeDashboard);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.o_dashboard {
2+
display: flex;
3+
flex-wrap: wrap;
4+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
<t t-name="awesome_dashboard.AwesomeDashboard">
4+
<Layout className="'o_dashboard'" display="{controlPanel: {}}">
5+
<t t-set-slot="layout-buttons">
6+
<button type="button" class="btn btn-primary" t-on-click="openCustomers">Customers</button>
7+
<button type="button" class="btn btn-primary" t-on-click="openLeads">Leads</button>
8+
</t>
9+
<t t-set-slot="control-panel-additional-actions">
10+
<button t-on-click="showDialog" class="d-print-none btn" data-hotkey="u" data-tooltip="Actions">
11+
<i class="fa fa-cog"/>
12+
</button>
13+
</t>
14+
<t t-foreach="items" t-as="item" t-key="item_index">
15+
<DashboardItem t-if="state.itemConfigs[item.id]" size="item.size || 1">
16+
<t t-set="itemProp" t-value="item.props ? item.props(statsResult) : {'data': statistics}"/>
17+
<t t-component="item.Component" t-props="itemProp" />
18+
</DashboardItem>
19+
</t>
20+
</Layout>
21+
</t>
22+
</templates>

0 commit comments

Comments
 (0)