diff --git a/awesome_dashboard/static/src/components/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/components/dashboard_item/dashboard_item.js
new file mode 100644
index 00000000000..ea2e1dcfb29
--- /dev/null
+++ b/awesome_dashboard/static/src/components/dashboard_item/dashboard_item.js
@@ -0,0 +1,26 @@
+import { Component, useState } from "@odoo/owl";
+
+export class DashboardItem extends Component {
+ static template = "awesome_dashboard.dashboard_item";
+ static props = {
+ title: {type: String, optional: true},
+ slots: {type: Object, optional: true},
+ size: {type: Number, optional: true}
+ };
+ static defaultProps = {
+ title: "",
+ size: 1
+ }
+
+ setup() {
+ this.open = useState({ value: true });
+ }
+
+ toggleOpen() {
+ this.open.value = !this.open.value
+ }
+
+ get widthSize() {
+ return `${18*this.props.size}rem;`
+ }
+}
diff --git a/awesome_dashboard/static/src/components/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/components/dashboard_item/dashboard_item.xml
new file mode 100644
index 00000000000..9fef9e5022c
--- /dev/null
+++ b/awesome_dashboard/static/src/components/dashboard_item/dashboard_item.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/components/pie_chart/pie_chart.js b/awesome_dashboard/static/src/components/pie_chart/pie_chart.js
new file mode 100644
index 00000000000..6576ade3ce2
--- /dev/null
+++ b/awesome_dashboard/static/src/components/pie_chart/pie_chart.js
@@ -0,0 +1,50 @@
+import { Component, useState, onWillStart, useRef, onMounted, onPatched, onWillUnmount, useEffect } from "@odoo/owl";
+import { loadJS } from "@web/core/assets";
+
+export class PieChart extends Component {
+ static template = "awesome_dashboard.pie_chart";
+ static props = {
+ title: {type: String, optional: true},
+ data: {
+ type: Object,
+ values: { type: Number },
+ optional: true
+ }
+ };
+
+ setup() {
+ this.canvasRef = useRef("canvas");
+
+ onWillStart(async () => loadJS("/web/static/lib/Chart/Chart.js"));
+
+ useEffect(() => {
+ this.renderChart();
+ });
+
+ onPatched(() => {
+ this.chart?.destroy();
+ this.renderChart();
+ });
+
+ onWillUnmount(() => {
+ this.chart?.destroy();
+ });
+ }
+
+ renderChart() {
+ const labels = Object.keys(this.props.data);
+ const data = Object.values(this.props.data);
+ this.chart = new Chart(this.canvasRef.el, {
+ type: "doughnut",
+ data: {
+ labels: labels,
+ datasets: [
+ {
+ data: data,
+ },
+ ],
+ },
+ });
+ }
+
+}
diff --git a/awesome_dashboard/static/src/components/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/components/pie_chart/pie_chart.xml
new file mode 100644
index 00000000000..d17d9ea5818
--- /dev/null
+++ b/awesome_dashboard/static/src/components/pie_chart/pie_chart.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js
index c4fb245621b..fab97c06b4a 100644
--- a/awesome_dashboard/static/src/dashboard.js
+++ b/awesome_dashboard/static/src/dashboard.js
@@ -1,8 +1,43 @@
-import { Component } from "@odoo/owl";
+import { Component, onWillStart, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
+import { useService } from "@web/core/utils/hooks";
+import { _t } from "@web/core/l10n/translation";
+import { rpc } from "@web/core/network/rpc";
+import { Layout } from "@web/search/layout"
+
+import { DashboardItem } from "./components/dashboard_item/dashboard_item";
+import { PieChart } from "./components/pie_chart/pie_chart";
class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
+ static components = { Layout, DashboardItem, PieChart };
+
+ setup() {
+ this.action = useService("action");
+ const dashboardService = useService("awesome_dashboard.statistics");
+
+ onWillStart(async () => {
+ this.statistics = await dashboardService.loadStatistics()
+ });
+ }
+
+ openCustomers() {
+ this.action.doAction("base.action_partner_form");
+ }
+
+ async openLeads(activity) {
+ /* Dynamic action to open Leads with only list and form view */
+
+ this.action.doAction({
+ type: "ir.actions.act_window",
+ name: "All leads",
+ res_model: "crm.lead",
+ views: [
+ [false, "list"],
+ [false, "form"],
+ ],
+ });
+ }
}
registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard);
diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard.scss
new file mode 100644
index 00000000000..32862ec0d82
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard.scss
@@ -0,0 +1,3 @@
+.o_dashboard {
+ background-color: gray;
+}
diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml
index 1a2ac9a2fed..ab57f9ff1de 100644
--- a/awesome_dashboard/static/src/dashboard.xml
+++ b/awesome_dashboard/static/src/dashboard.xml
@@ -2,7 +2,40 @@
- hello dashboard
+
+
+ Customers
+ Leads
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard_service.js b/awesome_dashboard/static/src/dashboard_service.js
new file mode 100644
index 00000000000..f85e04d5d8a
--- /dev/null
+++ b/awesome_dashboard/static/src/dashboard_service.js
@@ -0,0 +1,13 @@
+import { registry } from "@web/core/registry";
+import { rpc } from "@web/core/network/rpc";
+import { memoize } from "@web/core/utils/functions";
+
+export const dashboardService = {
+ start() {
+ return {
+ loadStatistics: memoize(() => rpc("/awesome_dashboard/statistics")),
+ };
+ }
+}
+
+registry.category("services").add("awesome_dashboard.statistics", dashboardService);
diff --git a/awesome_owl/static/src/components/card/card.js b/awesome_owl/static/src/components/card/card.js
new file mode 100644
index 00000000000..85fefd4ccf9
--- /dev/null
+++ b/awesome_owl/static/src/components/card/card.js
@@ -0,0 +1,14 @@
+import { Component, useState } from "@odoo/owl";
+
+export class Card extends Component {
+ static template = "awesome_owl.card";
+ static props = ["title", "slots?"]
+
+ setup() {
+ this.open = useState({ value: true });
+ }
+
+ toggleOpen() {
+ this.open.value = !this.open.value
+ }
+}
diff --git a/awesome_owl/static/src/components/card/card.xml b/awesome_owl/static/src/components/card/card.xml
new file mode 100644
index 00000000000..976144ac7db
--- /dev/null
+++ b/awesome_owl/static/src/components/card/card.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/components/counter/counter.js b/awesome_owl/static/src/components/counter/counter.js
new file mode 100644
index 00000000000..e080fdff05d
--- /dev/null
+++ b/awesome_owl/static/src/components/counter/counter.js
@@ -0,0 +1,15 @@
+import { Component, useState } from "@odoo/owl";
+
+export class Counter extends Component {
+ static template = "awesome_owl.counter";
+ static props = {
+ onChange: {type: Function, optional: true}
+ }
+
+ counter = useState({ value: 0 })
+
+ increment() {
+ this.counter.value += 1
+ this.props.onChange?.()
+ }
+}
diff --git a/awesome_owl/static/src/components/counter/counter.xml b/awesome_owl/static/src/components/counter/counter.xml
new file mode 100644
index 00000000000..4ae4e2a83b7
--- /dev/null
+++ b/awesome_owl/static/src/components/counter/counter.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/components/todo_list/todo_item.js b/awesome_owl/static/src/components/todo_list/todo_item.js
new file mode 100644
index 00000000000..14f6d1b4fd3
--- /dev/null
+++ b/awesome_owl/static/src/components/todo_list/todo_item.js
@@ -0,0 +1,23 @@
+import { Component, useState } from "@odoo/owl";
+
+export class TodoItem extends Component {
+ static template = "awesome_owl.todo_item";
+ static props = {
+ todo: {
+ type: Object,
+ shape: {
+ id: Number,
+ description: String,
+ isCompleted: Boolean
+ }
+ },
+ toggleState: {
+ type: Function,
+ optional: true
+ },
+ deleteElement: {
+ type: Function,
+ optional: true
+ }
+ }
+}
diff --git a/awesome_owl/static/src/components/todo_list/todo_item.xml b/awesome_owl/static/src/components/todo_list/todo_item.xml
new file mode 100644
index 00000000000..e5e5596b579
--- /dev/null
+++ b/awesome_owl/static/src/components/todo_list/todo_item.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/components/todo_list/todo_list.js b/awesome_owl/static/src/components/todo_list/todo_list.js
new file mode 100644
index 00000000000..9565d3417c6
--- /dev/null
+++ b/awesome_owl/static/src/components/todo_list/todo_list.js
@@ -0,0 +1,38 @@
+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() {
+ this.todos = useState([]);
+ this.inputAutofocusRef = useAutoFocus("input")
+
+ this.toggleState = this.toggleState.bind(this)
+ this.deleteElement = this.deleteElement.bind(this)
+ }
+
+ addTodo(event) {
+ if (event.keyCode === 13) {
+ const lastElement = this.todos.slice(-1)?.[0]
+ const lastId = lastElement?.id ?? 0
+ this.todos.push({ id: lastId + 1, description: event.target.value, isCompleted: false });
+ event.target.value = ""
+ }
+ }
+
+ toggleState(refId) {
+ const element = this.todos.find(({id}) => id === refId);
+ if (element) element.isCompleted = !element.isCompleted;
+ }
+
+ deleteElement(refId) {
+ const index = this.todos.findIndex(({id}) => id === refId);
+ if (typeof index === "number") {
+ this.todos.splice(index, 1)
+ }
+ }
+}
diff --git a/awesome_owl/static/src/components/todo_list/todo_list.xml b/awesome_owl/static/src/components/todo_list/todo_list.xml
new file mode 100644
index 00000000000..0dad37feaff
--- /dev/null
+++ b/awesome_owl/static/src/components/todo_list/todo_list.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js
index 4ac769b0aa5..f0eedc105c9 100644
--- a/awesome_owl/static/src/playground.js
+++ b/awesome_owl/static/src/playground.js
@@ -1,5 +1,23 @@
-import { Component } from "@odoo/owl";
+import { Component, useState, markup } from "@odoo/owl";
+
+import { Counter } from "./components/counter/counter";
+import { Card } from "./components/card/card"
+import { TodoList } from "./components/todo_list/todo_list";
export class Playground extends Component {
static template = "awesome_owl.playground";
+ static components = { Counter, Card, TodoList };
+
+ sum = useState({ value: 0 })
+
+ card1Content = "
some content
"
+ card2Content = markup("some content
")
+
+ setup() {
+ this.incrementSum = this.incrementSum.bind(this)
+ }
+
+ incrementSum() {
+ this.sum.value += 1
+ }
}
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml
index 4fb905d59f9..1fb638fb529 100644
--- a/awesome_owl/static/src/playground.xml
+++ b/awesome_owl/static/src/playground.xml
@@ -3,7 +3,30 @@
- hello world
+
+ hello world
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js
new file mode 100644
index 00000000000..71d0587af2a
--- /dev/null
+++ b/awesome_owl/static/src/utils.js
@@ -0,0 +1,17 @@
+import { useRef, onMounted } from "@odoo/owl";
+
+/**
+ * Custom hook to autofocus an element on mounted
+ *
+ * @param {String} ref name that will be used for the element
+ * @returns the reference created for the element
+ */
+export function useAutoFocus(ref) {
+ const inputRef = useRef(ref)
+
+ onMounted(() => {
+ inputRef.el?.focus();
+ })
+
+ return inputRef;
+}