diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..bd10cc6fc0c 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -24,6 +24,10 @@ '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' 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/configuration-dialog/configuration-dialog.js b/awesome_dashboard/static/src/dashboard/configuration-dialog/configuration-dialog.js new file mode 100644 index 00000000000..2333b87db24 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/configuration-dialog/configuration-dialog.js @@ -0,0 +1,32 @@ +import { Dialog } from "@web/core/dialog/dialog"; +import { Component } from "@odoo/owl"; + +export class DashboardDialog extends Component { + static template = "awesome_dashboard.configuration-dialog"; + static props = { + items: { type: Array, element: Object }, + disabledItems: { type: Array, element: String }, + updateConfiguration: Function, + close: { type: Function, optional: true }, + }; + static components = { Dialog }; + + setup() { + this.items = this.props.items.map((item) => ({ + ...item, + disabled: this.props.disabledItems.includes(item.id), + })); + } + + toggleMasked(item) { + item.disabled = !item.disabled; + } + + applyConfigurationChanges() { + const disabledItems = this.items.filter((item) => item.disabled).map((item) => item.id); + this.props.updateConfiguration(disabledItems); + if (this.props.close) { + 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..f47809daa5f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/configuration-dialog/configuration-dialog.xml @@ -0,0 +1,24 @@ + + + + + + + Which cards do you wish to see ? + + + + + + + + + + + + Done + + + + + 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..ab480f80323 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard-item/dashboard-item.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.dashboard-item"; + static props = { + size: Number, + slots: { type: Object, optional: true }, + }; +} 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..f0c6d50d65c --- /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-registry.js b/awesome_dashboard/static/src/dashboard/dashboard-registry.js new file mode 100644 index 00000000000..ebd59593590 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard-registry.js @@ -0,0 +1,63 @@ +import { registry } from "@web/core/registry"; +import { NumberCard } from "./number-card/number-card"; +import { PieChartCard } from "./pie-chart/pie-chart-card"; + +const registryItems = [ + { + id: "nb_new_orders", + description: "Number of new orders this month", + component: NumberCard, + props: (data) => ({ + title: "Number of new orders this month", + value: data.nb_new_orders, + }), + }, + { + id: "total_amount", + description: "Total amount of new orders this month", + component: NumberCard, + props: (data) => ({ + title: "Total amount of new orders this month", + value: data.total_amount, + }), + }, + { + id: "average_quantity", + description: "Average amount of t-shirt by order this month", + component: NumberCard, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity, + }), + }, + { + id: "nb_cancelled_orders", + description: "Number of cancelled orders this month", + component: NumberCard, + props: (data) => ({ + title: "Number of cancelled orders this month", + value: data.nb_cancelled_orders, + }), + }, + { + id: "average_time", + description: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + component: NumberCard, + props: (data) => ({ + title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + value: data.average_time, + }), + }, + { + id: "orders_by_size", + description: "Ordered T-shirts by size", + component: PieChartCard, + size: 2, + props: (data) => ({ + title: "Ordered T-shirts by size", + value: data.orders_by_size, + }), + }, +]; + +registryItems.forEach((item) => registry.category("awesome_dashboard").add(item.id, item)); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..44b06e45cfb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,59 @@ +import { registry } from "@web/core/registry"; +import { useService, useOwnedDialogs } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { Component, useState } from "@odoo/owl"; +import { DashboardItem } from "./dashboard-item/dashboard-item"; +import { NumberCard } from "./number-card/number-card"; +import { PieChartCard } from "./pie-chart/pie-chart-card"; +import { DashboardDialog } from "./configuration-dialog/configuration-dialog"; +import { browser } from "@web/core/browser/browser"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, NumberCard, PieChartCard }; + + setup() { + this.action = useService("action"); + this.statistics = useState(useService("awesome_dashboard.statistics")); + this.configDialog = useOwnedDialogs(); + + // Dashboard items + this.items = registry.category("awesome_dashboard").getAll(); + this.state = useState({ + disabledItems: JSON.parse( + browser.localStorage.getItem("awesome_dashboard.disabled") ?? "[]" + ), + }); + } + + actionCustomers() { + this.action.doAction("base.action_partner_form", { viewType: "kanban" }); + } + + actionLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: "All leads", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } + + updateConfiguration(disabledItems) { + browser.localStorage.setItem("awesome_dashboard.disabled", JSON.stringify(disabledItems)); + this.state.disabledItems = disabledItems; + } + + openDashboardConfig() { + this.configDialog(DashboardDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + updateConfiguration: this.updateConfiguration.bind(this), + }); + } +} + +registry.category("lazy_components").add("awesome_dashboard.dashboard", 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..5b6c1cd099c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,26 @@ + + + + + + + Customers + Leads + + + + + + + + + + + + + + + + + + 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..173497ff13f --- /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.number-card"; + static props = { + title: String, + value: Number, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/number-card/number-card.scss b/awesome_dashboard/static/src/dashboard/number-card/number-card.scss new file mode 100644 index 00000000000..5321e9b2a13 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number-card/number-card.scss @@ -0,0 +1,5 @@ +.number-card-value { + color: green; + font-weight: bold; + text-align: center; +} 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..b349bb6da35 --- /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-card.js b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart-card.js new file mode 100644 index 00000000000..4e2e767c05d --- /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.pie-chart-card"; + static props = { + title: String, + value: { type: Object, values: Number }, + }; + static components = { PieChart }; +} 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 00000000000..eff186e06bd --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart-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..b760cabb3f3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.js @@ -0,0 +1,57 @@ +import { + Component, + onWillStart, + onWillUnmount, + onMounted, + useRef, + onWillUpdateProps, +} from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; +import { DashboardItem } from "../dashboard-item/dashboard-item"; + +export class PieChart extends Component { + static template = "awesome_dashboard.pie-chart"; + static components = { DashboardItem }; + static props = { + data: { type: Object, values: Number }, + }; + + setup() { + this.canvasRef = useRef("canvas"); + this.chart = null; + + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(this.renderChart); + onWillUpdateProps(this.updateChart); + onWillUnmount(this.destroyChart); + } + + renderChart() { + this.destroyChart(); + + const labels = Object.keys(this.props.data); + const datapoints = Object.values(this.props.data); + this.chart = new Chart(this.canvasRef.el, { + type: "doughnut", + data: { + labels: labels, + datasets: [{ data: datapoints }], + }, + }); + } + + updateChart(newProps) { + if (this.chart) { + const datapoints = Object.values(newProps.data); + Object.assign(this.chart.data.datasets[0], { data: datapoints }); + this.chart.update(); + } + } + + destroyChart() { + if (this.chart) { + this.chart.destroy(); + this.chart = null; + } + } +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.xml similarity index 55% rename from awesome_dashboard/static/src/dashboard.xml rename to awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.xml index 1a2ac9a2fed..a01e05e71d6 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.xml @@ -1,8 +1,8 @@ - - hello dashboard + + 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..6bfffc593a5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics-service.js @@ -0,0 +1,25 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + +export const statisticsService = { + start() { + const statistics = reactive({ isReady: false }); + async function loadStatistics() { + Object.assign(statistics, await rpc("/awesome_dashboard/statistics"), { + isReady: true, + }); + } + + // Refresh every 10 seconds for testing + //const interval = 10000; + // Refresh every 10 minutes for real + const interval = 600000; + + setInterval(loadStatistics, interval); + loadStatistics(); + 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..15855537dbb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,12 @@ +import { Component, xml } from "@odoo/owl"; +import { LazyComponent } from "@web/core/assets"; +import { registry } from "@web/core/registry"; + +export class LazyDashboard extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", LazyDashboard);
Which cards do you wish to see ?