Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
dfc2387
[IMP] awesome_owl: free me from this pain
trcazier Sep 22, 2025
f7b4680
[IMP] awesome_owl: save this counter implementation because I like it…
trcazier Sep 22, 2025
dc2211e
[IMP] awesome_owl: add up to section 7
trcazier Sep 22, 2025
a22285a
[IMP] awesome_owl: apply PR suggestions
trcazier Sep 23, 2025
8611d61
[IMP] awesome_owl: add sections 8-10
trcazier Sep 23, 2025
70e3c05
[IMP] awesome_owl: add section 11
trcazier Sep 23, 2025
aa4125a
[IMP] awesome_owl: replace redundant ternary operator with ||
trcazier Sep 23, 2025
bf99024
[IMP] awesome_owl: replace redundant ternary operator with if
trcazier Sep 23, 2025
03af1db
[IMP] awesome_owl: add missing ;
trcazier Sep 23, 2025
86806ec
[IMP] awesome_owl: add section 12 and missing ;s
trcazier Sep 23, 2025
143eec9
[IMP] awesome_owl: add section 13
trcazier Sep 23, 2025
3156e29
[IMP] awesome_owl: set the starting todo index as 1
trcazier Sep 23, 2025
60483e6
[IMP] awesome_owl: add section 14
trcazier Sep 23, 2025
2209933
[IMP] awesome_dashboard: add section 1
trcazier Sep 23, 2025
adf412a
[IMP] awesome_dashboard: add section 2
trcazier Sep 23, 2025
ac47f9b
[IMP] awesome_dashboard: add sections 3-4
trcazier Sep 23, 2025
71636b1
[IMP] awesome_dashboard: add section 5
trcazier Sep 23, 2025
4115506
[IMP] awesome_dashboard: section 6
trcazier Sep 23, 2025
82f496d
[IMP] awesome_dashboard: add section 7
trcazier Sep 24, 2025
cabcad9
[IMP] awesome_dashboard: add section 8
trcazier Sep 24, 2025
036b648
[IMP] awesome_dashboard: add section 9
trcazier Sep 24, 2025
164bc52
[IMP] awesome_dashboard: remove console logs
trcazier Sep 24, 2025
a502aa5
[IMP] awesome_dashboard: add section 10
trcazier Sep 24, 2025
292c7b8
[IMP] awesome_dashboard: add section 10
trcazier Sep 24, 2025
9f7c018
[MERGE] branch '18.0-web-tutorial-trcaz' of github.com:odoo-dev/tutor…
trcazier Sep 24, 2025
82c0d40
[IMP] awesome_dashboard: move buttons into layout button bar
trcazier Sep 24, 2025
b62f551
[IMP] awesome_dashboard: rewrite xml ids using PascalCase
trcazier Sep 24, 2025
113773f
[IMP] awesome_dashboard: apply PR feedback
trcazier Sep 24, 2025
6f4a297
[IMP] awesome_dashboard: add section 11
trcazier Sep 24, 2025
0ad1da4
[IMP] awesome_dashboard: refactor settings dialog props
trcazier Sep 25, 2025
6aacfbe
[IMP] awesome_dashboard: change var name for clarity
trcazier Sep 25, 2025
3231834
[IMP] awesome_dashboard: add newline to file end
trcazier Sep 25, 2025
44e0d49
[IMP] awesome_dashboard: apply PR suggestion
trcazier Sep 25, 2025
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
3 changes: 3 additions & 0 deletions awesome_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
],
'awesome_dashboard.dashboard': [
'awesome_dashboard/static/src/dashboard/**/*'
]
},
'license': 'AGPL-3'
}
10 changes: 0 additions & 10 deletions awesome_dashboard/static/src/dashboard.js

This file was deleted.

36 changes: 36 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @odoo-module **/

import { Component, onWillStart, 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";

class AwesomeDashboard extends Component {
static template = "awesome_dashboard.awesome_dashboard";
static components = { Layout, DashboardItem, PieChart }

setup() {
this.action = useService("action");
this.stats = useState(useService("awesome_dashboard.statistics"))
}
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']
],
});
}

}

registry.category("lazy_components").add("awesome_dashboard.dashboard", AwesomeDashboard);
3 changes: 3 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.o_dashboard {
background-color: purple
}
31 changes: 31 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.awesome_dashboard">
<button class="btn btn-primary" t-on-click="() => this.openPartners()">
Customers
</button>
<button class="btn btn-primary" t-on-click="() => this.openLeads()">
Leads
</button>
<Layout className="'o_dashboard h-100'" display="{controlPanel: {} }">
<DashboardItem>
no size
</DashboardItem>
<DashboardItem size="2">
size of 2
</DashboardItem>
<t t-foreach="Object.keys(this.stats)" t-as="s" t-key="s">
<!-- please don't make me use css -->
<DashboardItem>
<t t-esc="s"/>
<t t-esc="this.stats[s]"/>
</DashboardItem>
</t>
<DashboardItem>
<PieChart data="this.stats"/>
</DashboardItem>
</Layout>
</t>

