Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8242913
[ADD] todo: add new todo, card and counter components
macocianradu Sep 23, 2025
b57ecf4
[IMP] todo: Focusing the input
macocianradu Sep 23, 2025
2d3beff
[IMP] todo: deleting todos
macocianradu Sep 23, 2025
0c74508
[IMP] awesome_owl: generic card with slots
macocianradu Sep 23, 2025
9cc166e
[IMP] awesome_owl: minimizing card content
macocianradu Sep 23, 2025
8ae5640
[IMP] awesome_dashboard: add some buttons for quick navigation
macocianradu Sep 23, 2025
3c99f45
[IMP] awesome_dashboard: add a dashboard item
macocianradu Sep 23, 2025
2726b2e
[IMP] awesome_dashboard: call the server, add some statistics
macocianradu Sep 23, 2025
0134803
[IMP] awesome_dashboard: cache network calls, create a service
macocianradu Sep 23, 2025
4346a21
[IMP] awesome_dashboard: display a pie chart
macocianradu Sep 24, 2025
a5fa22a
[IMP] awesome_dashboard: real life update
macocianradu Sep 24, 2025
abddaa5
[IMP] awesome_dashboard: lazy loading the dashboard
macocianradu Sep 24, 2025
3416192
[IMP] awesome_dashboard: making the dashboard extensible
macocianradu Sep 24, 2025
6e53f4b
[IMP] awesome_dashboard: make the dashboard extensible
macocianradu Sep 24, 2025
0df9683
[IMP] awesome_dashboard: add and remove dashboard items
macocianradu Sep 24, 2025
9842f55
[IMP] awesome_dashboard: add translations to text
macocianradu Sep 24, 2025
bce86ab
[FIX] awesome_dashboard: run prettier
macocianradu Sep 24, 2025
9a06420
[IMP] awesome_dashboard: opening orders on chart click
macocianradu Sep 25, 2025
abe7d49
[IMP] awesome_dashboard: make cards reactive on mobile
macocianradu Sep 25, 2025
df6646a
[IMP] awesome_dashboard: saving dashboard in config_properties
macocianradu Sep 25, 2025
f81a9cd
[IMP] awesome_dashboard: adding a controller action for user settings
macocianradu Sep 26, 2025
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
12 changes: 12 additions & 0 deletions awesome_dashboard/controllers/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import random
from typing import Required

from odoo import http
from odoo.http import request
Expand Down Expand Up @@ -34,3 +35,14 @@ def get_statistics(self):
'total_amount': random.randint(100, 1000)
}

@http.route('/awesome_dashboard/set_config/<int:record_id>', type='json', auth='user')
def save_config(self, record_id, **kwargs):
request.env['ir.config_parameter'].sudo().set_param(
'awesome_dashboard_config:' + str(record_id), kwargs['config']
)

@http.route('/awesome_dashboard/get_config/<int:record_id>', type='json', auth='user')
def get_config(self, record_id):
return request.env['ir.config_parameter'].sudo().get_param(
'awesome_dashboard_config:' + str(record_id))

10 changes: 0 additions & 10 deletions awesome_dashboard/static/src/dashboard.js

This file was deleted.

41 changes: 41 additions & 0 deletions awesome_dashboard/static/src/dashboard/configuration_dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useService } from "@web/core/utils/hooks";
import { Component } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";
import { CheckBox } from "@web/core/checkbox/checkbox";
import { session } from "@web/session";
import { rpc } from "@web/core/network/rpc";

export class ConfigurationDialog extends Component {
static template = "awesome_dashboard.ConfigurationDialog";
static components = { Dialog, CheckBox };
static props = {
items: Array,
close: {},
onApply: Function,
};

setup() {
this.orm = useService("orm");
this.userId = session.storeData['res.partner'][1].userId;
this.disabledElements = this.props.items
.filter((item) => !item.enabled)
.map((item) => item.element.id);
}

onChange(id) {
let index = this.disabledElements.indexOf(id);
if (index === -1) {
this.disabledElements.push(id);
return;
}
Comment on lines +27 to +30

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What your function does is:

  • If item is already disabled, remove it from the list
  • If item is not disabled add it to disabled list
    Thus, this if condition is actually half of your functionality. But when you return inside the block it looks like it's a corner case and for most records what will execute is this.disabledElements.splice(index, 1); which is not true.

In this case I would prefer to have

Suggested change
if (index === -1) {
this.disabledElements.push(id);
return;
}
if (index === -1) {
this.disabledElements.push(id);
}
else {
do other thing
}

so what your method does is clearer.
This is of course up for debate. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's a matter of preference.
I usually prefer to do early returns instead of else statements, so that if the function gets more complicated, I don't need to nest so much.
In this case it's probably the same.

this.disabledElements.splice(index, 1);
}

async apply() {
await rpc("/awesome_dashboard/set_config/" + this.userId, {
config: JSON.stringify(this.disabledElements)
})
this.props.onApply();
this.props.close();
}
}
22 changes: 22 additions & 0 deletions awesome_dashboard/static/src/dashboard/configuration_dialog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="perserve">
<t t-name="awesome_dashboard.ConfigurationDialog">
<Dialog title="'Dashboard configuration dialog'">
<div class="d-flex flex-column">
<t t-foreach="props.items" t-as="item" t-key="item.element.id">
<div class="d-inline-flex gap-2">
<p><t t-esc="item.element.description" /></p>
<CheckBox
value="item.enabled"
t-on-change="(_) => this.onChange(item.element.id)" />
</div>
</t>
</div>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="apply" >
Apply
</button>
</t>
</Dialog>
</t>
</templates>
71 changes: 71 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/** @odoo-module **/

