diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..cef305e4243 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,30 +1,28 @@ -# -*- 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', - '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", + "version": "0.1", + "application": True, + "installable": True, + "depends": ["base", "web", "mail", "crm"], + "data": [ + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + "assets": { + "web.assets_backend": [ + "awesome_dashboard/static/src/dashboard_action.js", + ], + "awesome_dashboard.dashboard": [ + "awesome_dashboard/static/src/dashboard/**/*", ], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } 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..a40ac61bdce --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,52 @@ +import { Component, useState } from "@odoo/owl"; +import { _t } from "@web/core/l10n/translation" +import { Layout } from "@web/search/layout" +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item"; +import { PieChart } from "./pie_chart"; +import { DashboardConfigDialog } from "./dashboard_config_dialog"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + + static components = { Layout, DashboardItem, PieChart, DashboardConfigDialog }; + + setup() { + this.action = useService("action"); + this.statisticsService = useService("awesome_dashboard.statistics") + this.dialog = useService("dialog"); + this.statistics = useState(this.statisticsService); + this.configState = useState({ + disabledItems: JSON.parse(localStorage.getItem("disabled_dashboard_items") || "[]") + }); + } + + get items() { + const allItems = registry.category("awesome_dashboard").getAll(); + return allItems.filter(item => !this.configState.disabledItems.includes(item.id)); + } + + openConfiguration() { + this.dialog.add(DashboardConfigDialog, { + onConfigSaved: () => { + this.configState.disabledItems = JSON.parse(localStorage.getItem("disabled_dashboard_items") || "[]"); + } + }); + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: _t("Leads"), + res_model: "crm.lead", + views: [[false, "list"], [false, "form"]], + }); + } +} + +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..d77f7439271 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,9 @@ +.o_dashboard { + background-color: #f0f2f5; + display: flex; + flex-direction: column; +} +.o_dashboard .display-1 { + color: var(--primary); + font-weight: 500; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..c50b7f3c91c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + +
+ + + + + + +
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.js b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.js new file mode 100644 index 00000000000..6c2cd7e65e9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.js @@ -0,0 +1,32 @@ +/** @odoo-module **/ +import { Component, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { registry } from "@web/core/registry"; + +export class DashboardConfigDialog extends Component { + static components = { Dialog }; + static template = "awesome_dashboard.DashboardConfigDialog"; + + setup() { + this.allItems = registry.category("awesome_dashboard").getAll(); + + const disabledItems = JSON.parse(localStorage.getItem("disabled_dashboard_items") || "[]"); + + const initialStatus = {}; + for (const item of this.allItems) { + initialStatus[item.id] = !disabledItems.includes(item.id); + } + + this.state = useState(initialStatus); + } + + onApply() { + const disabledIds = Object.keys(this.state).filter(id => !this.state[id]); + + localStorage.setItem("disabled_dashboard_items", JSON.stringify(disabledIds)); + + this.props.onConfigSaved(); + + this.props.close(); + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.xml b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.xml new file mode 100644 index 00000000000..54a87389267 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.xml @@ -0,0 +1,23 @@ + + + + +
+

Which cards do you wish to see?

+
+ +
+
+ + + + +
+
+
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..adad525a65a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js @@ -0,0 +1,13 @@ +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, 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..79ccac96655 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml @@ -0,0 +1,12 @@ + + + +
+
+
+ +
+
+
+
+
\ No newline at end of file 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..e4377598bf9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,67 @@ +import { registry } from "@web/core/registry"; +import { _t } from "@web/core/l10n/translation"; +import { NumberCard } from "./number_card"; +import { PieChartCard } from "./pie_chart_card"; + +const dashboardRegistry = registry.category("awesome_dashboard"); + +dashboardRegistry.add("nb_new_orders", { + id: "nb_new_orders", + description: _t("Number of new orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of new orders this month"), + value: data.nb_new_orders, + }), +}); + +dashboardRegistry.add("total_amount", { + id: "total_amount", + description: _t("Total amount of new orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Total amount of new orders this month"), + value: data.total_amount, + }), +}); + +dashboardRegistry.add("average_quantity", { + id: "average_quantity", + description: _t("Average amount of t-shirt by order this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Average amount of t-shirt by order this month"), + value: data.average_quantity, + }), +}); + +dashboardRegistry.add("nb_cancelled_orders", { + id: "nb_cancelled_orders", + description: _t("Number of cancelled orders this month"), + Component: NumberCard, + props: (data) => ({ + title: _t("Number of cancelled orders this month"), + value: data.nb_cancelled_orders, + }), +}); + +dashboardRegistry.add("average_time", { + 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, + }), +}); + +dashboardRegistry.add("orders_by_size", { + id: "orders_by_size", + description: _t("Shirt orders by size"), + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: _t("Shirt orders by size"), + data: data.orders_by_size, + }), +}); diff --git a/awesome_dashboard/static/src/dashboard/number_card.js b/awesome_dashboard/static/src/dashboard/number_card.js new file mode 100644 index 00000000000..b125f5b7c65 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card.js @@ -0,0 +1,16 @@ +import { Component, xml } from "@odoo/owl"; + +export class NumberCard extends Component { + static props = { + title: String, + value: { optional: true }, + }; + + static template = xml` + +
+

+
+
+ `; +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart.js new file mode 100644 index 00000000000..0a52dbf4f79 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart.js @@ -0,0 +1,54 @@ +import { Component, onWillStart, useRef, onMounted, onWillUnmount, useEffect } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static props = { + data: Object + } + + static template = "awesome_dashboard.PieChart"; + + setup() { + this.canvasRef = useRef("canvas"); + this.chart = null; + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js"); + }); + onMounted(() => { + this.renderChart(); + }); + onWillUnmount(() => { + this.chart?.destroy(); + }); + useEffect(() => { + this.renderChart(); + }, () => [this.props.data]); + } + + renderChart() { + if (!this.props.data) { + return; + } + if (this.chart) { + this.chart.destroy(); + } + const config = { + type: 'pie', + data: { + labels: Object.keys(this.props.data), + datasets: [{ + label: 'T-shirt sizes', + data: Object.values(this.props.data), + backgroundColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 205, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)' + ], + }], + }, + }; + this.chart = new Chart(this.canvasRef.el, config); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart.xml new file mode 100644 index 00000000000..90245125a46 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart.xml @@ -0,0 +1,8 @@ + + + +
+ +
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card.js new file mode 100644 index 00000000000..2d611b92b5b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card.js @@ -0,0 +1,18 @@ +import { Component, xml } from "@odoo/owl"; +import { PieChart } from "./pie_chart"; + +export class PieChartCard extends Component { + static props = { + data: Object + } + + static components = { PieChart }; + + static template = xml` + +
+

+ +

+
`; +} 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..f58c64ec5ec --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,18 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + +const statisticsService = { + start(env) { + const statistics = reactive({}); + async function loadStatistics() { + const result = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, result); + } + loadStatistics(); + setInterval(loadStatistics, 10000); + return statistics; + } +} + +registry.category("services").add("awesome_dashboard.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 00000000000..2e6c07a78ec --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,13 @@ +import { Component, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + +export class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); diff --git a/awesome_owl/__init__.py b/awesome_owl/__init__.py index 457bae27e11..e046e49fbe2 100644 --- a/awesome_owl/__init__.py +++ b/awesome_owl/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 55002ab81de..52b5287856c 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -1,43 +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', - 'version': '0.1', - + "category": "Tutorials", + "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'), - ('include', 'web._assets_backend_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"), + ("include", "web._assets_backend_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/controllers/__init__.py b/awesome_owl/controllers/__init__.py index 457bae27e11..e046e49fbe2 100644 --- a/awesome_owl/controllers/__init__.py +++ b/awesome_owl/controllers/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file +from . import controllers diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..940364c54b9 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,18 @@ +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({ isOpen: true }); + } + + toggleContent() { + 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..1c85d7876c7 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,18 @@ + + + +
+
+
+ +
+
+

+ +

+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..3fa56f77a13 --- /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(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..1b5169b0d74 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + +
+

Counter:

+ +
+
+
diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js index 1aaea902b55..1af6c827e0b 100644 --- a/awesome_owl/static/src/main.js +++ b/awesome_owl/static/src/main.js @@ -4,9 +4,8 @@ import { Playground } from "./playground"; const config = { dev: true, - name: "Owl Tutorial" + name: "Owl Tutorial", }; // Mount the Playground component when the document.body is ready whenReady(() => mountComponent(Playground, document.body, config)); - diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..3068cbb99d0 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,21 @@ -import { Component } from "@odoo/owl"; +import { markup, Component, useState } from "@odoo/owl"; +import { Counter } from './counter/counter'; +import { Card } from './card/card'; +import { TodoList } from './todo/todolist'; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.Playground"; + + static components = { Counter, Card, TodoList}; + + setup() { + this.state = useState({ sum: 0 }); + } + + incrementSum() { + this.state.sum++; + } + + html_card_1 = "Test Test Test Test Test Test Test Test Test"; + html_card_2 = markup("Test Test Test Test Test Test Test Test Test"); } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..b70a393a654 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,21 @@ - - -
- hello world + +
+
+ + + + + + +
+
+ Sum: +
+
+
+
- diff --git a/awesome_owl/static/src/todo/todoitem.js b/awesome_owl/static/src/todo/todoitem.js new file mode 100644 index 00000000000..7e9f3c3ef52 --- /dev/null +++ b/awesome_owl/static/src/todo/todoitem.js @@ -0,0 +1,14 @@ +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, optional: true }, + deleteTodo: { type: Function, optional: true }, + }; +} diff --git a/awesome_owl/static/src/todo/todoitem.xml b/awesome_owl/static/src/todo/todoitem.xml new file mode 100644 index 00000000000..a4b7fe57be0 --- /dev/null +++ b/awesome_owl/static/src/todo/todoitem.xml @@ -0,0 +1,14 @@ + + + +
+
+ + . +
+ +
+
+
diff --git a/awesome_owl/static/src/todo/todolist.js b/awesome_owl/static/src/todo/todolist.js new file mode 100644 index 00000000000..c02e1ce80e2 --- /dev/null +++ b/awesome_owl/static/src/todo/todolist.js @@ -0,0 +1,40 @@ +import { Component, useState } from "@odoo/owl"; +import { useAutoFocus } from "../utils"; +import { TodoItem } from "./todoitem"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + + static components = { TodoItem }; + + setup() { + this.todos = useState([]); + this.nextId = 1; + this.inputRef = useAutoFocus('input'); + } + + addTodo(ev) { + if (ev.key === "Enter") { + const content = ev.target.value.trim(); + if (content !== "") { + this.todos.push({ id: this.nextId, description: content, isCompleted: false }); + ev.target.value = ""; + this.nextId++; + } + } + } + + toggleTodo(id) { + const todo = this.todos.find(t => t.id === id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + deleteTodo(id) { + const index = this.todos.findIndex((t) => t.id === id); + if (index >= 0) { + this.todos.splice(index, 1); + } + } +} diff --git a/awesome_owl/static/src/todo/todolist.xml b/awesome_owl/static/src/todo/todolist.xml new file mode 100644 index 00000000000..e2e2cfe8790 --- /dev/null +++ b/awesome_owl/static/src/todo/todolist.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..4c2773a546a --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,9 @@ +import { useRef, onMounted } from "@odoo/owl"; + +export function useAutoFocus(refName) { + const ref = useRef(refName); + onMounted(() => { + ref.el.focus(); + }); + return ref; +}