diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..ec653ae92dc 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -23,7 +23,10 @@ ], 'assets': { 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + 'awesome_dashboard/static/src/dashboard_action.js', + ], + 'awesome_dashboard.dashboard_bundle': [ + '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.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/core/awesome_dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/core/awesome_dashboard/statistics_service.js new file mode 100644 index 00000000000..f26143de128 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/core/awesome_dashboard/statistics_service.js @@ -0,0 +1,22 @@ +import { reactive } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; + + +const statisticsService = { + start() { + const stats = reactive({}); + + async function loadStats() { + const stats_data = await rpc("/awesome_dashboard/statistics"); + Object.assign(stats, stats_data); + } + + loadStats(); + setInterval(loadStats, 10000*60); + + return stats; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", statisticsService); 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..f190aaa953a --- /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.dashboard_item"; + static props = { + slots: { type: Object, optional: true }, + size: { type: Number, optional: true }, + }; + static defaultProps = { + 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..ee08ad926bd --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard-item/dashboard-item.xml @@ -0,0 +1,12 @@ + + + + +
+
+ +
+
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..241b9a99964 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,55 @@ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { DashboardItem } from "./dashboard-item/dashboard-item"; +import { SettingsDialog } from "./settings-dialog/settings-dialog"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem }; + static props = []; + + setup() { + this.action = useService("action"); + const statisticsService = useService("awesome_dashboard.statistics"); + this.dialog = useService('dialog'); + + this.statistics = useState(statisticsService); + + this.state = useState({ items: this.getDisplayedItems() }); + + } + + getDisplayedItems() { + const filtered_item_ids = localStorage.getItem("awesome_dashboard.displayed_items") ?? []; + return registry + .category("awesome_dashboard") + .getAll() + .filter((item) => !filtered_item_ids.includes(item.id)); + } + + reloadDashboard() { + this.state.items = this.getDisplayedItems(); + } + + openPartnerForm() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: 'ir.actions.act_window', + name: 'Leads', + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']], + }); + } + + openSettingsDialog() { + this.dialog.add(SettingsDialog, {}, { onClose: () => this.reloadDashboard() }); + } + +} + +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..2508cc740ad --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: palegoldenrod; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..fcae74453a7 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + 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..cd33aec087f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,64 @@ +import { NumberCard } from "./number-card/number-card"; +import { PieChartCard } from "./pie-chart-card/pie-chart-card"; +import { registry } from "@web/core/registry"; + +const items = [ + { + 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: "Orders by size", + Component: PieChartCard, + props: (data) => ({ + title: "Orders by size", + data: data.orders_by_size, + }), + }, +]; + +for (const item of items) { + registry.category("awesome_dashboard").add("awesome_dashboard." + 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..f586334528e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number-card/number-card.js @@ -0,0 +1,8 @@ +import { Component } from "@odoo/owl"; +import { DashboardItem } from "../dashboard-item/dashboard-item"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.number_card"; + static props = ['title', 'value']; + static components = { DashboardItem }; +} 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..0efb0a51475 --- /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/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..fe2ae29ff8c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie-chart-card/pie-chart-card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; +import { DashboardItem } from "../dashboard-item/dashboard-item"; +import { PieChart } from "../pie-chart/pie-chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.pie_chart_card"; + static props = ['title', 'data']; + static components = { DashboardItem, PieChart }; +} 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..f56f1d0f746 --- /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/pie-chart/pie-chart.js b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.js new file mode 100644 index 00000000000..a85684e831c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.js @@ -0,0 +1,55 @@ +import { Component, onWillStart, useRef, useEffect } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.pie_chart" + static props = ['data'] + + setup() { + this.chart = null; + this.canvasRef = useRef("canvas"); + this.title = "Shirt orders by size" + onWillStart(async () => loadJS('/web/static/lib/Chart/Chart.js')); + + useEffect(() => { + this.renderChart(); + return () => { + if (this.chart) { + this.chart.destroy(); + } + }; + }); + } + + renderChart() { + const labels = Object.keys(this.props.data); + const data = Object.values(this.props.data); + const config = { + type: "doughnut", + data: { + datasets: [ + { + data: data, + backgroundColor: ["#1f77b4", "#dddddd"], + }, + ], + labels: labels, + }, + options: { + responsive: true, + maintainAspectRatio: false, + layout: { + padding: 5, + }, + plugins: { + title: { + display: true, + text: this.title, + padding: 4, + }, + }, + }, + }; + this.chart = new Chart(this.canvasRef.el, config); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.xml b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.xml new file mode 100644 index 00000000000..a3db9bc373d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie-chart/pie-chart.xml @@ -0,0 +1,13 @@ + + + + +
+ + + Chart + +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/settings-dialog/settings-dialog.js b/awesome_dashboard/static/src/dashboard/settings-dialog/settings-dialog.js new file mode 100644 index 00000000000..f5d78d25628 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/settings-dialog/settings-dialog.js @@ -0,0 +1,38 @@ +import { Component, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog" +import { registry } from "@web/core/registry"; + + +export class SettingsDialog extends Component { + static template = "awesome_dashboard.settings_dialog"; + static props = ['close']; + static components = { Dialog }; + + setup() { + this.state = useState({ items: {} }); + + const items = registry.category("awesome_dashboard").getAll(); + + const filtered_ids = localStorage.getItem("awesome_dashboard.displayed_items") ?? []; + + for (var i of items) { + this.state.items[i.id] = { display: !filtered_ids.includes(i.id), description: i.description }; + } + this.toggleDisplay = this.toggleDisplay.bind(this); + this.onSave = this.onSave.bind(this); + } + + toggleDisplay(id) { + this.state.items[id].display = !this.state.items[id].display; + } + + onSave() { + const filtered_ids = Object.entries(this.state.items) + .filter(([id, item]) => !item.display) + .map(([id]) => id); + + localStorage.setItem("awesome_dashboard.displayed_items", filtered_ids) + this.props.close(); + } + +} diff --git a/awesome_dashboard/static/src/dashboard/settings-dialog/settings-dialog.xml b/awesome_dashboard/static/src/dashboard/settings-dialog/settings-dialog.xml new file mode 100644 index 00000000000..738377acfd6 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/settings-dialog/settings-dialog.xml @@ -0,0 +1,20 @@ + + + + + + + + +
+
+ + + +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..3d7d553d247 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,13 @@ +import { Component, xml } from "@odoo/owl"; +import { LazyComponent } from "@web/core/assets"; +import { registry } from "@web/core/registry"; + +class AwesomeDashboardLazy extends Component { + static components = { LazyComponent }; + static template = xml` + + `; + static props = ['*']; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLazy); diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..be545f485e8 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,10 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = ['title?', 'slots'] + + setup() { + this.state = useState({ open: true }); + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..e34b162fb66 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,13 @@ + + + + +
+
+
+ +
+
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..5e0ceb2183b --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,13 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = ['onChange?'] + + state = useState({ value: 0 }); + + increment() { + this.state.value++; + 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..1f1cd5d6e54 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,13 @@ + + + + +
+ Counter: + +
+
+ +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..2f890565dd5 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,18 @@ -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/todo_list"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + state = useState({ value: 0 }); + + card1Content = '
some content 1
'; + card2Content = markup('
some content 2
'); + + incrementSum() { + this.state.value++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..9b25b9145f2 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,6 +4,16 @@
hello world + + +

Sum:

+ + + + + Hey ! + +
diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..df56ad30626 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,6 @@ +import { Component, useState } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + static props = ['id', 'description', 'isCompleted?', 'toggleState', 'removeTodo'] +} diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..3870e998924 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,13 @@ + + + + +
+ + . + + +
+
+ +
diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..d1fc401ecbf --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,35 @@ +import { Component, useState } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutoFocus } from "../utils" + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + + + setup() { + this.state = useState({ items: [], count: 0 }); + useAutoFocus("todo-input"); + this.toggleItemState = this.toggleItemState.bind(this); + this.removeTodo = this.removeTodo.bind(this); + } + + addTodo(ev) { + if (ev.key === "Enter" && ev.target.value.trim() !== ""){ + this.state.items.push({ 'id': this.state.count++, 'description': ev.target.value, 'isCompleted': false }); + ev.target.value = ""; + } + } + + toggleItemState(id) { + const item = this.state.items.find(item => item.id == id); + item['isCompleted'] = !item['isCompleted']; + } + + removeTodo(id) { + const item_index = this.state.items.findIndex(item => item.id == id); + this.state.items.splice(item_index, 1); + } + + +} diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..8d6cefa7af7 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,18 @@ + + + + +
+ + + +
+ +
+ +
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..dd255757856 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,9 @@ +import { useRef, useEffect } from "@odoo/owl"; + +export function useAutoFocus(name) { + let ref = useRef(name); + useEffect( + (el) => el && el.focus(), + () => [ref.el] + ); +}