diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py
index 31406e8addb..7105d960c13 100644
--- a/awesome_dashboard/__manifest__.py
+++ b/awesome_dashboard/__manifest__.py
@@ -25,6 +25,9 @@
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
],
+ '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 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..32758802c40
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.js
@@ -0,0 +1,73 @@
+/** @odoo-module **/
+
+import { Component, useState } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+import { Layout } from "@web/search/layout";
+import { useService } from "@web/core/utils/hooks";
+import { _t } from "@web/core/l10n/translation";
+import { DashboardItem } from "./dashboard_item";
+import { PieChart } from "./pie_chart/pie_chart";
+import { browser } from "@web/core/browser/browser";
+import { Dialog } from "@web/core/dialog/dialog";
+import { CheckBox } from "@web/core/checkbox/checkbox";
+
+class AwesomeDashboard extends Component {
+ static template = "awesome_dashboard.AwesomeDashboard";
+ static components = { Layout, DashboardItem, PieChart };
+
+ setup() {
+ this.action = useService("action");
+ this.stats = useState(useService("awesome_dashboard.statistics"));
+ this.items = registry.category("awesome_dashboard").getAll();
+ this.dialog = useService("dialog");
+ this.state = useState({ itemsNotShown: browser.localStorage.getItem("itemsNotShown")?.split(",") || []});
+ }
+
+ openPartners() {
+ this.action.doAction("base.action_partner_form");
+ }
+
+ async openLeads() {
+ this.action.doAction({
+ type: 'ir.actions.act_window',
+ name: _t('All Leads'),
+ res_model: 'crm.lead',
+ views: [
+ [false, 'list'],
+ [false, 'form']
+ ],
+ });
+ }
+
+ openSettingsDialog() {
+ this.dialog.add(SettingsDialog, {
+ items: this.items,
+ dashboard: this,
+ });
+ }
+}
+
+registry.category("lazy_components").add("awesome_dashboard.dashboard", AwesomeDashboard);
+
+class SettingsDialog extends Component {
+ static template = "awesome_dashboard.SettingsDialog";
+ static components = { Dialog, CheckBox };
+ static props = ["dashboard", "items"];
+
+ setup() {
+ this.items = useState(this.props.items);
+ this.items.forEach((item) => { item.shown = !this.props.dashboard.state.itemsNotShown.includes(item.id)});
+ }
+
+ toggleItem (isChecked, item) {
+ item.shown = isChecked;
+
+ const newItemsNotShown = Object.values(this.items)
+ .filter((i) => !i.shown)
+ .map((i) => i.id)
+
+ browser.localStorage.setItem("itemsNotShown", newItemsNotShown);
+
+ this.props.dashboard.state.itemsNotShown = newItemsNotShown;
+ }
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss
new file mode 100644
index 00000000000..4f794e54032
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.scss
@@ -0,0 +1,3 @@
+.o_dashboard {
+ background-color: purple
+}
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml
new file mode 100644
index 00000000000..32e5471b37f
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_action.js b/awesome_dashboard/static/src/dashboard/dashboard_action.js
new file mode 100644
index 00000000000..8b1d088b2ba
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_action.js
@@ -0,0 +1,13 @@
+import { LazyComponent } from "@web/core/assets";
+import { Component, xml, onWillStart, useState } from "@odoo/owl";
+import { registry } from "@web/core/registry";
+
+
+export class AwesomeDashboardLoader extends Component {
+ static components = { LazyComponent };
+ static template = xml`
+
+ `;
+}
+
+registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);
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..de456a82ecd
--- /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: {optional: true}
+ };
+
+ setup() {
+ this.size = this.props.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..ad387048260
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
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..ba2e6ff723b
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js
@@ -0,0 +1,65 @@
+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: "average_quantity",
+ description: "Average amount of t-shirt",
+ Component: NumberCard,
+ props: (data) => ({
+ title: "Average amount of t-shirt by order this month",
+ value: data.average_quantity,
+ })
+ },
+ {
+ id: "average_time",
+ description: "Average time for an order",
+ Component: NumberCard,
+ props: (data) => ({
+ title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'",
+ value: data.average_time,
+ })
+ },
+ {
+ id: "number_new_orders",
+ description: "New orders this month",
+ Component: NumberCard,
+ props: (data) => ({
+ title: "Number of new orders this month",
+ value: data.nb_new_orders,
+ })
+ },
+ {
+ id: "cancelled_orders",
+ description: "Cancelled orders this month",
+ Component: NumberCard,
+ props: (data) => ({
+ title: "Number of cancelled orders this month",
+ value: data.nb_cancelled_orders,
+ })
+ },
+ {
+ id: "amount_new_orders",
+ description: "amount orders this month",
+ Component: NumberCard,
+ props: (data) => ({
+ title: "Total amount of new orders this month",
+ value: data.total_amount,
+ })
+ },
+ {
+ id: "pie_chart",
+ description: "Shirt orders by size",
+ Component: PieChartCard,
+ size: 2,
+ props: (data) => ({
+ title: "Shirt orders by size",
+ values: data.orders_by_size,
+ })
+ }
+]
+
+items.forEach(i => {
+ registry.category("awesome_dashboard").add(i.id, i);
+});
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_service.js b/awesome_dashboard/static/src/dashboard/dashboard_service.js
new file mode 100644
index 00000000000..658de243915
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/dashboard_service.js
@@ -0,0 +1,28 @@
+import { registry } from "@web/core/registry";
+import { memoize } from "@web/core/utils/functions";
+import { rpc } from "@web/core/network/rpc";
+import { reactive } from "@odoo/owl";
+
+const dashboardService = {
+ start() {
+ let stats = reactive({
+ average_quantity: 0,
+ average_time: 0,
+ nb_cancelled_orders: 0,
+ nb_new_orders: 0,
+ orders_by_size: {},
+ total_amount: 0
+ });
+ async function loadData() {
+ const newStats = await rpc("/awesome_dashboard/statistics");
+ Object.assign(stats, newStats);
+ }
+ setInterval(loadData, 2*1000);
+ loadData();
+
+ return stats;
+ },
+};
+
+
+registry.category("services").add("awesome_dashboard.statistics", dashboardService);
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..571d15599e6
--- /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.NumberCard"
+ static props = {
+ title: String,
+ value: 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..d980f216f9d
--- /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..c88eeaa1f4b
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js
@@ -0,0 +1,37 @@
+import { Component, onMounted, onWillUnmount, onWillStart, useEffect, useRef } from "@odoo/owl";
+import { loadJS } from "@web/core/assets";
+
+export class PieChart extends Component {
+ static template = "awesome_dashboard.PieChart";
+ static props = { data: {optional: false}};
+
+ setup() {
+ this.canvasRef = useRef("canvas");
+
+ onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js"));
+
+ useEffect(() => {
+ if (this.pieChart) {
+ this.pieChart.destroy();
+ }
+ this.data = Object.values(this.props.data);
+ this.labels = Object.keys(this.props.data);
+
+ this.buildChart();
+ });
+
+ onWillUnmount(() => { if (this.pieChart) {this.pieChart.destroy()}; });
+ }
+
+ buildChart() {
+ this.pieChart = new Chart(this.canvasRef.el, {
+ type: "pie",
+ data: {
+ labels: this.labels,
+ datasets: [{
+ data: this.data,
+ }],
+ },
+ });
+ }
+};
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..6263d4cec7c 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..ea01d40141e
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js
@@ -0,0 +1,11 @@
+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: String,
+ values: {optional: false},
+ };
+}
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..9ddf84e8419
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js
new file mode 100644
index 00000000000..961faa9f494
--- /dev/null
+++ b/awesome_owl/static/src/card/card.js
@@ -0,0 +1,19 @@
+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({open: true});
+ this.title = this.props.title;
+ }
+}
diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml
new file mode 100644
index 00000000000..6c35e2c2836
--- /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..507311848ed
--- /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 = {
+ value: {optional: true},
+ side_effect: {type: Function, optional: true}
+ };
+
+ setup() {
+ this.state = useState({ value: this.props.value || 0});
+ }
+
+ increment() {
+ if (this.props.side_effect) {
+ this.props.side_effect();
+ }
+ this.state.value++;
+ }
+}
diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml
new file mode 100644
index 00000000000..f20e162d294
--- /dev/null
+++ b/awesome_owl/static/src/counter/counter.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js
index 657fb8b07bb..c2f825cd293 100644
--- a/awesome_owl/static/src/playground.js
+++ b/awesome_owl/static/src/playground.js
@@ -1,7 +1,20 @@
-/** @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 components = { Counter, Card, TodoList }
+
+ setup() {
+ this.str1 = "
some content
";
+ this.str2 = markup("some content
");
+ this.state = useState({ val1: 0, val2: 10 })
+ }
+
+ get sum() {
+ return this.state.val1 + this.state.val2;
+ }
+
}
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml
index 4fb905d59f9..898bf184d2b 100644
--- a/awesome_owl/static/src/playground.xml
+++ b/awesome_owl/static/src/playground.xml
@@ -5,6 +5,22 @@
hello world
+
+
+ The sum is:
+
+
+ New content for the new card
+
+
+ this should work with arbitrary html content
+
+
+ Here is a cool counter for you
+
+
+
+
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..7807fa30af4
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_item.js
@@ -0,0 +1,18 @@
+import { Component, useState } from "@odoo/owl";
+
+export class TodoItem extends Component {
+ static template = "awesome_owl.todo_item"
+ static props = {
+ id: String,
+ todo: {type: {description: String, isCompleted: Boolean}},
+ toggleState: Function,
+ removeTodo: Function
+ }
+
+ setup() {
+ this.id = this.props.id;
+ this.todo = useState(this.props.todo);
+ this.toggleState = this.props.toggleState;
+ this.removeTodo = this.props.removeTodo;
+ }
+}
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..4bc4ab34a37
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_item.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
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..9f4b96702e1
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_list.js
@@ -0,0 +1,33 @@
+import { Component, useState, useRef, onMounted } 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() {
+ useAutoFocus("todo-input");
+ this.state = useState({todos: {
+ 1: { description: "buy milk", isCompleted: false },
+ 2: { description: "buy tomatoes", isCompleted: true }
+ },
+ lastId: 2
+ });
+ }
+
+ toggleState(id) {
+ this.state.todos[id].isCompleted = !this.state.todos[id].isCompleted;
+ }
+
+ addTodo(ev) {
+ if (ev.keyCode === 13 && ev.target.value != "") {
+ this.state.todos[++this.state.lastId] = {description: ev.target.value, isCompleted: false};
+ ev.target.value = "";
+ }
+ }
+
+ removeTodo(id) {
+ delete this.state.todos[id];
+ }
+}
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..eed4efec8d7
--- /dev/null
+++ b/awesome_owl/static/src/todo_list/todo_list.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js
new file mode 100644
index 00000000000..1964cd7407a
--- /dev/null
+++ b/awesome_owl/static/src/utils.js
@@ -0,0 +1,9 @@
+import { onMounted, useRef } from '@odoo/owl'
+
+export function useAutoFocus(refName) {
+ const inputRef = useRef(refName);
+
+ onMounted(() => {
+ inputRef.el.focus();
+ });
+}