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 @@ + + + + + Which cards do you wish to see? + + + + + + + + + + + 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 @@ + + + + + + + +