Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
178f166
[IMP] awesome_owl: displaying the counter
aryep-odoo Aug 24, 2026
4ffc2d5
[IMP] awesome_owl: extract logic into its own sub-component
aryep-odoo Aug 24, 2026
640d269
[IMP] awesome_owl: new component for card
aryep-odoo Aug 24, 2026
f2b3ec2
[IMP] awesome_owl: escape the props of the card
aryep-odoo Aug 24, 2026
cee1564
[IMP] awesome_owl: bind callback functions
aryep-odoo Aug 24, 2026
86cb57a
[IMP] awesome_owl: new todo component
aryep-odoo Aug 25, 2026
bfc6570
[IMP] awesome_owl: new behavior for completed todo items
aryep-odoo Aug 25, 2026
a9f6cc1
[IMP] awesome_owl: enhance the behavior of the todo list component
aryep-odoo Aug 25, 2026
e178542
[IMP] awesome_owl: add autofocus behavior to todo list component
aryep-odoo Aug 25, 2026
795f68f
[IMP] awesome_owl: new checkbox to update state of current todo
aryep-odoo Aug 25, 2026
3028470
[IMP] awesome_owl: add deletion for todo items
aryep-odoo Aug 25, 2026
d226fb5
[IMP] awesome_owl: change content of the cards so they can be slots
aryep-odoo Aug 25, 2026
f9dacc6
[IMP] awesome_owl: new behavior to minimize card content
aryep-odoo Aug 25, 2026
cd0e449
[IMP] awesome_dashboard: new layout implementation for the view
aryep-odoo Aug 26, 2026
3cab36c
[IMP] awesome_dashboard: new actions for the dashboard layout"
aryep-odoo Aug 26, 2026
10826e3
[IMP] awesome_dashboard: new component for dashboard items
aryep-odoo Aug 26, 2026
e6a70df
[IMP] awesome_dashboard: new display of statistics using item component
aryep-odoo Aug 26, 2026
055a52a
[IMP] awesome_dashboard: new service to keep state of loaded statistics
aryep-odoo Aug 26, 2026
9980c0f
[IMP] awesome_dashboard: new pie chart component
aryep-odoo Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;`
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.dashboard_item">
<div class="card d-inline-block m-2" t-att-style="`width: ${widthSize}`;">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-1">
<h5 class="card-title m-0"><t t-out="props.title" /></h5>
</div>
<div t-if="open.value" class="card-text">
<t t-slot="default" />
<h1 t-if="props.slots.statistics" class="text-center text-success">
<t t-slot="statistics" />
</h1>
</div>
</div>
</div>
</t>

</templates>
50 changes: 50 additions & 0 deletions awesome_dashboard/static/src/components/pie_chart/pie_chart.js
Original file line number Diff line number Diff line change
@@ -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,
},
],
},
});
}

}
10 changes: 10 additions & 0 deletions awesome_dashboard/static/src/components/pie_chart/pie_chart.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.pie_chart">
<div t-if="props.data" class="canvas-container flex-grow-1">
<canvas t-ref="canvas" class="w-100"/>
</div>
</t>

</templates>
37 changes: 36 additions & 1 deletion awesome_dashboard/static/src/dashboard.js
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 3 additions & 0 deletions awesome_dashboard/static/src/dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.o_dashboard {
background-color: gray;
}
35 changes: 34 additions & 1 deletion awesome_dashboard/static/src/dashboard.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,40 @@
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">
hello dashboard
<Layout display="{ controlPanel: {} }" className="'o_dashboard h-100'">
<t t-set-slot="control-panel-create-button">
<button t-on-click="openCustomers" type="button" class="btn btn-primary" title="Customers">Customers</button>
<button t-on-click="openLeads" type="button" class="btn btn-primary" title="Leads">Leads</button>
</t>
<DashboardItem title="'Average amount of t-shirt by order this month'" size="1.5">
<t t-set-slot="statistics">
<t t-esc="statistics.average_quantity" />
</t>
</DashboardItem>
<DashboardItem title="'Average time for an order to go from ‘new’ to ‘sent’ or ‘cancelled’'" size="2">
<t t-set-slot="statistics">
<t t-esc="statistics.average_time" />
</t>
</DashboardItem>
<DashboardItem title="'Number of new orders this month'">
<t t-set-slot="statistics">
<t t-esc="statistics.nb_new_orders" />
</t>
</DashboardItem>
<DashboardItem title="'Number of cancelled orders this month'" size="1.2">
<t t-set-slot="statistics">
<t t-esc="statistics.nb_cancelled_orders" />
</t>
</DashboardItem>
<DashboardItem title="'Total amount of new orders this month'" size="1.2">
<t t-set-slot="statistics">
<t t-esc="statistics.total_amount" />
</t>
</DashboardItem>
<DashboardItem title="'Shirt orders by size'" size="2">
<PieChart data="statistics.orders_by_size" />
</DashboardItem>
</Layout>
</t>

</templates>
13 changes: 13 additions & 0 deletions awesome_dashboard/static/src/dashboard_service.js
Original file line number Diff line number Diff line change
@@ -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);
14 changes: 14 additions & 0 deletions awesome_owl/static/src/components/card/card.js
Original file line number Diff line number Diff line change
@@ -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
}
}
18 changes: 18 additions & 0 deletions awesome_owl/static/src/components/card/card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.card">
<div class="card d-inline-block m-2" style="width: 18rem;">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-1">
<h5 class="card-title m-0"><t t-out="props.title" /></h5>
<button t-on-click="toggleOpen">toggle</button>
</div>
<div t-if="open.value" class="card-text">
<t t-slot="default" />
</div>
</div>
</div>
</t>

</templates>
15 changes: 15 additions & 0 deletions awesome_owl/static/src/components/counter/counter.js
Original file line number Diff line number Diff line change
@@ -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?.()
}
}
11 changes: 11 additions & 0 deletions awesome_owl/static/src/components/counter/counter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.counter">
<div class="d-flex align-items-center p-3 gap-1">
<p class="m-0">Value: <t t-esc="counter.value"/></p>
<button t-on-click="increment">Increase</button>
</div>
</t>

</templates>
23 changes: 23 additions & 0 deletions awesome_owl/static/src/components/todo_list/todo_item.js
Original file line number Diff line number Diff line change
@@ -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
}
}
}
18 changes: 18 additions & 0 deletions awesome_owl/static/src/components/todo_list/todo_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.todo_item">
<div class="d-flex gap-1 mb-1 align-items-center" t-att-class='{"text-muted text-decoration-line-through": props.todo.isCompleted}'>
<input
type="checkbox"
t-att-value="props.todo.isCompleted"
t-on-change="() => props.toggleState?.(props.todo.id)"
class="mr-1"
/>
<span class="mr-1"><t t-esc="props.todo.id" />.</span>
<p class="m-0 pr-2"><t t-esc="props.todo.description" /></p>
<span class="fa fa-remove text-danger pb-1" role="button" t-on-click="() => props.deleteElement?.(props.todo.id)" />
</div>
</t>

</templates>
38 changes: 38 additions & 0 deletions awesome_owl/static/src/components/todo_list/todo_list.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
13 changes: 13 additions & 0 deletions awesome_owl/static/src/components/todo_list/todo_list.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.todo_list">
<div class="p-2 bg-light">
<input name="add_todo" placeholder="Add a todo" t-on-keyup="addTodo" t-ref="input" class="mb-1" />
<t t-foreach="todos" t-as="t" t-key="t.id">
<TodoItem todo="t" toggleState="toggleState" deleteElement="deleteElement" />
</t>
</div>
</t>

</templates>
20 changes: 19 additions & 1 deletion awesome_owl/static/src/playground.js
Original file line number Diff line number Diff line change
@@ -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 = "<div class='text-primary'>some content</div>"
card2Content = markup("<div class='text-primary'>some content</div>")

setup() {
this.incrementSum = this.incrementSum.bind(this)
}

incrementSum() {
this.sum.value += 1
}
}
Loading