diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..95d84598ce6 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -22,8 +22,11 @@ 'views/views.xml', ], 'assets': { + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*', + ], 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + 'awesome_dashboard/static/src/dashboard_action.js', ], }, '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/chart/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/chart/pie_chart/pie_chart.js new file mode 100644 index 00000000000..29405774d4a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/chart/pie_chart/pie_chart.js @@ -0,0 +1,54 @@ +import {Component, onWillStart, onWillUnmount, useEffect, useRef} from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_owl.pie_chart"; + static props = { + dataset: { + type: Object + }, + }; + + setup() { + this.chart = null; + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + useEffect(() => this.renderChart()); + onWillUnmount(this.onWillUnmount); + } + + destroyChart() { + if (this.chart) { + this.chart.destroy(); + } + } + + onWillUnmount() { + this.destroyChart(); + } + + renderChart() { + this.destroyChart(); + const config = this.getChartConfig(); + this.chart = new Chart(this.canvasRef.el, config); + } + + getChartConfig() { + const labels = Object.keys(this.props.dataset); + + const data = Object.values(this.props.dataset); + + return { + type: 'pie', + data: { + labels: labels, + datasets: [ + { + data: data, + } + ], + } + }; + } + +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard/chart/pie_chart/pie_chart.xml similarity index 56% rename from awesome_dashboard/static/src/dashboard.xml rename to awesome_dashboard/static/src/dashboard/chart/pie_chart/pie_chart.xml index 1a2ac9a2fed..48161459cbf 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/chart/pie_chart/pie_chart.xml @@ -1,8 +1,8 @@ - - 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..435126ade7d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,60 @@ +import {Component, onWillStart, useState, onWillUnmount } from "@odoo/owl"; +import {_t} from "@web/core/l10n/translation"; +import {useService} from "@web/core/utils/hooks"; +import {Layout} from "@web/search/layout"; +import {registry} from "@web/core/registry"; +import {DashboardItem} from "./dashboard_item/dashboard_item" +import {PieChart} from "./chart/pie_chart/pie_chart" + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, PieChart }; + + setup() { + this.display = { + controlPanel: {}, + }; + this.action = useService("action"); + + this.dashStatsService = useService("awesome_dashboard.statistics"); + + this.state = useState({ + stats: {}, + }); + + onWillStart(async () => { + this.state.stats = await this.dashStatsService.loadStatistics(); + }) + + const intervalId = setInterval(async () => { + const newStats = await this.dashStatsService.loadStatistics(); + // Update the stats state keeping the reference + Object.assign(this.state.stats, newStats); + }, 1000*60*10); + + onWillUnmount(() => { + clearInterval(intervalId); + }); + } + + openCustomers() { + this.action.doAction("base.action_partner_form", { + viewType: "kanban" + }); + } + + openLeads() { + this.action.doAction({ + type: 'ir.actions.act_window', + name: _t('Leads'), + target: 'new', + 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..94f7569d0b2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: #5f5e5e; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..4ca0b262d27 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,42 @@ + + + + +
+ + + + + + +
+ + Number of new orders this month + + + + Total amount of new orders this month + + + + Average amount of t-shirt by order this month + + + + Number of cancelled orders this month + + + + Average time for an order to go from ‘new’ to ‘sent’ or ‘cancelled’ + + + + Shirt orders by size + + +
+
+
+
+ +
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..b2e33d9635e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,30 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_owl.dashboard_item"; + static props = { + title: String, + size: { type: Number, optional: true }, + slots: { + type: Object, + optional: true, + shape: { + default: { optional: true }, + title: String, + value: String + }, + }, + }; + + static defaultProps = { + size: 1 + } + + setup() { + } + + get cardWidth() { + return 18 * this.props.size; + } + +} 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..e2e35093453 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,18 @@ + + + + +
+
+
+ +
+
+

+ +
+
+
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_stats_service.js b/awesome_dashboard/static/src/dashboard/dashboard_stats_service.js new file mode 100644 index 00000000000..5898bcc43ef --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_stats_service.js @@ -0,0 +1,23 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { memoize } from "@web/core/utils/functions"; + +export async function loadStatistics() { + return rpc("/awesome_dashboard/statistics"); +} + +export const dashboardStatsService = { + dependencies: [], + start() { + const memoizedLoad = memoize(loadStatistics); + + // Time key integer changing every 10m in order to miss the cache of memoize + const getTimeKey = () => Math.floor(Date.now() / (1000 * 60 * 10)); + + return { + loadStatistics: () => memoizedLoad(getTimeKey()) + }; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", dashboardStatsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..12296b17cd6 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,12 @@ +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/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..0cf2d816f61 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,31 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: String, + body: { type: String, optional: true }, + slots: { + type: Object, + optional: true, + shape: { + default: { optional: true }, + }, + }, + toggleMode: { + type: Boolean, + } + }; + + setup() { + this.state = useState({ + isToggleOn: false + }); + } + + toggle() { + if (this.props.toggleMode) { + this.state.isToggleOn = !this.state.isToggleOn; + } + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..c3f56a84535 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,24 @@ + + + + +
+
+
+ + + + +
+
+ + +
+ +
+
+
+
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..61599aa9584 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,27 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + + static props = { + initialValue: { type: Number, optional: true}, + onChange: { type: Function, optional: true }, + onRegister: { type: Function, optional: true }, + }; + + setup() { + this.state = useState({ value: this.props.initialValue ?? 0}); + + if (this.props.onRegister) { + this.props.onRegister(this) + } + } + + 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..eac5cb5d9cb --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,11 @@ + + + + +
+

Counter:

+ +
+
+ +
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..0642f24b6c7 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,33 @@ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo_list/TodoList"; export class Playground extends Component { static template = "awesome_owl.playground"; + + static components = { Counter, Card, TodoList}; + + setup() { + /* + I define the counters here so I can show them as much as I want + */ + this.state = useState({ + counters_num: 2, + counters_ref: [] + }); + } + + // It registry the reference of the child here + registerCounter(id, counterInstance) { + this.state.counters_ref[id] = counterInstance; + } + + get sum() { + return this.state.counters_ref.reduce( + (acc, counter) => acc + (counter?.state?.value || 0), + 0 + ) + } + } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..78e07121357 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -2,8 +2,30 @@ -
- hello world +
+
+ + + +
+ +
The sum is:
+
+ +
+
+ +
+
+ +
+ + + + + + +
diff --git a/awesome_owl/static/src/todo_list/TodoItem.js b/awesome_owl/static/src/todo_list/TodoItem.js new file mode 100644 index 00000000000..6610c4af1c4 --- /dev/null +++ b/awesome_owl/static/src/todo_list/TodoItem.js @@ -0,0 +1,25 @@ +import { Component, useState} from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + + static props = { + id: Number, + description: String, + isCompleted: Boolean, + toggleState: Function, + onRemove: Function + }; + + toggleTodo() { + if (this.props.toggleState) { + this.props.toggleState(this.props.id) + } + } + + removeTodo() { + if (this.props.onRemove) { + this.props.onRemove(this.props.id) + } + } +} diff --git a/awesome_owl/static/src/todo_list/TodoList.js b/awesome_owl/static/src/todo_list/TodoList.js new file mode 100644 index 00000000000..6c0a80ae087 --- /dev/null +++ b/awesome_owl/static/src/todo_list/TodoList.js @@ -0,0 +1,55 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { TodoItem } from "./TodoItem"; +import { useAutofocus} from "./../utils" + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + + static components = { TodoItem }; + + setup() { + this.todos = useState([ ]); + this.state = useState({ + next_id: 1, + }); + useAutofocus("input_todo"); + + } + + onInputKeyup(ev) { + if (ev.key === "Enter") { + const value = ev.target.value.trim(); + if (value) { + const next_id = this.state.next_id++; + this.todos.push( + { + id: next_id, + description: value, + isCompleted: false + } + ) + ev.target.value = ""; + } + } + } + + toogleTodoState(id) { + if (!id) { + return; + } + const todo = this.todos.find(item => item.id === id); + + if (!todo) { + return; + } + todo.isCompleted = !todo.isCompleted + } + + removeTodo(id) { + const index = this.todos.findIndex((elem) => elem.id === id); + if (index >= 0) { + this.todos.splice(index, 1); + } + } + +} 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..e967ed34e58 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,14 @@ + + + + +
+ + + +
+
+ +
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..6c0a81dab9e --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,13 @@ + + + + + +
    + +
  • +
    +
+
+ +
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..21689fd51e8 --- /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] + ); +}