diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..9ee31a5f7a3 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Dashboard", + 'name': 'Awesome Dashboard', 'summary': """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" @@ -10,8 +10,8 @@ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - 'author': "Odoo", - 'website': "https://www.odoo.com/", + 'author': 'Odoo', + 'website': 'https://www.odoo.com/', 'category': 'Tutorials/AwesomeDashboard', 'version': '0.1', 'application': True, @@ -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' } diff --git a/awesome_dashboard/i18n/fr.po b/awesome_dashboard/i18n/fr.po new file mode 100644 index 00000000000..aafd453f36d --- /dev/null +++ b/awesome_dashboard/i18n/fr.po @@ -0,0 +1,119 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * awesome_dashboard +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-30 09:40+0000\n" +"PO-Revision-Date: 2025-05-30 09:40+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 "Average amount of t-shirt" +msgstr "Montant moyen des t-shirts" + +#. 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 "Montant moyen des t-shirts par commande ce mois-ci" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average time for an order" +msgstr "Temps moyen pour une commande" + +#. 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 'cancelled'" +msgstr "" +"Temps moyen pour qu'une commande passe de « nouvelle » à « envoyée » ou « " +"annulée »" + +#. module: awesome_dashboard +#: model:ir.ui.menu,name:awesome_dashboard.menu_root +msgid "Awesome Dashboard" +msgstr "Tableau de bord génial" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Cancelled orders this month" +msgstr "Commandes annulées ce mois-ci" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Customers" +msgstr "Clients" + +#. module: awesome_dashboard +#: model:ir.actions.client,name:awesome_dashboard.dashboard +#: model:ir.ui.menu,name:awesome_dashboard.dashboard_menu +msgid "Dashboard" +msgstr "Tableau de bord" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Done" +msgstr "Terminé" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Leads" +msgstr "Pistes" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "New orders this month" +msgstr "Nouvelles commandes ce mois-ci" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Number of cancelled orders this month" +msgstr "Nombre de commandes annulées ce mois-ci" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Number of new orders this month" +msgstr "Nombre de nouvelles commandes ce mois-ci" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Shirt orders by size" +msgstr "Commandes de t-shirts par taille" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Total amount of new orders this month" +msgstr "Montant total des nouvelles commandes ce mois-ci" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Which cards do you whish to see ?" +msgstr "Quelles cartes souhaitez-vous voir ?" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "amount orders this month" +msgstr "montant des commandes ce mois-ci" diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 637fa4bb972..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @odoo-module **/ - -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..39c3982538e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,86 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; +import { LazyComponent } from "@web/core/assets"; +import { browser } from "@web/core/browser/browser"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { Dialog } from "@web/core/dialog/dialog"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { PieChart } from "../pie_chart/pie_chart"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.dashboard"; + static components = { Layout, DashboardItem, PieChart, LazyComponent } + async setup() { + super.setup(); + this.display = { + controlPanel: {}, + }; + this.action = useService("action"); + this.statistics = useState(useService("awesome_dashboard.statistics")); + this.items = registry.category("awesome_dashboard").getAll(); + this.dialog = useService("dialog"); + this.state = useState({ + uncheckedItems: browser.localStorage.getItem("uncheckedItems")?.split(",") || [], + }); + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: "All leads", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } + + openConfigurations() { + this.dialog.add(DashboardConfiguration, { + items: this.items, + uncheckedItems: this.state.uncheckedItems, + update: this.update.bind(this), + }); + } + + update(updatedUncheckedItems) { + this.state.uncheckedItems = updatedUncheckedItems; + } +} + +class DashboardConfiguration extends Component { + static template = "awesome_dashboard.dashboard_configuration"; + static components = { Dialog, CheckBox }; + static props = ["close", "items", "uncheckedItems", "update"]; + + setup() { + this.items = useState(this.props.items.map((item) => { + return { + ...item, + enabled: !this.props.uncheckedItems.includes(item.id), + } + })); + } + + done() { + const updatedUncheckedItems = this.items.filter((i) => !i.enabled).map((i) => i.id); + browser.localStorage.setItem("uncheckedItems", updatedUncheckedItems); + this.props.update(updatedUncheckedItems); + this.props.close(); + } + + update(ev, item) { + item.enabled = ev.target.checked; + } +} + +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..066914db228 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: #7f5a7b; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..efa7b485046 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,40 @@ + + + + + + + + + + +
+ + + + + + +
+
+
+ + + + Which cards do you whish to see ? + + + + + + + + + + + +
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..d6262ae0c44 --- /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.dashboard_item"; + static props = { + "size": { + type: Number, + optional: true, + default: 1 + }, + slots: { + type: Object, + shape: { + default: true + }, + } + }; +} 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..9eac1c38a83 --- /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_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..a5e19bc57d5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,68 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { _t } from "@web/core/l10n/translation"; + +const items = [ + { + id: "average_quantity", + description: _t("Average amount of t-shirt"), + Component: NumberCard, + props: (data) => ({ + title: _t("Average amount of t-shirt by order this month"), + value: data.average_quantity, + }) + }, + { + id: "average_time", + description: _t("Average time for an order"), + Component: NumberCard, + props: (data) => ({ + title: _t("Average time for an order to go from 'new' to 'sent' or 'cancelled'"), + value: data.average_time, + }) + }, + { + id: "number_new_orders", + description: _t("New orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of new orders this month"), + value: data.nb_new_orders, + }) + }, + { + id: "cancelled_orders", + description: _t("Cancelled orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of cancelled orders this month"), + value: data.nb_cancelled_orders, + }) + }, + { + id: "amount_new_orders", + description: _t("amount orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Total amount of new orders this month"), + value: data.total_amount, + }) + }, + { + id: "pie_chart", + description: _t("Shirt orders by size"), + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: _t("Shirt orders by size"), + 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..fb35d41957f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,15 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.number_card"; + 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..59043eba235 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,9 @@ + + + + +
+ +
+
+
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..91657329fdb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,17 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; +import { PieChart } from "../../pie_chart/pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.pie_chart_card"; + 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..5f27c515862 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..32b7f652738 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,10 @@ +import { Component, xml } from "@odoo/owl"; +import { LazyComponent } from "@web/core/assets"; +import { registry } from "@web/core/registry"; + +class AwesomeDashboardAction extends Component { + static components = { LazyComponent }; + static template = xml``; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardAction); 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..005c57890e2 --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.js @@ -0,0 +1,41 @@ +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 = { + label: String, + data: Object + }; + + setup() { + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"])); + onMounted(() => { + this.renderChart(); + }); + onWillUnmount(() => { + this.chart.destroy(); + }); + } + + renderChart() { + const labels = Object.keys(this.props.data); + const data = Object.values(this.props.data); + const color = labels.map((_, index) => getColor(index)); + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + label: this.props.label, + data: data, + backgroundColor: color, + }, + ], + }, + }); + } +} 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..242451cf26c --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.xml @@ -0,0 +1,10 @@ + + + +
+
+ +
+
+
+
diff --git a/awesome_dashboard/static/src/services/statistics_service.js b/awesome_dashboard/static/src/services/statistics_service.js new file mode 100644 index 00000000000..aac02baf7da --- /dev/null +++ b/awesome_dashboard/static/src/services/statistics_service.js @@ -0,0 +1,20 @@ +/** @odoo-module **/ + +import { reactive } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; + +const statisticsService = { + start(env) { + const statistics = reactive({ isReady: false }); + async function loadData() { + const updates = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, updates, { isReady: true }); + } + setInterval(loadData, 10 * 60 *1000); + loadData(); + return 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 new file mode 100644 index 00000000000..24adb684763 --- /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": { + type: String, + validate: s => s.startsWith("[Odoo]") + }, + slots: { + type: Object, + shape: { + default: true + }, + } + } + + setup() { + this.state = useState({value: true}); + } + + toggleState() { + this.state.value = !this.state.value; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..04a1bed081b --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,15 @@ + + + + + + + +
+ Title: + +
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..9597136d7ca --- /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 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..01755ae4006 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,10 @@ + + + + +
+ + Counter: +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..fead2823420 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,20 @@ /** @odoo-module **/ -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_list/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + setup() { + this.title = "[Odoo] New version here!!!"; + 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..c8ce7b6468b 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,7 +4,19 @@
hello world + +
+ +
+ Total counter sum: +
+ + + +
+
+
+
- diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js new file mode 100644 index 00000000000..d47928ff5ee --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,25 @@ +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: Function, + deleteTodo: Function + } + + onChange() { + this.props.toggleState(this.props.todo.id); + } + + onDelete() { + this.props.deleteTodo(this.props.todo.id); + } +} diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml new file mode 100644 index 00000000000..a6e42688ddd --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,11 @@ + + + +
+ + + + +
+
+
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..a97d431cde2 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,41 @@ +import { Component, useState } from "@odoo/owl"; +import { useAutofocus } from "../utils"; +import { TodoItem } from "./todo_item"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + static components = {TodoItem}; + + setup() { + this.idCounter = 0; + this.todos = useState([]); + useAutofocus("input"); + this.toggleState = this.toggleState.bind(this); + this.deleteTodo = this.deleteTodo.bind(this); + } + + addTodo(ev) { + if (ev.keyCode === 13 && ev.target.value != "") { + this.todos.push({ + id: this.idCounter++, + description: ev.target.value, + isCompleted: false + }); + ev.target.value = ""; + } + } + + toggleState(id) { + const todo = this.todos.find((t) => t.id === id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + deleteTodo(id) { + const index = this.todos.findIndex((t) => t.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..b903290cd5c --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,12 @@ + + + +
+

Todo List

+ + + + +
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..a392f3f1be2 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,8 @@ +import { onMounted, useRef } from "@odoo/owl"; + +export function useAutofocus(refName) { + var inputRef = useRef(refName); + onMounted(() => { + inputRef.el.focus(); + }); +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..cccaef5d2ce --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.ruff] +line-length = 120 +indent-width = 4 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "Q", "I"] +ignore = ["F401"] + + +[tool.ruff.lint.flake8-quotes] +inline-quotes = "single" +multiline-quotes = "double" +docstring-quotes = "double" + +[tool.ruff.format] +quote-style = "single"