From 51a3f8830a0a5e03de20bc4591944a0bdd3d17de Mon Sep 17 00:00:00 2001 From: ngtpn Date: Tue, 18 Nov 2025 10:14:17 +0100 Subject: [PATCH 01/39] [ADD] estate: Add module --- estate/__init__.py | 0 estate/__manifest__.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..19d1621f0ba --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': "Real Estate", + 'version': '1.0', + 'depends': ['base'], + 'author': "ngtpn", + 'category': 'Category', + 'description': """ + Real Estate Advertisement module + """, +} From a184c4a5aaa2f45d85aadb0609dbdb812a235828 Mon Sep 17 00:00:00 2001 From: ngtpn Date: Tue, 18 Nov 2025 15:01:31 +0100 Subject: [PATCH 02/39] [IMP] estate: Add name / expected price to property model --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..8ff5319e297 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,28 @@ +from odoo import fields, models + + +class Property(models.Model): + _name = "estate.property" + _description = "All properties" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + string='Garden orientation', + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ], + ) From 6500c2ab2b841609f41fd1573de3328cf3f9fd52 Mon Sep 17 00:00:00 2001 From: ngtpn Date: Tue, 18 Nov 2025 15:24:48 +0100 Subject: [PATCH 03/39] [IMP] estate: Add access right --- estate/__manifest__.py | 3 +++ estate/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 19d1621f0ba..653d794bd13 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,4 +7,7 @@ 'description': """ Real Estate Advertisement module """, + 'data': [ + 'security/ir.model.access.csv', + ], } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..32389642d4f --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 From 812c7c19a999c8e14b358e168977735ae7b67d3c Mon Sep 17 00:00:00 2001 From: ngtpn Date: Tue, 18 Nov 2025 15:26:52 +0100 Subject: [PATCH 04/39] [IMP] estate: Add UIs / Update fields --- estate/__manifest__.py | 3 +++ estate/models/estate_property.py | 23 ++++++++++++++++++++--- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 7 +++++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 653d794bd13..3b7b1a22b84 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,6 @@ { 'name': "Real Estate", + 'license': 'LGPL-3', 'version': '1.0', 'depends': ['base'], 'author': "ngtpn", @@ -9,5 +10,7 @@ """, 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8ff5319e297..86828384650 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import fields, models +from dateutil.relativedelta import relativedelta class Property(models.Model): @@ -8,10 +9,13 @@ class Property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date( + copy=False, + default=fields.Datetime.today() + relativedelta(months=+3), + ) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -26,3 +30,16 @@ class Property(models.Model): ('west', 'West'), ], ) + active = fields.Boolean(default=True) + state = fields.Selection( + string='State', + required=True, + default='new', + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..346daf55f7b --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..57ed16dac1f --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate.property + list,form + + From 99bba1fa3beadab2fdd6485022f244307b47a60c Mon Sep 17 00:00:00 2001 From: ngtpn Date: Wed, 19 Nov 2025 10:01:33 +0100 Subject: [PATCH 05/39] [IMP] estate: Fix xml indentation --- estate/views/estate_menus.xml | 10 +++++----- estate/views/estate_property_views.xml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 346daf55f7b..fc9d132180a 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 57ed16dac1f..4d544c18597 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,7 +1,7 @@ - - Properties - estate.property - list,form - + + Properties + estate.property + list,form + From f5deed012b666e951fa1d0f40ec3e6382cdcd7ed Mon Sep 17 00:00:00 2001 From: ngtpn Date: Wed, 19 Nov 2025 10:02:11 +0100 Subject: [PATCH 06/39] [IMP] estate: Use date helper / Rename model --- estate/__manifest__.py | 3 ++- estate/models/estate_property.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3b7b1a22b84..2a807519a79 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,11 +3,12 @@ 'license': 'LGPL-3', 'version': '1.0', 'depends': ['base'], - 'author': "ngtpn", + 'author': "Odoo S.A.", 'category': 'Category', 'description': """ Real Estate Advertisement module """, + 'application': True, 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 86828384650..67da8661949 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,8 +1,7 @@ from odoo import fields, models -from dateutil.relativedelta import relativedelta -class Property(models.Model): +class EstateProperty(models.Model): _name = "estate.property" _description = "All properties" @@ -11,7 +10,7 @@ class Property(models.Model): postcode = fields.Char() date_availability = fields.Date( copy=False, - default=fields.Datetime.today() + relativedelta(months=+3), + default=fields.Date.add(fields.Date.today(), months=3), ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) From 2ca404233368afe056c346687cda06ce95ff1a47 Mon Sep 17 00:00:00 2001 From: ngtpn Date: Wed, 19 Nov 2025 10:30:51 +0100 Subject: [PATCH 07/39] [IMP] estate: Add list / form / search UI --- estate/__manifest__.py | 7 ++- estate/views/estate_menus.xml | 2 +- estate/views/estate_property_form_views.xml | 44 +++++++++++++++++++ estate/views/estate_property_list_views.xml | 17 +++++++ estate/views/estate_property_search_views.xml | 22 ++++++++++ 5 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 estate/views/estate_property_form_views.xml create mode 100644 estate/views/estate_property_list_views.xml create mode 100644 estate/views/estate_property_search_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 2a807519a79..4d403e49f93 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,9 +1,9 @@ { - 'name': "Real Estate", + 'name': 'Real Estate', 'license': 'LGPL-3', 'version': '1.0', 'depends': ['base'], - 'author': "Odoo S.A.", + 'author': 'Odoo S.A.', 'category': 'Category', 'description': """ Real Estate Advertisement module @@ -13,5 +13,8 @@ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menus.xml', + 'views/estate_property_list_views.xml', + 'views/estate_property_form_views.xml', + 'views/estate_property_search_views.xml', ], } diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index fc9d132180a..9d4fd8d1c7a 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,6 +1,6 @@ - + diff --git a/estate/views/estate_property_form_views.xml b/estate/views/estate_property_form_views.xml new file mode 100644 index 00000000000..22887798fb3 --- /dev/null +++ b/estate/views/estate_property_form_views.xml @@ -0,0 +1,44 @@ + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/estate/views/estate_property_list_views.xml b/estate/views/estate_property_list_views.xml new file mode 100644 index 00000000000..43fae5ae820 --- /dev/null +++ b/estate/views/estate_property_list_views.xml @@ -0,0 +1,17 @@ + + + estate.property.list + estate.property + + + + + + + + + + + + + diff --git a/estate/views/estate_property_search_views.xml b/estate/views/estate_property_search_views.xml new file mode 100644 index 00000000000..c18a402c02c --- /dev/null +++ b/estate/views/estate_property_search_views.xml @@ -0,0 +1,22 @@ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + From 719f23ac72ebd3582dd4176bb6bcd663750ce0e2 Mon Sep 17 00:00:00 2001 From: ngtpn Date: Wed, 19 Nov 2025 11:06:34 +0100 Subject: [PATCH 08/39] [IMP] estate: Add perperty type / tag / offer --- estate/__manifest__.py | 4 +++ estate/models/__init__.py | 3 +++ estate/models/estate_property.py | 16 ++++++++++-- estate/models/estate_property_offer.py | 17 ++++++++++++ estate/models/estate_property_tag.py | 8 ++++++ estate/models/estate_property_type.py | 8 ++++++ estate/security/ir.model.access.csv | 3 +++ estate/views/estate_menus.xml | 4 +++ estate/views/estate_property_form_views.xml | 13 +++++++++- estate/views/estate_property_list_views.xml | 2 ++ .../estate_property_offer_form_views.xml | 26 +++++++++++++++++++ .../estate_property_offer_list_views.xml | 14 ++++++++++ estate/views/estate_property_search_views.xml | 1 + estate/views/estate_property_tag_views.xml | 7 +++++ estate/views/estate_property_type_views.xml | 7 +++++ 15 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_form_views.xml create mode 100644 estate/views/estate_property_offer_list_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4d403e49f93..7128f366854 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,9 +12,13 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', 'views/estate_menus.xml', 'views/estate_property_list_views.xml', 'views/estate_property_form_views.xml', 'views/estate_property_search_views.xml', + 'views/estate_property_offer_list_views.xml', + 'views/estate_property_offer_form_views.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 67da8661949..cc2df50111c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,8 +2,8 @@ class EstateProperty(models.Model): - _name = "estate.property" - _description = "All properties" + _name = 'estate.property' + _description = 'All properties' name = fields.Char(required=True) description = fields.Text() @@ -42,3 +42,15 @@ class EstateProperty(models.Model): ('cancelled', 'Cancelled'), ], ) + property_type_id = fields.Many2one( + 'estate.property.type', string='Property Type' + ) + buyer_id = fields.Many2one( + 'res.partner', string='Buyer', copy=False, + ) + salesperson_id = fields.Many2one( + 'res.users', string='Salesperson', + default=lambda self: self.env.user, + ) + tag_ids = fields.Many2many('estate.property.tag', string='Tags') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..e293504e6a7 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'All offers' + + price = fields.Float(required=True) + status = fields.Selection( + string='Status', + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + ) + partner_id = fields.Many2one('res.partner', string='Partner') + property_id = fields.Many2one('estate.property', string='Property') diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..23a0b4b5180 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'All property tags' + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..06a5e441fdf --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'All property types' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 32389642d4f..89f97c50842 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 9d4fd8d1c7a..9be236faae9 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -3,5 +3,9 @@
diff --git a/estate/views/estate_property_form_views.xml b/estate/views/estate_property_form_views.xml index 22887798fb3..ac7698e34e5 100644 --- a/estate/views/estate_property_form_views.xml +++ b/estate/views/estate_property_form_views.xml @@ -9,6 +9,12 @@ + + + + + + @@ -33,7 +39,12 @@ - + + + + + + diff --git a/estate/views/estate_property_list_views.xml b/estate/views/estate_property_list_views.xml index 43fae5ae820..bf378601786 100644 --- a/estate/views/estate_property_list_views.xml +++ b/estate/views/estate_property_list_views.xml @@ -11,6 +11,8 @@ + + diff --git a/estate/views/estate_property_offer_form_views.xml b/estate/views/estate_property_offer_form_views.xml new file mode 100644 index 00000000000..2e8417d37b2 --- /dev/null +++ b/estate/views/estate_property_offer_form_views.xml @@ -0,0 +1,26 @@ + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + + + + + + + + + + +
+
+
+
diff --git a/estate/views/estate_property_offer_list_views.xml b/estate/views/estate_property_offer_list_views.xml new file mode 100644 index 00000000000..4d8261cd6f8 --- /dev/null +++ b/estate/views/estate_property_offer_list_views.xml @@ -0,0 +1,14 @@ + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + diff --git a/estate/views/estate_property_search_views.xml b/estate/views/estate_property_search_views.xml index c18a402c02c..1590db1878c 100644 --- a/estate/views/estate_property_search_views.xml +++ b/estate/views/estate_property_search_views.xml @@ -10,6 +10,7 @@ + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..bbb5013739c --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,7 @@ + + + Property Tags + estate.property.tag + list,form + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..382925bd424 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,7 @@ + + + Property Types + estate.property.type + list,form + + From 5051e18be46d38c1c459ae5e3b4d08519f791c02 Mon Sep 17 00:00:00 2001 From: ngtpn Date: Wed, 19 Nov 2025 13:51:07 +0100 Subject: [PATCH 09/39] [IMP] estate: Compute / Inverse field value --- estate/models/estate_property.py | 31 ++++++++++++++++--- estate/models/estate_property_offer.py | 17 +++++++++- estate/views/estate_property_form_views.xml | 13 ++++++-- .../estate_property_offer_form_views.xml | 6 ++++ .../estate_property_offer_list_views.xml | 4 ++- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index cc2df50111c..0628caeceb6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -14,12 +14,14 @@ class EstateProperty(models.Model): ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) + best_price = fields.Float(compute='_compute_best_price') bedrooms = fields.Integer(default=2) living_area = fields.Integer() + garden_area = fields.Integer() + total_area = fields.Integer(compute='_compute_total_area') facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() garden_orientation = fields.Selection( string='Garden orientation', selection=[ @@ -41,9 +43,9 @@ class EstateProperty(models.Model): ('sold', 'Sold'), ('cancelled', 'Cancelled'), ], - ) + ) property_type_id = fields.Many2one( - 'estate.property.type', string='Property Type' + 'estate.property.type', string='Property Type', ) buyer_id = fields.Many2one( 'res.partner', string='Buyer', copy=False, @@ -54,3 +56,24 @@ class EstateProperty(models.Model): ) tag_ids = fields.Many2many('estate.property.tag', string='Tags') offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for record in self: + living_area = record.living_area or 0 + garden_area = record.garden_area or 0 + record.total_area = living_area + garden_area + + @api.depends('offer_ids') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price')) if self.offer_ids else 0 + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = None + self.garden_orientation = None diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e293504e6a7..3dddfd66a42 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -15,3 +15,18 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one('res.partner', string='Partner') property_id = fields.Many2one('estate.property', string='Property') + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute='_compute_deadline', inverse="_inverse_deadline") + + @api.depends('create_date', 'validity') + def _compute_deadline(self): + for record in self: + created_date = record.create_date or fields.Date.today() + record.date_deadline = fields.Date.add( + created_date, days=record.validity, + ) + + def _inverse_deadline(self): + for record in self: + created_date = record.create_date.date() or fields.Date.today() + record.validity = (record.date_deadline - created_date).days diff --git a/estate/views/estate_property_form_views.xml b/estate/views/estate_property_form_views.xml index ac7698e34e5..c3eac4e9d8f 100644 --- a/estate/views/estate_property_form_views.xml +++ b/estate/views/estate_property_form_views.xml @@ -27,18 +27,27 @@ + + + - + - + + + + + + + diff --git a/estate/views/estate_property_offer_form_views.xml b/estate/views/estate_property_offer_form_views.xml index 2e8417d37b2..4dfcfa7d40c 100644 --- a/estate/views/estate_property_offer_form_views.xml +++ b/estate/views/estate_property_offer_form_views.xml @@ -18,6 +18,12 @@ + + + + + + diff --git a/estate/views/estate_property_offer_list_views.xml b/estate/views/estate_property_offer_list_views.xml index 4d8261cd6f8..16dd7f33869 100644 --- a/estate/views/estate_property_offer_list_views.xml +++ b/estate/views/estate_property_offer_list_views.xml @@ -5,8 +5,10 @@ - + + + From 9deb1644e2bd9046f4ba81389f7642c4fe5b99df Mon Sep 17 00:00:00 2001 From: ngtpn Date: Wed, 19 Nov 2025 15:01:33 +0100 Subject: [PATCH 10/39] [IMP] estate: Add actions --- estate/models/estate_property.py | 15 +++++++++++++ estate/models/estate_property_offer.py | 22 +++++++++++++++++++ estate/views/estate_property_form_views.xml | 18 +++++++++++++++ .../estate_property_offer_list_views.xml | 3 ++- 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0628caeceb6..f97850ec9ca 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -77,3 +78,17 @@ def _onchange_garden(self): else: self.garden_area = None self.garden_orientation = None + + def action_mark_as_sold(self): + for record in self: + if record.state == 'cancelled': + raise UserError('Property is already cancelled') + + record.state = 'sold' + + def action_mark_as_cancelled(self): + for record in self: + if record.state == 'sold': + raise UserError('Property is already sold') + + record.state = 'cancelled' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 3dddfd66a42..d7a1f232c24 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ from odoo import api, fields, models +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -30,3 +31,24 @@ def _inverse_deadline(self): for record in self: created_date = record.create_date.date() or fields.Date.today() record.validity = (record.date_deadline - created_date).days + + def action_mark_as_accepted(self): + for record in self: + if ( + record.property_id.state != 'new' + and record.property_id.state != 'offer_received' + ): + raise UserError('Cannot accept offer in this state') + + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + record.property_id.state = 'offer_accepted' + + def action_mark_as_refused(self): + for record in self: + if record.status == 'accepted': + record.property_id.state = 'offer_received' + record.property_id.selling_price = None + record.property_id.buyer_id = None + record.status = 'refused' diff --git a/estate/views/estate_property_form_views.xml b/estate/views/estate_property_form_views.xml index c3eac4e9d8f..3363cf4a399 100644 --- a/estate/views/estate_property_form_views.xml +++ b/estate/views/estate_property_form_views.xml @@ -4,6 +4,21 @@ estate.property
+
+

