From 18a45be1770bce1744915267c14ba346790d714b Mon Sep 17 00:00:00 2001 From: odoo Date: Wed, 2 Jul 2025 19:03:45 +0530 Subject: [PATCH 01/11] [ADD] estate: initial module for real estate properties Created new 'estate' module. Added base model 'estate.property' with fields mentioned in exercise. Set up module structure. Set 'name' and 'expected_price' as required fields. --- estate/__init__.py | 1 + estate/__manifest__.py | 7 +++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py 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 new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..f291e7eb0b7 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,7 @@ +{ + "name": "Real Estate", + "category": "Tutorials/RealEstate", + "application": True, + "installable": True, + "license": "AGPL-3", +} 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..db0cf5eb60e --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,28 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Model" + + name = fields.Char(string="Name") + description = fields.Text(string="Description") + postcode = fields.Text(string="Postcode") + date_availability = fields.Date(string="Date Availability") + expected_price = fields.Float(string="Expected Price") + selling_price = fields.Integer(string="Selling Price") + bedrooms = fields.Integer(string="Bedrooms") + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area") + garden_orientation = fields.Selection( + string="Type", + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], + ) From 63b8f9cdc42c53dd70aa6dfb7d1dd48722a06656 Mon Sep 17 00:00:00 2001 From: odoo Date: Fri, 4 Jul 2025 10:30:03 +0530 Subject: [PATCH 02/11] [ADD] estate: initial module with security and basic views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces the initial version of the estate module as part of the Odoo 18 developer tutorial. The module includes basic models, access rights, security rules, and simple form and tree views for managing real estate properties. The purpose of this change is to set up a foundational structure for the module. It follows the official tutorial steps to demonstrate Odoo’s ORM, security mechanisms, and view definitions. Adding proper access control ensures that only authorized users can interact with the module. The initial UI provides the groundwork for extending functionality later. This is part of a learning exercise to understand Odoo’s server framework and how to implement a feature-rich module following best practices. --- estate/__manifest__.py | 5 ++ estate/models/estate_property.py | 14 ++++ estate/security/ir.model.access.csv | 2 + estate/views/estate_menus.xml | 7 ++ estate/views/estate_property_views.xml | 91 ++++++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 estate/security/ir.model.access.csv 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 f291e7eb0b7..c031ed65c46 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,5 +3,10 @@ "category": "Tutorials/RealEstate", "application": True, "installable": True, + "data": [ + "views/estate_property_views.xml", + "views/estate_menus.xml", + "security/ir.model.access.csv", + ], "license": "AGPL-3", } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index db0cf5eb60e..c1a1ac01e98 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -26,3 +26,17 @@ class EstateProperty(models.Model): ("west", "West"), ], ) + + active = fields.Boolean(default=False, string="Active") + + state = fields.Selection( + selection=[ + ("new", "New"), + ("offer_received", "Offer Received "), + ("offer_accepted", "Offer Accepted"), + ("Cancelled", "Cancelled"), + ], + string="State", + default="new", + copy=False, + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..138b28cc071 --- /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,base.group_user +estate.access_estate_property, ir.model.access,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..f496a645db6 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..309a638b786 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,91 @@ + + + Properties + estate.property + list,form + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + +
From 7e42acbbca0eac35ee84c7329c2b855b216e2b7e Mon Sep 17 00:00:00 2001 From: odoo Date: Mon, 7 Jul 2025 16:00:35 +0530 Subject: [PATCH 03/11] [IMP] estate: Add new features relations, computed fields, and actions -Created new models for property offers,tags and types. -Defined relation between these data models for accessing data across the model. -Created new views for property types,property tags. -Defined action on button click , created computed fields . --- estate/models/__init__.py | 3 + estate/models/estate_property.py | 85 +++++++++++++++++++++++++- estate/models/estate_property_offer.py | 78 +++++++++++++++++++++++ estate/models/estate_property_tag.py | 14 +++++ estate/models/estate_property_type.py | 8 +++ estate/security/ir.model.access.csv | 9 ++- estate/views/estate_menus.xml | 10 ++- estate/views/estate_property_views.xml | 57 +++++++++++++++++ 8 files changed, 260 insertions(+), 4 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 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 c1a1ac01e98..df6b3071fce 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ -from odoo import fields, models +from odoo import fields, models, api +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -40,3 +41,85 @@ class EstateProperty(models.Model): default="new", copy=False, ) + + # property will have Many to one relation with property since many properties can belong to one property + + property_type_id = fields.Many2one("estate.property.type", "Property Type") + + user_id = fields.Many2one( + "res.users", + string="Salesperson", + copy=False, + default=lambda self: self.env.user, + ) + + partner_id = fields.Many2one( + "res.partner", + string="Buyer", + copy=False, + ) + + tag_ids = fields.Many2many("estate.property.tag", string="Tags") + + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + + total_area = fields.Integer( + compute="_compute_total_property_area", string="Total Area" + ) + + best_price = fields.Integer(compute="_compute_best_price", string="Best Price") + + status = fields.Char(default="new", string="Status") + + @api.depends("garden_area", "living_area") + def _compute_total_property_area(self): + for area in self: + self.total_area = self.garden_area + self.living_area + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + offers_list = record.mapped("offer_ids.price") + if offers_list: + record.best_price = max(offers_list) + else: + record.best_price = 0 + + # on change of garden status , update gardern area and its orientation + + @api.onchange("garden") + def _onchange_garden_status(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + return + self.garden_area = 0 + self.garden_orientation = "" + + # acts when property is sold + # In case property is cancelled it cannot be sold + def action_sell_property(self): + # dictionary for the property status + property_sell_status_dict = {"new": True, "sold": True, "cancelled": False} + + for record in self: + print("the object on sell action", record.read()) + if property_sell_status_dict[record.status]: + record.status = "sold" + else: + raise UserError("Cancelled property cannot be sold.") + + # action in case of cancel property button + # If property is sold than Cannot be cancelled + + def action_cancel_property_selling(self): + property_cancel_status_dict = { + "new": True, + "cancelled": True, + "sold": False, + } + for record in self: + if property_cancel_status_dict[record.status]: + record.status = "cancelled" + else: + raise UserError("Sold property cannot be cancelled.") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..2021284c1b0 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,78 @@ + + + + +# offers model that will offer us the offers + + + +from odoo import fields, models, api +from datetime import datetime +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Offer model for the properties of real estate' + price = fields.Float() + status = fields.Selection( + selection=[('accepted', 'Accepted'), ('refused', 'Refused ')], + string='Status', + copy = False + ) + + 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_offer_deadline', inverse = '_deadline_update') + + + + + + + + # deadline will be computed based upon the validity date + @api.depends('validity') + def _compute_offer_deadline(self): + for offer in self: + if not offer.create_date: + offer.date_deadline = datetime.now() + relativedelta(days=(offer.validity or 0)) + return + + offer.date_deadline = offer.create_date + relativedelta(days=(offer.validity or 0)) + + + # deadline date can also be changed and once this is saved validity will be updated + def _deadline_update(self): + for offer in self: + offer.validity = (offer.date_deadline - (offer.create_date or datetime.now()).date()).days + + + # action for the accepting the offer + def action_offer_confirm(self): + for record in self: + # since saling price is only updated when offer is accepted therefore it validates if offer + # is already accepted than warning + + if record.property_id.selling_price > 0 : + raise UserError('Offer price already accepted for the property') + + record.status = 'accepted' + record.property_id.selling_price = self.price + record.property_id.partner_id = record.partner_id + + # action for the refusal of the status + def action_offer_refuse(self): + + for record in self: + if (record.status == 'accepted'): + record.property_id.selling_price = 0 + record.property_id.partner_id = '' + record.status = 'refused' + + + \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..f4fe2d056f2 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,14 @@ + + +# the tag model will be here + + + +from odoo import fields, models + + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Tag model for the estate properties" + 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..449517da629 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + +# Property type model for properties +# prpoerties can be of type house, penthouse, etc. +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Type of properties of estate model" + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 138b28cc071..8ba48a75b9e 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,7 @@ -id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink,base.group_user -estate.access_estate_property, ir.model.access,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property, ir.model.access,model_estate_property,base.group_user,1,1,1,1 +estate.access_estate_property_type,ir.model.access,model_estate_property_type,base.group_user,1,1,1,1 + +estate.access_estate_property_tag,ir.model.access,model_estate_property_tag,base.group_user,1,1,1,1 + +estate.access_estate_property_offer,ir.model.access,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index f496a645db6..265ec21a3e6 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,15 @@ - + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 309a638b786..535c9abebd4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,6 +5,19 @@ list,form + + Property Type + estate.property.type + list,form + + + + + Property Tag + estate.property.tag + list,form + + estate.property.list estate.property @@ -26,13 +39,31 @@ estate.property
+
+
+

