Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bc2c78f
[ADD] Add Counter component
Codoeh Mar 24, 2026
ea0db04
[ADD] Add Card component
Codoeh Mar 24, 2026
082fa45
[ADD] Add Card props validation and small improvements in view
Codoeh Mar 24, 2026
f3ec4f0
[ADD] The sum of two Counter
Codoeh Mar 24, 2026
691cdb4
[ADD] Add TodoItem & TodoList components
Codoeh Mar 25, 2026
ec5cb93
[ADD] Add useAutofocus in utils and attach it to input in TodoList
Codoeh Mar 25, 2026
a23cdad
[ADD] Responsive checkbox to TodoItem
Codoeh Mar 25, 2026
6d94e6e
[ADD] Add remove button to TodoList
Codoeh Mar 25, 2026
45bb3e8
[ADD] Add slot to Card component
Codoeh Mar 25, 2026
f0f7512
[STY] Counter/Playground: style fixes
Codoeh Mar 25, 2026
c66bb60
[IMP] awesome_owl: Add toggle button to the Card component
Codoeh Mar 26, 2026
a6cfd75
[IMP] awesome_dashboard: Update to use the Layout component.
Codoeh Mar 26, 2026
9a85be7
[IMP] awesome_dashboard: Add layout buttons to CustomerView and Leads
Codoeh Mar 26, 2026
7c2e735
[IMP] awesome_dashboard: Add DashboardItem component
Codoeh Mar 26, 2026
bd84c5a
[ADD] awesome_dashboard: add statistics cards to dashboard
Codoeh Mar 26, 2026
24a1e3b
[ADD] awesome_dashboard: add statisticsService
Codoeh Mar 26, 2026
5d32db9
[ADD] awesome_dashboard: add PieChart component to dashboard
Codoeh Mar 26, 2026
a03cefb
[IMP] awesome_dashboard: add auto refresh update to statisticsService
Codoeh Mar 27, 2026
2534c83
[IMP] awesome_dashboard: Add lazy loading to Dashboard
Codoeh Mar 27, 2026
2394c4e
[IMP] awesome_dashboard: Make Dashboard generic.
Codoeh Mar 27, 2026
010ad75
[IMP] awesome_dashboard: Make Dashboard extensible
Codoeh Mar 27, 2026
6d36d5c
[ADD] awesome_dashboard: Add Dashboard configuration.
Codoeh Mar 27, 2026
ac0a5c5
[FIX] awesome_dashboard: Fixed wrong names in assets bundle configura…
Codoeh Mar 29, 2026
dcf37d8
[IMP] awesome_dashboard: Add translation ability to Dashboard
Codoeh Mar 29, 2026
c6c116b
[IMP] awesome_dashboard: Make Dashboard responsive.
Codoeh Mar 29, 2026
264857c
[IMP] awesome_dashboard: Add Dashboard configuration save and load on…
Codoeh Mar 29, 2026
56d066c
[ADD] Add: awesome_owl, website_airproof, awesome_gallery, awesome_ka…
Codoeh Mar 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
Empty file added addons/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions addons/awesome_dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-

from . import controllers
from . import models
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
],
'assets': {
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
'awesome_dashboard/static/src/dashboard_action.js',
],
'awesome_dashboard.assets_dashboard': [
'awesome_dashboard/static/src/dashboard/**/*',
],
},
'license': 'AGPL-3'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

logger = logging.getLogger(__name__)


class AwesomeDashboard(http.Controller):
@http.route('/awesome_dashboard/statistics', type='jsonrpc', auth='user')
def get_statistics(self):
Expand All @@ -34,3 +35,10 @@ def get_statistics(self):
'total_amount': random.randint(100, 1000)
}

@http.route('/awesome_dashboard/save_config', type='jsonrpc', auth='user')
def save_config(self, config):
request.env.user.dashboard_config = config

@http.route('/awesome_dashboard/load_config', type='jsonrpc', auth='user')
def load_config(self):
return request.env.user.dashboard_config
1 change: 1 addition & 0 deletions addons/awesome_dashboard/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import res_users
7 changes: 7 additions & 0 deletions addons/awesome_dashboard/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import models, fields


class ResUsers(models.Model):
_inherit = 'res.users'

dashboard_config = fields.Char(string='Dashboard Configuration', default='[]')
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Component, onWillStart, onMounted, useRef} from "@odoo/owl";
import {loadJS} from "@web/core/assets";