@@ -12,6 +27,9 @@ + + + diff --git a/estate/views/estate_property_offer_list_views.xml b/estate/views/estate_property_offer_list_views.xml index 16dd7f33869..668096060a2 100644 --- a/estate/views/estate_property_offer_list_views.xml +++ b/estate/views/estate_property_offer_list_views.xml @@ -8,8 +8,9 @@ + + + + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..eb45bc1e6e0 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,19 @@ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import Counter from "./counter/counter"; +import Card from "./card/card"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card }; + + setup() { + this.state = useState({ sum: 0 }); + } + + incrementSum() { + this.state.sum++; + } + + card_content = "
content
" + card2_content = markup("
content
") } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..be2568852b2 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,6 +4,19 @@
hello world +
+ + +

The sum is:

+
+ +
+ + +
From 6e6513626840fd68d5d6cc4f207ab3ee38b01f33 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Mon, 24 Nov 2025 10:57:43 +0100 Subject: [PATCH 23/39] [IMP] awesome_owl: Add todo list --- awesome_owl/static/src/playground.js | 3 ++- awesome_owl/static/src/playground.xml | 4 ++++ awesome_owl/static/src/todo_list/todo_item.js | 8 ++++++++ awesome_owl/static/src/todo_list/todo_item.xml | 11 +++++++++++ awesome_owl/static/src/todo_list/todo_list.js | 18 ++++++++++++++++++ awesome_owl/static/src/todo_list/todo_list.xml | 13 +++++++++++++ 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 awesome_owl/static/src/todo_list/todo_item.js create mode 100644 awesome_owl/static/src/todo_list/todo_item.xml create mode 100644 awesome_owl/static/src/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/todo_list/todo_list.xml diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index eb45bc1e6e0..7c1473e6e6f 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,10 +1,11 @@ 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 }; + static components = { Counter, Card, TodoList }; setup() { this.state = useState({ sum: 0 }); diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index be2568852b2..5e91fe30456 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -17,6 +17,10 @@ /> + +
> + +
diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js new file mode 100644 index 00000000000..6f52255397d --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,8 @@ +import { Component } from "@odoo/owl"; + +class TodoItem extends Component { + static template = "awesome_owl.todo_item" + static props = { item: { optional: true } } +} + +export default TodoItem; diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml new file mode 100644 index 00000000000..2fa23dd800d --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,11 @@ + + + + +
+ . + +
+
+ +
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..39fc43a0ecb --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,18 @@ +import { Component, useState } from "@odoo/owl"; +import TodoItem from "./todo_item"; + +class TodoList extends Component { + static template = "awesome_owl.todo_list" + static components = { TodoItem }; + static props = {} + + setup() { + this.todos = useState([ + { id: 1, description: "Todo 1", isCompleted: false }, + { id: 2, description: "Todo 2", isCompleted: false }, + { id: 3, description: "Todo 3", isCompleted: true }, + ]); + } +} + +export default TodoList; diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..221401365bd --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,13 @@ + + + + +
+

ToDo List

+ + + +
+
+ +
From 10bb2390b6cd6d5b01c404a70771a5a2065784bf Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Mon, 24 Nov 2025 11:56:57 +0100 Subject: [PATCH 24/39] [IMP] awesome_owl: Add todo item --- .../static/src/todo_list/todo_item.xml | 6 ++++- awesome_owl/static/src/todo_list/todo_list.js | 24 +++++++++++++++---- .../static/src/todo_list/todo_list.xml | 5 ++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml index 2fa23dd800d..6f60c5fbf79 100644 --- a/awesome_owl/static/src/todo_list/todo_item.xml +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -2,7 +2,11 @@ -
+
.
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js index 39fc43a0ecb..b458fa29599 100644 --- a/awesome_owl/static/src/todo_list/todo_list.js +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -7,11 +7,25 @@ class TodoList extends Component { static props = {} setup() { - this.todos = useState([ - { id: 1, description: "Todo 1", isCompleted: false }, - { id: 2, description: "Todo 2", isCompleted: false }, - { id: 3, description: "Todo 3", isCompleted: true }, - ]); + this.state = useState({ todos: [], counter: 0 }); + } + + addTodo(event) { + if (event.keyCode === 13) { + const value = event.target.value; + if (value.length) { + this.state.counter++; + this.state.todos = [ + ...this.state.todos, + { + id: this.state.counter, + description: event.target.value, + isCompleted: false, + }, + ]; + event.target.value = ""; + } + } } } diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml index 221401365bd..a8cc9ddbd82 100644 --- a/awesome_owl/static/src/todo_list/todo_list.xml +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -3,8 +3,9 @@
-

ToDo List

- +

ToDo List ()

+ +
From 4b60b5cb75ff84e4d2a5d6c6d8a217af11b89a41 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Mon, 24 Nov 2025 14:08:16 +0100 Subject: [PATCH 25/39] [IMP] awesome_owl: Auto focus --- awesome_owl/static/src/todo_list/todo_list.js | 32 ++++++++++--------- .../static/src/todo_list/todo_list.xml | 6 +++- awesome_owl/static/src/utils.js | 11 +++++++ 3 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js index b458fa29599..2bc8ade7a09 100644 --- a/awesome_owl/static/src/todo_list/todo_list.js +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -1,5 +1,6 @@ import { Component, useState } from "@odoo/owl"; import TodoItem from "./todo_item"; +import { useAutofocus } from "../utils"; class TodoList extends Component { static template = "awesome_owl.todo_list" @@ -8,24 +9,25 @@ class TodoList extends Component { setup() { this.state = useState({ todos: [], counter: 0 }); + this.inputRef = useAutofocus({ refName: "input" }); } addTodo(event) { - if (event.keyCode === 13) { - const value = event.target.value; - if (value.length) { - this.state.counter++; - this.state.todos = [ - ...this.state.todos, - { - id: this.state.counter, - description: event.target.value, - isCompleted: false, - }, - ]; - event.target.value = ""; - } - } + if (event.keyCode === 13) { + const value = event.target.value; + if (value.length) { + this.state.counter++; + this.state.todos = [ + ...this.state.todos, + { + id: this.state.counter, + description: event.target.value, + isCompleted: false, + }, + ]; + event.target.value = ""; + } + } } } diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml index a8cc9ddbd82..3c28faef847 100644 --- a/awesome_owl/static/src/todo_list/todo_list.xml +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -4,7 +4,11 @@

ToDo List ()

- + diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..d1c886c763d --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,11 @@ +import { useEffect, useRef } from "@odoo/owl"; + +export function useAutofocus({ refName } = {}) { + const ref = useRef(refName || "autofocus"); + + useEffect((el) => { + el?.focus(); + }, () => [ref.el]); + + return ref; +} From 68638997857c1189369248338aab0176482c831c Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Mon, 24 Nov 2025 15:04:00 +0100 Subject: [PATCH 26/39] [IMP] awesome_owl: Toggle / Delete items --- awesome_owl/static/src/todo_list/todo_item.js | 6 +++++- awesome_owl/static/src/todo_list/todo_item.xml | 9 +++++++++ awesome_owl/static/src/todo_list/todo_list.js | 14 ++++++++++++++ awesome_owl/static/src/todo_list/todo_list.xml | 6 +++++- 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js index 6f52255397d..e7119aa36c6 100644 --- a/awesome_owl/static/src/todo_list/todo_item.js +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -2,7 +2,11 @@ import { Component } from "@odoo/owl"; class TodoItem extends Component { static template = "awesome_owl.todo_item" - static props = { item: { optional: true } } + static props = { + item: { optional: true }, + toggleState: { type: Function }, + removeTodo: { type: Function }, + } } export default TodoItem; diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml index 6f60c5fbf79..97bf2b735c6 100644 --- a/awesome_owl/static/src/todo_list/todo_item.xml +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -7,8 +7,17 @@ t-att-class="{'text-muted': props.item.isCompleted, 'text-decoration-line-through': props.item.isCompleted }" style="width: 18rem;" > + . +
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js index 2bc8ade7a09..cc62a703c36 100644 --- a/awesome_owl/static/src/todo_list/todo_list.js +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -10,6 +10,8 @@ class TodoList extends Component { setup() { this.state = useState({ todos: [], counter: 0 }); this.inputRef = useAutofocus({ refName: "input" }); + this.removeTodo = this.removeTodo.bind(this); + this.toggleState = this.toggleState.bind(this); } addTodo(event) { @@ -29,6 +31,18 @@ class TodoList extends Component { } } } + + toggleState(targetId) { + this.state.todos = this.state.todos.map(todo => ( + todo.id === targetId ? { ...todo, isCompleted: !todo.isCompleted } : todo + )); + } + + removeTodo(targetId) { + this.state.todos = this.state.todos.filter( + ({ id }) => id !== targetId + ); + } } export default TodoList; diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml index 3c28faef847..2e1cfa48e56 100644 --- a/awesome_owl/static/src/todo_list/todo_list.xml +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -10,7 +10,11 @@ t-on-keyup="event => this.addTodo(event)" /> - +
From 6c47a4471a5854812e718303baee6067b0e6d9f6 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Mon, 24 Nov 2025 15:21:23 +0100 Subject: [PATCH 27/39] [IMP] awesome_owl: Update styles --- awesome_owl/static/src/counter/counter.xml | 11 +++++++++-- awesome_owl/static/src/playground.xml | 3 ++- awesome_owl/static/src/todo_list/todo_item.xml | 7 +++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml index 5e0ec2135c0..73adf71f83a 100644 --- a/awesome_owl/static/src/counter/counter.xml +++ b/awesome_owl/static/src/counter/counter.xml @@ -2,8 +2,15 @@ -

Counter:

- +
+
Counter:
+ +
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 5e91fe30456..5ac01687fa2 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -3,7 +3,8 @@
- hello world + Hello World +
diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml index 97bf2b735c6..034bc9025e8 100644 --- a/awesome_owl/static/src/todo_list/todo_item.xml +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -10,12 +10,15 @@ . -
From 32d13f95f99afda6ec7c4dcfa754e8a5e8b0bf06 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Mon, 24 Nov 2025 16:34:34 +0100 Subject: [PATCH 28/39] [IMP] awesome_owl: Card slot / toggle --- awesome_owl/static/src/card/card.js | 11 ++++++++++- awesome_owl/static/src/card/card.xml | 12 ++++++++++-- awesome_owl/static/src/playground.xml | 7 ++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 5315d8be85a..e3ae539f3fd 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -1,10 +1,19 @@ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; class Card extends Component { static template = "awesome_owl.card" static props = { title: {type: String}, content: {type: String}, + slots: {type: Object, optional: true} + } + + setup() { + this.state = useState({ open: true }); + } + + toggleOpen() { + this.state.open = !this.state.open; } } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index 50e8811e860..3a2c66b06e2 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -4,8 +4,16 @@
-
-

+

+
+ +
+
+

+ Default content +

diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 5ac01687fa2..1432b34aa30 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -16,7 +16,12 @@ title="'Card 1'" content="card_content" /> - + + Hello + + + +
> From cac97e2abd0a97937f9b1c33d88808a92d91373c Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Tue, 25 Nov 2025 10:50:21 +0100 Subject: [PATCH 29/39] [IMP] awesome_dashboard: Add Layout / navigation buttons --- awesome_dashboard/static/src/dashboard.js | 23 +++++++++++++++++++++ awesome_dashboard/static/src/dashboard.scss | 3 +++ awesome_dashboard/static/src/dashboard.xml | 15 +++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 awesome_dashboard/static/src/dashboard.scss diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index c4fb245621b..e9d2af2ce02 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,8 +1,31 @@ import { Component } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; import { registry } from "@web/core/registry"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout }; + + setup() { + this.action = useService("action"); + } + + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: "Leads", + res_model: "crm.lead", + views: [ + [false, 'list'], + [false, 'form'], + ], + }); + } + + openCustomers() { + this.action.doAction("base.action_partner_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..db7d4c2b31e 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -2,7 +2,20 @@ - hello dashboard + + + + + + Hello world + From 9db9c2715ebb94ed834b7b0c500e5fe881c405a9 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Tue, 25 Nov 2025 11:19:48 +0100 Subject: [PATCH 30/39] [IMP] awesome_dashboard: Dashboard item --- awesome_dashboard/static/src/dashboard.js | 5 +++-- awesome_dashboard/static/src/dashboard.xml | 5 ++++- .../src/dashboard_item/dashboard_item.js | 8 ++++++++ .../src/dashboard_item/dashboard_item.xml | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.xml diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index e9d2af2ce02..925ac153302 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,11 +1,12 @@ import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; -import { registry } from "@web/core/registry"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout }; + static components = { Layout, DashboardItem }; setup() { this.action = useService("action"); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index db7d4c2b31e..9e21565e776 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -14,7 +14,10 @@ Leads - Hello world + + + Longer one + diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..68be9b5d6f3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js @@ -0,0 +1,8 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { type: Number, default: 1 }, + } +} diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..fa911d245ce --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -0,0 +1,19 @@ + + + + +
+
+
+
+ + Some content + +
+
+
+
+
+ +
+ From f757c6de852d8e7fd16975e6ac73b20bf86169cd Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Tue, 25 Nov 2025 11:34:41 +0100 Subject: [PATCH 31/39] [IMP] awesome_dashboard: Show statistics --- awesome_dashboard/static/src/dashboard.js | 7 ++++- awesome_dashboard/static/src/dashboard.xml | 28 ++++++++++++++++--- .../src/dashboard_item/dashboard_item.xml | 5 +++- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 925ac153302..7e5c3a2e616 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,4 +1,5 @@ -import { Component } from "@odoo/owl"; +import { Component, onWillStart } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; @@ -10,6 +11,10 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); + onWillStart(async () => { + const result = await rpc("/awesome_dashboard/statistics"); + this.statistics = result; + }); } openLeads() { diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 9e21565e776..2e80870dc84 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -14,10 +14,30 @@ Leads - - - Longer one - +
+ + Average amount of t-shirt by order this month + + + + Average time for an order to go from 'new' to 'sent' or 'cancelled' + + + + Number of new orders this month + + +
+
+ + Number of cancelled orders this month + + + + Total amount of new orders this month + + +
diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml index fa911d245ce..52cef61b51b 100644 --- a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -3,13 +3,16 @@
-
+
Some content
+

+ +

From 37acf5f7b010a551f3610661e627709b59f8b8e0 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Tue, 25 Nov 2025 12:01:18 +0100 Subject: [PATCH 32/39] [IMP] awesome_dashboard: statistics service --- awesome_dashboard/static/src/dashboard.js | 6 +++--- .../static/src/statistics_service.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 awesome_dashboard/static/src/statistics_service.js diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 7e5c3a2e616..88b7e31af7a 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,5 +1,4 @@ import { Component, onWillStart } from "@odoo/owl"; -import { rpc } from "@web/core/network/rpc"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; @@ -11,9 +10,10 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); + this.statisticService = useService("statistics"); + onWillStart(async () => { - const result = await rpc("/awesome_dashboard/statistics"); - this.statistics = result; + this.statistics = await this.statisticService.loadStatistic(); }); } diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js new file mode 100644 index 00000000000..9248df2e22b --- /dev/null +++ b/awesome_dashboard/static/src/statistics_service.js @@ -0,0 +1,15 @@ +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; +import { memoize } from "@web/core/utils/functions"; + +export async function loadStatistic() { + return await rpc("/awesome_dashboard/statistics"); +} + +export const statisticsService = { + start() { + return { loadStatistic: memoize(loadStatistic) }; + } +} + +registry.category("services").add("statistics", statisticsService); From 8750017c705db62be91ace20dac2ccb89e10d684 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Tue, 25 Nov 2025 15:22:01 +0100 Subject: [PATCH 33/39] [IMP] awesome_dashboard: Add pie chart --- awesome_dashboard/static/src/dashboard.js | 3 +- awesome_dashboard/static/src/dashboard.xml | 10 ++- .../src/dashboard_item/dashboard_item.xml | 2 +- .../static/src/pie_chart/pie_chart.js | 61 +++++++++++++++++++ .../static/src/pie_chart/pie_chart.xml | 11 ++++ 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.js create mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.xml diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 88b7e31af7a..873a7835c41 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -3,10 +3,11 @@ import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { PieChart } from './pie_chart/pie_chart'; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout, DashboardItem }; + static components = { Layout, DashboardItem, PieChart }; setup() { this.action = useService("action"); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 2e80870dc84..9586e0afb2c 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -14,7 +14,7 @@ Leads -
+
Average amount of t-shirt by order this month @@ -28,7 +28,7 @@
-
+
Number of cancelled orders this month @@ -37,6 +37,12 @@ Total amount of new orders this month + + Shirt orders by size + + + +
diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml index 52cef61b51b..8ad6381c0b6 100644 --- a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -2,7 +2,7 @@ -
+
diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/pie_chart/pie_chart.js new file mode 100644 index 00000000000..ed511dfa142 --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.js @@ -0,0 +1,61 @@ +import { + Component, + onWillStart, + onWillUnmount, + useRef, + useEffect, +} from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { data: { optional: true } } + + setup() { + this.rootRef = useRef("root"); + this.canvasRef = useRef("canvas"); + this.containerRef = useRef("container"); + + this.chart = null; + this.tooltip = null; + this.legendTooltip = null; + + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js") + }); + + useEffect(() => { + this.renderChart(); + }); + + onWillUnmount(this.onWillUnmount); + } + + onWillUnmount() { + if (this.chart) { + this.chart.destroy(); + } + } + + renderChart() { + if (this.chart) { + this.chart.destroy(); + } + + const labels = Object.keys(this.props.data); + const values = Object.values(this.props.data); + + const config = { + type: 'pie', + data: { + labels: labels, + datasets: [{ + label: "Orders by size", + data: values, + }], + }, + } + + this.chart = new Chart(this.canvasRef.el, config); + } +} diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..c55ce92b62c --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.xml @@ -0,0 +1,11 @@ + + + + +
+ +
+
+ +
+ From e4bf1fb48996ac27a5b64bfda3fa80e3bbf9a754 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Tue, 25 Nov 2025 17:06:54 +0100 Subject: [PATCH 34/39] [IMP] awesome_dashboard: real life update --- awesome_dashboard/static/src/dashboard.js | 7 ++----- awesome_dashboard/static/src/pie_chart/pie_chart.js | 4 +++- awesome_dashboard/static/src/statistics_service.js | 11 +++++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 873a7835c41..9b6fc3ee055 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,4 +1,4 @@ -import { Component, onWillStart } from "@odoo/owl"; +import { Component, onWillStart, useState } from "@odoo/owl"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; @@ -12,10 +12,7 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); this.statisticService = useService("statistics"); - - onWillStart(async () => { - this.statistics = await this.statisticService.loadStatistic(); - }); + this.statistics = useState(this.statisticService); } openLeads() { diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/pie_chart/pie_chart.js index ed511dfa142..3fd8d3148f8 100644 --- a/awesome_dashboard/static/src/pie_chart/pie_chart.js +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.js @@ -9,7 +9,9 @@ import { loadJS } from "@web/core/assets"; export class PieChart extends Component { static template = "awesome_dashboard.PieChart"; - static props = { data: { optional: true } } + static props = { + data: { default: {}, type: Object }, + } setup() { this.rootRef = useRef("root"); diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js index 9248df2e22b..052d9753a63 100644 --- a/awesome_dashboard/static/src/statistics_service.js +++ b/awesome_dashboard/static/src/statistics_service.js @@ -1,3 +1,4 @@ +import { reactive } from "@odoo/owl"; import { rpc } from "@web/core/network/rpc"; import { registry } from "@web/core/registry"; import { memoize } from "@web/core/utils/functions"; @@ -7,8 +8,14 @@ export async function loadStatistic() { } export const statisticsService = { - start() { - return { loadStatistic: memoize(loadStatistic) }; + async start() { + const data = reactive(await loadStatistic()); + + setInterval(async () => { + data = await loadStatistic(); + }, 10 * 60 * 1000); + + return data; } } From 868a1fa00a701c40a5ecde7cdd2cbae0aae37a33 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Wed, 26 Nov 2025 10:34:54 +0100 Subject: [PATCH 35/39] [IMP] awesome_dashboard: Lazy load dashboard --- awesome_dashboard/__manifest__.py | 3 +++ .../static/src/{ => dashboard}/dashboard.js | 6 +++--- .../static/src/{ => dashboard}/dashboard.scss | 0 .../static/src/{ => dashboard}/dashboard.xml | 0 .../{ => dashboard}/dashboard_item/dashboard_item.js | 0 .../dashboard_item/dashboard_item.xml | 0 .../src/{ => dashboard}/pie_chart/pie_chart.js | 0 .../src/{ => dashboard}/pie_chart/pie_chart.xml | 0 .../static/src/{ => dashboard}/statistics_service.js | 0 awesome_dashboard/static/src/dashboard_action.js | 12 ++++++++++++ 10 files changed, 18 insertions(+), 3 deletions(-) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.js (82%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.scss (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.xml (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard_item/dashboard_item.js (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard_item/dashboard_item.xml (100%) rename awesome_dashboard/static/src/{ => dashboard}/pie_chart/pie_chart.js (100%) rename awesome_dashboard/static/src/{ => dashboard}/pie_chart/pie_chart.xml (100%) rename awesome_dashboard/static/src/{ => dashboard}/statistics_service.js (100%) create mode 100644 awesome_dashboard/static/src/dashboard_action.js diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..adb7542effb 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -22,6 +22,9 @@ 'views/views.xml', ], 'assets': { + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*', + ], 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', ], diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js similarity index 82% rename from awesome_dashboard/static/src/dashboard.js rename to awesome_dashboard/static/src/dashboard/dashboard.js index 9b6fc3ee055..1adcdbe1678 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -3,11 +3,11 @@ import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; import { DashboardItem } from "./dashboard_item/dashboard_item"; -import { PieChart } from './pie_chart/pie_chart'; +import { PieChart } from "./pie_chart/pie_chart"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout, DashboardItem, PieChart }; + static components = { DashboardItem, Layout, PieChart }; setup() { this.action = useService("action"); @@ -32,4 +32,4 @@ class AwesomeDashboard extends Component { } } -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss similarity index 100% rename from awesome_dashboard/static/src/dashboard.scss rename to awesome_dashboard/static/src/dashboard/dashboard.scss diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml similarity index 100% rename from awesome_dashboard/static/src/dashboard.xml rename to awesome_dashboard/static/src/dashboard/dashboard.xml diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js similarity index 100% rename from awesome_dashboard/static/src/dashboard_item/dashboard_item.js rename to awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml similarity index 100% rename from awesome_dashboard/static/src/dashboard_item/dashboard_item.xml rename to awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js similarity index 100% rename from awesome_dashboard/static/src/pie_chart/pie_chart.js rename to awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml similarity index 100% rename from awesome_dashboard/static/src/pie_chart/pie_chart.xml rename to awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js similarity index 100% rename from awesome_dashboard/static/src/statistics_service.js rename to awesome_dashboard/static/src/dashboard/statistics_service.js diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..27a8fa94cb2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,12 @@ +import { Component, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + +export class DashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashboardLoader); From ca9b0deb4e5f178ea1cb271181ae54ce6c5829c2 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Wed, 26 Nov 2025 13:50:49 +0100 Subject: [PATCH 36/39] [IMP] awesome_dashboard: Generic dashboard --- .../static/src/dashboard/dashboard.js | 2 + .../static/src/dashboard/dashboard.xml | 36 ++-------- .../dashboard_item/dashboard_item.xml | 2 +- .../static/src/dashboard/dashboard_items.js | 65 +++++++++++++++++++ .../src/dashboard/number_card/number_card.js | 9 +++ .../src/dashboard/number_card/number_card.xml | 16 +++++ .../pie_chart_card/pie_chart_card.js | 11 ++++ .../pie_chart_card/pie_chart_card.xml | 16 +++++ 8 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index 1adcdbe1678..69736c3e43b 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -4,6 +4,7 @@ import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; import { DashboardItem } from "./dashboard_item/dashboard_item"; import { PieChart } from "./pie_chart/pie_chart"; +import dashboard_items from "./dashboard_items"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; @@ -13,6 +14,7 @@ class AwesomeDashboard extends Component { this.action = useService("action"); this.statisticService = useService("statistics"); this.statistics = useState(this.statisticService); + this.items = dashboard_items; } openLeads() { diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index 9586e0afb2c..713c03dad56 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -14,35 +14,13 @@ Leads -
- - Average amount of t-shirt by order this month - - - - Average time for an order to go from 'new' to 'sent' or 'cancelled' - - - - Number of new orders this month - - -
-
- - Number of cancelled orders this month - - - - Total amount of new orders this month - - - - Shirt orders by size - - - - +
+ + + + + +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml index 8ad6381c0b6..51456739b59 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -11,7 +11,7 @@

- +

diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..59f53f20b39 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,65 @@ +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { NumberCard } from "./number_card/number_card"; + +export default [ + { + id: "average_quantity", + description: "Average amount of t-shirt", + size: 1, + Component: NumberCard, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity, + }), + }, + { + id: "average_time", + description: "Average order time", + size: 2, + Component: NumberCard, + props: (data) => ({ + title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + value: data.average_time, + }), + }, + { + id: "nb_new_orders", + description: "New orders", + size: 2, + Component: NumberCard, + props: (data) => ({ + title: "Number of new orders this month", + value: data.nb_new_orders, + }), + }, + { + id: "nb_cancelled_orders", + description: "Average amount of t-shirt", + size: 1, + Component: NumberCard, + props: (data) => ({ + title: "Number of cancelled orders this month", + value: data.nb_cancelled_orders, + }), + }, + { + id: "total_amount", + description: "Total new orders", + size: 1, + Component: NumberCard, + props: (data) => ({ + title: "Total amount of new orders this month", + value: data.total_amount, + }), + }, + { + id: "orders_by_size", + description: "Shirt orders by size", + size: 2, + Component: PieChartCard, + props: (data) => ({ + title: "Shirt orders by size", + value: data.orders_by_size, + }), + }, +] diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..3296d42cb9b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: String, + value: Number, + } +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..cd78cac70cb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,16 @@ + + + + +
+
+ +
+

+ +

+
+
+ +
+ diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js new file mode 100644 index 00000000000..8ae3e7b8d15 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,11 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../pie_chart/pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { PieChart }; + static props = { + title: String, + value: Object, + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml new file mode 100644 index 00000000000..31c745b689e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,16 @@ + + + + +
+
+ +
+
+ +
+
+
+ +
+ From 0a449a352eb0bb378a59ecc29561212c0bd3ae52 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Wed, 26 Nov 2025 14:19:51 +0100 Subject: [PATCH 37/39] [IMP] awesome_dashboard: Fix reload --- awesome_dashboard/static/src/dashboard/dashboard.js | 5 ++--- awesome_dashboard/static/src/dashboard/dashboard.xml | 2 +- awesome_dashboard/static/src/dashboard/statistics_service.js | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index 69736c3e43b..beb5479dac9 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,4 +1,4 @@ -import { Component, onWillStart, useState } from "@odoo/owl"; +import { Component, onWillStart, reactive } from "@odoo/owl"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; @@ -12,8 +12,7 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); - this.statisticService = useService("statistics"); - this.statistics = useState(this.statisticService); + this.state = reactive({ statistics: useService("statistics") }); this.items = dashboard_items; } diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index 713c03dad56..99fc391ad10 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -17,7 +17,7 @@
- + diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js index 052d9753a63..7e88b3595cf 100644 --- a/awesome_dashboard/static/src/dashboard/statistics_service.js +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -1,4 +1,3 @@ -import { reactive } from "@odoo/owl"; import { rpc } from "@web/core/network/rpc"; import { registry } from "@web/core/registry"; import { memoize } from "@web/core/utils/functions"; @@ -9,7 +8,7 @@ export async function loadStatistic() { export const statisticsService = { async start() { - const data = reactive(await loadStatistic()); + let data = await loadStatistic(); setInterval(async () => { data = await loadStatistic(); From eadf4c3f3c2055cc7350c72372e4dca5f8f0b93d Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Wed, 26 Nov 2025 15:02:50 +0100 Subject: [PATCH 38/39] [IMP] awesome_dashboard: Extensible dashboard --- awesome_dashboard/static/src/dashboard/dashboard.js | 2 +- awesome_dashboard/static/src/dashboard/dashboard_items.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index beb5479dac9..2b4985eb70c 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -13,7 +13,7 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action"); this.state = reactive({ statistics: useService("statistics") }); - this.items = dashboard_items; + this.items = registry.category("awesome_dashboard").getAll(); } openLeads() { diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js index 59f53f20b39..2f55839c8e0 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard_items.js +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -1,7 +1,8 @@ +import { registry } from "@web/core/registry"; import { PieChartCard } from "./pie_chart_card/pie_chart_card"; import { NumberCard } from "./number_card/number_card"; -export default [ +const items = [ { id: "average_quantity", description: "Average amount of t-shirt", @@ -63,3 +64,7 @@ export default [ }), }, ] + +items.forEach(item => { + registry.category("awesome_dashboard").add(item.id, item); +}); From 5d78b623e28394d5addd147de9fe9fd9a45f1607 Mon Sep 17 00:00:00 2001 From: "Nguyen (ngtpn)" Date: Wed, 26 Nov 2025 16:56:16 +0100 Subject: [PATCH 39/39] [IMP] awesome_dashboard: Allow selecting items in dashboard --- .../configuration_dialog.js | 33 ++++++++++++++++++ .../configuration_dialog.xml | 27 +++++++++++++++ .../static/src/dashboard/dashboard.js | 34 ++++++++++++++++++- .../static/src/dashboard/dashboard.xml | 10 +++++- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js create mode 100644 awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js new file mode 100644 index 00000000000..cacc3f548f3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.js @@ -0,0 +1,33 @@ +import { Component } from "@odoo/owl"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { Dialog } from "@web/core/dialog/dialog"; +import { registry } from "@web/core/registry"; +import dashboard_items from "../dashboard_items"; + +export class ConfigurationDialog extends Component { + static template = "awesome_dashboard.ConfigurationDialog"; + static components = { CheckBox, Dialog }; + static props = { + items: Array, + close: Function, + enabled_items: Array, + setEnabledItems: Function, + }; + + setup() { + this.all_items = this.props.enabled_items; + } + + toggleValue(item) { + if (this.all_items.includes(item)) { + this.all_items = this.all_items.filter(name => name !== item); + } else { + this.all_items = [...this.all_items, item]; + } + } + + onApply() { + this.props.setEnabledItems(this.all_items); + this.props.close(); + } +} diff --git a/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml new file mode 100644 index 00000000000..ecec5472aad --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/configuration_dialog/configuration_dialog.xml @@ -0,0 +1,27 @@ + + + + + +

Which cards do you wish to see?

+ + + + + + + + +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index 2b4985eb70c..00104198bee 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,9 +1,10 @@ -import { Component, onWillStart, reactive } from "@odoo/owl"; +import { Component, onWillStart, reactive, useState } from "@odoo/owl"; import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { Layout } from "@web/search/layout"; import { DashboardItem } from "./dashboard_item/dashboard_item"; import { PieChart } from "./pie_chart/pie_chart"; +import { ConfigurationDialog } from "./configuration_dialog/configuration_dialog"; import dashboard_items from "./dashboard_items"; class AwesomeDashboard extends Component { @@ -14,6 +15,26 @@ class AwesomeDashboard extends Component { this.action = useService("action"); this.state = reactive({ statistics: useService("statistics") }); this.items = registry.category("awesome_dashboard").getAll(); + this.dialog = useService("dialog"); + this.context = useState({ enabled_items: this.getEnabledItems() }); + } + + getEnabledItems() { + // Open all items by default + const stored_items = JSON.parse( + localStorage.getItem("enabled_items") + ); + if (!stored_items) { + const all_items = this.items.map((item) => item.id); + this.setEnabledItems(all_items); + return all_items; + } + return stored_items || []; + } + + setEnabledItems(values) { + localStorage.setItem("enabled_items", JSON.stringify(values)); + this.context.enabled_items = values; } openLeads() { @@ -31,6 +52,17 @@ class AwesomeDashboard extends Component { openCustomers() { this.action.doAction("base.action_partner_form"); } + + openConfiguration() { + this.dialog.add( + ConfigurationDialog, + { + items: this.items, + enabled_items: this.context.enabled_items, + setEnabledItems: this.setEnabledItems.bind(this), + }, + ); + } } registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index 99fc391ad10..d055c5a99ba 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -14,9 +14,17 @@ Leads + + +
- +