+ + + + + + + + + + + + + @@ -40,6 +71,9 @@ + + + @@ -60,6 +94,28 @@ + + + + + + + + + + + + + + + +
+

+ +

+
+ + + + + + + + + + + +
+
+
+
+ + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 535c9abebd4..9b66b4e4d6a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,6 +3,7 @@ Properties estate.property list,form + {'search_default_available': 1} @@ -22,14 +23,16 @@ estate.property.list estate.property - + - + @@ -42,6 +45,7 @@
@@ -52,7 +56,7 @@ - + @@ -90,8 +94,8 @@ - - + + @@ -101,13 +105,19 @@ - - + + - - + + + diff --git a/awesome_dashboard/static/src/dashboard/component/numberCard.js b/awesome_dashboard/static/src/dashboard/component/numberCard.js new file mode 100644 index 00000000000..709ef9c2c13 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/component/numberCard.js @@ -0,0 +1,8 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component{ + static template = "awesome_dashboard.NumberCard"; + static props = ["title", "value"]; +} diff --git a/awesome_dashboard/static/src/dashboard/component/numberCard.xml b/awesome_dashboard/static/src/dashboard/component/numberCard.xml new file mode 100644 index 00000000000..a2d40c1bfbc --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/component/numberCard.xml @@ -0,0 +1,9 @@ + + + +
+
+

