Skip to content
1 change: 1 addition & 0 deletions awesome_dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-

from . import controllers
from . import models
6 changes: 5 additions & 1 deletion awesome_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@
'version': '0.1',
'application': True,
'installable': True,
'depends': ['base', 'web', 'mail', 'crm'],
'depends': ['base', 'web', 'mail', 'crm', 'sale'],

'data': [
'views/views.xml',
],
'assets': {
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
('remove', 'awesome_dashboard/static/src/dashboard/**/*'),
],
'awesome_dashboard.dashboard': [
'awesome_dashboard/static/src/dashboard/**/*',
],
},
'license': 'AGPL-3'
Expand Down
2 changes: 2 additions & 0 deletions awesome_dashboard/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import res_users
from . import sale_order
15 changes: 15 additions & 0 deletions awesome_dashboard/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import fields, models


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

dashboard_disabled_items = fields.Char(default='[]')

@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + ['dashboard_disabled_items']

@property
def SELF_WRITEABLE_FIELDS(self):
return super().SELF_WRITEABLE_FIELDS + ['dashboard_disabled_items']
10 changes: 10 additions & 0 deletions awesome_dashboard/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import fields, models


class SaleOrder(models.Model):
_inherit = 'sale.order'

size = fields.Selection(
[('s', 'S'), ('m', 'M'), ('xl', 'XL')],
string="T-shirt size",
)
8 changes: 0 additions & 8 deletions awesome_dashboard/static/src/dashboard.js

This file was deleted.

69 changes: 69 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Component, xml, 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 { _t } from "@web/core/l10n/translation";

import { DashboardItem } from "./dashboard_item";
import { SettingsDialog } from "./settings_dialog";
import "./dashboard_items";
import "./disabled_items_service";

class AwesomeDashboard extends Component {
static template = xml`
<div class="p-2 d-flex gap-2">
<button t-on-click="gotoCustomers" class="btn btn-primary">Customers</button>
<button t-on-click="gotoLeads" class="btn btn-primary">Leads</button>
<button t-on-click="openSettings" class="btn btn-secondary ms-auto" title="Settings">
<i class="fa fa-cog"/>
</button>
</div>
<Layout display="{ controlPanel: {} }" className="'o_dashboard h-100'">
<t t-foreach="items" t-as="item" t-key="item.id">
<DashboardItem t-if="!item.disabled" 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>
</Layout>
`;

static components = { Layout, DashboardItem }

setup() {
this.action = useService("action")
this.dialog = useService("dialog")
this.statistics = useService("awesome_dashboard.statistics")
this.disabledItems = useService("awesome_dashboard.disabled_items")
this.items = useState(registry.category("awesome_dashboard").getAll().map(
(item) => ({ ...item, disabled: false })
))
onWillStart(async () => {
const disabled = await this.disabledItems.load()
for (const item of this.items) {
item.disabled = disabled.includes(item.id)
}
})
}

openSettings() {
this.dialog.add(SettingsDialog, { items: this.items })
}

gotoCustomers() {
this.action.doAction("base.action_partner_form")
}

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

}

registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
11 changes: 11 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.o_dashboard {
background-color: gray;
}

@media (max-width: 768px) {
.o_dashboard .card {
display: block !important;
// override the inline width set by DashboardItem
width: auto !important;
}
}
18 changes: 18 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component, xml } from "@odoo/owl"

export class DashboardItem extends Component {
static props = {
slots: { type: Object, optional: true },
size: { type: Number, optional: true },
}

static defaultProps = { size: 1 }

static template = xml`
<div class="card d-inline-block m-2" t-attf-style="width: {{18 * props.size}}rem;">
<div class="card-body d-flex flex-column justify-content-center">
<t t-slot="default"/>
</div>
</div>
`
}
72 changes: 72 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { registry } from "@web/core/registry"
import { _t } from "@web/core/l10n/translation"

import { NumberCard } from "./number_card"
import { PieChartCard } from "./pie_chart_card"

