diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..89481aa5f4a 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/**/*'), + ], + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*', ], }, - 'license': 'AGPL-3' + 'license': 'AGPL-3', } diff --git a/awesome_dashboard/i18n/nl.po b/awesome_dashboard/i18n/nl.po new file mode 100644 index 00000000000..4c0b01847a0 --- /dev/null +++ b/awesome_dashboard/i18n/nl.po @@ -0,0 +1,117 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * awesome_dashboard +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-04-29 12:41+0000\n" +"PO-Revision-Date: 2026-04-29 12:41+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Amount of orders this month" +msgstr "Aantal orders per maand" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Apply" +msgstr "Opslaan" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average amount of t-shirt" +msgstr "Gemiddeld aantal t-shirt" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average amount of t-shirt by order this month" +msgstr "Gemiddeld aantal t-shirts per bestelling deze maand" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average time for an order" +msgstr "Gemiddelde tijd voor een order" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average time for an order to go from new to sent or canceled" +msgstr "Gemiddelde tijd voor een bestelling om van nieuw naar verzonden of geannuleerd te gaan" + +#. module: awesome_dashboard +#: model:ir.ui.menu,name:awesome_dashboard.menu_root +msgid "Awesome Dashboard" +msgstr "Magisch overzicht" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Canceled orders this month" +msgstr "Geannulleerde orders deze maand" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Customers" +msgstr "Klanten" + +#. module: awesome_dashboard +#: model:ir.actions.client,name:awesome_dashboard.dashboard +#: model:ir.ui.menu,name:awesome_dashboard.dashboard_menu +msgid "Dashboard" +msgstr "Overzicht" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.js:0 +msgid "Dashboard items configuration" +msgstr "Dashboard items configureren" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Leads" +msgstr "Prospecten" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "New orders this month" +msgstr "Nieuwe bestellingen deze maand" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Number of canceled orders this month" +msgstr "Aantal geannuleerde bestellingen deze maand" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Number of new orders this month" +msgstr "Aantal nieuwe orders deze maand" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Shirt orders by size" +msgstr "Shirt bestellingen per maat" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Total amount of orders this month" +msgstr "Totaal aantal bestellingen deze maand" 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..72325add281 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,44 @@ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { DashboardConfiguration } from "./dashboard_configuration/dashboard_configuration"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = {Layout, DashboardItem}; + + setup() { + this.action = useService("action"); + this.statistics = useState(useService("awesome_dashboard.statistics").loadStatistics); + this.dialogService = useService("dialog"); + this.items = registry.category("awesome_dashboard").getAll(); + this.state = useState({ + disabledItems: localStorage.getItem("awesome_dashboard_disabled")?.split(',') || [] + }); + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: 'ir.actions.act_window', target: 'current', res_model: 'crm.lead', views: [[false, "list"], [false, 'form'],], + }); + } + + updateDashboard(disabledItems) { + this.state.disabledItems = disabledItems; + } + + openDialog() { + this.dialog = this.dialogService.add( + DashboardConfiguration, + {items: this.items, disabledItems: this.state.disabledItems, doneUpdating: this.updateDashboard.bind(this)}, + {}); + } +} + +registry.category("lazy_components").add("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..44d19099628 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: darkgrey; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..b6d922e1b1e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_configuration/dashboard_configuration.js b/awesome_dashboard/static/src/dashboard/dashboard_configuration/dashboard_configuration.js new file mode 100644 index 00000000000..b94aa3d56e3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_configuration/dashboard_configuration.js @@ -0,0 +1,28 @@ +import { Component, useRef, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { _t } from "@web/core/l10n/translation"; + +export class DashboardConfiguration extends Component { + static template = "awesome_dashboard.DashboardConfiguration"; + static components = {Dialog, CheckBox, _t}; + static props = ["close", "items", "disabledItems", "doneUpdating"]; + + + setup() { + this.options = useRef("options"); + this.items = useState(this.props.items.map(item => ({...item, disabled: this.props.disabledItems.includes(item.id)}))); + this.title = _t('Dashboard items configuration'); + } + + updateDisabled(item, checked) { + item.disabled = !checked; + } + + apply() { + const disabledItems = this.items.filter(item => item.disabled).map(item => item.id); + localStorage.setItem("awesome_dashboard_disabled", disabledItems); + this.props.doneUpdating(disabledItems); + this.props.close(); + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_configuration/dashboard_configuration.xml b/awesome_dashboard/static/src/dashboard/dashboard_configuration/dashboard_configuration.xml new file mode 100644 index 00000000000..d583bdd05ed --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_configuration/dashboard_configuration.xml @@ -0,0 +1,14 @@ + + + +
+ + + +
+ + + +
+
+
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..ba7597b9389 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,18 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { + type: Number, + optional: true, + }, + slots: { + type: Object, + shape: {default: true}, + } + }; + static defaultProps = { + 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..4b4e80e5829 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,13 @@ + + + +
+
+

+ +

+
+
+
+ +
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..640b24a0f84 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,70 @@ +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { registry } from "@web/core/registry"; +import { _t } from "@web/core/l10n/translation"; + +export let items = [ + { + id: "average_quantity", + description: _t("Average amount of t-shirt"), + Component: NumberCard, + // size and props are optionals + size: 3, + props: (data) => ({ + title: _t("Average amount of t-shirt by order this month"), + value: data.average_quantity + }), + }, + { + id: "average_time", + description: _t("Average time for an order"), + Component: NumberCard, + // size and props are optionals + props: (data) => ({ + title: _t("Average time for an order to go from new to sent or canceled"), + value: data.average_time + }), + }, + { + id: "nb_new_orders", + description: _t("New orders this month"), + Component: NumberCard, + // size and props are optionals + props: (data) => ({ + title: _t("Number of new orders this month"), + value: data.nb_new_orders + }), + }, { + id: "nb_cancelled_orders", + description: _t("Canceled orders this month"), + Component: NumberCard, + // size and props are optionals + props: (data) => ({ + title: _t("Number of canceled orders this month"), + value: data.nb_cancelled_orders + }), + }, + { + id: "total_amount", + description: _t("Amount of orders this month"), + Component: NumberCard, + // size and props are optionals + props: (data) => ({ + title: _t("Total amount of orders this month"), + value: data.total_amount + }), + }, + { + id: "orders_by_size", + description: _t("Shirt orders by size"), + Component: PieChartCard, + // size and props are optionals + props: (data) => ({ + title: _t("Shirt orders by size"), + values: data.orders_by_size + }), + }, + +]; + +items.forEach(item => registry.category("awesome_dashboard").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..66305675c9d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,13 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: { + type: String, + }, + value: { + type: Number, + } + }; +} 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..b69ba44ad0a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,6 @@ + + + +
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js new file mode 100644 index 00000000000..d45d981738a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,40 @@ +import { Component, onMounted, onPatched, onWillStart, onWillUnmount, useRef } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + data: { + type: Object, + } + } + + setup() { + this.canvas = useRef("canvas"); + onWillStart(async () => await loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(() => this.renderChart()); + onPatched(() => { + this.destroyChart(); + this.renderChart(); + }); + onWillUnmount(() => this.destroyChart()); + } + + destroyChart() { + this.chart?.destroy(); + } + + renderChart() { + this.chart = new Chart(this.canvas.el, { + type: "pie", + data: { + labels: Object.keys(this.props.data), + datasets: [ + { + data: Object.values(this.props.data), + }, + ], + } + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..697b3287b6e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -0,0 +1,7 @@ + + + + + + + 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..9af0398ac38 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,15 @@ +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, + }, + values: { + type: Object, + } + }; +} 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..01d3b5f0da3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,6 @@ + + + + + + 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..86d481de2fb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,23 @@ +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; + + +export const statisticsService = { + start() { + const statistics = reactive({isLoaded: false}); + + async function loadStatistics() { + Object.assign(statistics, await rpc("/awesome_dashboard/statistics"), {isLoaded: true}); + } + + setInterval(loadStatistics, 10 * 60 * 1000); + loadStatistics(); + + return { + loadStatistics: statistics, + }; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_loader.js b/awesome_dashboard/static/src/dashboard_loader.js new file mode 100644 index 00000000000..f86023a1e17 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,12 @@ +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; +import { Component, xml } from "@odoo/owl"; + +export class DashBoardComponentLoader extends Component { + static components = {LazyComponent}; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashBoardComponentLoader); diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..2f1c5f10730 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,17 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.Card"; + static props = { + 'title': {type: String}, + 'slots': {type: Object, shape: {default: true}}, + }; + + setup() { + this.state = useState({isOpen: true}); + } + + toggleOpen() { + this.state.isOpen = !this.state.isOpen; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..46f6f651c74 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,13 @@ + + +
+
+
+
+

+ +

+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..e10df9f3be5 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,19 @@ +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(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..be723fcf7dd --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,8 @@ + + +
+ Counter: + +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..a080b864d35 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,20 @@ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo/todo_list"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.Playground"; + static components = {Counter, Card, TodoList}; + static props = []; + + setup() { + this.content1 = "
some content
"; + this.content2 = markup("
some content
"); + this.state = useState({sum: 0}); + } + + increment_total() { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..77100743552 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,20 @@ - - - +
- hello world +
hello world
+
+ + + + the sum of the first two counters is: +
+
+ + +
+
+ +
-
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..3f10d602805 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,29 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.TodoItem"; + static props = { + "todo": { + type: Object, + shape: { + id: Number, + description: String, + isCompleted: Boolean + } + }, + "toggleState": { + type: Function + }, + "removeTodo": { + type: Function + }, + } + + onChange(event) { + this.props.toggleState(this.props.todo.id); + } + + onClickDelete(event) { + 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..418c365686b --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,10 @@ + + +
+ + . + + +
+
+
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..9acc16e9110 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,36 @@ +import { Component, useState } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutofocus } from "../utils"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + static components = {TodoItem}; + static props = []; + + setup() { + this.todos = useState([]); + this.nextId = this.todos.length; + useAutofocus("addTodoInput"); + } + + addToDo(event) { + if (event.keyCode === 13 && event.target.value !== "") { + this.todos.push({id: this.nextId++, description: event.target.value, isCompleted: false}); + event.target.value = ""; + } + } + + toggleStateOfTodo(id) { + const todo = this.todos.find((todo) => todo.id === id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + deleteTodo(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/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..52c34981df6 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,10 @@ + + +
+ + + + +
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..d7652f3d82a --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,8 @@ +import { onMounted, useRef } from "@odoo/owl"; + +export function useAutofocus(named_ref) { + const ref = useRef(named_ref); + onMounted(() => { + ref.el.focus(); + }); +}