Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
078cbd1
[ADD] Estate: Added estate property module declaration and models
mohamedakhalil18 Jan 19, 2026
5fedcdf
[FIX] Estate: Added linting and formatting
mohamedakhalil18 Jan 20, 2026
814bf0d
[IMP] Estate: Added access control to my module
mohamedakhalil18 Jan 20, 2026
06130c7
[IMP] Estate: Added action and menus to enable user to add properties
mohamedakhalil18 Jan 20, 2026
6cd9489
[IMP] Estate: Added search, filtering, grouping functionalities and r…
mohamedakhalil18 Jan 20, 2026
196e7e3
[IMP] Estate: Added related fields to the estate module
mohamedakhalil18 Jan 21, 2026
fbb2a57
[IMP] Estate: Added computational methods, onchange, @api calls
mohamedakhalil18 Jan 21, 2026
aa670af
[IMP] Estate: Added action buttons to sell/cancel an apartment and ac…
mohamedakhalil18 Jan 21, 2026
35bf86d
[IMP] Estate: Constraints added to improve business logic
mohamedakhalil18 Jan 22, 2026
49b86b6
[IMP] Estate: Added stat_button, inline editting, and widgets
mohamedakhalil18 Jan 23, 2026
88b6b68
[FIX] Estate: Resolved reviewer comments on previous commits regardin…
mohamedakhalil18 Jan 23, 2026
918e85b
[IMP] Estate: Modified CRUD actions as well as display the apartments…
mohamedakhalil18 Jan 23, 2026
76d2025
[ADD] Estate_Account module was added in order to invoice sold proper…
mohamedakhalil18 Jan 26, 2026
bfb5972
[IMP] Added kanban views to the estate properties in improve overall …
mohamedakhalil18 Jan 26, 2026
d66b18a
[FIX] Refactored the directory and code to follow coding guidelines
mohamedakhalil18 Jan 26, 2026
f2f8323
[FIX] Editted the codebase to unblock the runbot merging
mohamedakhalil18 Jan 26, 2026
3c9f708
[FIX] Estate: Rearranged the views in estate directory and dependencies
mohamedakhalil18 Jan 27, 2026
f35b786
[FIX] Estate: reordering data imports in the manifest to fix dependen…
mohamedakhalil18 Jan 28, 2026
b4fbe67
[IMP] Awesome_Owl: Added extra components and wrappers to the module
mohamedakhalil18 Jan 28, 2026
9a92494
[IMP] Awesome_Owl: Added extra components and wrappers to the module
mohamedakhalil18 Jan 28, 2026
f4b7afd
[IMP] Awesome Dashboard: The addition of layout, number and pie chart…
mohamedakhalil18 Jan 30, 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
1 change: 0 additions & 1 deletion awesome_clicker/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
# -*- coding: utf-8 -*-
36 changes: 15 additions & 21 deletions awesome_clicker/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
# -*- coding: utf-8 -*-
{
'name': "Awesome Clicker",

'summary': """
"name": "Awesome Clicker",
"summary": """
Starting module for "Master the Odoo web framework, chapter 1: Build a Clicker game"
""",

'description': """
"description": """
Starting module for "Master the Odoo web framework, chapter 1: Build a Clicker game"
""",

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

'data': [],
'assets': {
'web.assets_backend': [
'awesome_clicker/static/src/**/*',
"author": "Odoo",
"website": "https://www.odoo.com/",
"category": "Tutorials",
"version": "0.1",
"application": True,
"installable": True,
"depends": ["base", "web"],
"data": [],
"assets": {
"web.assets_backend": [
"awesome_clicker/static/src/**/*",
],

},
'license': 'AGPL-3'
"license": "AGPL-3",
}
1 change: 0 additions & 1 deletion awesome_dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-

from . import controllers
37 changes: 16 additions & 21 deletions awesome_dashboard/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
# -*- 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/**/*",
],
},
'license': 'AGPL-3'
"license": "AGPL-3",
}
3 changes: 1 addition & 2 deletions awesome_dashboard/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-

from . import controllers
from . import controllers
24 changes: 11 additions & 13 deletions awesome_dashboard/controllers/controllers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-

import logging
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 +21,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),
}

49 changes: 49 additions & 0 deletions awesome_dashboard/static/src/components/dashboard/chart/chart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, useRef, onWillStart, onMounted, onWillUnmount } from "@odoo/owl";
import { loadJS } from "@web/core/assets";