import { _t } from "@web/core/l10n/translation";
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";
import { PieChart } from "./pie_chart/pie_chart";
import { ConfigurationDialog } from "./configuration_dialog";
import { rpc } from "@web/core/network/rpc";
import { session } from "@web/session";

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

setup() {
this.action = useService("action");
this.orm = useService("orm");
this.userId = session.storeData['res.partner'][1].userId;
this.statistics = useService("awesome_dashboard.statistics");
this.state = useState({ items: [], stats: this.statistics });
this.dialog = useService("dialog");

onWillStart(async () => {
await this.updateDashboard()
})
}

async updateDashboard() {
const config = JSON.parse(
await rpc("/awesome_dashboard/get_config/" + this.userId))
const disabledElements = config || []
this.state.items = registry
.category("awesome_dashboard")
.getAll()
.filter((el) => !disabledElements.some((e) => e == el.id));
}

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

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

customizeDashboard() {
const all_dashboard_items = registry.category("awesome_dashboard").getAll();

this.dialog.add(ConfigurationDialog, {
items: all_dashboard_items.map(item => ({
element: item,
enabled: this.state.items.some((element) => item.id == element.id)
}))
,
onApply: this.updateDashboard.bind(this),
});
}
}

registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
3 changes: 3 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.o_dashboard {
background-color: gray;
}
27 changes: 27 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">
<Layout display="{ controlPanel: {} }" className="'o_dashboard h-100 overflow-scroll'">
<t t-set-slot='layout-buttons'>
<button class="btn btn-primary" t-on-click="openCustomers">Customers</button>
<button class="btn btn-primary" t-on-click="openLeads">Leads</button>
<button class="btn" t-on-click="customizeDashboard">
<span class="fa fa-gear" />
</button>
</t>
<div class="d-flex p-0 p-md-4 flex-wrap ">
<t t-foreach="state.items" t-as="item" t-key="item.id">
<DashboardItem size="item.size || 1">
<t t-set="itemProp" t-value="item.props
? item.props(state.stats)
: {'data': state.stats}"
/>
<t t-component="item.Component" t-props="itemProp" />
</DashboardItem>
</t>
</div>
</Layout>
</t>

</templates>
14 changes: 14 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from "@odoo/owl";

export class DashboardItem extends Component {
static template = "awesome_dashboard.DashboardItem";

static props = {
size: { type: Number, optional: true },
slots: {},
};

static defaultProps = {
size: 1
}
}
11 changes: 11 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_item.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
h1 {
color: green;
}

.card {
width: 100%;

@media (min-width: 758px) {
width: var(--desktop-card-width);
}
}
13 changes: 13 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_item.xml
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.DashboardItem">
<div
class="card m-0 m-md-2"
t-attf-style="--desktop-card-width:{{18 * props.size}}rem;">
<div class="card-body">
<t t-slot="default">
</t>
</div>
</div>
</t>
</templates>
73 changes: 73 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { NumberCard } from "./number_card";
import { registry } from "@web/core/registry";
import { PieChartCard } from "./pie_chart_card";
import { _t } from "@web/core/l10n/translation";

const items = [
{
id: "average_quantity",
description: _t("Average amount of t-shirt"),
Component: NumberCard,
size: 1,
props: (data) => ({
title: _t("Average amount of t-shirt by order this month"),
value: data.average_quantity,
}),
},
{
id: "average_time",
description: _t("Average time for orders"),
Component: NumberCard,
size: 1,
props: (data) => ({
title: _t(
"Average time for an order to go from 'new' to 'sent' or 'cancelled'",
),
value: data.average_time,
}),
},
{
id: "nb_new_orders",
description: _t("Number of new orders"),
Component: NumberCard,
size: 1,
props: (data) => ({
title: _t("Number of new orders this month"),
value: data.nb_new_orders,
}),
},
{
id: "nb_cancelled_orders",
description: _t("Number of cancelled orders"),
Component: NumberCard,
size: 1,
props: (data) => ({
title: _t("Number of cancelled orders this month"),
value: data.nb_cancelled_orders,
}),
},
{
id: "total_amount",
description: _t("Total amount of new orders"),
Component: NumberCard,
size: 1,
props: (data) => ({
title: _t("Total amount of new orders this month"),
value: data.total_amount,
}),
},
{
id: "orders_by_size",
description: _t("Shirt orders by size"),
Component: PieChartCard,
size: 2,
props: (data) => ({
title: _t("Shirt orders by size"),
value: data.orders_by_size,
}),
},
];

for (let item of items) {
registry.category("awesome_dashboard").add(item.id, item);
}
12 changes: 12 additions & 0 deletions awesome_dashboard/static/src/dashboard/number_card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component, xml } from "@odoo/owl";

export class NumberCard extends Component {
static template = xml`
<p><t t-esc="props.title" /></p>
<h1><b><t t-esc="props.value" /></b></h1>
`;
static props = {
title: String,
value: [String, Number],
};
}
Loading