Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e99d693
[IMP] awesome_owl: Technical onboarding, chapter 1, step 1 to 5
fmalfroid Sep 22, 2025
a5aa410
[IMP] awesome_owl: Discover the web framework step 6: The sum of two …
fmalfroid Sep 22, 2025
cdc5670
[IMP] awesome_owl: Discover the web framework step 7: A todo list
fmalfroid Sep 22, 2025
045c2fd
[IMP] awesome_owl: Discover the web framework step 8: Use dynamic att…
fmalfroid Sep 22, 2025
103de17
[IMP] awesome_owl: Discover the web framework step 9: Adding a todo
fmalfroid Sep 22, 2025
9b751cc
[IMP] awesome_owl: Discover the web framework step 10: Focusing the i…
fmalfroid Sep 22, 2025
a1b31a0
[IMP] awesome_owl: Discover the web framework step 11: Toggling todos
fmalfroid Sep 23, 2025
295aaf0
[IMP] awesome_owl: Discover the web framework step 12: Deleting todos
fmalfroid Sep 23, 2025
6866ff9
[IMP] awesome_owl: Discover teh web framework step 13: Generic Card w…
fmalfroid Sep 23, 2025
1bf3b4a
[IMP] awesome_owl: Discover the web framework Chapter 1: Owl components
fmalfroid Sep 23, 2025
d8c3e56
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
0cc3c1d
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, step …
fmalfroid Sep 23, 2025
17662bf
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
d2432e2
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
fb73735
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
15347e5
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
cd512a5
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
096972b
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 24, 2025
46a5eca
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, step …
fmalfroid Sep 24, 2025
15df7b2
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, step …
fmalfroid Sep 24, 2025
58fffbe
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 24, 2025
346aea5
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 24, 2025
a3d43b7
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, list …
fmalfroid Sep 24, 2025
7cb706e
[IMP] awesome_owl: modifications based on review comments
fmalfroid Sep 25, 2025
bea33c7
[IMP] awesome_owl: changed useAutoFocus to use onMounted instead of u…
fmalfroid Sep 25, 2025
8344dc3
[IMP] awesome_dashboard: Cards are 100% of the width on mobile
fmalfroid Sep 25, 2025
d1d1bac
[IMP] awesome_dashboard: scrollable dashboard
fmalfroid Sep 25, 2025
6ad8344
[IMP] awesome_clicker: Built a clicker game for Matser the web framew…
fmalfroid Sep 30, 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
3 changes: 3 additions & 0 deletions awesome_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
],
'awesome_dashboard.dashboard': [
'awesome_dashboard/static/src/dashboard/**/*',
],
},
'license': 'AGPL-3'
}
10 changes: 0 additions & 10 deletions awesome_dashboard/static/src/dashboard.js

This file was deleted.

8 changes: 0 additions & 8 deletions awesome_dashboard/static/src/dashboard.xml

This file was deleted.

56 changes: 56 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/** @odoo-module **/

import { _t } from "@web/core/l10n/translation";
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { Layout } from "@web/search/layout";
import { DashboardItem } from "./dashboard_item/dashboard_item";
import { PieChart } from "./dashboard_pie_chart/dashboard_pie_chart";
import { DashboardConfig } from "./dashboard_config/dashboard_config";

class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
static components = { Layout, DashboardItem, PieChart };
static props = ['*'];

setup() {
this.action = useService("action");
this.title = _t("Awesome Dashboard");
this.statistics = useState(useService("awesome_dashboard.statistics"));
this.items = registry.category("awesome_dashboard.items").getAll();
this.dialog = useService("dialog");
this.state = useState({
disabledItems: localStorage.getItem("disabledItems") ? localStorage.getItem("disabledItems").split(",") : []
});
}

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, 'list'], [false, 'form']],
target: 'current',
})
}

openDashboardConfig = () => {
this.dialog.add(DashboardConfig, {
items: this.items,
applyFunction: this.onApplyConfiguration.bind(this),
disabledItems: this.state.disabledItems
});
}

onApplyConfiguration = (ids) => {
this.state.disabledItems = ids;
localStorage.setItem("disabledItems", ids);
}
}

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;
}
26 changes: 26 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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="() => openCustomers()">Customers</button>
<button class="btn btn-primary" t-on-click="() => openLeads()">Leads</button>
</t>
<t t-set-slot="control-panel-additional-actions">
<i class="fa fa-cog align-self-center cursor-pointer" t-on-click="() => openDashboardConfig()"/>
</t>
<t t-set-slot="default">
<div class="d-flex flex-wrap">
<t t-foreach="items" t-as="item" t-key="item.id">
<DashboardItem size="item.size || 1" t-if="!state.disabledItems.includes(item.id)">
<t t-set="itemProp" t-value="item.props ? item.props(statistics) : {'data': statistics}"/>
<t t-component="item.Component" t-props="itemProp" />
</DashboardItem>
</t>
</div>
</t>
</Layout>
</t>

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

export class DashboardConfig extends Component {
static template = "awesome_dashboard.DashboardConfig";
static components = { Dialog };
static props = {
items: Object,
applyFunction: Function,
disabledItems: Array,
close: Function
}

setup() {
this.disabledItems = this.props.disabledItems.slice();

this.onChange = (ev, item_id) => {
if (ev.target.checked) {
const index = this.disabledItems.indexOf(item_id);
if (index >= 0) {
this.disabledItems.splice(index, 1);
}
} else {
this.disabledItems.push(item_id);
}
}
}

onApply() {
this.props.applyFunction(this.disabledItems);
this.props.close();
}
}
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.DashboardConfig">
<Dialog size="'md'" title.translate="Dashboard items configuration" modalRef="modalRef">
<div>
<p>Which cards do you wish to see ?</p>
<t t-foreach="props.items" t-as="item" t-key="item.id">
<div class="form-check">
<input type="checkbox" class="form-check-input" t-att-id="item.id" t-att-checked="!props.disabledItems.includes(item.id)" t-on-change="(ev) => {this.onChange(ev, item.id)}"/>
<t t-esc="item.description"/>
</div>
</t>
</div>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="onApply">Apply</button>
</t>
</Dialog>
</t>

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

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

<t t-name="awesome_dashboard.DashboardItem">
<div class="card m-2 d-inline-block" t-att-style="'width: ' + (18 * (props.size || 1)) + 'rem;'">
<t t-slot="default"/>
</div>
</t>

</templates>
66 changes: 66 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_items.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry";
import { NumberCard } from "./number_card/number_card";
import { PieChartCard } from "./pie_chart_card/pie_chart_card";

const items = [
{
id: "average_quantity",
description: _t("Average amount of t-shirt"),
Component: NumberCard,
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,
props: (data) => ({
title: _t("Average time for an order to go from 'new' to 'sent' or 'cancelled'"),
value: data.average_time,
})
},
{
id: "number_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: "cancelled_orders",
description: _t("Cancelled orders this month"),
Component: NumberCard,
props: (data) => ({
title: _t("Number of cancelled orders this month"),
value: data.nb_cancelled_orders,
})
},
{
id: "amount_new_orders",
description: _t("Amount orders this month"),
Component: NumberCard,
props: (data) => ({
title: _t("Total amount of new orders this month"),
value: data.total_amount,
})
},
{
id: "pie_chart",
description: _t("Shirt orders by size"),
Component: PieChartCard,
size: 2,
props: (data) => ({
title: _t("Shirt orders by size"),
value: data.orders_by_size,
})
}
]

items.forEach(item => {
registry.category("awesome_dashboard.items").add(item.id, item);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, onWillStart, useEffect, onWillUnmount, useRef } from "@odoo/owl";
import { loadJS } from "@web/core/assets";

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

setup() {
this.canvasRef = useRef('canvas');
onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
useEffect(() => this.renderChart());
onWillUnmount(() => {
if (this.chart) {
this.chart.destroy();
}
});
}

/**
* Creates and binds the chart on `canvasRef`.
*/
renderChart() {
if (this.chart) {
this.chart.destroy();
}
const ctx = this.canvasRef.el.getContext('2d');
this.chart = new Chart(ctx, this.getChartConfig());
}

/**
* @returns {object} Chart config for the current data
*/
getChartConfig() {
return {
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
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<t t-name="awesome_dashboard.PieChart">
<div class="o_pie_chart">
<canvas t-ref="canvas"/>
</div>
</t>
</odoo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { reactive } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";

const dashboardStatisticsService = {
start(env) {
const statistics = reactive({});

async function loadData() {
Object.assign(statistics, await rpc("/awesome_dashboard/statistics"));
}

setInterval(loadData, 10 * 60 * 1000);
loadData();

return statistics
},
};

registry.category("services").add("awesome_dashboard.statistics", dashboardStatisticsService);
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.NumberCard";
static props = {
title: String,
value: Number
}
}
13 changes: 13 additions & 0 deletions awesome_dashboard/static/src/dashboard/number_card/number_card.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.NumberCard">
<div class="card-header">
<h6 t-esc="props.title"></h6>
</div>
<div class="card-body d-flex align-items-center justify-content-center">
<h1 class="text-success" t-esc="props.value || 0"></h1>
</div>
</t>

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

export class PieChartCard extends Component {
static template = "awesome_dashboard.PieChartCard";
static components = { PieChart };
static props = {
title: String,
value: Object
};
}
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.PieChartCard">
<div class="card-header">
<h6 t-esc="props.title"></h6>
</div>
<div class="card-body d-flex align-items-center justify-content-center">
<PieChart data="props.value || {}"/>
</div>
</t>

</templates>
Loading