const items = [
{
id: "average_quantity",
description: _t("Average amount of t-shirt"),
Component: NumberCard,
size: 1.5,
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 an order"),
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,
}),
},
{
id: "nb_new_orders",
description: _t("New orders this month"),
Component: NumberCard,
props: (data) => ({
title: _t("Number of new orders this month"),
value: data.nb_new_orders,
}),
},
{
id: "nb_cancelled_orders",
description: _t("Cancelled orders this month"),
Component: NumberCard,
size: 1.5,
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 this month"),
Component: NumberCard,
size: 1.5,
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"),
values: data.orders_by_size,
}),
},
]

const itemsRegistry = registry.category("awesome_dashboard")
for (const item of items) {
itemsRegistry.add(item.id, item)
}
22 changes: 22 additions & 0 deletions awesome_dashboard/static/src/dashboard/disabled_items_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { registry } from "@web/core/registry";
import { user } from "@web/core/user";

const disabledItemsService = {
dependencies: ["orm"],

start(env, { orm }) {
return {
async load() {
const [data] = await orm.read("res.users", [user.userId], ["dashboard_disabled_items"]);
return JSON.parse(data.dashboard_disabled_items || "[]");
},
save(ids) {
return orm.write("res.users", [user.userId], {
dashboard_disabled_items: JSON.stringify(ids),
});
},
};
},
};

registry.category("services").add("awesome_dashboard.disabled_items", disabledItemsService);
17 changes: 17 additions & 0 deletions awesome_dashboard/static/src/dashboard/number_card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component, xml } from "@odoo/owl"

export class NumberCard extends Component {
static props = {
title: { type: String },
value: { type: [Number, String] },
}

static template = xml`
<h3 class="card-title text-center">
<t t-esc="props.title"/>
</h3>
<span class="fs-1 text-center text-primary">
<t t-esc="props.value"/>
</span>
`
}
19 changes: 19 additions & 0 deletions awesome_dashboard/static/src/dashboard/pie_chart_card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Component, xml } from "@odoo/owl"

import { PieChart } from "../piechart/piechart"

export class PieChartCard extends Component {
static props = {
title: { type: String },
values: { type: Object },
}

static components = { PieChart }

static template = xml`
<h3 class="card-title text-center">
<t t-esc="props.title"/>
</h3>
<PieChart data="props.values"/>
`
}
47 changes: 47 additions & 0 deletions awesome_dashboard/static/src/dashboard/settings_dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Component, xml, useState } from "@odoo/owl"

import { Dialog } from "@web/core/dialog/dialog"
import { CheckBox } from "@web/core/checkbox/checkbox"
import { useService } from "@web/core/utils/hooks"
import { _t } from "@web/core/l10n/translation"

export class SettingsDialog extends Component {
static props = {
items: { type: Array },
close: { type: Function },
}

static components = { Dialog, CheckBox }

static template = xml`
<Dialog title="title">
Which cards do you wish to see?
<t t-foreach="props.items" t-as="item" t-key="item.id">
<CheckBox value="!disabled[item.id]" onChange="() => disabled[item.id] = !disabled[item.id]">
<t t-esc="item.description"/>
</CheckBox>
</t>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="onApply">Apply</button>
</t>
</Dialog>
`

setup() {
this.title = _t("Dashboard items configuration")
this.disabledItems = useService("awesome_dashboard.disabled_items")
this.disabled = useState({})
for (const item of this.props.items) {
this.disabled[item.id] = item.disabled
}
}

onApply() {
for (const item of this.props.items) {
item.disabled = this.disabled[item.id]
}
const ids = this.props.items.filter((item) => item.disabled).map((item) => item.id)
this.disabledItems.save(ids)
this.props.close()
}
}
22 changes: 22 additions & 0 deletions awesome_dashboard/static/src/dashboard/statistics_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { reactive } from "@odoo/owl";

import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";

const statisticsService = {
async _loadData(statistics) {
const updates = await rpc("/awesome_dashboard/statistics");
Object.assign(statistics, updates, { isReady: true });
},

start() {
const statistics = reactive({ isReady: false });

setInterval(() => this._loadData(statistics), 10_000);
this._loadData(statistics);

return statistics;
},
};

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

import { registry } from "@web/core/registry";
import { LazyComponent } from "@web/core/assets";

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

registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);
Loading