From c9d9d16c2181e1b4b01db2290f04d8f66b807b1e Mon Sep 17 00:00:00 2001 From: nsirjacobs Date: Tue, 23 Sep 2025 14:31:10 +0200 Subject: [PATCH] [ADD] AwesomeDashboard: Created an interactive dashboard to display data of a t-shirt selling company --- awesome_dashboard/__init__.py | 1 + awesome_dashboard/__manifest__.py | 39 +++--- awesome_dashboard/i18n/en_US.po | 124 ++++++++++++++++++ awesome_dashboard/i18n/fr.po | 124 ++++++++++++++++++ awesome_dashboard/models/__init__.py | 3 + awesome_dashboard/models/res_users.py | 7 + .../security/ir.model.access.csv | 2 + awesome_dashboard/static/src/dashboard.js | 10 -- .../static/src/dashboard/dashboard.js | 85 ++++++++++++ .../static/src/dashboard/dashboard.scss | 3 + .../static/src/dashboard/dashboard.xml | 23 ++++ .../dashboard_dialog/dashboard_dialog.js | 39 ++++++ .../dashboard_dialog/dashboard_dialog.xml | 26 ++++ .../dashboard_item/dashboard_item.js | 12 ++ .../dashboard_item/dashboard_item.xml | 10 ++ .../static/src/dashboard/dashboard_items.js | 68 ++++++++++ .../src/dashboard/number_card/number_card.js | 13 ++ .../src/dashboard/number_card/number_card.xml | 12 ++ .../src/dashboard/pie_chart/pie_chart.js | 56 ++++++++ .../pie_chart/pie_chart.xml} | 6 +- .../pie_chart_card/pie_chart_card.js | 15 +++ .../pie_chart_card/pie_chart_card.xml | 9 ++ .../src/dashboard/statistics_service.js | 27 ++++ .../static/src/dashboard_loader.js | 14 ++ awesome_owl/__manifest__.py | 51 ++++--- awesome_owl/static/src/card/card.js | 23 ++++ awesome_owl/static/src/card/card.xml | 16 +++ awesome_owl/static/src/counter/counter.js | 18 +++ awesome_owl/static/src/counter/counter.xml | 9 ++ awesome_owl/static/src/playground.js | 23 +++- awesome_owl/static/src/playground.xml | 18 ++- awesome_owl/static/src/todo_list/todo_item.js | 20 +++ .../static/src/todo_list/todo_item.xml | 11 ++ awesome_owl/static/src/todo_list/todo_list.js | 33 +++++ .../static/src/todo_list/todo_list.xml | 11 ++ awesome_owl/static/src/utils.js | 8 ++ 36 files changed, 899 insertions(+), 70 deletions(-) create mode 100644 awesome_dashboard/i18n/en_US.po create mode 100644 awesome_dashboard/i18n/fr.po create mode 100644 awesome_dashboard/models/__init__.py create mode 100644 awesome_dashboard/models/res_users.py create mode 100644 awesome_dashboard/security/ir.model.access.csv delete mode 100644 awesome_dashboard/static/src/dashboard.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js rename awesome_dashboard/static/src/{dashboard.xml => dashboard/pie_chart/pie_chart.xml} (54%) create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/statistics_service.js create mode 100644 awesome_dashboard/static/src/dashboard_loader.js create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml create mode 100644 awesome_owl/static/src/todo_list/todo_item.js create mode 100644 awesome_owl/static/src/todo_list/todo_item.xml create mode 100644 awesome_owl/static/src/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/todo_list/todo_list.xml create mode 100644 awesome_owl/static/src/utils.js 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..49413ed4139 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,30 +1,29 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Dashboard", - - 'summary': """ + "name": "Awesome Dashboard", + "summary": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'description': """ + "description": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com/", - 'category': 'Tutorials/AwesomeDashboard', - 'version': '0.1', - 'application': True, - 'installable': True, - 'depends': ['base', 'web', 'mail', 'crm'], - - 'data': [ - 'views/views.xml', + "author": "Odoo", + "website": "https://www.odoo.com/", + "category": "Tutorials/AwesomeDashboard", + "version": "0.1", + "application": True, + "installable": True, + "depends": ["base", "web", "mail", "crm"], + "data": [ + "security/ir.model.access.csv", + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + "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/en_US.po b/awesome_dashboard/i18n/en_US.po new file mode 100644 index 00000000000..5d0ab2fe70b --- /dev/null +++ b/awesome_dashboard/i18n/en_US.po @@ -0,0 +1,124 @@ +# 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-26 14:52+0000\n" +"PO-Revision-Date: 2025-09-26 16:54+0200\n" +"Last-Translator: odoo \n" +"Language-Team: English\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: en_US\n" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average amount of t-shirt" +msgstr "Average amount of 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 "Average amount of t-shirt by order this month" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average time for an order" +msgstr "Average time for an 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 'cancelled'" +msgstr "Average time for an order to go from 'new' to 'sent' or 'cancelled'" + +#. module: awesome_dashboard +#: model:ir.ui.menu,name:awesome_dashboard.menu_root +msgid "Awesome Dashboard" +msgstr "Awesome Dashboard" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.xml:0 +msgid "Cancel" +msgstr "Cancel" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Cancelled orders this month" +msgstr "Cancelled orders this month" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.xml:0 +msgid "Confirm" +msgstr "Confirm" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Customers" +msgstr "Customers" + +#. module: awesome_dashboard +#: model:ir.actions.client,name:awesome_dashboard.dashboard +#: model:ir.ui.menu,name:awesome_dashboard.dashboard_menu +msgid "Dashboard" +msgstr "Dashboard" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.js:0 +msgid "Dashboard Configuration" +msgstr "Dashboard Configuration" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard.xml:0 +msgid "Leads" +msgstr "Leads" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "New orders this month" +msgstr "New orders this month" + +#. 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" + +#. 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" + +#. 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" + +#. 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" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "amount orders this month" +msgstr "amount orders this month" diff --git a/awesome_dashboard/i18n/fr.po b/awesome_dashboard/i18n/fr.po new file mode 100644 index 00000000000..e52fc392f31 --- /dev/null +++ b/awesome_dashboard/i18n/fr.po @@ -0,0 +1,124 @@ +# 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-26 14:52+0000\n" +"PO-Revision-Date: 2025-09-26 16:54+0200\n" +"Last-Translator: odoo \n" +"Language-Team: French \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Language: fr\n" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "Average amount of t-shirt" +msgstr "Nombre moyen de 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 "Nombre moyen de t-shirt par commande ce mois" + +#. 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 'nouveau' à 'envoyée' ou 'annulée'" + +#. module: awesome_dashboard +#: model:ir.ui.menu,name:awesome_dashboard.menu_root +msgid "Awesome Dashboard" +msgstr "Awesome Dashboard" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.xml:0 +msgid "Cancel" +msgstr "Annuler" + +#. 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" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.xml:0 +msgid "Confirm" +msgstr "Confirmer" + +#. 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 "Dashboard" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_dialog/dashboard_dialog.js:0 +msgid "Dashboard Configuration" +msgstr "Configuration du Dashboard" + +#. 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" + +#. 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" + +#. 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" + +#. 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-Shirt 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 "Nombre total de nouvelles commandes ce mois" + +#. module: awesome_dashboard +#. odoo-javascript +#: code:addons/awesome_dashboard/static/src/dashboard/dashboard_items.js:0 +msgid "amount orders this month" +msgstr "Quantitées de commandes ce mois" diff --git a/awesome_dashboard/models/__init__.py b/awesome_dashboard/models/__init__.py new file mode 100644 index 00000000000..7d84ac6d4e7 --- /dev/null +++ b/awesome_dashboard/models/__init__.py @@ -0,0 +1,3 @@ +from . import ( + res_users +) \ No newline at end of file diff --git a/awesome_dashboard/models/res_users.py b/awesome_dashboard/models/res_users.py new file mode 100644 index 00000000000..56f35591a1f --- /dev/null +++ b/awesome_dashboard/models/res_users.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class ResUsers(models.Model): + _inherit = "res.users" + + dashboard_config = fields.Text("Dashboard Configuration") + \ No newline at end of file diff --git a/awesome_dashboard/security/ir.model.access.csv b/awesome_dashboard/security/ir.model.access.csv new file mode 100644 index 00000000000..9cad4a90d4d --- /dev/null +++ b/awesome_dashboard/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_res_users,res.users,model_res_users,base.group_user,1,1,1,1 \ No newline at end of file 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/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..bcfc1f05954 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,85 @@ +/** @odoo-module **/ + +import { Component, onMounted, 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 { DashboardDialog } from "./dashboard_dialog/dashboard_dialog"; +import { reactive } from "@odoo/owl"; +import { user } from "@web/core/user"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, DashboardDialog }; + + async setup() { + this.orm = useService("orm"); + this.action = useService("action"); + this.statistics = useState(useService("awesome_dashboard.statistics")); + this.items = registry.category("awesome_dashboard").getAll(); + this.dialog = useService("dialog"); + + this.display = { + controlPanel: {}, + }; + + onMounted(async () => { + let result = await this.loadDashboard(); + + if (result.length === 0) { + this.items.forEach((item) => { + this.displayedItems.add(item.id); + }); + } else { + result.forEach((item) => { + this.displayedItems.add(item); + }); + } + }); + + this.displayedItems = useState(reactive(new Set())); + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } + + openConfiguration() { + this.dialog.add(DashboardDialog, { + onConfirm: (displayedItems) => { + this.displayedItems.clear(); + displayedItems.forEach((id) => this.displayedItems.add(id)); + this.saveDashboard(displayedItems); + }, + }); + } + + async saveDashboard(data) { + await this.orm.write("res.users", [user.userId], { + dashboard_config: JSON.stringify(data), + }); + } + + async loadDashboard() { + const result = await this.orm.read( + "res.users", + [user.userId], + ["dashboard_config"] + ); + return JSON.parse(result[0]["dashboard_config"] || "[]"); + } +} + +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..933b8c91d1f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: rgb(90, 89, 90); +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..e7ecadf98c2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + 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..856e07faf0f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,12 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = ["size?"]; + + setup() { + if (!this.props.size) { + this.props.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..cebd859a979 --- /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..082570f997b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,68 @@ +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"; + +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..bc00479cc9f --- /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..3083f98ca9e --- /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 00000000000..62e99a53df8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,56 @@ +import { loadJS } from "@web/core/assets"; +import { getColor } from "@web/core/colors/colors"; +import { + Component, + onWillStart, + useRef, + useEffect, + onWillUnmount, +} from "@odoo/owl"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + data: Object, + }; + + setup() { + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + useEffect(() => { + this.renderChart(); + }); + onWillUnmount(this.onWillUnmount); + } + + onWillUnmount() { + if (this.chart) { + this.chart.destroy(); + } + } + + renderChart() { + if (this.chart) { + this.chart.destroy(); + } + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: Object.keys(this.props.data), + datasets: [{ data: Object.values(this.props.data) }], + }, + options: { + onClick: (e, elems) => { + const index = elems[0].index; + const label = this.chart.data.labels[index]; + const value = this.chart.data.datasets[0].data[index]; + + console.log("Clicked slice:", label, value); + + //[TODO] Would have to make an action leading to orders (with domain etc); but need to create orders first + this.env.services.action.doAction("crm.crm_lead_action_pipeline"); + }, + }, + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml similarity index 54% rename from awesome_dashboard/static/src/dashboard.xml rename to awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml index 1a2ac9a2fed..3b72f3f32a8 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -1,8 +1,6 @@ - - - hello dashboard + + - 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..5d6be057632 --- /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..e6c2013a7ad --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,9 @@ + + + +
+ +
+ +
+
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..c37b131665c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,27 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { memoize } from "@web/core/utils/functions"; +import { reactive } from "@odoo/owl"; + +export async function loadStatistics() { + return await rpc("/awesome_dashboard/statistics"); +} + +export const statisticsService = { + start() { + const state = reactive({}); + + async function loadAndUpdateStats() { + Object.assign(state, await loadStatistics()); + } + + loadAndUpdateStats(); + + setInterval(loadAndUpdateStats, 10 * 1000); + return state; + }, +}; + +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..226d0751084 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,14 @@ +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; +import { Component, xml } from "@odoo/owl"; + +class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry + .category("actions") + .add("awesome_dashboard.dashboard", AwesomeDashboardLoader); diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 77abad510ef..a381f2e7ad6 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -1,42 +1,37 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Owl", - - 'summary': """ + "name": "Awesome Owl", + "summary": """ Starting module for "Discover the JS framework, chapter 1: Owl components" """, - - 'description': """ + "description": """ Starting module for "Discover the JS framework, chapter 1: Owl components" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com", - + "author": "Odoo", + "website": "https://www.odoo.com", # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list - 'category': 'Tutorials/AwesomeOwl', - 'version': '0.1', - + "category": "Tutorials/AwesomeOwl", + "version": "0.1", # any module necessary for this one to work correctly - 'depends': ['base', 'web'], - 'application': True, - 'installable': True, - 'data': [ - 'views/templates.xml', + "depends": ["base", "web"], + "application": True, + "installable": True, + "data": [ + "views/templates.xml", ], - 'assets': { - 'awesome_owl.assets_playground': [ - ('include', 'web._assets_helpers'), - 'web/static/src/scss/pre_variables.scss', - 'web/static/lib/bootstrap/scss/_variables.scss', - 'web/static/lib/bootstrap/scss/_maps.scss', - ('include', 'web._assets_bootstrap'), - ('include', 'web._assets_core'), - 'web/static/src/libs/fontawesome/css/font-awesome.css', - 'awesome_owl/static/src/**/*', + "assets": { + "awesome_owl.assets_playground": [ + ("include", "web._assets_helpers"), + "web/static/src/scss/pre_variables.scss", + "web/static/lib/bootstrap/scss/_variables.scss", + "web/static/lib/bootstrap/scss/_maps.scss", + ("include", "web._assets_bootstrap"), + ("include", "web._assets_core"), + "web/static/src/libs/fontawesome/css/font-awesome.css", + "awesome_owl/static/src/**/*", ], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..7ae9e54ccbe --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,23 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.Card"; + static props = { + title: String, + slots: { + type: Object, + shape: { + default: true + }, + } + }; + + setup() { + this.state = useState({ value: false }); + } + + toggle(){ + 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..2815ac9e997 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,16 @@ + + + +
+
+
+ + +
+