+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/component/pieChart.js b/awesome_dashboard/static/src/dashboard/component/pieChart.js new file mode 100644 index 00000000000..05834fed5f4 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/component/pieChart.js @@ -0,0 +1,50 @@ +/** @odoo-module **/ + +import { loadJS } from "@web/core/assets"; +import { Component, onWillStart,useRef,onWillUpdateProps,onMounted } from "@odoo/owl"; + +export class PieChart extends Component{ + + static template = "awesome_dashboard.pie_chart" + + setup(){ + this.canvasRef = useRef("canvas") + onWillStart(async()=>{ + await loadJS("/web/static/lib/Chart/Chart.js") + }) + onMounted(() => { + this.renderChart(); + }); + onWillUpdateProps((nextProps) => { + if (this.chart) { + this.chart.destroy(); + } + this.renderChart(nextProps.data); + }); + } + + renderChart(){ + const labels = Object.keys(this.props.data); + const data = Object.values(this.props.data); + const color = ["#007BFF", "#FFA500", "#808090",]; + const ctx = this.canvasRef.el.getContext("2d"); + this.chart = new Chart(ctx,{ + type:"pie", + data:{ + labels:labels, + datasets: [ + { + data:data, + backgroundColor: color + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + }, + + }) + + } +} diff --git a/awesome_dashboard/static/src/dashboard/component/pieChart.xml b/awesome_dashboard/static/src/dashboard/component/pieChart.xml new file mode 100644 index 00000000000..e138f1ea925 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/component/pieChart.xml @@ -0,0 +1,11 @@ + + + +

+ +

+
+ +
+
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..5f6165d021b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,65 @@ +/** @odoo-module **/ + +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 "./component/dashboardItem"; +import { PieChart } from "./component/pieChart"; +import { NumberCard } from "./component/numberCard"; +import { DashboardSettingsDialog } from "./component/dashboard_settings_dialog"; +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = {Layout, DashboardItem, PieChart, NumberCard} + + setup(){ + let staticsService = useService("awesome_dashboard.statistics"); + this.dialog = useService("dialog"); + this.display = {controlPanel: {} }; + this.action = useService("action"); + this.stats = useState(staticsService.data); + this.removedItemIds = useState(this.getRemovedItems()); + this.items = registry.category("awesome_dashboard.items").getAll(); + } + + openCustomersKanban() + { + this.action.doAction("base.action_partner_form"); + } + + openLeads() + { + this.action.doAction({ + type: 'ir.actions.act_window', + name: "Leads", + res_model: "crm.lead", + views:[ + [false,"list"], + [false,"form"] + ] + }); + + } + + get visibleItems() { + return this.items.filter(item => !this.removedItemIds.includes(item.id)); + } + + getRemovedItems() { + return JSON.parse(localStorage.getItem("awesome_dashboard.removed_items") || "[]"); + } + + openSettings() + { + this.dialog.add(DashboardSettingsDialog, { + items: this.items, + removedIds: this.removedItemIds, + onSave: (removed) => { + localStorage.setItem("awesome_dashboard.removed_items", JSON.stringify(removed)); + this.removedItemIds.splice(0, this.removedItemIds.length, ...removed); + } + }); + } +} + +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 new file mode 100644 index 00000000000..4dd9e6841ee --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,23 @@ + + + + + + + + + + +
+ + + + + + +
+
+
+
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..f06a0972b92 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,69 @@ + +import { registry } from "@web/core/registry"; +import { PieChart } from "./component/pieChart"; +import { NumberCard } from "./component/numberCard"; +import { _t } from "@web/core/l10n/translation"; + +const dashboardItems = [ + { + id: "nb_new_orders", + description: _t("Number of new orders"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("New Orders This Month"), + value: data.nb_new_orders, + }), + }, + { + id: "total_amount", + description: _t("Total order amount"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Total Order Amount", + value: data.total_amount, + }), + }, + { + id: "average_quantity", + description: _t("Average t-shirt quantity"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("Avg T-shirts per Order"), + value: data.average_quantity, + }), + }, + { + id: "nb_cancelled_orders", + description:_t("Cancelled Orders"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Cancelled Orders", + value: data.nb_cancelled_orders, + }), + }, + { + id: "average_time", + description: _t("Avg processing time"), + Component: NumberCard, + size: 2, + props: (data) => ({ + title: "Avg Time to Ship/Cancel", + value: data.average_time + " min", + }), + }, + { + id: "pie_chart", + description:_t("Orders by size"), + Component: PieChart, + size: 2, + props: (data) => ({ data: data.orders_by_size }), + }, +]; + +for (const item of dashboardItems) { + registry.category("awesome_dashboard.items").add(item.id, item); +} diff --git a/awesome_dashboard/static/src/dashboard/service/dashboardStatisticsService.js b/awesome_dashboard/static/src/dashboard/service/dashboardStatisticsService.js new file mode 100644 index 00000000000..492212754a8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/service/dashboardStatisticsService.js @@ -0,0 +1,35 @@ +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; + +const dashboardStatisticsService = { + + start() { + const stats = reactive({ + nb_new_orders: 0, + total_amount: 0, + average_quantity: 0, + nb_cancelled_orders: 0, + average_time: 0, + orders_by_size: { m: 0, s: 0, xl: 0 } + }); + async function fetchStatistics() { + const result = await rpc("/awesome_dashboard/statistics"); + if (result) { + Object.assign(stats, result); + } + } + + fetchStatistics(); + + setInterval(() => { + fetchStatistics(); + }, 10601000); + return { + data : stats + }; + }, + +} + +registry.category("services").add("awesome_dashboard.statistics", dashboardStatisticsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..23e3f4dcbaf --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ + +import { Component,xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + +class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 77abad510ef..d068840247d 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -33,6 +33,7 @@ 'web/static/lib/bootstrap/scss/_variables.scss', 'web/static/lib/bootstrap/scss/_maps.scss', ('include', 'web._assets_bootstrap'), + ("include", "web._assets_bootstrap_backend"), ('include', 'web._assets_core'), 'web/static/src/libs/fontawesome/css/font-awesome.css', 'awesome_owl/static/src/**/*', diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..df098e50877 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,25 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + + static template = "awesome_owl.Card"; + static props = { + title: String, + slots: { + type : Object, + optional : true + } + }; + + setup() + { + this.state = useState({ + isOpen : true + }) + } + + toggleContent() + { + this.state.open = !this.state.open; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..590005d1ca2 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,20 @@ + + + +
+
+
+
+ +
+ +
+
+ +
+
+
+
+
diff --git a/awesome_owl/static/src/components/todoItem.js b/awesome_owl/static/src/components/todoItem.js new file mode 100644 index 00000000000..8b1cfd8ae34 --- /dev/null +++ b/awesome_owl/static/src/components/todoItem.js @@ -0,0 +1,16 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component{ + + static template = "awesome_owl.todoItem"; + static props = { + todo: Object, + callback: {type : Function, optional: true}, + deleteTodo:{type : Function, optional: true} + } + + onClickCheckbox() + { + this.props.callback(this.props.todo.id); + } +} diff --git a/awesome_owl/static/src/components/todoItem.xml b/awesome_owl/static/src/components/todoItem.xml new file mode 100644 index 00000000000..212ae2a763b --- /dev/null +++ b/awesome_owl/static/src/components/todoItem.xml @@ -0,0 +1,16 @@ + + + +

+ + + : + + + +

+
+
diff --git a/awesome_owl/static/src/components/todoList.js b/awesome_owl/static/src/components/todoList.js new file mode 100644 index 00000000000..5de536c73da --- /dev/null +++ b/awesome_owl/static/src/components/todoList.js @@ -0,0 +1,38 @@ +/** @odoo-module **/ + +import { Component,useState } from "@odoo/owl"; +import { TodoItem } from "./todoItem"; + +export class TodoList extends Component{ + + static template = "awesome_owl.todoList"; + static components = {TodoItem}; + static props = {} + setup() + { + this.id = useState({value : 0}); + this.todos = useState([]); + this.toggle = this.toggle.bind(this); + this.deleteTodo = this.deleteTodo.bind(this); + } + + addTodo(event) + { + if(event.keyCode == 13 && (event.target.value).trim() != "") + { + this.todos.push({id : this.id.value++, description: event.target.value, isCompleted: false}); + event.target.value = ""; + } + } + + toggle(itemId){ + let todoItem = this.todos.find(todo => todo.id == itemId); + todoItem.isCompleted = !todoItem.isCompleted; + } + + deleteTodo(todoId) + { + let todoIndex = this.todos.findIndex(todo => todo.id == todoId) + this.todos.splice(todoIndex,1) + } +} diff --git a/awesome_owl/static/src/components/todoList.xml b/awesome_owl/static/src/components/todoList.xml new file mode 100644 index 00000000000..3d5bf9299e8 --- /dev/null +++ b/awesome_owl/static/src/components/todoList.xml @@ -0,0 +1,11 @@ + + + +
+ + + + +
+
+
diff --git a/awesome_owl/static/src/counter.js b/awesome_owl/static/src/counter.js new file mode 100644 index 00000000000..21dbb6e645c --- /dev/null +++ b/awesome_owl/static/src/counter.js @@ -0,0 +1,18 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.Counter"; + static props = { + onchange : { type: Function, optional: true }, + } + + setup() { + this.state = useState({ value: 1 }); + } + + increment(){ + this.state.value++; + if(this.props.onchange) + this.props.onchange(1) + } +} diff --git a/awesome_owl/static/src/counter.xml b/awesome_owl/static/src/counter.xml new file mode 100644 index 00000000000..97031825879 --- /dev/null +++ b/awesome_owl/static/src/counter.xml @@ -0,0 +1,7 @@ + + + +

Counter:

+ +
+
\ No newline at end of file diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..5a9ad9655b6 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,25 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; +import { Counter } from "./counter"; +import { Card } from "./card/card"; +import { TodoList } from "./components/todoList"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList}; + + setup() { + this.sum = useState({value:2 }) + this.addCount = this.addCount.bind(this); + } + + increment(){ + this.state.value++; + } + + addCount(count) + { + this.sum.value = this.sum.value + count; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..64f5aa9ad24 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,7 +4,18 @@
hello world + + +

Sum :

+ +

This is new title prop

+
+ + + +
+ +
-