From af84bf0cbf0bc98e300c031e277fa36109d0913c Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Thu, 28 May 2026 09:29:18 +0200 Subject: [PATCH 1/2] [ADD] awesome_owl, awesome_dashboard: partially implement web framework tutorial add card, counter, and todo components to awesome_owl. add almost reactive pie chart to awesome_dashboard. --- awesome_dashboard/static/src/dashboard.js | 30 +++++++++++- awesome_dashboard/static/src/dashboard.scss | 3 ++ awesome_dashboard/static/src/dashboard.xml | 15 +++++- .../src/dashboard_item/dashboard_item.js | 19 ++++++++ .../src/dashboard_item/dashboard_item.xml | 11 +++++ .../static/src/pie_chart/pie_chart.js | 46 +++++++++++++++++++ .../static/src/pie_chart/pie_chart.xml | 8 ++++ .../static/src/statistics_service.js | 22 +++++++++ awesome_owl/static/src/card/card.js | 25 ++++++++++ awesome_owl/static/src/card/card.xml | 17 +++++++ awesome_owl/static/src/counter/counter.js | 21 +++++++++ awesome_owl/static/src/counter/counter.xml | 10 ++++ awesome_owl/static/src/main.js | 1 - awesome_owl/static/src/playground.js | 14 +++++- awesome_owl/static/src/playground.xml | 12 +++++ awesome_owl/static/src/todo/todo_item.js | 37 +++++++++++++++ awesome_owl/static/src/todo/todo_item.xml | 15 ++++++ awesome_owl/static/src/todo/todo_list.js | 41 +++++++++++++++++ awesome_owl/static/src/todo/todo_list.xml | 15 ++++++ awesome_owl/static/src/utils.js | 10 ++++ 20 files changed, 368 insertions(+), 4 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.js create mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.xml create mode 100644 awesome_dashboard/static/src/statistics_service.js create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml 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_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index c4fb245621b..88527030c13 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,8 +1,36 @@ -import { Component } from "@odoo/owl"; +import { Component, onWillStart, useState } from "@odoo/owl"; import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { _t } from "@web/core/l10n/translation"; + +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { Layout } from "@web/search/layout" +import { PieChart } from "./pie_chart/pie_chart"; + class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, PieChart }; + + setup() { + this.action = useService("action"); + this.state = useState({ + stats: useService("awesome_dashboard.statistics"), + }); + } + + async openCustomersKanban() { + this.action.doAction('base.action_partner_form'); + } + + async openLeads() { + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t('Lots of Leads'), + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']], + }); + } } 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..12d12f7d1f8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: gray +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 1a2ac9a2fed..c1da3edcec5 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -2,7 +2,20 @@ - hello dashboard + + + + + + + +

Counter:

+
+ + + + +
diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..62fa57253ff --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js @@ -0,0 +1,19 @@ +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, + shape: { + default: true + } + }, + }; +} diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..b1bb5acc1a2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -0,0 +1,11 @@ + + + +
+
+ +
+
+
+ +
diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/pie_chart/pie_chart.js new file mode 100644 index 00000000000..28a79543642 --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.js @@ -0,0 +1,46 @@ +import { Component, onMounted, onWillStart, onWillUnmount, useRef } from "@odoo/owl" +import { loadJS } from "@web/core/assets"; +import { getColor } from "@web/core/colors/colors"; + + +export class PieChart extends Component { + static template = "awesome_dashboard.pie_chart" + static props = { + data: Object, + optional: true + } + + setup() { + this.state = + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js") + }) + + this.canvasRef = useRef("canvas"); + + if (this.props.data) { + onMounted(() => { + const keys = Object.keys(this.props.data) + const values = Object.values(this.props.data) + const colors = keys.map((_, index) => getColor(index)); + + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: keys, + datasets: [ + { + backgroundColor: colors, + data: values, + }, + ] + } + }); + }) + + onWillUnmount(() => { + this.chart.destroy(); + }); + } + } +} diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..09b6f9835f7 --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/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 new file mode 100644 index 00000000000..48d5ea2fb31 --- /dev/null +++ b/awesome_dashboard/static/src/statistics_service.js @@ -0,0 +1,22 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + + +const statisticsService = { + start() { + let stats = reactive({}) + + async function getStats() { + let updates = await rpc("/awesome_dashboard/statistics"); + Object.assign(stats, updates); + } + + getStats() + setInterval(getStats, 10*1000) + + return stats + } +} + +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 new file mode 100644 index 00000000000..5260a66fb58 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,25 @@ +import { Component, useState } from "@odoo/owl"; + + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: String, + slots: { + type: Object, + shape: { + default: true + } + }, + }; + + setup() { + this.state = useState({ + state: false, + }) + } + + toggleState() { + this.state.state = !this.state.state; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..85b728a00fe --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,17 @@ + + + +
+
+
+ + +
+

+ +

+
+
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..3289fd964ef --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,21 @@ +import { Component, useState } from "@odoo/owl"; + + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static prop = { + onChange: { + type: Function, + optional: true, + }, + } + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + if (this.props.onChange) { this.props.onChange(); } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..c5d85afe20c --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,10 @@ + + + +
+

Counter:

+ +
+
+ +
diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js index 1aaea902b55..6c108687e29 100644 --- a/awesome_owl/static/src/main.js +++ b/awesome_owl/static/src/main.js @@ -9,4 +9,3 @@ const config = { // Mount the Playground component when the document.body is ready whenReady(() => mountComponent(Playground, document.body, config)); - diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..53bc34c32d3 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,17 @@ -import { Component } from "@odoo/owl"; +import {Component, useState} from "@odoo/owl"; +import { Card } from "./card/card"; +import { Counter } from "./counter/counter"; +import { TodoList } from "./todo/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + setup() { + this.sum = useState({ value: 0 }); + } + + incrementSum() { + this.sum.value++ + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..0dc4b3d5e4d 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,6 +4,18 @@
hello world + + +
The sum is:
+ + + + + +
some content
+
+ +
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..fd63fcba41c --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,37 @@ +import { Component } 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 + }, + + deleteTodo: { + type: Function, + optional: true + } + }; + + onChange() { + if (this.props.toggleState) { + this.props.toggleState(this.props.todo.id); + } + } + + onRemove() { + if (this.props.deleteTodo) { + this.props.deleteTodo(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..19beb0d32ff --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
+ +
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..727a1f3d434 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,41 @@ +import { Component, useState } from "@odoo/owl" +import { TodoItem } from "./todo_item"; +import { Autofocus } from "../utils"; + + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + + setup() { + this.nextId = 0; + this.todos = useState([]); + Autofocus("input") + } + + addTodo(event) { + if (event.key === 'Enter' && event.target.value !== '') { + this.todos.push({ + id: this.nextId++, + description: event.target.value, + isCompleted: false + }); + + event.target.value = ''; + } + } + + listToggleComplete(itemId) { + const todoItem = this.todos.find(item => item.id === itemId); + if (todoItem) { + todoItem.isCompleted = !todoItem.isCompleted; + } + } + + removeTodo(itemId) { + const todoIndex = this.todos.findIndex((todo) => todo.id === itemId); + if (todoIndex >= 0) { + this.todos.splice(todoIndex, 1); + } + } +} 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..6366d399b6c --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,15 @@ + + + + +
+ +
+ + + +
+
+
+ +
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..696db80c603 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,10 @@ +import { onMounted, useRef } from "@odoo/owl" + + +export function Autofocus(refName) { + const ref = useRef(refName); + + onMounted(() => { + ref.el.focus() + }) +} From 41db150a252fbda3453eccf0120df4f0a4e91518 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Mon, 1 Jun 2026 09:42:52 +0200 Subject: [PATCH 2/2] [IMP] awesome_dashboard: allow pie chart to update by destroying and rerendering onPatch --- awesome_dashboard/static/src/dashboard.xml | 2 +- .../static/src/pie_chart/pie_chart.js | 48 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index c1da3edcec5..07d9d85249d 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -12,7 +12,7 @@

Counter:

- + diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/pie_chart/pie_chart.js index 28a79543642..45265fd3581 100644 --- a/awesome_dashboard/static/src/pie_chart/pie_chart.js +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.js @@ -1,4 +1,4 @@ -import { Component, onMounted, onWillStart, onWillUnmount, useRef } from "@odoo/owl" +import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useRef, useState } from "@odoo/owl" import { loadJS } from "@web/core/assets"; import { getColor } from "@web/core/colors/colors"; @@ -11,7 +11,6 @@ export class PieChart extends Component { } setup() { - this.state = onWillStart(async () => { await loadJS("/web/static/lib/Chart/Chart.js") }) @@ -20,27 +19,36 @@ export class PieChart extends Component { if (this.props.data) { onMounted(() => { - const keys = Object.keys(this.props.data) - const values = Object.values(this.props.data) - const colors = keys.map((_, index) => getColor(index)); - - this.chart = new Chart(this.canvasRef.el, { - type: "pie", - data: { - labels: keys, - datasets: [ - { - backgroundColor: colors, - data: values, - }, - ] - } - }); + this.renderChart() }) - onWillUnmount(() => { - this.chart.destroy(); + onPatched(() => { + this.chart.destroy() + this.renderChart() + }) + + onWillUnmount(() => { + this.chart.destroy() }); } } + + renderChart() { + const keys = Object.keys(this.props.data) + const values = Object.values(this.props.data) + const colors = keys.map((_, index) => getColor(index)); + + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: keys, + datasets: [ + { + backgroundColor: colors, + data: values, + }, + ] + } + }); + } }