export class PieChart extends Component {
static template = "awesome_dashboard.pie_chart";
static props = {
data: Object,
}

setup() {
this.canvasRef = useRef("canvas");

onWillStart(async () => {
await loadJS("/web/static/lib/Chart/Chart.js")
})

onMounted(() => {
new Chart(this.canvasRef.el, {
type: "pie",
data: {
labels: Object.keys(this.props.data),
datasets: [{
data: Object.values(this.props.data),
}]
},
})
})
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">
hello dashboard
<t t-name="awesome_dashboard.pie_chart">
<canvas t-ref="canvas"/>
</t>

</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Component} from "@odoo/owl";
import {PieChart} from "../pie_chart/pie_chart";

export class PieChartCard extends Component {
static template = "awesome_dashboard.pie_chart_card";
static props = {
title: {type: String, optional: true},
data: {type: Object},
};
static components = {PieChart}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.pie_chart_card">
<t t-esc="props.title"/>
<PieChart data="props.data"/>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {Component, useState} from "@odoo/owl";
import {Dialog} from "@web/core/dialog/dialog";
import {rpc} from "@web/core/network/rpc";

export class ConfigDialog extends Component {
static template = "awesome_dashboard.config_dialog";
static props = {
items: {type: Array},
close: {type: Function},
onApply: {type: Function},
disabledItems: {type: Array},
};
static components = {Dialog}

setup() {
this.state = useState({
disabledItems: [...this.props.disabledItems]
})
}

async apply() {
await rpc("/awesome_dashboard/save_config", {
config: JSON.stringify(this.state.disabledItems)
});
this.props.onApply(this.state.disabledItems);
this.props.close();
}

toggleItem(id, isChecked) {
if (isChecked) {
this.state.disabledItems = this.state.disabledItems.filter(i => i !== id);
} else {
this.state.disabledItems = [...this.state.disabledItems, id];
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.config_dialog">
<Dialog title="'Dashboard Configuration'">
<span>Which cards do you wish to see?</span>
<t t-foreach="props.items" t-as="item" t-key="item.description">
<div>
<input
type="checkbox"
t-att-checked="!state.disabledItems.includes(item.description)"
t-on-change="(ev) => this.toggleItem(item.description, ev.target.checked)"
/>
<span t-esc="item.description" class="p-2"/>
</div>
</t>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="apply">Apply</button>
</t>
</Dialog>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {Component, useState, onWillStart} from "@odoo/owl";
import {registry} from "@web/core/registry";
import {Layout} from "@web/search/layout";
import {useService} from "@web/core/utils/hooks";
import {DashboardItem} from "../dashboard_item/dashboard_item";
import {PieChart} from "../charts/pie_chart/pie_chart";
import "./dashboard_items";
import {PieChartCard} from "../charts/pie_chart_card/pie_chart_card";
import {NumberCard} from "../number_card/number_card";
import {ConfigDialog} from "../config_dialog/config_dialog";
import {rpc} from "@web/core/network/rpc";


class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
static components = {Layout, DashboardItem, PieChart, PieChartCard, NumberCard, ConfigDialog};


setup() {
this.action = useService("action");
const {statistics} = useService("awesome_dashboard.statistics");
this.statistics = useState(statistics);
this.items = registry.category("awesome_dashboard").getAll();
this.dialog = useService("dialog")
this.state = useState({disabledItems: []})

onWillStart(async () => {
const config = await rpc("/awesome_dashboard/load_config");
this.state.disabledItems = JSON.parse(config || "[]");
});

}

openCustomerView() {
this.action.doAction("base.action_partner_form");
}

openLeads() {
this.action.doAction({
type: "ir.actions.act_window",
res_model: "crm.lead",
views: [[false, "list"], [false, "form"]],
name: "All leads",
});
}

get visibleItems() {
return this.items.filter(item => !this.state.disabledItems.includes(item.description));
}

openSettings() {
this.dialog.add(ConfigDialog, {
items: this.items,
disabledItems: this.state.disabledItems,
onApply: (disabledItems) => {
this.state.disabledItems = disabledItems;
}
});
}
}

registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.o_dashboard {
background-color: grey;

@media (max-width: 768px) {
.card {
width: 100% !important;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.AwesomeDashboard">
<Layout className="'o_dashboard h-100'" display="{controlPanel: {}}">
<t t-set-slot="layout-buttons">
<button class="btn btn-primary" t-on-click="openCustomerView">Customers</button>
<button class="btn btn-primary" t-on-click="openLeads">Leads</button>
<button class="btn btn-primary" t-on-click="openSettings"><i class="fa fa-gear"/></button>
</t>
<div class="d-flex flex-wrap gap-3 p-3">
<t t-foreach="visibleItems" t-as="item" t-key="item.description">
<DashboardItem size="item.size || 1">
<t t-set="itemProp" t-value="item.props ? item.props(statistics) : {'data': statistics}"/>
<t t-component="item.Component" t-props="itemProp" />
</DashboardItem>
</t>
</div>
</Layout>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {NumberCard} from "../number_card/number_card";
import {PieChartCard} from "../charts/pie_chart_card/pie_chart_card";
import {registry} from "@web/core/registry";
import {_t} from "@web/core/l10n/translation";


registry.category("awesome_dashboard").add("average_quantity", {
description: _t("Average amount of t-shirt by order this month"),
Component: NumberCard,
size: 1.5,
props: (data) => ({
title: _t("Average amount of t-shirt by order this month"),
value: data.average_quantity,
})
});
registry.category("awesome_dashboard").add("average_time", {
description: _t("Average time for an order to go from 'new' to 'sent' or 'cancelled'"),
Component: NumberCard,
size: 2,
props: (data) => ({
title: _t("Average time for an order to go from 'new' to 'sent' or 'cancelled'"),
value: data.average_time,
})
});
registry.category("awesome_dashboard").add("nb_new_orders", {
description: _t("Number of new orders this month"),
Component: NumberCard,
props: (data) => ({
title: _t("Number of new orders this month"),
value: data.nb_new_orders,
})
});
registry.category("awesome_dashboard").add("nb_cancelled_orders", {
description: _t("Number of cancelled orders this month"),
Component: NumberCard,
props: (data) => ({
title: _t("Number of cancelled orders this month"),
value: data.nb_cancelled_orders,
})
});
registry.category("awesome_dashboard").add("total_amount", {
description: _t("Total amount of new orders this month"),
Component: NumberCard,
props: (data) => ({
title: _t("Total amount of new orders this month"),
value: data.total_amount,
})
});
registry.category("awesome_dashboard").add("orders_by_size", {
id: "orders_by_size",
description: _t("Shirt orders by size"),
Component: PieChartCard,
props: (data) => ({
data: data.orders_by_size,
title: _t("Shirt orders by size"),
})
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {Component} from "@odoo/owl"
import {NumberCard} from "../number_card/number_card";


export class DashboardItem extends Component {
static template = "awesome_dashboard.dashboard_item";
static props = {
size: {type: Number, optional: true, default: 1},
slots: {type: Object, optional: true, default: null},
};
static components = {NumberCard};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.dashboard_item">
<div class="card" t-att-style="'width: ' + (props.size || 1) * 18 + 'rem'">
<div class="card-body border border-dark rounded-3 fw-bold d-flex flex-column align-items-center justify-content-center text-center">
<t t-slot="default">
</t>
</div>
</div>
</t>

</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Component} from "@odoo/owl";

export class NumberCard extends Component {
static template = "awesome_dashboard.number_card";
static props = {
title: {type: String},
value: {type: Number},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.number_card">
<span><t t-esc="props.title"/></span>
<p class="text-success fw-bold fs-1">
<t t-esc="props.value"/>
</p>
</t>
</templates>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {registry} from "@web/core/registry";
import {rpc} from "@web/core/network/rpc";
import {reactive} from "@odoo/owl"


const statisticsService = {
start() {
const statistics = reactive({});

async function loadStatistics() {
const data = await rpc("/awesome_dashboard/statistics");
Object.assign(statistics, data)
}

loadStatistics();
setInterval(loadStatistics, 10000);

return {statistics};
}
}

registry.category("services").add("awesome_dashboard.statistics", statisticsService);
10 changes: 10 additions & 0 deletions addons/awesome_dashboard/static/src/dashboard_action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {Component, xml} from "@odoo/owl";
import {registry} from "@web/core/registry";
import {LazyComponent} from "@web/core/assets";

class DashboardAction extends Component {
static components = {LazyComponent};
static template = xml`<LazyComponent bundle="'awesome_dashboard.assets_dashboard'" Component="'AwesomeDashboard'"/>`;
}

registry.category("actions").add("awesome_dashboard.dashboard", DashboardAction);
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading