From d547bfebd0757bfc521e8d0a7f1e5480d4e39da8 Mon Sep 17 00:00:00 2001 From: usseif Date: Tue, 23 Sep 2025 16:33:59 +0200 Subject: [PATCH 01/11] chapter1-No.6 --- awesome_owl/static/src/card/card.js | 6 ++++++ awesome_owl/static/src/card/card.xml | 2 +- awesome_owl/static/src/counter/counter.js | 7 +++++++ awesome_owl/static/src/playground.js | 17 +++++++++++++++-- awesome_owl/static/src/playground.xml | 14 +++++++------- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 4cfd7ac3550..8a9023b723c 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -2,4 +2,10 @@ import { Component, useState } from "@odoo/owl"; export class Card extends Component { static template = "awesome_owl.card"; + + // Explicit Props + static props = { + title: String, + content: String, + }; } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index 39acbf4c9be..cfed94505dc 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -4,7 +4,7 @@
-
+

diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js index 9e8c7b7d081..dc98ffbf90a 100644 --- a/awesome_owl/static/src/counter/counter.js +++ b/awesome_owl/static/src/counter/counter.js @@ -3,11 +3,18 @@ import { Component, useState } from "@odoo/owl"; export class Counter extends Component { static template = "awesome_owl.counter"; + static props = { + onChange: { type: Function, optional: true }, + }; + setup() { this.state = useState({ value: 0 }); } increment() { this.state.value++; + if (this.props.onChange) { + this.props.onChange(); // Call the parent callback with the new value + } } } diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index ce6a8be244b..6c5b0daccb9 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,12 +1,25 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, useState, markup } from "@odoo/owl"; import { Counter } from "./counter/counter"; import { Card } from "./card/card"; export class Playground extends Component { static template = "awesome_owl.playground"; - static components = { Counter, Card }; + setup() { + this.state = useState({ sum: 2 }); + this.incrementSum = this.incrementSum.bind(this); // Bind the method to its instance + } + + incrementSum(){ + this.state.sum++; + } + + cards = [ + { title: "Card 1", content: markup("
some content
") }, + { title: "Card 2", content: "This is the second card." }, + { title: "Card 3", content: "Another card example." }, + ]; } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index d4c8bf9ae91..9f491298f41 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,17 +2,17 @@ -
- hello world +
- - + +
+

Sum:

- - - + + +
From 126d3db0d4767fdb0469fcc14061fc58c07e1ee4 Mon Sep 17 00:00:00 2001 From: usseif Date: Tue, 23 Sep 2025 16:38:39 +0200 Subject: [PATCH 02/11] chapter1-No.6_2 --- awesome_owl/static/src/playground.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 9f491298f41..2a8a189d52e 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -14,6 +14,5 @@
-
From bf823a99aa9c63fca90f3d78e08485a0e020d621 Mon Sep 17 00:00:00 2001 From: usseif Date: Wed, 24 Sep 2025 12:59:12 +0200 Subject: [PATCH 03/11] chapter1 done --- awesome_owl/static/src/card/card.js | 11 +++++- awesome_owl/static/src/card/card.xml | 3 +- awesome_owl/static/src/playground.js | 10 ++--- awesome_owl/static/src/playground.xml | 27 +++++++++++--- awesome_owl/static/src/todo/todo_item.js | 25 +++++++++++++ awesome_owl/static/src/todo/todo_item.xml | 8 ++++ awesome_owl/static/src/todo/todo_list.js | 45 +++++++++++++++++++++++ awesome_owl/static/src/todo/todo_list.xml | 8 ++++ awesome_owl/static/src/utils.js | 21 +++++++++++ 9 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 awesome_owl/static/src/todo/todo_item.js create mode 100644 awesome_owl/static/src/todo/todo_item.xml create mode 100644 awesome_owl/static/src/todo/todo_list.js create mode 100644 awesome_owl/static/src/todo/todo_list.xml create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 8a9023b723c..03dba728272 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -3,9 +3,16 @@ import { Component, useState } from "@odoo/owl"; export class Card extends Component { static template = "awesome_owl.card"; - // Explicit Props static props = { title: String, - content: String, + slots: { type: Object, optional: true } }; + + setup() { + this.state = useState({contentState: true}); + } + + toggleContent(){ + this.state.contentState = !this.state.contentState; + } } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index cfed94505dc..a700bf47231 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -5,7 +5,8 @@
-

+ +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 6c5b0daccb9..d282ff16f6f 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,16 +1,16 @@ /** @odoo-module **/ import { Component, useState, markup } from "@odoo/owl"; +import { TodoList } from "./todo/todo_list"; import { Counter } from "./counter/counter"; import { Card } from "./card/card"; export class Playground extends Component { static template = "awesome_owl.playground"; - static components = { Counter, Card }; + static components = { Counter, Card, TodoList }; setup() { this.state = useState({ sum: 2 }); - this.incrementSum = this.incrementSum.bind(this); // Bind the method to its instance } incrementSum(){ @@ -18,8 +18,8 @@ export class Playground extends Component { } cards = [ - { title: "Card 1", content: markup("
some content
") }, - { title: "Card 2", content: "This is the second card." }, - { title: "Card 3", content: "Another card example." }, + { title: "Card 1"}, + { title: "Card 2"}, + { title: "Card 3"}, ]; } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 2a8a189d52e..56b8fd2ab14 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,17 +2,32 @@ +

Playground

+

This is a simple playground to test Owl features.

+
+

Counters

- - + +

Sum:

-
- - - +
+
+

Cards

+
+ + + + + +
+
+
+
+

Todo List

+
diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..bdf6ac44990 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,25 @@ +import { Component, useState } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + + static props = { + todo: { type: Object, shape: {id: Number, description: String, isCompleted: Boolean} }, + toggleState: { type: Function, optional: true }, + removeTodo: { type: Function, optional: true }, + }; + + setup() {} + + toggle(event){ + if(this.props.toggleState){ + this.props.toggleState(this.props.todo.id, event.target.checked); + } + } + + remove(){ + if(this.props.removeTodo){ + this.props.removeTodo(this.props.todo.id); + } + } +} diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..75c343a6f2e --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,8 @@ + +
+ + ID: - + + +
+
\ No newline at end of file diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..9be26ceff38 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,45 @@ +import { Component, useState, onMounted, } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutofocus } from "../utils"; + + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + + static props = {}; + + setup() { + this.todos = useState([ + { id: 1, description: "buy milk", isCompleted: true }, + { id: 2, description: "buy cheese", isCompleted: false }, + { id: 3, description: "buy bread", isCompleted: false }]); + useAutofocus(this, "todoInput"); + } + + addTodo(event) { + if(event.key === "Enter" && event.target.value.trim() !== ""){ + const newId = this.todos.length ? Math.max(...this.todos.map(t => t.id)) +1 : 1; + this.todos.push({ + id: newId, + description: event.target.value.trim(), + isCompleted: false + }); + event.target.value = ""; + } + } + + removeTodo(elemId){ + const index = this.todos.findIndex((elem) => elem.id === elemId); + if(index >= 0){ + this.todos.splice(index, 1); + } + } + + toggleTodoState(id, state){ + const todo = this.todos.find(t => t.id === id); + if(todo){ + todo.isCompleted = state; + } + } +} diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..78b1b4ea789 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,8 @@ + +
+ + + + +
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..e9a152da5ac --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,21 @@ +/** + * useAutofocus - helper to focus an input element + * @param {Component} component - the component instance + * @param {string} refName - the t-ref name of the input element + */ +export function useAutofocus(component, refName) { + // Store original mounted method + const originalMounted = component.mounted; + + component.mounted = function () { + // Call original mounted() if it exists + if (originalMounted) { + originalMounted.call(this); + } + + // Focus the element + if (this.refs && this.refs[refName]) { + this.refs[refName].focus(); + } + }; +} From 4dad56bbf6b5e504b6af8a4932c77f402c7475c0 Mon Sep 17 00:00:00 2001 From: usseif Date: Wed, 24 Sep 2025 14:07:55 +0200 Subject: [PATCH 04/11] chapter2.3 --- awesome_dashboard/static/src/dashboard.js | 26 ++++++++++++++++++++- awesome_dashboard/static/src/dashboard.scss | 9 +++++++ awesome_dashboard/static/src/dashboard.xml | 17 +++++++++++++- tutorials | 1 + 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard.scss create mode 160000 tutorials diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 637fa4bb972..ee015f441fd 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,10 +1,34 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { Component } from "@odoo/owl"; + class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout }; + + setup() { + this.action = useService("action"); + } + + openCustomers() { + this.action.doAction('base.action_partner_form'); // get the action by its XML ID + } + + openLeads(){ + this.action.doAction({ // define the action inline + type: 'ir.actions.act_window', + name: 'Leads', + target: 'current', + res_model: 'crm.lead', + views: [ + [false, 'form'], + [false, 'list']], + }); + } } registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard.scss new file mode 100644 index 00000000000..8bb95166581 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard.scss @@ -0,0 +1,9 @@ +.o_dashboard { + background-color: #f5f5f5; + padding: 20px; +} + +.o_dashboard_buttons { + display: flex; + gap: 0.5rem; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 1a2ac9a2fed..5cfebc520ec 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -2,7 +2,22 @@ - hello dashboard + +
+
+ + + +
+ +

Dashboard

+ +
+
diff --git a/tutorials b/tutorials new file mode 160000 index 00000000000..696a0cc0644 --- /dev/null +++ b/tutorials @@ -0,0 +1 @@ +Subproject commit 696a0cc064431d4b62e2629fc34919d4a9a69ee5 From 9e75e82ceaf05181ad27936062d82164e59194da Mon Sep 17 00:00:00 2001 From: usseif Date: Wed, 24 Sep 2025 16:57:09 +0200 Subject: [PATCH 05/11] chapter2.6 --- awesome_dashboard/static/src/dashboard.js | 17 +++++++++------ awesome_dashboard/static/src/dashboard.scss | 4 ++-- awesome_dashboard/static/src/dashboard.xml | 21 ++++++++++++++++++- .../static/src/dashboard_item.js | 13 ++++++++++++ .../static/src/dashboard_item.xml | 11 ++++++++++ .../static/src/statistics_service.js | 13 ++++++++++++ awesome_owl/static/src/card/card.js | 2 +- awesome_owl/static/src/card/card.xml | 1 - 8 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/statistics_service.js diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index ee015f441fd..0c6acd7fd54 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -3,19 +3,24 @@ import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; -import { Component } from "@odoo/owl"; - - +import { rpc } from "@web/core/network/rpc"; +import { DashboardItem } from "./dashboard_item"; +import { Component, useState, onWillStart } from "@odoo/owl"; + class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout }; + static components = { Layout, DashboardItem }; setup() { - this.action = useService("action"); + this.action = useService("action"); + this.statistics = useService("awesome_dashboard.statistics") + onWillStart(async () => { + this.result = await this.statistics.data(); + }) } openCustomers() { - this.action.doAction('base.action_partner_form'); // get the action by its XML ID + this.action.doAction('base.action_partner_form'); // get the action by its XML ID } openLeads(){ diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard.scss index 8bb95166581..bdf7fe1d1cb 100644 --- a/awesome_dashboard/static/src/dashboard.scss +++ b/awesome_dashboard/static/src/dashboard.scss @@ -5,5 +5,5 @@ .o_dashboard_buttons { display: flex; - gap: 0.5rem; -} \ No newline at end of file + gap: 0.5rem; +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 5cfebc520ec..8b39f4eb428 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -15,8 +15,27 @@

Dashboard

- + +

Average amount

+

+ + +

Average Time

+

+ + +

Number of new orders

+

+ + +

Number of cancelled orders

+

+ + +

Total amount

+

+ diff --git a/awesome_dashboard/static/src/dashboard_item.js b/awesome_dashboard/static/src/dashboard_item.js new file mode 100644 index 00000000000..a29a474ea76 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item.js @@ -0,0 +1,13 @@ +import { Component } from "@odoo/owl"; + + +export class DashboardItem extends Component { + static template = "awesome_dashboard.dashboard_item"; + + static props = { + size: { type: Number, default: 1, optional: true }, + slots: { type: Object, optional: true }, + }; + +} + diff --git a/awesome_dashboard/static/src/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item.xml new file mode 100644 index 00000000000..b907c90f762 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item.xml @@ -0,0 +1,11 @@ + + + + +

+
+ +
+
+
+
diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js new file mode 100644 index 00000000000..47ec6c3f156 --- /dev/null +++ b/awesome_dashboard/static/src/statistics_service.js @@ -0,0 +1,13 @@ +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; +import { memoize } from "@web/core/utils/functions"; + + +export const StatisticsService = { + start() { + return { + data: memoize(async() => await rpc("/awesome_dashboard/statistics")) + }; + } +}; +registry.category("services").add("awesome_dashboard.statistics", StatisticsService); diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 03dba728272..a513debda4f 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -5,7 +5,7 @@ export class Card extends Component { static props = { title: String, - slots: { type: Object, optional: true } + slots: { type: Object, optional: true }, }; setup() { diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index a700bf47231..8d74d3b4e72 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -1,5 +1,4 @@ -
From 6704c0439c4dd648469ea133bf91155a16c72a5d Mon Sep 17 00:00:00 2001 From: usseif Date: Thu, 25 Sep 2025 14:38:57 +0200 Subject: [PATCH 06/11] chapter2.7 - The most difficult sofar --- awesome_dashboard/static/src/dashboard.js | 16 ++--- awesome_dashboard/static/src/dashboard.xml | 16 +++-- .../static/src/dashboard_item.js | 2 - awesome_dashboard/static/src/pie_chart.js | 69 +++++++++++++++++++ awesome_dashboard/static/src/pie_chart.xml | 8 +++ .../static/src/statistics_service.js | 27 +++++++- 6 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 awesome_dashboard/static/src/pie_chart.js create mode 100644 awesome_dashboard/static/src/pie_chart.xml diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 0c6acd7fd54..537982ea2b8 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,26 +1,22 @@ -/** @odoo-module **/ - import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; -import { rpc } from "@web/core/network/rpc"; +import { PieChart } from "./pie_chart"; import { DashboardItem } from "./dashboard_item"; -import { Component, useState, onWillStart } from "@odoo/owl"; + +import { Component, onWillStart, useState } from "@odoo/owl"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout, DashboardItem }; + static components = { Layout, DashboardItem, PieChart }; setup() { this.action = useService("action"); - this.statistics = useService("awesome_dashboard.statistics") - onWillStart(async () => { - this.result = await this.statistics.data(); - }) + this.statistics = useState(useService("awesome_dashboard.statistics")); // useState because it's reactive } openCustomers() { - this.action.doAction('base.action_partner_form'); // get the action by its XML ID + this.action.doAction('base.action_partner_form'); } openLeads(){ diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 8b39f4eb428..8bce6a6b395 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -18,24 +18,30 @@

Average amount

-

+

Average Time

-

+

Number of new orders

-

+

Number of cancelled orders

-

+

Total amount

-

+

+ + + + + + diff --git a/awesome_dashboard/static/src/dashboard_item.js b/awesome_dashboard/static/src/dashboard_item.js index a29a474ea76..8ee760d46d3 100644 --- a/awesome_dashboard/static/src/dashboard_item.js +++ b/awesome_dashboard/static/src/dashboard_item.js @@ -8,6 +8,4 @@ export class DashboardItem extends Component { size: { type: Number, default: 1, optional: true }, slots: { type: Object, optional: true }, }; - } - diff --git a/awesome_dashboard/static/src/pie_chart.js b/awesome_dashboard/static/src/pie_chart.js new file mode 100644 index 00000000000..660bfeaeb80 --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart.js @@ -0,0 +1,69 @@ +import { loadJS } from "@web/core/assets"; +import { Component, onWillStart, onMounted, onWillUnmount, useEffect, useRef } from "@odoo/owl"; + +export class PieChart extends Component { + static template = "awesome_dashboard.pie_chart"; + static props = { + title: String, + data: Object, + }; + + setup() { + this.canvasRef = useRef("canvas") + + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js"); + }); + + useEffect(() => { + if (this.pieChart) { + this.pieChart.destroy(); + } + this.renderPieChart(); + }); + onWillUnmount(() => { + if (this) { + this.destroy(); + } + }); + } + + renderPieChart() { + if (!this.canvasRef.el || !this.props.data) { + return; + } + + const key = Object.keys(this.props.data); + const value = Object.values(this.props.data); + const backgroundColor = [ + 'rgba(255, 99, 132, 0.2)', + 'rgba(54, 162, 235, 0.2)', + 'rgba(255, 206, 86, 0.2)', + 'rgba(75, 192, 192, 0.2)', + 'rgba(153, 102, 255, 0.2)', + 'rgba(255, 159, 64, 0.2)' + ]; + const borderColor = [ + 'rgba(255, 99, 132, 1)', + 'rgba(54, 162, 235, 1)', + 'rgba(255, 206, 86, 1)', + 'rgba(75, 192, 192, 1)', + 'rgba(153, 102, 255, 1)', + 'rgba(255, 159, 64, 1)' + ]; + + this.pieChart = new Chart(this.canvasRef.el, { + type: 'pie', + data: { + labels: key, + datasets: [{ + label: this.props.title, + data: value, + backgroundColor: backgroundColor, + borderColor: borderColor, + borderWidth: 1 + }] + }, + }); + } +} diff --git a/awesome_dashboard/static/src/pie_chart.xml b/awesome_dashboard/static/src/pie_chart.xml new file mode 100644 index 00000000000..8b6e186221a --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart.xml @@ -0,0 +1,8 @@ + + + + +

+ + + diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js index 47ec6c3f156..43e4f1b6ee0 100644 --- a/awesome_dashboard/static/src/statistics_service.js +++ b/awesome_dashboard/static/src/statistics_service.js @@ -1,13 +1,34 @@ import { rpc } from "@web/core/network/rpc"; import { registry } from "@web/core/registry"; import { memoize } from "@web/core/utils/functions"; +import { reactive } from "@odoo/owl"; +async function loadData() { + return await rpc("/awesome_dashboard/statistics"); +} export const StatisticsService = { - start() { - return { - data: memoize(async() => await rpc("/awesome_dashboard/statistics")) + interval: null, + async start() { + const reactiveStatistics = reactive({ isReady: false }, () => { + console.log("StatisticsService: reactive data object has been updated."); + }); + + const fetchDataAndUpdate = async () => { + try { + const newData = await loadData(); // remove memoized cache + Object.assign(reactiveStatistics, newData, { isReady: true }); + } catch (e) { + console.error("Failed to fetch data:", e); + } }; + await fetchDataAndUpdate(); + this.interval = setInterval(fetchDataAndUpdate, 10000 * 60 * 10); // every 10 minutes + return reactiveStatistics; + + }, + onDestroy() { // used with Services to clean up when the service is stopped + clearInterval(this.interval); } }; registry.category("services").add("awesome_dashboard.statistics", StatisticsService); From a715595843fba0a16be0e0c21d9552a5c491a401 Mon Sep 17 00:00:00 2001 From: usseif Date: Thu, 25 Sep 2025 15:56:18 +0200 Subject: [PATCH 07/11] chapter2.8 - Guess what, it's more difficuilt than the previous --- awesome_dashboard/__manifest__.py | 4 +++ .../static/src/{ => dashboard}/dashboard.js | 8 ++--- .../static/src/{ => dashboard}/dashboard.scss | 0 .../static/src/{ => dashboard}/dashboard.xml | 0 .../src/{ => dashboard}/dashboard_item.js | 0 .../src/{ => dashboard}/dashboard_item.xml | 0 .../{ => dashboard/pie_chart}/pie_chart.js | 0 .../{ => dashboard/pie_chart}/pie_chart.xml | 0 .../src/dashboard/statistics_service.js | 17 ++++++++++ .../static/src/dashboard_action.js | 12 +++++++ .../static/src/statistics_service.js | 34 ------------------- 11 files changed, 36 insertions(+), 39 deletions(-) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.js (84%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.scss (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.xml (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard_item.js (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard_item.xml (100%) rename awesome_dashboard/static/src/{ => dashboard/pie_chart}/pie_chart.js (100%) rename awesome_dashboard/static/src/{ => dashboard/pie_chart}/pie_chart.xml (100%) create mode 100644 awesome_dashboard/static/src/dashboard/statistics_service.js create mode 100644 awesome_dashboard/static/src/dashboard_action.js delete mode 100644 awesome_dashboard/static/src/statistics_service.js diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..bd5b758e969 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -24,7 +24,11 @@ 'assets': { 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', + ('remove', 'awesome_dashboard/static/src/dashboard/*'), # Lazy loaded, so remove from main bundle ], + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/*' # create new bundle to be lazy loaded + ] }, 'license': 'AGPL-3' } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js similarity index 84% rename from awesome_dashboard/static/src/dashboard.js rename to awesome_dashboard/static/src/dashboard/dashboard.js index 537982ea2b8..d5b04b9696d 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,9 +1,8 @@ import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; -import { PieChart } from "./pie_chart"; +import { PieChart } from "./pie_chart/pie_chart"; import { DashboardItem } from "./dashboard_item"; - import { Component, onWillStart, useState } from "@odoo/owl"; class AwesomeDashboard extends Component { @@ -12,7 +11,7 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); - this.statistics = useState(useService("awesome_dashboard.statistics")); // useState because it's reactive + this.statistics = useState(useService("awesome_dashboard.statistics")); // useState() because it's reactive } openCustomers() { @@ -31,5 +30,4 @@ class AwesomeDashboard extends Component { }); } } - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss similarity index 100% rename from awesome_dashboard/static/src/dashboard.scss rename to awesome_dashboard/static/src/dashboard/dashboard.scss diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml similarity index 100% rename from awesome_dashboard/static/src/dashboard.xml rename to awesome_dashboard/static/src/dashboard/dashboard.xml diff --git a/awesome_dashboard/static/src/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js similarity index 100% rename from awesome_dashboard/static/src/dashboard_item.js rename to awesome_dashboard/static/src/dashboard/dashboard_item.js diff --git a/awesome_dashboard/static/src/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item.xml similarity index 100% rename from awesome_dashboard/static/src/dashboard_item.xml rename to awesome_dashboard/static/src/dashboard/dashboard_item.xml diff --git a/awesome_dashboard/static/src/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js similarity index 100% rename from awesome_dashboard/static/src/pie_chart.js rename to awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js diff --git a/awesome_dashboard/static/src/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml similarity index 100% rename from awesome_dashboard/static/src/pie_chart.xml rename to awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js new file mode 100644 index 00000000000..dcc2b80ad26 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,17 @@ +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; + +const statisticsService = { + start() { + const statistics = reactive({isReady : false}); + async function loadingData() { + const data = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, data, {isReady : true}); + setInterval(loadingData, 1000 * 60 * 10); + } + loadingData(); + return statistics; + }, +}; +registry.category("services").add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..b24476265dc --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,12 @@ +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` + + `; + +} +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js deleted file mode 100644 index 43e4f1b6ee0..00000000000 --- a/awesome_dashboard/static/src/statistics_service.js +++ /dev/null @@ -1,34 +0,0 @@ -import { rpc } from "@web/core/network/rpc"; -import { registry } from "@web/core/registry"; -import { memoize } from "@web/core/utils/functions"; -import { reactive } from "@odoo/owl"; - -async function loadData() { - return await rpc("/awesome_dashboard/statistics"); -} - -export const StatisticsService = { - interval: null, - async start() { - const reactiveStatistics = reactive({ isReady: false }, () => { - console.log("StatisticsService: reactive data object has been updated."); - }); - - const fetchDataAndUpdate = async () => { - try { - const newData = await loadData(); // remove memoized cache - Object.assign(reactiveStatistics, newData, { isReady: true }); - } catch (e) { - console.error("Failed to fetch data:", e); - } - }; - await fetchDataAndUpdate(); - this.interval = setInterval(fetchDataAndUpdate, 10000 * 60 * 10); // every 10 minutes - return reactiveStatistics; - - }, - onDestroy() { // used with Services to clean up when the service is stopped - clearInterval(this.interval); - } -}; -registry.category("services").add("awesome_dashboard.statistics", StatisticsService); From 66a023621a312697aeefffdf9cc223c25d70f540 Mon Sep 17 00:00:00 2001 From: usseif Date: Fri, 26 Sep 2025 10:54:41 +0200 Subject: [PATCH 08/11] chapter2.9 - refactor --- .../static/src/dashboard/dashboard.js | 5 +- .../static/src/dashboard/dashboard.xml | 33 +++------- .../static/src/dashboard/dashboard_items.js | 60 +++++++++++++++++++ .../src/dashboard/number_card/number_card.js | 9 +++ .../src/dashboard/number_card/number_card.xml | 9 +++ .../pie_chart_card/pie_chart_card.js | 11 ++++ .../pie_chart_card/pie_chart_card.xml | 7 +++ .../src/dashboard/statistics_service.js | 2 +- 8 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index d5b04b9696d..4607a0d484b 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,9 +1,11 @@ import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; +import { items } from "./dashboard_items"; import { PieChart } from "./pie_chart/pie_chart"; import { DashboardItem } from "./dashboard_item"; -import { Component, onWillStart, useState } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; + class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; @@ -12,6 +14,7 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); this.statistics = useState(useService("awesome_dashboard.statistics")); // useState() because it's reactive + this.items = items; } openCustomers() { diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index 8bce6a6b395..22b912ab7ec 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -16,32 +16,13 @@

Dashboard

- -

Average amount

-

- - -

Average Time

-

- - -

Number of new orders

-

- - -

Number of cancelled orders

-

- - -

Total amount

-

- - - - - - - + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..7ee63884f27 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,60 @@ +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; + +export const items = [ + { + id: "average_quantity", + description: "Average amount", + Component: NumberCard, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity + }), + }, + { + id: "average_time", + description: "Average Time", + Component: NumberCard, + props: (data) => ({ + title: "Average Time", + value: data.average_time + }), + }, + { + id: "nb_new_orders", + description: "Number of new orders", + Component: NumberCard, + props: (data) => ({ + title: "Number of new orders", + value: data.nb_new_orders + }), + }, + { + id: "nb_cancelled_orders", + description: "Number of cancelled orders", + Component: NumberCard, + props: (data) => ({ + title: "ANumber of cancelled orders", + value: data.nb_cancelled_orders + }), + }, + { + id: "total_amount", + description: "Total amount", + Component: NumberCard, + props: (data) => ({ + title: "Total amount", + value: data.total_amount + }), + }, + { + id: "pie_chart", + description: "Shirts orders by size", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "Shirts orders by size", + value: data.orders_by_size + }), + }, +]; \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..921c9bf831a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: { type: String }, + value: { type: Number }, + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..73bc3cac926 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,9 @@ + + + + +

+ +
+ + \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js new file mode 100644 index 00000000000..39fbbd16bc8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -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.PieChartCard"; + static components = { PieChart } + static props = { + title: { type: String }, + value: { type: Object }, + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml new file mode 100644 index 00000000000..3d786c807d8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js index dcc2b80ad26..fce2dbe2674 100644 --- a/awesome_dashboard/static/src/dashboard/statistics_service.js +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -8,9 +8,9 @@ const statisticsService = { async function loadingData() { const data = await rpc("/awesome_dashboard/statistics"); Object.assign(statistics, data, {isReady : true}); - setInterval(loadingData, 1000 * 60 * 10); } loadingData(); + setInterval(loadingData, 1000 * 60 * 10); return statistics; }, }; From 2448ca386f4da74a39ae1dbb06b5e0761985f6de Mon Sep 17 00:00:00 2001 From: usseif Date: Fri, 26 Sep 2025 11:45:58 +0200 Subject: [PATCH 09/11] chapter2.10 - registery --- awesome_dashboard/static/src/dashboard/dashboard.js | 3 +-- .../static/src/dashboard/dashboard_items.js | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index 4607a0d484b..b7df4c71ca0 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,7 +1,6 @@ import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; -import { items } from "./dashboard_items"; import { PieChart } from "./pie_chart/pie_chart"; import { DashboardItem } from "./dashboard_item"; import { Component, useState } from "@odoo/owl"; @@ -14,7 +13,7 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); this.statistics = useState(useService("awesome_dashboard.statistics")); // useState() because it's reactive - this.items = items; + this.items = registry.category("awesome_dashboard.items").getAll(); } openCustomers() { diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js index 7ee63884f27..37fa96fa29d 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard_items.js +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -1,7 +1,9 @@ import { NumberCard } from "./number_card/number_card"; import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { registry } from "@web/core/registry"; -export const items = [ + +const items = [ { id: "average_quantity", description: "Average amount", @@ -57,4 +59,8 @@ export const items = [ value: data.orders_by_size }), }, -]; \ No newline at end of file +] + +items.forEach((item => { + registry.category("awesome_dashboard.items").add(item.id, item); +})); \ No newline at end of file From 143511d35739d402bd0417acef60ed20c37e081a Mon Sep 17 00:00:00 2001 From: usseif Date: Fri, 26 Sep 2025 16:19:57 +0200 Subject: [PATCH 10/11] chapter2.11 - challenging but done --- .../configuration_dialog.js | 39 +++++++++++++++++++ .../configuration_dialog.xml | 18 +++++++++ .../static/src/dashboard/dashboard.js | 18 +++++++++ .../static/src/dashboard/dashboard.xml | 18 ++++----- .../static/src/dashboard/dashboard_item.js | 2 - .../static/src/dashboard/dashboard_items.js | 5 +-- .../src/dashboard/pie_chart/pie_chart.js | 4 +- 7 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js create mode 100644 awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js new file mode 100644 index 00000000000..28f82c508cf --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js @@ -0,0 +1,39 @@ +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; +import { Component, useState } from "@odoo/owl"; + +export class ConfigurationDialog extends Component { + static template = "awesome_dashboard.ConfigurationDialog"; + static components = { Dialog, CheckBox }; + + static props = { + items: Array, + disabledItems: Array, + onUpdateConfiguration: Function, + }; + + + setup() { + this.checkBoxItems = useState(this.props.items.map((item) => { // Add 'enabled' field to Item -> return Array of Objects + return { + ...item, + enabled: !this.props.disabledItems.includes(item.id), + } + })); + } + + onChange(checked, changedItem) { + changedItem.enabled = checked; + } + + onDone(){ + const newDisabledItems = this.checkBoxItems.filter(item => !item.enabled).map(item => item.id); + browser.localStorage.setItem( + "disabledDashboardItems", + newDisabledItems, + ); + this.props.onUpdateConfiguration(newDisabledItems); + this.props.close(); + } +} diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml new file mode 100644 index 00000000000..82eb01340bd --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml @@ -0,0 +1,18 @@ + + + + + Which cards do you whish to see ? + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index b7df4c71ca0..a29875acb26 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,8 +1,10 @@ import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; +import { browser } from "@web/core/browser/browser"; import { PieChart } from "./pie_chart/pie_chart"; import { DashboardItem } from "./dashboard_item"; +import { ConfigurationDialog } from "./configuration_dialog/configuration_dialog"; import { Component, useState } from "@odoo/owl"; @@ -12,8 +14,12 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); + this.dialog = useService("dialog"); this.statistics = useState(useService("awesome_dashboard.statistics")); // useState() because it's reactive this.items = registry.category("awesome_dashboard.items").getAll(); + this.state = useState({ // useState() because it's reactive + disabledItems: browser.localStorage.getItem("disabledDashboardItems")?.split(",") || [] + }); } openCustomers() { @@ -31,5 +37,17 @@ class AwesomeDashboard extends Component { [false, 'list']], }); } + + openConfiguration(){ + this.dialog.add(ConfigurationDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + onUpdateConfiguration: this.updateConfiguration.bind(this), + }); + } + + updateConfiguration(newDisabledItems){ + this.state.disabledItems = newDisabledItems // update the state + } } registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index 22b912ab7ec..1c2f903003f 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -5,19 +5,18 @@
- - - + +
+
+

Dashboard

+
- -

Dashboard

- + @@ -25,5 +24,4 @@
- diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js index 8ee760d46d3..f0e6bca5d49 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard_item.js +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js @@ -1,9 +1,7 @@ import { Component } from "@odoo/owl"; - export class DashboardItem extends Component { static template = "awesome_dashboard.dashboard_item"; - static props = { size: { type: Number, default: 1, optional: true }, slots: { type: Object, optional: true }, diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js index 37fa96fa29d..eb48a357083 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard_items.js +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -2,7 +2,6 @@ import { NumberCard } from "./number_card/number_card"; import { PieChartCard } from "./pie_chart_card/pie_chart_card"; import { registry } from "@web/core/registry"; - const items = [ { id: "average_quantity", @@ -36,7 +35,7 @@ const items = [ description: "Number of cancelled orders", Component: NumberCard, props: (data) => ({ - title: "ANumber of cancelled orders", + title: "Number of cancelled orders", value: data.nb_cancelled_orders }), }, @@ -63,4 +62,4 @@ const items = [ items.forEach((item => { registry.category("awesome_dashboard.items").add(item.id, item); -})); \ No newline at end of file +})); diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js index 660bfeaeb80..fd84ddab579 100644 --- a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -22,8 +22,8 @@ export class PieChart extends Component { this.renderPieChart(); }); onWillUnmount(() => { - if (this) { - this.destroy(); + if (this.pieChart) { + this.pieChart.destroy(); } }); } From 3867e9e370c4e003c89ffa8339c6da4316674aa3 Mon Sep 17 00:00:00 2001 From: usseif Date: Mon, 29 Sep 2025 15:42:18 +0200 Subject: [PATCH 11/11] chapter2.12 + Faruk reviews & Viola --- .vscode/launch.json | 17 ++++++++++++ awesome_dashboard/__init__.py | 1 + awesome_dashboard/__manifest__.py | 1 + awesome_dashboard/controllers/__init__.py | 3 ++- .../awesome_dashboard_controller.py | 27 +++++++++++++++++++ awesome_dashboard/models/__init__.py | 1 + .../models/user_dashboard_settings.py | 8 ++++++ .../security/ir.model.access.csv | 2 ++ .../configuration_dialog.js | 10 +++---- .../static/src/dashboard/dashboard.js | 11 +++++--- .../static/src/dashboard/dashboard.scss | 8 ++++++ .../static/src/dashboard/dashboard_items.js | 27 ++++++++++--------- .../src/dashboard/number_card/number_card.js | 6 ++--- .../src/dashboard/number_card/number_card.xml | 2 +- .../src/dashboard/pie_chart/pie_chart.js | 24 +++++++++++++++-- .../pie_chart_card/pie_chart_card.js | 6 ++--- .../pie_chart_card/pie_chart_card.xml | 2 +- awesome_owl/static/src/todo/todo_list.js | 2 +- 18 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 awesome_dashboard/controllers/awesome_dashboard_controller.py create mode 100644 awesome_dashboard/models/__init__.py create mode 100644 awesome_dashboard/models/user_dashboard_settings.py create mode 100644 awesome_dashboard/security/ir.model.access.csv diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000000..fcf77ca1563 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "Python Debugger: Current File with Arguments", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "args": "${command:pickArgs}" + } + ] +} \ No newline at end of file diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py index b0f26a9a602..aa4d0fd63a9 100644 --- a/awesome_dashboard/__init__.py +++ b/awesome_dashboard/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- from . import controllers +from . import models diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index bd5b758e969..70686a93aba 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -19,6 +19,7 @@ 'depends': ['base', 'web', 'mail', 'crm'], 'data': [ + 'security/ir.model.access.csv', 'views/views.xml', ], 'assets': { diff --git a/awesome_dashboard/controllers/__init__.py b/awesome_dashboard/controllers/__init__.py index 457bae27e11..f296d20d017 100644 --- a/awesome_dashboard/controllers/__init__.py +++ b/awesome_dashboard/controllers/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- -from . import controllers \ No newline at end of file +from . import controllers +from . import awesome_dashboard_controller diff --git a/awesome_dashboard/controllers/awesome_dashboard_controller.py b/awesome_dashboard/controllers/awesome_dashboard_controller.py new file mode 100644 index 00000000000..bf5cbc41712 --- /dev/null +++ b/awesome_dashboard/controllers/awesome_dashboard_controller.py @@ -0,0 +1,27 @@ +from odoo import http +from odoo.http import request + +class AwesomeDashboardController(http.Controller): + + @http.route('/awesome_dashboard/save_settings', type='json', auth='user') + def save_settings(self, new_disabled_items): + current_user = http.request.env.user + user_dashboard_settings = http.request.env['user.dashboard.settings'].sudo().search([('user_id', '=', current_user.id)], limit=1) + disabled_items = ",".join(new_disabled_items) + + if user_dashboard_settings: + user_dashboard_settings.write({'disabled_items': disabled_items}) + else: + http.request.env['user.dashboard.settings'].sudo().create({ + 'user_id': current_user.id, + 'disabled_items': disabled_items + }) + return True + + @http.route('/awesome_dashboard/get_settings', type='json', auth='user') + def get_settings(self): + current_user = http.request.env.user + user_dashboard_settings = http.request.env['user.dashboard.settings'].sudo().search([('user_id', '=', current_user.id)], limit=1) + if user_dashboard_settings and user_dashboard_settings.disabled_items: + return user_dashboard_settings.disabled_items.split(',') + return [] diff --git a/awesome_dashboard/models/__init__.py b/awesome_dashboard/models/__init__.py new file mode 100644 index 00000000000..de10c9a0443 --- /dev/null +++ b/awesome_dashboard/models/__init__.py @@ -0,0 +1 @@ +from . import user_dashboard_settings diff --git a/awesome_dashboard/models/user_dashboard_settings.py b/awesome_dashboard/models/user_dashboard_settings.py new file mode 100644 index 00000000000..7d11bc90030 --- /dev/null +++ b/awesome_dashboard/models/user_dashboard_settings.py @@ -0,0 +1,8 @@ +from odoo import models, fields + +class UserDashboardSettings(models.Model): + _name = "user.dashboard.settings" + _description = "User Dashboard Settings" + + user_id = fields.Many2one('res.users', string="User", required=True) + disabled_items = fields.Char(string='Dashboard Disabled Items') # Add this Field diff --git a/awesome_dashboard/security/ir.model.access.csv b/awesome_dashboard/security/ir.model.access.csv new file mode 100644 index 00000000000..db5675d1408 --- /dev/null +++ b/awesome_dashboard/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_user_dashboard_settings_model,access_user_dashboard_settings_model,model_user_dashboard_settings,base.group_user,1,1,1,1 diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js index 28f82c508cf..dc6c753ace4 100644 --- a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js +++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js @@ -1,6 +1,6 @@ import { Dialog } from "@web/core/dialog/dialog"; import { CheckBox } from "@web/core/checkbox/checkbox"; -import { browser } from "@web/core/browser/browser"; +import { rpc } from "@web/core/network/rpc"; import { Component, useState } from "@odoo/owl"; export class ConfigurationDialog extends Component { @@ -13,7 +13,6 @@ export class ConfigurationDialog extends Component { onUpdateConfiguration: Function, }; - setup() { this.checkBoxItems = useState(this.props.items.map((item) => { // Add 'enabled' field to Item -> return Array of Objects return { @@ -27,12 +26,9 @@ export class ConfigurationDialog extends Component { changedItem.enabled = checked; } - onDone(){ + onDone() { const newDisabledItems = this.checkBoxItems.filter(item => !item.enabled).map(item => item.id); - browser.localStorage.setItem( - "disabledDashboardItems", - newDisabledItems, - ); + rpc("/awesome_dashboard/save_settings", { new_disabled_items: newDisabledItems }); this.props.onUpdateConfiguration(newDisabledItems); this.props.close(); } diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index a29875acb26..e6d059a654c 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -5,7 +5,8 @@ import { browser } from "@web/core/browser/browser"; import { PieChart } from "./pie_chart/pie_chart"; import { DashboardItem } from "./dashboard_item"; import { ConfigurationDialog } from "./configuration_dialog/configuration_dialog"; -import { Component, useState } from "@odoo/owl"; +import { Component, useState, onWillStart } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; class AwesomeDashboard extends Component { @@ -17,8 +18,12 @@ class AwesomeDashboard extends Component { this.dialog = useService("dialog"); this.statistics = useState(useService("awesome_dashboard.statistics")); // useState() because it's reactive this.items = registry.category("awesome_dashboard.items").getAll(); - this.state = useState({ // useState() because it's reactive - disabledItems: browser.localStorage.getItem("disabledDashboardItems")?.split(",") || [] + this.state = useState({ + disabledItems: [], // useState() because it's reactive + }); + + onWillStart(async () => { + this.state.disabledItems = await rpc("/awesome_dashboard/get_settings"); }); } diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss index bdf7fe1d1cb..136e67a3cbb 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.scss +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -7,3 +7,11 @@ display: flex; gap: 0.5rem; } + +@media (max-width: 767.98px) { + .o_dashboard .card { + width: 100% !important; + max-width: 100% !important; + display: block !important; + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js index eb48a357083..6976f907241 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard_items.js +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -1,60 +1,61 @@ +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"; -import { registry } from "@web/core/registry"; const items = [ { id: "average_quantity", - description: "Average amount", + description: _t("Average amount"), Component: NumberCard, props: (data) => ({ - title: "Average amount of t-shirt by order this month", + title: _t("Average amount of t-shirt by order this month"), value: data.average_quantity }), }, { id: "average_time", - description: "Average Time", + description: _t("Average Time"), Component: NumberCard, props: (data) => ({ - title: "Average Time", + title: _t("Average Time"), value: data.average_time }), }, { id: "nb_new_orders", - description: "Number of new orders", + description: _t("Number of new orders"), Component: NumberCard, props: (data) => ({ - title: "Number of new orders", + title: _t("Number of new orders"), value: data.nb_new_orders }), }, { id: "nb_cancelled_orders", - description: "Number of cancelled orders", + description: _t("Number of cancelled orders"), Component: NumberCard, props: (data) => ({ - title: "Number of cancelled orders", + title: _t("Number of cancelled orders"), value: data.nb_cancelled_orders }), }, { id: "total_amount", - description: "Total amount", + description: _t("Total amount"), Component: NumberCard, props: (data) => ({ - title: "Total amount", + title: _t("Total amount"), value: data.total_amount }), }, { id: "pie_chart", - description: "Shirts orders by size", + description: _t("Shirts orders by size"), Component: PieChartCard, size: 2, props: (data) => ({ - title: "Shirts orders by size", + title: _t("Shirts orders by size"), value: data.orders_by_size }), }, diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js index 921c9bf831a..3296d42cb9b 100644 --- a/awesome_dashboard/static/src/dashboard/number_card/number_card.js +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -3,7 +3,7 @@ import { Component } from "@odoo/owl"; export class NumberCard extends Component { static template = "awesome_dashboard.NumberCard"; static props = { - title: { type: String }, - value: { type: Number }, + title: String, + value: Number, } -} \ No newline at end of file +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml index 73bc3cac926..3a0713623fa 100644 --- a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -6,4 +6,4 @@ - \ No newline at end of file + diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js index fd84ddab579..95df4b89055 100644 --- a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -1,5 +1,6 @@ import { loadJS } from "@web/core/assets"; -import { Component, onWillStart, onMounted, onWillUnmount, useEffect, useRef } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { Component, onWillStart, onWillUnmount, useEffect, useRef } from "@odoo/owl"; export class PieChart extends Component { static template = "awesome_dashboard.pie_chart"; @@ -10,6 +11,7 @@ export class PieChart extends Component { setup() { this.canvasRef = useRef("canvas") + this.action = useService("action"); onWillStart(async () => { await loadJS("/web/static/lib/Chart/Chart.js"); @@ -32,7 +34,6 @@ export class PieChart extends Component { if (!this.canvasRef.el || !this.props.data) { return; } - const key = Object.keys(this.props.data); const value = Object.values(this.props.data); const backgroundColor = [ @@ -64,6 +65,25 @@ export class PieChart extends Component { borderWidth: 1 }] }, + options: { + onClick: (event, elements) => { + if(elements.length > 0){ + const index = elements[0].index; + const size = Object.keys(this.props.data)[index]; + this.action.doAction({ + type: "ir.actions.act_window", + name: "Orders List", + res_model: "sale.order", + views: [ + [false, 'list'], + [false, 'form']], + domain: [ + ['order_line.name', 'ilike', size] + ], + }); + } + } + } }); } } diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js index 39fbbd16bc8..7af6d6ddf95 100644 --- a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -5,7 +5,7 @@ export class PieChartCard extends Component { static template = "awesome_dashboard.PieChartCard"; static components = { PieChart } static props = { - title: { type: String }, - value: { type: Object }, + title: String, + value: Object, } -} \ No newline at end of file +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml index 3d786c807d8..c2d1dbdf09d 100644 --- a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -4,4 +4,4 @@ - \ No newline at end of file + diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js index 9be26ceff38..fa2121e06a2 100644 --- a/awesome_owl/static/src/todo/todo_list.js +++ b/awesome_owl/static/src/todo/todo_list.js @@ -38,7 +38,7 @@ export class TodoList extends Component { toggleTodoState(id, state){ const todo = this.todos.find(t => t.id === id); - if(todo){ + if (todo) { todo.isCompleted = state; } }