</templates>
13 changes: 13 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_action.js
Original file line number Diff line number Diff line change
@@ -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`
<LazyComponent bundle="'awesome_dashboard.dashboard'" Component="'awesome_dashboard.dashboard'"/>
`;
}

registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);
14 changes: 14 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from "@odoo/owl";

export class DashboardItem extends Component {
static template = "awesome_dashboard.dashboard_item"
static props = {
size: {type: Number, optional: true},
slots: {optional: true}
}

setup() {
this.size = this.props.size || 1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use defaultProps

this.width = `${18 * this.size}rem`
}
}
15 changes: 15 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.dashboard_item">
<div class="dashboard_item d-inline-block m-2" t-att-style="'width: ' + this.width + '; border: 1px solid black; border-radius: 5px'">
<div class="dashboard_item-body">
<h5 class="dashboard_item-title"><t t-esc="this.title"/></h5>
<p class="dashboard_item-text">
<t t-slot="default"/>
</p>
</div>
</div>
</t>

</templates>
30 changes: 30 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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() {
console.log("slt")
const newStats = await rpc("/awesome_dashboard/statistics");
Object.keys(newStats).forEach((k) => stats[k] = newStats[k])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can't you do something like stats = newStats?
why did you have to iterate over the keys?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I did that it didn't register the change, maybe because it overwrote the useState, idk
I can try again

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope doesn't work with stats = newStats

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object.assign(stats, newStats) maybe

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How on god's green earth was I supposed to find that out

console.log(newStats)
}
setInterval(loadData, 2*1000);
loadData();

return stats;
},
};


registry.category("services").add("awesome_dashboard.statistics", dashboardService);
37 changes: 37 additions & 0 deletions awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js
Original file line number Diff line number Diff line change
@@ -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.pie_chart";
static props = { data: {optional: false}}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every prop is required by default. You don't have to write it unless it's optional: true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know but I just didn't want to type check the data because it's json and annoying but I still wanted to require the data prop


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.orders_by_size)
this.labels = Object.keys(this.props.data.orders_by_size)

this.buildChart()
});

onWillUnmount(() => this.pieChart.destroy());
}

buildChart() {
this.pieChart = new Chart(this.canvasRef.el, {
type: "pie",
data: {
labels: this.labels,
datasets: [{
data: this.data,
}],
},
});
}
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">
hello dashboard
<t t-name="awesome_dashboard.pie_chart">
<canvas t-ref="canvas"/>
</t>

</templates>
19 changes: 19 additions & 0 deletions awesome_owl/static/src/card/card.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
18 changes: 18 additions & 0 deletions awesome_owl/static/src/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">
<h5 class="card-title"><t t-esc="this.title"/></h5>
<button class="btn btn-primary" t-on-click="() => this.state.open = !this.state.open">Toggle</button>
<t t-if="this.state.open">
<p class="card-text">
<t t-slot="default"/>
</p>
</t>
</div>
</div>
</t>

</templates>
20 changes: 20 additions & 0 deletions awesome_owl/static/src/counter/counter.js
Original file line number Diff line number Diff line change
@@ -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++;
}
}
11 changes: 11 additions & 0 deletions awesome_owl/static/src/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="p-3">
<p>Counter: <t t-esc="state.value"/></p>
<button class="btn btn-primary" t-on-click="increment">Increment</button>
</div>
</t>

</templates>
19 changes: 16 additions & 3 deletions awesome_owl/static/src/playground.js
Original file line number Diff line number Diff line change
@@ -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 = "<div class='text-primary'>some content</div>";
this.str2 = markup("<div class='text-primary'>some content</div>");
this.state = useState({ val1: 0, val2: 10 })
}

get sum() {
return this.state.val1 + this.state.val2;
}

}
16 changes: 16 additions & 0 deletions awesome_owl/static/src/playground.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@
<div class="p-3">
hello world
</div>
<Counter value="this.state.val1" side_effect.bind="() => this.state.val1++"/>
<Counter value="this.state.val2" side_effect.bind="() => this.state.val2++"/>
<div>The sum is: <t t-esc="sum"/></div>
<Counter/>
<Card title="'epic card title'">
New content for the new card
</Card>
<Card title="'cute card title'">
this should work with arbitrary html content
</Card>
<Card title="'scary card title'">
Here is a cool counter for you
<Counter/>
</Card>

<TodoList/>
</t>

</templates>
18 changes: 18 additions & 0 deletions awesome_owl/static/src/todo_list/todo_item.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 13 additions & 0 deletions awesome_owl/static/src/todo_list/todo_item.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_item">
<div class="todo_item-body" t-att-class="{'text-muted text-decoration-line-through': this.todo.isCompleted}">
<p class="todo_item-text">
<input type="checkbox" t-att-checked="this.todo.isCompleted" t-on-change="this.toggleState"/>
<t t-esc="this.id"/>. <t t-out="this.todo.description"/> <span class="fa fa-remove" t-on-click="this.removeTodo"/>
</p>
</div>
</t>

</templates>
Loading