Skip to content
Draft
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
a321dfa
[ADD] estate: implement estate advertisement module.
pupat-odoo Feb 5, 2026
9e1664e
[ADD] estate: estate_property table created.
pupat-odoo Feb 6, 2026
0568700
[IMP] estate: define the csv file used in security directory.
pupat-odoo Feb 8, 2026
9358125
[ADD] estate: secuity policy is added this covers access right proper…
pupat-odoo Feb 8, 2026
2d38553
[IMP] estate: estate_property table's attributes are added with some…
pupat-odoo Feb 9, 2026
30e0df5
[ADD] estate: add custom list, form and search views
pupat-odoo Feb 13, 2026
f2d7658
[ADD] estate: added relationship between models, covers(ch-7)
pupat-odoo Feb 16, 2026
615ccd3
[FIX] estate: fixed the warning related to naming convention, covers…
pupat-odoo Feb 16, 2026
ce34b60
[ADD] estate: added computed fields, and onchange method decorator,co…
pupat-odoo Feb 17, 2026
96ac9f4
[IMP] estate: implement action button and usererror function, covers(…
pupat-odoo Feb 19, 2026
ae13a47
[IMP] estate: implement data constraints and custom validations, cove…
pupat-odoo Feb 20, 2026
d822ba1
[IMP] estate: implemented advance UI features with improved search,co…
pupat-odoo Feb 24, 2026
37f35b0
[IMP] estate: implement python , model and view inheritance (ch-12)
pupat-odoo Feb 27, 2026
8933d70
[IMP] estate: implement invoice creation with link module estate_acco…
pupat-odoo Mar 5, 2026
a30afed
[IMP] estate: implemented kanban view using Qweb (ch-14)
pupat-odoo Mar 6, 2026
c05bf57
[ADD] awesome_owl: added OWL components
pupat-odoo Mar 11, 2026
a118eaf
[IMP] awesome_owl: implement OWL components
pupat-odoo Mar 13, 2026
d50807c
[ADD] awesome_dashboard: added dynamic dashboard.
pupat-odoo Mar 23, 2026
7d0990c
[ADD] purchase_global_discount: added global discount on purchase ord…
pupat-odoo Mar 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
39 changes: 19 additions & 20 deletions awesome_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
# -*- coding: utf-8 -*-
{
'name': "Awesome Dashboard",

'summary': """
"name": "Awesome Dashboard",
"summary": """
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
""",

'description': """
"description": """
Starting module for "Discover the JS framework, chapter 2: Build a dashboard"
""",

'author': "Odoo",
'website': "https://www.odoo.com/",
'category': 'Tutorials',
'version': '0.1',
'application': True,
'installable': True,
'depends': ['base', 'web', 'mail', 'crm'],

'data': [
'views/views.xml',
"author": "Odoo",
"website": "https://www.odoo.com/",
"category": "Tutorials",
"version": "0.1",
"application": True,
"installable": True,
"depends": ["base", "web", "mail", "crm"],
"data": [
"views/views.xml",
],
'assets': {
'web.assets_backend': [
'awesome_dashboard/static/src/**/*',
"assets": {
"web.assets_backend": [
"awesome_dashboard/static/src/dashboard_loader.js",
],
"awesome_dashboard.dashboard": [
"awesome_dashboard/static/src/dashboard/**/*",
],
},
'license': 'AGPL-3'
"license": "AGPL-3",
}
23 changes: 11 additions & 12 deletions awesome_dashboard/controllers/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import random

from odoo import http
from odoo.http import request

logger = logging.getLogger(__name__)


class AwesomeDashboard(http.Controller):
@http.route('/awesome_dashboard/statistics', type='jsonrpc', auth='user')
@http.route("/awesome_dashboard/statistics", type="jsonrpc", auth="user")
def get_statistics(self):
"""
Returns a dict of statistics about the orders:
Expand All @@ -22,15 +22,14 @@ def get_statistics(self):
"""

return {
'average_quantity': random.randint(4, 12),
'average_time': random.randint(4, 123),
'nb_cancelled_orders': random.randint(0, 50),
'nb_new_orders': random.randint(10, 200),
'orders_by_size': {
'm': random.randint(0, 150),
's': random.randint(0, 150),
'xl': random.randint(0, 150),
"average_quantity": random.randint(4, 12),
"average_time": random.randint(4, 123),
"nb_cancelled_orders": random.randint(0, 50),
"nb_new_orders": random.randint(10, 200),
"orders_by_size": {
"m": random.randint(0, 150),
"s": random.randint(0, 150),
"xl": random.randint(0, 150),
},
'total_amount': random.randint(100, 1000)
"total_amount": random.randint(100, 1000),
}

8 changes: 0 additions & 8 deletions awesome_dashboard/static/src/dashboard.js

This file was deleted.

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

import { _t } from "@web/core/l10n/translation";
import { Component, useState } from "@odoo/owl";
import { DashboardItem } from "./document_item/document_item";
import { registry } from "@web/core/registry";
import { Layout } from "@web/search/layout";
import { DashboardSettings } from "./dashboard_settings/dashboard_settings";
import { useService } from "@web/core/utils/hooks";
import { Dialog } from "@web/core/dialog/dialog";
import {CheckBox} from "@web/core/checkbox/checkbox"
import { browser } from "@web/core/browser/browser";
export class AwesomeDashboard extends Component {
static template = "awesome_dashboard.AwesomeDashboard";
static components = { Layout, DashboardItem, DashboardSettings};

setup() {
this.action = useService("action");
this.statistics = useService("awesome_dashboard.statistics");
this.display = {
controlPanel: {},
};
this.dialog = useService("dialog");
this.allItems = registry.category("awesome_dashboard").getAll();

this.hiddenItems = JSON.parse(localStorage.getItem("dashboard_hidden") || "[]");

this.items = this.allItems.filter(
(item) => !this.hiddenItems.includes(item.id)
);
this.state = useState({
disabledItems:
browser.localStorage.getItem("disabledDashboardItems")?.split(",") ||
[],
});
}
openConfiguration() {
this.dialog.add(ConfigurationDialog, {
items: this.items,
disabledItems: this.state.disabledItems,
onUpdateConfiguration: this.updateConfiguration.bind(this),
});
}

updateConfiguration(newDisabledItems) {
this.state.disabledItems = newDisabledItems;
}


openCustomerView() {
this.action.doAction("base.action_partner_form");
}

openLeads() {
this.action.doAction({
type: "ir.actions.act_window",
name: _t("All leads"),
res_model: "crm.lead",
views: [
[false, "list"],
[false, "form"],
],
});
}
}
class ConfigurationDialog extends Component {
static template = "awesome_dashboard.ConfigurationDialog";
static components = { Dialog, CheckBox };
static props = ["close", "items", "disabledItems", "onUpdateConfiguration"];

setup() {
this.items = useState(this.props.items.map((item) => {
return {
...item,
enabled: !this.props.disabledItems.includes(item.id),
}
}));
}

done() {
this.props.close();
}

onChange(checked, changedItem) {
changedItem.enabled = checked;
const newDisabledItems = Object.values(this.items).filter(
(item) => !item.enabled
).map((item) => item.id)

browser.localStorage.setItem(
"disabledDashboardItems",
newDisabledItems,
);
this.props.onUpdateConfiguration(newDisabledItems);
}
}
registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
17 changes: 17 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.o_dashboard {
background-color : gray;
height : 100%;
}
.o_card {
background: white;
padding: 20px;
margin: 10px;
border: 1px solid #ccc;
text-align: center;
}

.pie-container {
width: 220px;
height: 220px;
margin: auto;
}
56 changes: 56 additions & 0 deletions awesome_dashboard/static/src/dashboard/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">
<Layout display="display" className="'o_dashboard h-100'">
<t t-set-slot="layout-buttons">
<button class="btn btn-primary" t-on-click="openCustomerView">Customers</button>
<button class="btn btn-primary" t-on-click="openLeads">Leads</button>
</t>

<t t-set-slot="control-panel-additional-actions">
<button t-on-click="openConfiguration" class="btn p-0 ms-1 border-0">
<i class="fa fa-cog"></i>
</button>
</t>

<div class="d-flex flex-wrap" t-if="statistics.isReady">

<t t-foreach="items" t-as="item" t-key="item.id">

<div t-attf-class="col-#{item.size || 4}">

<DashboardItem t-if="!state.disabledItems.includes(item.id)"
size="item.size || 3">

<t t-set="itemProp"
t-value="item.props ? item.props(statistics) : {'data': statistics}"/>

<t t-component="item.Component"
t-props="itemProp"/>

</DashboardItem>

</div>
</t>
</div>

</Layout>
</t>
<t t-name="awesome_dashboard.ConfigurationDialog">
<Dialog title="'Dashboard items configuration'">
Which cards do you whish to see ?
<t t-foreach="items" t-as="item" t-key="item.id">
<CheckBox value="item.enabled" onChange="(ev) => this.onChange(ev, item)">
<t t-esc="item.description"/>
</CheckBox>
</t>
<t t-set-slot="footer">
<button class="btn btn-primary" t-on-click="done">
Done
</button>
</t>
</Dialog>
</t>

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

import { registry } from "@web/core/registry";
import { NumberCard } from "./number_card/number_card";
import { PieChartCard } from "./piechart_card/piechart_card";

const dashboardItems = [
{
id: "average_quantity",
description: "Average T-Shirts",
Component: NumberCard,
size: 3,
props: (data) => ({
title: "Average t-shirt per order",
value: data.average_quantity,
}),
},
{
id: "average_time",
description: "Average processing time",
Component: NumberCard,
size: 3,
props: (data) => ({
title: "Average processing time",
value: data.average_time,
}),
},
{
id: "nb_new_orders",
description: "New Orders",
Component: NumberCard,
size: 3,
props: (data) => ({
title: "New Orders",
value: data.nb_new_orders,
}),
},
{
id: "nb_cancelled_orders",
description: "Cancelled Orders",
Component: NumberCard,
size: 3,
props: (data) => ({
title: "Cancelled Orders",
value: data.nb_cancelled_orders,
}),
},
{
id: "total_amount",
description: "Total Amount",
Component: NumberCard,
size: 3,
props: (data) => ({
title: "Total Amount",
value: data.total_amount,
}),
},
{
id: "orders_by_size",
description: "Orders by Size",
Component: PieChartCard,
size: 3,
props: (data) => ({
title: "Orders by Size",
data: data.orders_by_size || {},
}),
},
];


const dashboardItemRegistry = registry.category("awesome_dashboard");

for (const item of dashboardItems) {
dashboardItemRegistry.add(item.id, item);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/** @odoo-module **/

import { Component, useState } from "@odoo/owl";

export class DashboardSettings extends Component {
static template = "awesome_dashboard.DashboardSettings";

setup() {
this.state = useState({
hidden: new Set(this.props.hiddenItems),
});
}

toggle(id) {
if (this.state.hidden.has(id)) {
this.state.hidden.delete(id);
} else {
this.state.hidden.add(id);
}
}

apply() {
const hiddenItems = Array.from(this.state.hidden);

localStorage.setItem("dashboard_hidden", JSON.stringify(hiddenItems));

this.props.close();

location.reload();
}
}
Loading