export class DashboardChart extends Component {
static template = "awesome_dashboard.DashboardChart";
static props = {
label: { type: String, optional: true, default: "Dataset" },
data: { type: Object, required: true },
};
setup() {
this.chartRef = useRef("DashboardChart");
onWillStart(async () => {
// Ensure Chart.js library is loaded before we try to use it
await loadJS("/web/static/lib/Chart/Chart.js");
});

onMounted(() => {
// The canvas now exists in the DOM
this.renderChart();
});
}

renderChart() {
if (this.chart) {
this.chart.destroy();
}
const labels = Object.keys(this.props.data);
const data = Object.values(this.props.data);
this.chart = new Chart(this.chartRef.el, {
type: "pie",
data: {
labels: labels,
datasets: [
{
label: this.props.label,
data: data,
},],
},
options: {
responsive: true,
maintainAspectRatio: false, // <--- Add this line
}
});
console.log("Chart rendered with data:", this.chartRef.el);
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_dashboard.DashboardChart">
<div style="height: 400px; width: 100%; position: relative;">
<canvas t-ref="DashboardChart"/>
</div>
</t>
</templates>
100 changes: 100 additions & 0 deletions awesome_dashboard/static/src/components/dashboard/dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 { DashboardItem } from "./dashboardItem";
import { DashboardChart } from "./chart/chart";
import { LazyComponent } from "@web/core/assets";
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, DashboardChart, LazyComponent };

setup() {
let savedDisabledItems = [];
try {
const rawValue = browser.localStorage.getItem("disabledDashboardItems");
// Only parse if the value exists
savedDisabledItems = rawValue ? JSON.parse(rawValue) : [];
} catch (e) {
console.error("Failed to parse local storage, resetting to empty array");
savedDisabledItems = [];
}

this.state = useState({
controlPanel: {},
disabledItems: savedDisabledItems,
});

this.items = registry.category("awesome_dashboard").getAll();
this.action = useService("action");
this.statisticsService = useState(useService("awesome_dashboard.statistics"));
console.log("Statistics:", this.statisticsService);
this.state.result = this.statisticsService;
this.dialog = useService("dialog");

}

openCustomers() {
this.action.doAction("base.action_partner_form");
}
openLeads() {
this.action.doAction({
type: "ir.actions.act_window",
res_model: "crm.lead",
views : [
[false, "list"],
[false, "form"]
],});
}
openConfiguration() {
this.dialog.add(ConfigurationDialog, {
items: this.items,
disabledItems: this.state.disabledItems,
onUpdateConfiguration: this.updateConfiguration.bind(this),
});
}
updateConfiguration(newdisabledItems) {
this.state.disabledItems = newdisabledItems;
}
}

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);

// FIX: Wrap the array in JSON.stringify
browser.localStorage.setItem(
"disabledDashboardItems",
JSON.stringify(newdisabledItems)
);

this.props.onUpdateConfiguration(newdisabledItems);
}
}
registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.o_dashboard {
background-color: grey;
display: flex;
}
42 changes: 42 additions & 0 deletions awesome_dashboard/static/src/components/dashboard/dashboard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_dashboard.AwesomeDashboard">

<button class="btn btn-primary m-3" type="button" t-on-click="openCustomers">
Customers
</button>
<button class="btn btn-primary m-3" type="button" t-on-click="openLeads">
Leads
</button>
<button t-on-click="openConfiguration" class="btn p-0 ms-1 border-0">
<i class="fa fa-cog"></i>
</button>
<t t-name="awesome_dashboard.Layout">
<Layout className="'o_dashboard h-100'" display="state.controlPanel">
<t t-foreach="items" t-as="item" t-key="item.id">
<DashboardItem t-if="!state.disabledItems.includes(item.id)" size="item.size || 10">
<t t-set="itemProp" t-value="item.props? item.props(state.result): {'data':state.result}"/>
<t t-component="item.component" t-props="itemProp"/>
</DashboardItem>
</t>
</Layout>
</t>
</t>
<t t-name="awesome_dashboard.ConfigurationDialog">
<Dialog title="'Dashboard items configuration'">
Which cards do you wish 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>
16 changes: 16 additions & 0 deletions awesome_dashboard/static/src/components/dashboard/dashboardItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component } from "@odoo/owl";


export class DashboardItem extends Component {
static template = "awesome_dashboard.DashboardItem";
static props = {
size: { type: Number, default: 1, optional: true },
content: { type: String, optional: true },
slots: { type: Object, optional: true },
};

setup() {
this.state = { size: this.props.size, width: 18*this.props.size , content: this.props.content };
}

}
Loading