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();
+ });
+}