diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py
index b0f26a9a602..aa4d0fd63a9 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/__manifest__.py b/awesome_dashboard/__manifest__.py
index 31406e8addb..475bc3eabf1 100644
--- a/awesome_dashboard/__manifest__.py
+++ b/awesome_dashboard/__manifest__.py
@@ -25,6 +25,7 @@
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
],
+ 'awesome_dashboard.dashboard': ['awesome_dashboard/static/src/dashboard/**/*'],
},
'license': 'AGPL-3'
}
diff --git a/awesome_dashboard/i18n/ar_001.po b/awesome_dashboard/i18n/ar_001.po
new file mode 100644
index 00000000000..e836bf4fc2b
--- /dev/null
+++ b/awesome_dashboard/i18n/ar_001.po
@@ -0,0 +1,87 @@
+# 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-09-29 07:53+0000\n"
+"PO-Revision-Date: 2025-09-29 07:53+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 by order this month"
+msgstr "Average amount of t-shirt by order this month (AR)"
+
+#. 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' of 'cancelled'"
+msgstr "Average time for an order to go from 'new' to 'sent' of 'cancelled' (AR)"
+
+#. module: awesome_dashboard
+#: model:ir.ui.menu,name:awesome_dashboard.menu_root
+msgid "Awesome Dashboard"
+msgstr "Awesome Dashboard (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0
+msgid "Customers"
+msgstr "Customers (AR)"
+
+#. module: awesome_dashboard
+#: model:ir.actions.client,name:awesome_dashboard.dashboard
+#: model:ir.ui.menu,name:awesome_dashboard.dashboard_menu
+msgid "Dashboard"
+msgstr "Dashboard (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml:0
+msgid "Done"
+msgstr "Done (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0
+msgid "Leads"
+msgstr "Leads (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0
+msgid "Number of cancelled orders this month"
+msgstr "Number of cancelled orders this month (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0
+msgid "Number of new orders this month"
+msgstr "Number of new orders this month (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0
+msgid "Shirt orders by size"
+msgstr "Shirt orders by size (AR)"
+
+#. 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 "Total amount of new orders this month (AR)"
+
+#. module: awesome_dashboard
+#. odoo-javascript
+#: code:addons/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml:0
+msgid "Which cards do you wish to see?"
+msgstr "Which cards do you wish to see (AR)?"
diff --git a/awesome_dashboard/models/__init__.py b/awesome_dashboard/models/__init__.py
new file mode 100644
index 00000000000..82fcc4bfe96
--- /dev/null
+++ b/awesome_dashboard/models/__init__.py
@@ -0,0 +1 @@
+from . import res_users_settings
diff --git a/awesome_dashboard/models/res_users_settings.py b/awesome_dashboard/models/res_users_settings.py
new file mode 100644
index 00000000000..3ce39b0d6c0
--- /dev/null
+++ b/awesome_dashboard/models/res_users_settings.py
@@ -0,0 +1,7 @@
+from odoo import models, fields
+
+
+class ResUsersSettings(models.Model):
+ _inherit = "res.users.settings"
+
+ disabled_dashboard_items = fields.Text()
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/chart_card/chart_card.js b/awesome_dashboard/static/src/dashboard/chart_card/chart_card.js
new file mode 100644
index 00000000000..5a0c4db1fec
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/chart_card/chart_card.js
@@ -0,0 +1,12 @@
+import { Component } from "@odoo/owl";
+import { DashboardChart } from "../dashboard_chart";
+
+export class ChartCard extends Component {
+ static template = "awesome_dashboard.ChartCard";
+ static components = { DashboardChart };
+ static props = {
+ title: String,
+ value: Object,
+ type: String,
+ }
+}
diff --git a/awesome_dashboard/static/src/dashboard/chart_card/chart_card.xml b/awesome_dashboard/static/src/dashboard/chart_card/chart_card.xml
new file mode 100644
index 00000000000..a1403939c78
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/chart_card/chart_card.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
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 00000000000..3e3cb2433c5
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js
@@ -0,0 +1,42 @@
+import { Component, useState } from "@odoo/owl";
+import { Dialog } from "@web/core/dialog/dialog";
+import { CheckBox } from "@web/core/checkbox/checkbox";
+
+
+export class ConfigurationDialog extends Component {
+ static template = "awesome_dashboard.ConfigurationDialog";
+ static components = { Dialog, CheckBox };
+ static props = {
+ items: { type: Array },
+ disabledItems: {
+ type: Array,
+ element: {
+ type: String,
+ }
+ },
+ onUpdateConfig: Function,
+ close: Function,
+ }
+
+ setup() {
+ this.items = useState(this.props.items.map((item) => {
+ return {
+ id: item.id,
+ description: item.description,
+ enabled: !this.props.disabledItems.includes(item.id),
+ }
+ }));
+ }
+
+ onChange(checked, item) {
+ item.enabled = checked;
+ const updatedDisabledItems = Object.values(this.items).filter(
+ (item) => !item.enabled
+ ).map((item) => item.id);
+ this.props.onUpdateConfig(updatedDisabledItems);
+ }
+
+ done() {
+ 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 00000000000..842689a1e8a
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js
new file mode 100644
index 00000000000..c2e1c669a39
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.js
@@ -0,0 +1,57 @@
+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";
+import { ConfigurationDialog } from "./configuration_dialog/configuration_dialog";
+import { user } from "@web/core/user";
+
+class AwesomeDashboard extends Component {
+ static template = "awesome_dashboard.AwesomeDashboard";
+ static components = { Layout, DashboardItem };
+
+ setup() {
+ this.action = useService("action");
+ this.statistics = useState(useService("statistics_service"));
+ this.items = registry.category("awesome_dashboard").getAll();
+ this.dialog = useService("dialog");
+ this.state = useState({ disabledItems: this.getDisabledItems() })
+ }
+
+ getDisabledItems() {
+ const disabled_items = user.settings.disabled_dashboard_items;
+ return disabled_items ? disabled_items.split(",") : [];
+ }
+
+ openCustomers() {
+ this.action.doAction("base.action_partner_form");
+ }
+
+ openLeads() {
+ this.action.doAction({
+ type: 'ir.actions.act_window',
+ name: "Leads",
+ target: 'current',
+ res_model: 'crm.lead',
+ views: [
+ [false, 'list'],
+ [false, 'form'],
+ ],
+ });
+ }
+
+ updateConfig(newDisabledItems) {
+ this.state.disabledItems = newDisabledItems;
+ user.setUserSettings("disabled_dashboard_items", this.state.disabledItems.join(","));
+ }
+
+ openConfig() {
+ this.dialog.add(ConfigurationDialog, {
+ items: this.items,
+ disabledItems: this.state.disabledItems,
+ onUpdateConfig: this.updateConfig.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..32862ec0d82
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.scss
@@ -0,0 +1,3 @@
+.o_dashboard {
+ background-color: gray;
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml
new file mode 100644
index 00000000000..087207dfd50
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_chart.js b/awesome_dashboard/static/src/dashboard/dashboard_chart.js
new file mode 100644
index 00000000000..d72b99aa173
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_chart.js
@@ -0,0 +1,39 @@
+import { Component, onWillStart, useRef, useEffect, onWillUnmount } from "@odoo/owl";
+import { loadJS } from "@web/core/assets";
+
+export class DashboardChart extends Component {
+ static template = "awesome_dashboard.DashboardChart"
+ static props = {
+ label: String,
+ data: Object,
+ type: { validate: t => ["pie", "bar", "line"].includes(t) },
+ }
+
+ setup() {
+ this.canvasRef = useRef('canvas')
+ onWillStart(() => {
+ return loadJS("/web/static/lib/Chart/Chart.js")
+ })
+ useEffect(() => this.renderChart());
+ onWillUnmount(() => {
+ this.chart?.destroy();
+ })
+ }
+
+ renderChart() {
+ this.chart?.destroy();
+ const config = {
+ type: this.props.type,
+ data: {
+ labels: Object.keys(this.props.data),
+ datasets: [{
+ label: this.props.label,
+ data: Object.values(this.props.data)
+
+ }],
+ }
+
+ };
+ this.chart = new Chart(this.canvasRef.el, config);
+ }
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_chart.xml b/awesome_dashboard/static/src/dashboard/dashboard_chart.xml
new file mode 100644
index 00000000000..d62b10926dd
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_chart.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
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 00000000000..27e17d4e865
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js
@@ -0,0 +1,15 @@
+import { Component } from "@odoo/owl";
+
+export class DashboardItem extends Component {
+ static template = "awesome_dashboard.DashboardItem";
+ static props = {
+ slots: Object,
+ size: {
+ type: Number,
+ optional: true,
+ }
+ }
+ static defaultProps = {
+ size: 1,
+ }
+}
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 00000000000..19401f5a238
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
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..da96ca70a73
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js
@@ -0,0 +1,67 @@
+import { NumberCard } from "./number_card/number_card";
+import { ChartCard } from "./chart_card/chart_card";
+import { registry } from "@web/core/registry";
+import { _t } from "@web/core/l10n/translation";
+
+export const items = [
+ {
+ id: "average_quantity",
+ description: "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: "Average time for order",
+ Component: NumberCard,
+ props: (data) => ({
+ title: _t("Average time for an order to go from 'new' to 'sent' of 'cancelled'"),
+ value: data.average_time,
+ }),
+ },
+ {
+ id: "nb_new_orders",
+ description: "Number of new orders",
+ Component: NumberCard,
+ props: (data) => ({
+ title: _t("Number of new orders this month"),
+ value: data.nb_new_orders,
+ }),
+ },
+ {
+ id: "nb_cancelled_orders",
+ description: "Number of cancelled orders",
+ Component: NumberCard,
+ props: (data) => ({
+ title: _t("Number of cancelled orders this month"),
+ value: data.nb_cancelled_orders,
+ }),
+ },
+ {
+ id: "total_amount",
+ description: "Number of new orders",
+ Component: NumberCard,
+ props: (data) => ({
+ title: _t("Total amount of new orders this month"),
+ value: data.total_amount,
+ }),
+ },
+ {
+ id: "orders_by_size",
+ description: "Shirt orders by size",
+ Component: ChartCard,
+ size: 2,
+ props: (data) => ({
+ title: _t("Shirt orders by size"),
+ value: data['orders_by_size'],
+ type: "pie"
+ }),
+ },
+]
+
+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..3296d42cb9b
--- /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 00000000000..957fa1ea5ea
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
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..aa55ee9c731
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/statistics_service.js
@@ -0,0 +1,21 @@
+import { reactive } from "@odoo/owl";
+import { rpc } from "@web/core/network/rpc";
+import { registry } from "@web/core/registry";
+
+const fetchStatistics = () => {
+ return rpc("/awesome_dashboard/statistics");
+}
+
+export const statisticsService = {
+ start() {
+ const statistics = reactive({ isReady: false });
+ const updateData = async () => {
+ Object.assign(statistics, await fetchStatistics(), { isReady: true });
+ }
+ updateData();
+ setInterval(updateData, 1000 * 60 * 10);
+ return statistics;
+ },
+}
+
+registry.category("services").add("statistics_service", 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..2bfa18bb3c6
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard_loader.js
@@ -0,0 +1,10 @@
+import { Component } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+import { LazyComponent } from "@web/core/assets";
+
+export class DashboardLoader extends Component {
+ static components = { LazyComponent };
+ static template = "awesome_dashboard.Loader";
+}
+
+registry.category("actions").add("awesome_dashboard.dashboard", DashboardLoader);
diff --git a/awesome_dashboard/static/src/dashboard_loader.xml b/awesome_dashboard/static/src/dashboard_loader.xml
new file mode 100644
index 00000000000..f17e7127acd
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard_loader.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+