diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py
index b0f26a9a60..aa4d0fd63a 100644
--- a/awesome_dashboard/__init__.py
+++ b/awesome_dashboard/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import controllers
+from . import models
diff --git a/awesome_dashboard/models/__init__.py b/awesome_dashboard/models/__init__.py
new file mode 100644
index 0000000000..0e877c3e38
--- /dev/null
+++ b/awesome_dashboard/models/__init__.py
@@ -0,0 +1 @@
+from . import res_user_settings
diff --git a/awesome_dashboard/models/res_user_settings.py b/awesome_dashboard/models/res_user_settings.py
new file mode 100644
index 0000000000..9533ec2cb9
--- /dev/null
+++ b/awesome_dashboard/models/res_user_settings.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class ResUsersSettings(models.Model):
+ _inherit = 'res.users.settings'
+
+ dashboard_config = fields.Json(string="Dashboard Configuration", readonly=True)
diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js
deleted file mode 100644
index 637fa4bb97..0000000000
--- 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 1a2ac9a2fe..0000000000
--- a/awesome_dashboard/static/src/dashboard.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- hello dashboard
-
-
-
diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js
new file mode 100644
index 0000000000..dd5b40849a
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js
@@ -0,0 +1,26 @@
+import { Component, useState } from "@odoo/owl";
+import { Dialog } from "@web/core/dialog/dialog";
+import { useChildRef } from "@web/core/utils/hooks";
+import { user } from "@web/core/user";
+
+export class ConfigurationDialog extends Component {
+ static template = "awesome_dashboard.ConfigurationDialog";
+ static components = { Dialog };
+ static props = { items: Array };
+
+ setup() {
+ super.setup();
+ this.modalRef = useChildRef();
+ this.state = useState({
+ config: JSON.parse(user.settings.dashboard_config || "null"),
+ });
+ }
+
+ async onCloseClicked() {
+ await user.setUserSettings(
+ "dashboard_config",
+ JSON.stringify(this.state.config)
+ );
+ this.props.close?.();
+ }
+}
diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml
new file mode 100644
index 0000000000..3dcdb158f1
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js
new file mode 100644
index 0000000000..cb38f96852
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.js
@@ -0,0 +1,76 @@
+import { Component, useState, reactive } from "@odoo/owl";
+import { _t } from "@web/core/l10n/translation";
+import { Layout } from "@web/search/layout";
+import { useService } from "@web/core/utils/hooks";
+import { registry } from "@web/core/registry";
+import { DashboardItem } from "./dashboard_item";
+import { ConfigurationDialog } from "./configuration_dialog/configuration_dialog";
+import { user } from "@web/core/user";
+
+class AwesomeDashboard extends Component {
+ static template = "awesome_dashboard.AwesomeDashboard";
+
+ setup() {
+ this.action = useService("action");
+ this.state = useState({ statistics: useService("statistics"), config: {} });
+ this.items = registry.category("awesome_dashboard").getAll();
+
+ if (!user.settings.dashboard_config) {
+ this.state.config = this.initializeDashboardConfig();
+ } else {
+ this.state.config = JSON.parse(user.settings.dashboard_config);
+ }
+ this.dialogService = useService("dialog");
+ }
+
+ initializeDashboardConfig() {
+ const dashboard_config = this.items.reduce((acc, item) => {
+ acc[item.id] = true;
+ return acc;
+ }, {});
+
+ user.setUserSettings("dashboard_config", JSON.stringify(dashboard_config));
+ return dashboard_config;
+ }
+
+ updateUserSettings() {
+ this.state.config = JSON.parse(user.settings.dashboard_config);
+ }
+ sDSA;
+
+ showCustomers() {
+ this.action.doAction("base.action_partner_form");
+ }
+
+ showLeads() {
+ this.action.doAction({
+ type: "ir.actions.act_window",
+ name: _t("Leads"),
+ target: "current",
+ res_model: "crm.lead",
+ views: [
+ [false, "list"],
+ [false, "form"],
+ ],
+ });
+ }
+
+ showConfigurationDialog(ev) {
+ console.log(user.settings);
+ ev?.stopPropagation();
+ this.dialogService.add(
+ ConfigurationDialog,
+ {
+ items: this.items,
+ },
+ {
+ context: this,
+ onClose: this.updateUserSettings.bind(this),
+ }
+ );
+ }
+
+ static components = { Layout, DashboardItem, ConfigurationDialog };
+}
+
+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 0000000000..b496cf14a4
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.scss
@@ -0,0 +1,3 @@
+.o_dashboard {
+ background-color: rgb(255, 215, 142);
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml
new file mode 100644
index 0000000000..5324622b2d
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js
new file mode 100644
index 0000000000..4644048af2
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js
@@ -0,0 +1,18 @@
+import { Component } from "@odoo/owl";
+
+export class DashboardItem extends Component {
+ static template = "awesome_dashboard.DashboardItem";
+ static props = {
+ slots: {
+ type: Object,
+ shape: {
+ default: Object,
+ },
+ },
+ size: {
+ type: Number,
+ default: 1,
+ optional: true,
+ },
+ };
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item.xml
new file mode 100644
index 0000000000..376abfa23c
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
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 0000000000..c5e0001f14
--- /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/pie_chart_card";
+import { _t } from "@web/core/l10n/translation";
+import { registry } from "@web/core/registry";
+
+const dashboard_items = [
+ {
+ id: "average_quantity",
+ description: _t("Average amount of t-shirt"),
+ Component: NumberCard,
+ props: (data) => ({
+ title: "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: 2,
+ props: (data) => ({
+ title:
+ "Average time for an order to go from new to 'sent' or 'cancelled'",
+ value: data.average_time,
+ }),
+ },
+ {
+ id: "nb_new_orders",
+ description: _t("Number of new orders"),
+ Component: NumberCard,
+ props: (data) => ({
+ title: "Number of new orders this month",
+ value: data.nb_new_orders,
+ }),
+ },
+ {
+ id: "nb_cancelled_orders",
+ description: _t("Number of cancelled orders"),
+ Component: NumberCard,
+ size: 2,
+ props: (data) => ({
+ title: "Number of cancelled orders this month",
+ value: data.nb_cancelled_orders,
+ }),
+ },
+ {
+ id: "total_amount",
+ description: _t("Total amount of new orders"),
+ Component: NumberCard,
+ size: 2,
+ props: (data) => ({
+ title: "Total amount of new orders this month",
+ value: data.total_amount,
+ }),
+ },
+ {
+ id: "orders_by_size",
+ description: _t("Shirt orders by size"),
+ Component: PieChartCard,
+ size: 2,
+ props: (data) => ({
+ title: "Shirt orders by size",
+ value: data.orders_by_size,
+ }),
+ },
+];
+
+for (const item of dashboard_items) {
+ 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 0000000000..a14272af92
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js
@@ -0,0 +1,9 @@
+import { Component } from "@odoo/owl";
+
+export class NumberCard extends Component {
+ static template = "awesome_dashboard.NumberCard";
+ static props = {
+ title: String,
+ value: 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 0000000000..0f98c58e62
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
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 0000000000..77a3765c68
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js
@@ -0,0 +1,41 @@
+import { Component, onWillStart, useRef, onMounted } from "@odoo/owl";
+import { loadJS } from "@web/core/assets";
+
+export class PieChart extends Component {
+ static template = "awesome_dashboard.PieChart";
+
+ static props = {
+ data: Object,
+ title: String,
+ };
+
+ setup() {
+ this.canvasRef = useRef("canvas");
+ onWillStart(async () => {
+ await loadJS("/web/static/lib/Chart/Chart.js");
+ });
+ onMounted(() => {
+ this.renderChart();
+ });
+ }
+
+ renderChart() {
+ const labels = Object.keys(this.props.data);
+ const data = Object.values(this.props.data);
+ const color = ["#ff6384", "#36a2eb", "#cc65fe"];
+ this.chart = new Chart(this.canvasRef.el, {
+ type: "pie",
+ data: {
+ labels: labels,
+ datasets: [
+ {
+ label: this.props.title,
+ data: data,
+ backgroundColor: color,
+ },
+ ],
+ },
+ options: { aspectRatio: 1 },
+ });
+ }
+}
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 0000000000..14e6684262
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart_card.js
new file mode 100644
index 0000000000..d1dc66237e
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart_card.js
@@ -0,0 +1,11 @@
+import { Component } from "@odoo/owl";
+import { PieChart } from "./pie_chart";
+
+export class PieChartCard extends Component {
+ static template = "awesome_dashboard.PieChartCard";
+ static components = { PieChart };
+ static props = {
+ value: Object,
+ title: String,
+ };
+}
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart_card.xml
new file mode 100644
index 0000000000..a55ab688b7
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart_card.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/statistics.js b/awesome_dashboard/static/src/dashboard/statistics.js
new file mode 100644
index 0000000000..88165a4f8e
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/statistics.js
@@ -0,0 +1,22 @@
+import { registry } from "@web/core/registry";
+import { reactive } from "@odoo/owl";
+import { rpc } from "@web/core/network/rpc";
+
+async function loadStatistics(statistics) {
+ const updates = await rpc("/awesome_dashboard/statistics");
+ Object.assign(statistics, updates, { isReady: true });
+}
+
+const statisticsService = {
+ start() {
+ const statistics = reactive({ isReady: false });
+
+ setInterval(loadStatistics(statistics), 600 * 1000);
+
+ loadStatistics(statistics);
+
+ return statistics;
+ },
+};
+
+registry.category("services").add("statistics", statisticsService);
diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js
new file mode 100644
index 0000000000..40f99f8afb
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard_action.js
@@ -0,0 +1,14 @@
+import { LazyComponent } from "@web/core/assets";
+import { Component, xml } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+
+export class DashboardAction extends Component {
+ static components = { LazyComponent };
+ static template = xml`
+
+ `;
+}
+
+registry
+ .category("actions")
+ .add("awesome_dashboard.dashboard", DashboardAction);
diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js
new file mode 100644
index 0000000000..0348b79379
--- /dev/null
+++ b/awesome_owl/static/src/card/card.js
@@ -0,0 +1,20 @@
+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({ open: true });
+ }
+
+ toggle() {
+ this.state.open = !this.state.open;
+ }
+
+ static components = { Card };
+}
diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml
new file mode 100644
index 0000000000..be6b062af2
--- /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 0000000000..5dc90567c2
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.js
@@ -0,0 +1,20 @@
+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();
+ }
+ }
+
+ static components = { Counter };
+}
diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml
new file mode 100644
index 0000000000..97f70683fb
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Counter:
+
+
+
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js
index 657fb8b07b..7d00584756 100644
--- a/awesome_owl/static/src/playground.js
+++ b/awesome_owl/static/src/playground.js
@@ -1,7 +1,22 @@
-/** @odoo-module **/
-
-import { Component } from "@odoo/owl";
+import { Component, useState, markup } 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 props = {};
+
+ setup() {
+ this.state = useState({ sum: 0 });
+ }
+
+ incrementSum() {
+ this.state.sum++;
+ }
+
+ content1 = "
some content
";
+ content2 = markup(this.content1);
+
+ static components = { Counter, Card, TodoList, Playground };
}
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml
index 4fb905d59f..f87e651d4a 100644
--- a/awesome_owl/static/src/playground.xml
+++ b/awesome_owl/static/src/playground.xml
@@ -1,10 +1,29 @@
-
+
-
- hello world
+
+
+
+
+
+ The sum 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 0000000000..55992d9971
--- /dev/null
+++ b/awesome_owl/static/src/todo/todo_item.js
@@ -0,0 +1,18 @@
+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 },
+ },
+ onDelete: { type: Function, optional: true },
+ };
+
+ deleteTodo() {
+ this.props.onDelete(this.props.todo);
+ }
+
+ static components = { TodoItem };
+}
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 0000000000..138a077f0d
--- /dev/null
+++ b/awesome_owl/static/src/todo/todo_item.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+ .
+
+
+
+
+
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 0000000000..15329d33b2
--- /dev/null
+++ b/awesome_owl/static/src/todo/todo_list.js
@@ -0,0 +1,37 @@
+import { Component, useState, useRef } from "@odoo/owl";
+import { TodoItem } from "./todo_item";
+import useAutofocus from "../utils";
+
+export class TodoList extends Component {
+ static template = "awesome_owl.todo_list";
+ static props = {};
+
+ setup() {
+ this.id = 1;
+ this.todos = useState([]);
+ this.input = useState({ value: "" });
+ this.inputRef = useRef("todo_input");
+ useAutofocus(this.inputRef);
+ }
+
+ addTodo(ev) {
+ if (ev.keyCode === 13 && ev.target.value) {
+ this.todos.push({
+ id: this.id++,
+ description: this.input.value,
+ isCompleted: false,
+ });
+ this.input.value = "";
+ }
+ }
+
+ removeTodo(todo) {
+ const index = this.todos.findIndex((elem) => elem.id === todo.id);
+ console.log(todo, index);
+ if (index >= 0) {
+ this.todos.splice(index, 1);
+ }
+ }
+
+ static components = { TodoList, TodoItem };
+}
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 0000000000..2426a35167
--- /dev/null
+++ b/awesome_owl/static/src/todo/todo_list.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js
new file mode 100644
index 0000000000..a0a179e2c0
--- /dev/null
+++ b/awesome_owl/static/src/utils.js
@@ -0,0 +1,7 @@
+import { onMounted } from "@odoo/owl";
+
+export default useAutofocus = (ref) => {
+ onMounted(() => {
+ ref.el.focus();
+ });
+};