Compare commits

...

9 commits

14 changed files with 303 additions and 0 deletions

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
{
'name': "Awesome Dashboard",
'summary': """
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
""",
'description': """
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
""",
'author': "Odoo",
'website': "https://www.odoo.com/",
'category': 'Tutorials/AwesomeDashboard',
'version': '0.1',
'application': True,
'installable': True,
'depends': ['base', 'web', 'mail', 'crm'],
'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'
}

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
import logging
import random
from odoo import http
from odoo.http import request
logger = logging.getLogger(__name__)
class AwesomeDashboard(http.Controller):
@http.route('/awesome_dashboard/statistics', type='json', auth='user')
def get_statistics(self):
"""
Returns a dict of statistics about the orders:
'average_quantity': the average number of t-shirts by order
'average_time': the average time (in hours) elapsed between the
moment an order is created, and the moment is it sent
'nb_cancelled_orders': the number of cancelled orders, this month
'nb_new_orders': the number of new orders, this month
'total_amount': the total amount of orders, this month
"""
return {
'average_quantity': random.randint(4, 12),
'average_time': random.randint(4, 123),
'nb_cancelled_orders': random.randint(0, 50),
'nb_new_orders': random.randint(10, 200),
'orders_by_size': {
'm': random.randint(0, 150),
's': random.randint(0, 150),
'xl': random.randint(0, 150),
},
'total_amount': random.randint(100, 1000)
}

View file

@ -0,0 +1,38 @@
/** @odoo-module **/
import { Component, useState } 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 "./piechart/piechart";
class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
static components = { Layout, DashboardItem, Piechart};
setup() {
this.display = {
controlPanel: {},
};
this.action = useService("action");
this.stats = useState(useService('awesome_dashboard.statistics'));
}
openCustomers() {
this.action.doAction("base.action_partner_form");
}
openLeads() {
this.action.doAction({
type: 'ir.actions.act_window',
name: 'Leads',
res_model: 'crm.lead',
views: [[false, 'tree'],[false, 'form']],
});
}
}
registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);

View file

@ -0,0 +1,3 @@
.o_dashboard {
background-color:rgb(0, 116, 151);
}

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.AwesomeDashboard">
<Layout display="display" className="'o_dashboard h-100'">
<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>
</t>
<div class="d-flex flex-wrap" t-if="stats.isReady">
<DashboardItem>
Number of new orders this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.nb_new_orders"/>
</div>
</DashboardItem>
<DashboardItem>
Total amount of new orders this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.total_amount"/>
</div>
</DashboardItem>
<DashboardItem>
Average amount of t-shirt by order this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.average_quantity"/>
</div>
</DashboardItem>
<DashboardItem>
Number of cancelled orders this month
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.nb_cancelled_orders"/>
</div>
</DashboardItem>
<DashboardItem>
Average time for an order to go from new to sent or cancelled
<div class="fs-1 fw-bold text-success text-center">
<t t-out="stats.average_time"/>
</div>
</DashboardItem>
<DashboardItem size='4'>
Shirt orders by size
<Piechart data="stats.orders_by_size" label="'Shirt orders by size'"/>
</DashboardItem>
</div>
</Layout>
</t>
</templates>

View file

@ -0,0 +1,20 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
export class DashboardItem extends Component {
static template = "awesome_dashboard.DashboardItem";
static props = {
size: {
type: Number,
default: 1,
optional: true,
},
slots: {
type: Object,
shape: {
default: Object
},
}
};
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.DashboardItem">
<section>
<div class="card d-inline-block m-2" t-attf-style="width:{{18*props.size}}rem;">
<div class="card-body">
<t t-slot="default">some content</t>
</div>
</div>
</section>
</t>
</templates>

View file

@ -0,0 +1,45 @@
/** @odoo-module **/
import { loadJS } from "@web/core/assets";
import { getColor } from "@web/core/colors/colors";
import { Component, onWillStart, useRef, onMounted, onWillUnmount } from "@odoo/owl";
export class Piechart extends Component {
static template = "awesome_dashboard.Piechart";
static props = {
label: String,
data: Object,
};
setup() {
this.canvasRef = useRef("canvas");
onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"]));
onMounted(() => {
this.renderChart();
});
onWillUnmount(() => {
this.chart.destroy();
});
}
renderChart() {
const labels = Object.keys(this.props.data);
const data = Object.values(this.props.data);
const color = labels.map((_, index) => getColor(index));
this.chart = new Chart(this.canvasRef.el, {
type: "pie",
data: {
labels: labels,
datasets: [
{
label: this.props.label,
data: data,
backgroundColor: color,
},
],
},
});
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.Piechart">
<div t-att-class="'h-100 ' + props.class" t-ref="root">
<div class="h-100 position-relative" t-ref="container">
<canvas t-ref="canvas" />
</div>
</div>
</t>
</templates>

View file

@ -0,0 +1,21 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { reactive } from "@odoo/owl";
const statisticsService = {
dependencies: ["rpc"],
start(env, { rpc }) {
const statistics = reactive({ isReady: false });
async function loadData() {
const updates = await rpc("/awesome_dashboard/statistics");
Object.assign(statistics, updates, { isReady: true });
}
setInterval(loadData, 10*60*1000);
loadData();
return statistics;
},
};
registry.category("services").add("awesome_dashboard.statistics", statisticsService);

View file

@ -0,0 +1,15 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { LazyComponent } from "@web/core/assets";
import { Component, xml } from "@odoo/owl";
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);

View file

@ -0,0 +1,11 @@
<odoo>
<data>
<record model="ir.actions.client" id="dashboard">
<field name="name">Dashboard</field>
<field name="tag">awesome_dashboard.dashboard</field>
</record>
<menuitem name="Awesome Dashboard" id="awesome_dashboard.menu_root" groups="base.group_user" web_icon="awesome_dashboard,static/description/icon.png"/>
<menuitem name="Dashboard" id="awesome_dashboard.dashboard_menu" parent="awesome_dashboard.menu_root" action="awesome_dashboard.dashboard" sequence="1"/>
</data>
</odoo>