+ +

+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..a5dd9b93525 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,18 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.Counter"; + static props = ["onChange?"]; + + setup() { + this.state = useState({ value: 1 }); + } + + 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..f70a5801e58 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + +
+ Counter: + +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..8f2b504eab5 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, markup, useState} from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo_list/todo_list"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + + content = markup("
some text
"); + content2 = "
some text
"; + + setup() { + this.sum = useState({value:2}); + } + + incrementSum(){ + this.sum.value++; + } + } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..6889c9f0175 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,9 +2,19 @@ -
- hello world -
-
+

Hello World

+ + +

Sum :

+ + + + + + +

Blablabla

+
+ +
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..30c45e632ed --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,20 @@ +import { Component, useState } 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 }, + }, + removeTodo: Function, + }; + + delete() { + this.props.removeTodo(this.props.todo.id); + } + + toggleState(ev) { + this.props.todo.isCompleted = !this.props.todo.isCompleted; + } +} 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..0c5a06580a2 --- /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..f7ba43f6da1 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,33 @@ +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 }; + static props = []; + + setup() { + this.todos = useState([]); + this.index = 0; + useAutofocus("input"); + } + + addTodo(ev) { + if (ev.keyCode === 13 && ev.target.value != "") { + this.todos.push({ + id: this.index++, + description: ev.target.value, + isCompleted: false, + }); + ev.target.value = ""; + } + } + + removeTodo(elemId) { + const index = this.todos.findIndex((elem) => elem.id === elemId); + if (index !== -1) { + 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..f4977dbac1e --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,11 @@ + + + +
+ +

+ +

+
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..f452f103aa0 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,8 @@ +import { useRef, onMounted } from "@odoo/owl"; + +export function useAutofocus(refName) { + const ref = useRef(refName); + onMounted(() => { + ref.el.focus(); + }); +}