From 480348a7bcdbc876cf9a06a01f608257a5fabbb7 Mon Sep 17 00:00:00 2001 From: 4yuub Date: Thu, 28 May 2026 11:18:39 +0200 Subject: [PATCH] [ADD] awesome_owl: Implement card, counter, todo item, and todo list components with templates and functionality --- awesome_dashboard/static/src/dashboard.js | 8 -- awesome_dashboard/static/src/dashboard.xml | 8 -- .../static/src/dashboard/dashboard.js | 22 ++++++ .../static/src/dashboard/dashboard.scss | 10 +++ .../static/src/dashboard/dashboard.xml | 22 ++++++ .../dashboard_item/dashboard_item.js | 14 ++++ .../dashboard_item/dashboard_item.xml | 10 +++ .../src/dashboard/dashboard_item/index.js | 1 + .../static/src/dashboard/dashboard_items.js | 73 +++++++++++++++++++ .../src/dashboard/number_card/number_card.js | 10 +++ .../dashboard/number_card/number_card.scss | 11 +++ .../src/dashboard/number_card/number_card.xml | 15 ++++ .../pie_chart_card/pie_chart_card.js | 58 +++++++++++++++ .../pie_chart_card/pie_chart_card.scss | 5 ++ .../pie_chart_card/pie_chart_card.xml | 13 ++++ .../static/src/dashboard/statistics.js | 35 +++++++++ .../static/src/dashboard_action.js | 14 ++++ awesome_owl/static/src/card/card.js | 14 ++++ awesome_owl/static/src/card/card.xml | 21 ++++++ awesome_owl/static/src/counter/counter.js | 18 +++++ awesome_owl/static/src/counter/counter.xml | 9 +++ awesome_owl/static/src/playground.js | 18 ++++- awesome_owl/static/src/playground.xml | 19 +++-- awesome_owl/static/src/todo_item/todo_item.js | 15 ++++ .../static/src/todo_item/todo_item.xml | 15 ++++ awesome_owl/static/src/todo_list/todo_list.js | 40 ++++++++++ .../static/src/todo_list/todo_list.xml | 11 +++ awesome_owl/static/src/utils.js | 15 ++++ 28 files changed, 501 insertions(+), 23 deletions(-) delete mode 100644 awesome_dashboard/static/src/dashboard.js delete mode 100644 awesome_dashboard/static/src/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/index.js 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.scss 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.scss create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/statistics.js create mode 100644 awesome_dashboard/static/src/dashboard_action.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_item/todo_item.js create mode 100644 awesome_owl/static/src/todo_item/todo_item.xml create mode 100644 awesome_owl/static/src/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/todo_list/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 deleted file mode 100644 index c4fb245621b..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index 1a2ac9a2fed..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..f3a80ff3b3e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,22 @@ +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'; + +class AwesomeDashboard extends Component { + static template = 'awesome_dashboard.AwesomeDashboard'; + + static components = { Layout, DashboardItem }; + + setup() { + this.items = registry.category('awesome_dashboard.items').getAll(); + + this.statisticsService = useService('statistics_service'); + this.statistics = useState(this.statisticsService.loadStatistics()); + } +} + +registry + .category('lazy_components') + .add('awesome_dashboard.AwesomeDashboard', AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..9baf14e0e77 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,10 @@ +.o_dashboard { + background-color: #e3e3e3; + color: #1f1f1f; + padding: 1rem; + gap: 2rem; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); + // align-items: start; + align-content: start; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..9937fbd44bb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,22 @@ + + + + + + +
+ + + +
+
+ + + + + + +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..03ddedbbde4 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,14 @@ +import { Component } from '@odoo/owl'; + +export class DashboardItem extends Component { + static template = 'awesome_dashboard.dashboard_item'; + + static props = { + slots: { type: Object, optional: true }, + size: { type: Number, optional: true, default: 1 }, + }; + + get itemSize() { + return this.props.size ?? 1; + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..96a51dd84af --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,10 @@ + + + + +
+ +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/index.js b/awesome_dashboard/static/src/dashboard/dashboard_item/index.js new file mode 100644 index 00000000000..21a699ef00b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/index.js @@ -0,0 +1 @@ +export * from './dashboard_item'; 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..1b37f7faa98 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,73 @@ +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: 'nb_new_orders', + description: 'Number of new orders this month', + Component: NumberCard, + size: 1, + props: (data) => ({ + title: 'Number of new orders this month', + value: data.nb_new_orders, + }), + }, + { + id: 'total_amount', + description: 'Total amount of new orders this month', + Component: NumberCard, + size: 2, + props: (data) => ({ + title: 'Total amount of new orders this month', + value: data.total_amount, + }), + }, + { + id: 'average_quantity', + description: 'Average amount of t-shirt by order this month', + Component: NumberCard, + size: 3, + props: (data) => ({ + title: 'Average amount of t-shirt by order this month', + value: data.average_quantity, + }), + }, + { + id: 'nb_cancelled_orders', + description: 'Number of cancelled orders this month', + Component: NumberCard, + size: 2, + props: (data) => ({ + title: 'Number of cancelled orders this month', + value: data.nb_cancelled_orders, + }), + }, + { + id: 'average_time', + description: + 'Average time for an order to go from new to sent or cancelled', + Component: NumberCard, + size: 2, + props: (data) => ({ + title: + 'Average time for an order to go from ‘new’ to ‘sent’ or ‘cancelled’', + value: data.average_time, + }), + }, + { + id: 'order_by_size', + description: 'Shirt order by size', + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: 'Shirt order by size', + dataset: Object.values(data.orders_by_size), + lables: Object.keys(data.orders_by_size), + }), + }, +]; + +for (const item of items) { + registry.category('awesome_dashboard.items').add(item.id, item); +} 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..1236bba9103 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,10 @@ +import { Component } from '@odoo/owl'; + +export class NumberCard extends Component { + static template = 'awesome_dashboard.number_card'; + + static props = { + title: { type: String, optional: true }, + value: { type: Number, optional: true }, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.scss b/awesome_dashboard/static/src/dashboard/number_card/number_card.scss new file mode 100644 index 00000000000..3ce98e1de5b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.scss @@ -0,0 +1,11 @@ +.o_dashboard_item_title { + font-size: 0.85rem; + font-weight: 600; + color: #6c757d; +} + +.o_dashboard_item_value { + font-size: 2.25rem; + font-weight: 700; + color: #378a00; +} 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..b9007598c11 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,15 @@ + + + + +
+
+ +
+
+ +
+
+
+ +
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..eaa70d07eb8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,58 @@ +import { + Component, + onMounted, + onWillStart, + onWillUnmount, + useRef, +} from '@odoo/owl'; +import { loadJS } from '@web/core/assets'; + +export class PieChartCard extends Component { + static template = 'awesome_dashboard.pie_chart_card'; + + static props = { + title: { type: String, optional: true }, + dataset: { type: Array, optional: true }, + labeles: { type: Array, optional: true }, + }; + + setup() { + this.canvasRef = useRef('canvasRef'); + this.chart = null; + + onWillStart(async () => { + await loadJS('/web/static/lib/Chart/Chart.js'); + }); + + onMounted(() => { + this.renderChart(); + }); + + onWillUnmount(() => { + if (this.chart) { + this.chart.destroy(); + } + }); + } + + renderChart() { + const data = { + datasets: [ + { + data: this.props.dataset, + }, + ], + labels: this.props.labels, + }; + + const options = {}; + + const ctx = this.canvasRef.el.getContext('2d'); + + this.chart = new Chart(ctx, { + type: 'pie', + data: data, + options: options, + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.scss b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.scss new file mode 100644 index 00000000000..47535753aa7 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.scss @@ -0,0 +1,5 @@ +.o_dashboard_item_title { + font-size: 0.85rem; + font-weight: 600; + color: #6c757d; +} 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..f6b85851163 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,13 @@ + + + + +
+
+ +
+ +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/statistics.js b/awesome_dashboard/static/src/dashboard/statistics.js new file mode 100644 index 00000000000..f016fb8589f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics.js @@ -0,0 +1,35 @@ +import { reactive } from '@odoo/owl'; +import { rpc } from '@web/core/network/rpc'; +import { registry } from '@web/core/registry'; + +const statisticsService = { + start(env) { + async function fetchStatistics() { + const result = await rpc('/awesome_dashboard/statistics'); + return result; + } + + async function updateData() { + const results = await fetchStatistics(); + + for (const key in data) { + delete data[key]; + } + + Object.assign(data, results); + } + + const data = reactive({}); + + updateData(); + setInterval(updateData, 10000); + + return { + loadStatistics() { + return data; + }, + }; + }, +}; + +registry.category('services').add('statistics_service', 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..bb30edcb882 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,14 @@ +import { Component, xml } from '@odoo/owl'; +import { LazyComponent } from '@web/core/assets'; +import { registry } from '@web/core/registry'; + +export class LazyDashboard extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry + .category('actions') + .add('awesome_dashboard.dashboard', LazyDashboard); diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..1e9d17d75cf --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,14 @@ +import { Component, useState } from '@odoo/owl'; + +export class Card extends Component { + static template = 'awesome_owl.card'; + + static props = { + title: String, + slots: { type: Object, optional: true }, + }; + + setup() { + this.state = useState({ isOpen: true }); + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..fe81e95cdc7 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,21 @@ + + + +
+
+
+
+ +
+ +
+
+ +
+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..35d09e68c68 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,18 @@ +import { Component, useState } from '@odoo/owl'; + +export class Counter extends Component { + static template = 'awesome_owl.counter'; + + static props = { + onChange: Function + } + + setup() { + this.state = useState({ value: 1 }); + } + + increment() { + this.state.value++; + 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..b331841b8e6 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + +
+

Counter:

+ +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..5bebeeec16b 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,19 @@ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from '@odoo/owl'; +import { Card } from './card/card'; +import { Counter } from './counter/counter'; +import { TodoList } from './todo_list/todo_list'; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = 'awesome_owl.playground'; + static components = { Counter, Card, TodoList }; + + setup() { + this.state = useState({ + sum: 2, + }); + } + + incrementSum() { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..2fb4858393f 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,19 @@ - + - -
- hello world +
+
+ + + sum: +
+ + + + +

Hello Slots

+
+
- diff --git a/awesome_owl/static/src/todo_item/todo_item.js b/awesome_owl/static/src/todo_item/todo_item.js new file mode 100644 index 00000000000..d08fbbbd8fb --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.js @@ -0,0 +1,15 @@ +import { Component } from '@odoo/owl'; + +export class TodoItem extends Component { + static template = 'awesome_owl.todo_item'; + + static props = { + todo: { + id: Number, + description: String, + isCompleted: Boolean, + }, + toggleState: Function, + removeTodo: Function, + }; +} diff --git a/awesome_owl/static/src/todo_item/todo_item.xml b/awesome_owl/static/src/todo_item/todo_item.xml new file mode 100644 index 00000000000..144974dee8d --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.xml @@ -0,0 +1,15 @@ + + + +
+ + + . + + + +
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..a5075ad519b --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,40 @@ +import { Component, useState } from '@odoo/owl'; +import { TodoItem } from '../todo_item/todo_item'; +import { useAutoFocus } from '../utils'; + +export class TodoList extends Component { + static template = 'awesome_owl.todo_list'; + static components = { TodoItem }; + + setup() { + this.todos = useState([]); + this.lastId = 1; + this.inputRef = useAutoFocus('taskInput'); + } + + addTodo(ev) { + const value = ev.target.value.trim(); + if (value && ev.keyCode === 13) { + this.todos.push({ + id: this.lastId++, + description: value, + isCompleted: false, + }); + ev.target.value = ''; + } + } + + toggleState(id) { + const todo = this.todos.find((todo) => todo.id === id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + removeTodo(id) { + const index = this.todos.findIndex((todo) => todo.id === id); + if (index >= 0) { + this.todos.splice(index, 1); + } + } +} diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..411118d1ae9 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,11 @@ + + + +
+ + + + +
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..2c7493954ee --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,15 @@ +import { onMounted, useRef } from '@odoo/owl'; + +function useAutoFocus(refName) { + const ref = useRef(refName); + + onMounted(() => { + if (ref.el && ref.el.focus) { + ref.el.focus(); + } + }); + + return ref; +} + +export { useAutoFocus };