@ -0,0 +1,12 @@ | 
				
			|||
<?xml version="1.0" encoding="UTF-8" ?> | 
				
			|||
<odoo> | 
				
			|||
    <data> | 
				
			|||
<!--    Demo Dashboard Theme    --> | 
				
			|||
        <record id="demo_theme" model="dashboard.theme"> | 
				
			|||
            <field name="name">Demo</field> | 
				
			|||
            <field name="color_x">#4158D0</field> | 
				
			|||
            <field name="color_y">#C850C0</field> | 
				
			|||
            <field name="color_z">#FFCC70</field> | 
				
			|||
        </record> | 
				
			|||
    </data> | 
				
			|||
</odoo> | 
				
			|||
@ -1,7 +1,13 @@ | 
				
			|||
## Module <odoo_dynamic_dashboard> | 
				
			|||
 | 
				
			|||
#### 02.03.2024 | 
				
			|||
#### 18.05.2024 | 
				
			|||
#### Version 17.0.1.0.0 | 
				
			|||
#### ADD | 
				
			|||
##### ADD | 
				
			|||
- Initial commit for Odoo Dynamic Dashboard | 
				
			|||
 | 
				
			|||
- Initial Commit for Odoo Dynamic Dashboard | 
				
			|||
## Module <odoo_dynamic_dashboard> | 
				
			|||
 | 
				
			|||
#### 20.07.2024 | 
				
			|||
#### Version 17.0.2.0.0 | 
				
			|||
##### UPDT | 
				
			|||
- New Features has been Added. | 
				
			|||
@ -0,0 +1,53 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
from odoo import api, fields, models | 
				
			|||
 | 
				
			|||
 | 
				
			|||
class DashboardTheme(models.Model): | 
				
			|||
    _name = 'dashboard.theme' | 
				
			|||
    _description = 'Dashboard Theme' | 
				
			|||
 | 
				
			|||
    name = fields.Char(string='Theme Name', help='Name of the theme') | 
				
			|||
    color_x = fields.Char(string='Color X', help='Select the color_x for theme', | 
				
			|||
                          default='#4158D0') | 
				
			|||
    color_y = fields.Char(string='Color Y', help='Select the color_y for theme', | 
				
			|||
                          default='#C850C0') | 
				
			|||
    color_z = fields.Char(string='Color Z', help='Select the color_z for theme', | 
				
			|||
                          default='#FFCC70') | 
				
			|||
    body = fields.Html(string='Body', help='Preview of the theme will be shown') | 
				
			|||
    style = fields.Char(string='Style', | 
				
			|||
                        help='It store the style of the gradient') | 
				
			|||
 | 
				
			|||
    @api.constrains('name', 'color_x', 'color_y', 'color_z') | 
				
			|||
    def save_record(self): | 
				
			|||
        """ | 
				
			|||
            Function for saving the datas including body and style | 
				
			|||
        """ | 
				
			|||
        self.body = f"<div style='width:300px; height:300px;background-image: linear-gradient(50deg, {self.color_x} 0%, {self.color_y} 46%, {self.color_z} 100%);'/>" | 
				
			|||
        self.style = f"background-image: linear-gradient(50deg, {self.color_x} 0%, {self.color_y} 46%, {self.color_z} 100%);" | 
				
			|||
 | 
				
			|||
    def get_records(self): | 
				
			|||
        """ | 
				
			|||
            Function for returning all records with fields name and style | 
				
			|||
        """ | 
				
			|||
        records = self.search_read([], ['name', 'style']) | 
				
			|||
        return records | 
				
			|||
		
		
			
  | 
| 
		 Before Width: | Height: | Size: 127 KiB  | 
| 
		 Before Width: | Height: | Size: 15 KiB  | 
| 
		 Before Width: | Height: | Size: 22 KiB  | 
| 
		 Before Width: | Height: | Size: 57 KiB  | 
| 
		 Before Width: | Height: | Size: 56 KiB  | 
| 
		 Before Width: | Height: | Size: 64 KiB  | 
| 
		 Before Width: | Height: | Size: 43 KiB  | 
| 
		 Before Width: | Height: | Size: 33 KiB  | 
| 
		 Before Width: | Height: | Size: 41 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB  | 
| 
		 Before Width: | Height: | Size: 27 KiB  | 
| 
		 After Width: | Height: | Size: 55 KiB  | 
| 
		 After Width: | Height: | Size: 89 KiB  | 
| 
		 After Width: | Height: | Size: 72 KiB  | 
| 
		 After Width: | Height: | Size: 100 KiB  | 
| 
		 After Width: | Height: | Size: 172 KiB  | 
| 
		 After Width: | Height: | Size: 36 KiB  | 
| 
		 After Width: | Height: | Size: 91 KiB  | 
| 
		 After Width: | Height: | Size: 171 KiB  | 
| 
		 After Width: | Height: | Size: 40 KiB  | 
| 
		 After Width: | Height: | Size: 27 KiB  | 
| 
		 Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 370 KiB  | 
| 
		 After Width: | Height: | Size: 188 KiB  | 
| 
		 After Width: | Height: | Size: 43 KiB  | 
| 
		 After Width: | Height: | Size: 47 KiB  | 
| 
		 After Width: | Height: | Size: 78 KiB  | 
| 
		 After Width: | Height: | Size: 173 KiB  | 
| 
		 After Width: | Height: | Size: 284 KiB  | 
| 
		 After Width: | Height: | Size: 41 KiB  | 
| 
		 After Width: | Height: | Size: 173 KiB  | 
| 
		 After Width: | Height: | Size: 78 KiB  | 
| 
		 After Width: | Height: | Size: 441 KiB  | 
@ -0,0 +1,344 @@ | 
				
			|||
.row { | 
				
			|||
    margin: 1rem; | 
				
			|||
} | 
				
			|||
.resize-drag { | 
				
			|||
  border-radius: 8px; | 
				
			|||
  padding: 20px; | 
				
			|||
  margin: 1rem; | 
				
			|||
  color: white; | 
				
			|||
  font-size: 20px; | 
				
			|||
  font-family: sans-serif; | 
				
			|||
 | 
				
			|||
  touch-action: none; | 
				
			|||
  box-sizing: border-box; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.theme { | 
				
			|||
    position: absolute; | 
				
			|||
    right: 60.5em; | 
				
			|||
    padding-top:inherit; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.card { | 
				
			|||
    background-color: transparent !important; | 
				
			|||
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | 
				
			|||
    border-radius: 5px !important; | 
				
			|||
    cursor: pointer; | 
				
			|||
    transition: transform 0.2s; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.dashboard_pdf{ | 
				
			|||
    position: absolute; | 
				
			|||
    right: 26.5em; | 
				
			|||
    padding-top:inherit; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.dashboard_mail{ | 
				
			|||
    position: absolute; | 
				
			|||
    right: 24.5em; | 
				
			|||
    padding-top:inherit; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.resize-drag { | 
				
			|||
    position: relative !important; | 
				
			|||
    font-size: 100%; | 
				
			|||
    overflow:hidden; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
h2, h3 { | 
				
			|||
    font-size: 150% !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.container { | 
				
			|||
    max-width: 100% !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.tile { | 
				
			|||
    display: flex; | 
				
			|||
    flex-direction: column; | 
				
			|||
    align-items: center; | 
				
			|||
    justify-content: center; | 
				
			|||
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | 
				
			|||
    border-radius: 5px; | 
				
			|||
    cursor: pointer; | 
				
			|||
    transition: transform 0.2s; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
div.card-header { | 
				
			|||
    color: #383838; | 
				
			|||
    background-color: #70659647 !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/* The toggle-btn - the box around the slider */ | 
				
			|||
.layout-switch { | 
				
			|||
    font-size: 15px; | 
				
			|||
    position: absolute; | 
				
			|||
    left: 10.5em; | 
				
			|||
    display: inline-block; | 
				
			|||
    padding: 0 0 0 0; | 
				
			|||
    border-radius: 0.5rem; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.modal-header .btn-close { | 
				
			|||
    display: none; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.modal-title{ | 
				
			|||
    margin-inline-end: auto; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.theme-text { | 
				
			|||
    font-family: Arial, sans-serif; | 
				
			|||
    font-size: 1em; | 
				
			|||
    opacity: 70%; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.search-group { | 
				
			|||
    position: absolute; | 
				
			|||
    right: 1.7em; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.block_setting { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 7px; | 
				
			|||
    left: 10px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.block_delete { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 7px; | 
				
			|||
    right: 10px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.tile_edit { | 
				
			|||
    color: #d2d2d2; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.block_edit { | 
				
			|||
    color: #3f3f3f; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.block_setting, .block_delete, .block_image, .block_pdf, .block_xlsx, .block_csv, .block_export { | 
				
			|||
    display: none; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.resize-drag:hover .block_setting, | 
				
			|||
.resize-drag:hover .block_xlsx, | 
				
			|||
.resize-drag:hover .block_csv, | 
				
			|||
.resize-drag:hover .block_image, | 
				
			|||
.resize-drag:hover .block_pdf, | 
				
			|||
.resize-drag:hover .block_export, | 
				
			|||
.resize-drag:hover .block_delete { | 
				
			|||
    display: block; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart-edit { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 0px; | 
				
			|||
    left: 3px; | 
				
			|||
    font-size: 16px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart-image { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 3px; | 
				
			|||
    right: 106px; | 
				
			|||
    font-size: 16px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart-pdf { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 3px; | 
				
			|||
    right: 82px; | 
				
			|||
    font-size: 16px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart-csv { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 3px; | 
				
			|||
    right: 58px; | 
				
			|||
    font-size: 16px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart-xlsx { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 6px; | 
				
			|||
    right: 34px; | 
				
			|||
    font-size: 15px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart-setting { | 
				
			|||
    position: absolute; | 
				
			|||
    top: 0px; | 
				
			|||
    right: 3px; | 
				
			|||
    font-size: 16px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.chart_title { | 
				
			|||
    padding-top: 0.7em; | 
				
			|||
    text-align: center; | 
				
			|||
    font-size: 16px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
input:checked + .slider:before { | 
				
			|||
    -webkit-transform: translateX(26px); | 
				
			|||
    -ms-transform: translateX(26px); | 
				
			|||
    transform: translateX(26px); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.move_slider { | 
				
			|||
    -webkit-transform: translateX(26px); | 
				
			|||
    -ms-transform: translateX(26px); | 
				
			|||
    transform: translateX(26px); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/* Rounded sliders */ | 
				
			|||
.slider.round { | 
				
			|||
    border-radius: 34px; | 
				
			|||
    margin: -1px 6px 11px 0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.slider.round:before { | 
				
			|||
    border-radius: 50%; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.o_kanban_record { | 
				
			|||
    background-color: #e8e8e8 !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.oe_module_name { | 
				
			|||
    background-color: #ffffff !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bootbox .modal-footer .btn-danger:hover { | 
				
			|||
    background-color: #ff595c; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bootbox .modal-footer .btn-danger { | 
				
			|||
    box-shadow: none; | 
				
			|||
    margin-left: 0.5rem; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bootbox .modal-header { | 
				
			|||
    text-shadow: -1px 3px 5px #b4b4b4; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bootbox .modal-body { | 
				
			|||
    font-size: 14px; | 
				
			|||
    text-shadow: -1px 3px 5px #b4b4b4; | 
				
			|||
    color: #000000; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.dropdown-addblock, .o-dropdown-menu { | 
				
			|||
    left: 2.5em; | 
				
			|||
    top: 2.5em; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.navbar { | 
				
			|||
    top:10px; | 
				
			|||
    padding: 1.2rem 0 2.6rem 0 !important; | 
				
			|||
    border-bottom: 1px solid #3f3f3f1a !important; | 
				
			|||
    color: #444444; | 
				
			|||
    background-color: #ffffff !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.theme_icon:hover { | 
				
			|||
    text-shadow: 0px 0px 5px #9388ff; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.navbar-style { | 
				
			|||
    margin-top: 1.2em; | 
				
			|||
    border-radius: 0.5em; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.dropdown-add-items { | 
				
			|||
    position: absolute; | 
				
			|||
    color: #e4e4e4; | 
				
			|||
    left: 2em; | 
				
			|||
    font-size: 16px; | 
				
			|||
    text-transform: uppercase; | 
				
			|||
    background-color: #71639e; | 
				
			|||
    border: 1px solid transparent; | 
				
			|||
    padding: 0.375rem 0.75rem; | 
				
			|||
    font-size: 1.08333333rem; | 
				
			|||
    border-radius: 0.25rem; | 
				
			|||
    transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.dropdown-add-items:hover { | 
				
			|||
    color: white; | 
				
			|||
    background-color: #59507b; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
@media (max-width: 767px) { | 
				
			|||
    .navbar { | 
				
			|||
        padding: 1.2rem 0 1.2rem 0; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .navbar:focus, .navbar:active { | 
				
			|||
        padding: 1.2rem 0 2rem 0; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .o_web_client.o_touch_device .btn, .o_web_client.o_touch_device .btn .btn-sm, .o_web_client.o_touch_device .btn .btn-group-sm > .btn { | 
				
			|||
        font-size: 1.08333333rem; | 
				
			|||
        padding: 6px 8px; | 
				
			|||
        margin-top: 39px; | 
				
			|||
        margin-left: -39px; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .dropdown-item { | 
				
			|||
        font-size: 1.08333333rem; | 
				
			|||
        font-weight: 500; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .dropdown-addblock, .o-dropdown-menu { | 
				
			|||
        left: -3.5em; | 
				
			|||
        padding: 0em 7em; | 
				
			|||
        margin-top: 38px; | 
				
			|||
       width: 276px; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .toggle-btn { | 
				
			|||
        position: absolute; | 
				
			|||
        margin-top: -16px; | 
				
			|||
        margin-left: 56px; | 
				
			|||
        left: 1.2em; | 
				
			|||
        top: 2.3em; | 
				
			|||
         height: 10px; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    #edit_layout { | 
				
			|||
        display: none; | 
				
			|||
        border-radius: inherit; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    #search-button { | 
				
			|||
        position: relative; | 
				
			|||
        left: 3.5em; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    #searchclear { | 
				
			|||
        position: absolute; | 
				
			|||
        margin: 0.3rem 0 0 9.9rem; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .search-box { | 
				
			|||
        top: -3em; | 
				
			|||
        left: 7.5em; | 
				
			|||
        margin: 0em -1.9em; | 
				
			|||
        width: 79%; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    .navbar-toggler { | 
				
			|||
        margin: 0.2em 1em; | 
				
			|||
        padding: 0.25rem 0.5rem; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    .dropdown-add-items { | 
				
			|||
        position: absolute; | 
				
			|||
        color: #e4e4e4; | 
				
			|||
        top: -2.6em; | 
				
			|||
        left: 3.5em; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -1,54 +0,0 @@ | 
				
			|||
/** @odoo-module */ | 
				
			|||
 | 
				
			|||
import { registry} from '@web/core/registry'; | 
				
			|||
import { DynamicDashboardTile} from './DynamicDashboardTile' | 
				
			|||
import { DynamicDashboardChart} from './DynamicDashboardChart' | 
				
			|||
import { useService } from "@web/core/utils/hooks"; | 
				
			|||
const { Component, mount} = owl | 
				
			|||
export class DynamicDashboard extends Component { | 
				
			|||
    setup(){ | 
				
			|||
        this.action = useService("action"); | 
				
			|||
        this.rpc = this.env.services.rpc | 
				
			|||
        this.renderDashboard() | 
				
			|||
    } | 
				
			|||
    async renderDashboard() { | 
				
			|||
        const action = this.action | 
				
			|||
        const rpc = this.rpc | 
				
			|||
        await this.rpc('/get/values', {'action_id': this.props.actionId}).then(function(response){ | 
				
			|||
            if ($('.o_dynamic_dashboard')[0]){ | 
				
			|||
                for (let i = 0; i < response.length; i++) { | 
				
			|||
                    if (response[i].type === 'tile'){ | 
				
			|||
                        mount(DynamicDashboardTile, $('.o_dynamic_tile')[0], { props: { | 
				
			|||
                            widget: response[i], doAction: action | 
				
			|||
                        }}); | 
				
			|||
                    } | 
				
			|||
                    else{ | 
				
			|||
                        mount(DynamicDashboardChart, $('.o_dynamic_graph')[0], { props: { | 
				
			|||
                            widget: response[i], doAction: action, rpc: rpc | 
				
			|||
                        }}); | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
    async _onClick_add_block(e){ | 
				
			|||
        var self = this; | 
				
			|||
        var self_props = this.props; | 
				
			|||
        var self_env = self.env; | 
				
			|||
        var type = $(e.target).attr('data-type'); | 
				
			|||
        await this.rpc('/create/tile',{'type' : type, 'action_id': self.props.actionId}).then(function(response){ | 
				
			|||
                if(response['type'] == 'tile'){ | 
				
			|||
                    mount(DynamicDashboardTile, $('.o_dynamic_tile')[0], { props: { | 
				
			|||
                        widget: response, doAction: self.action | 
				
			|||
                        }}); | 
				
			|||
                    } | 
				
			|||
                else{ | 
				
			|||
                    mount(DynamicDashboardChart, $('.o_dynamic_graph')[0], { props: { | 
				
			|||
                          widget: response, doAction: self.action | 
				
			|||
                          }}); | 
				
			|||
                } | 
				
			|||
            }) | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
DynamicDashboard.template = "owl.dynamic_dashboard" | 
				
			|||
registry.category("actions").add("owl.dynamic_dashboard", DynamicDashboard) | 
				
			|||
@ -1,82 +0,0 @@ | 
				
			|||
/** @odoo-module */ | 
				
			|||
 | 
				
			|||
import { registry} from '@web/core/registry'; | 
				
			|||
import { loadJS} from '@web/core/assets'; | 
				
			|||
import { getColor } from "@web/core/colors/colors"; | 
				
			|||
const { Component, xml, onWillStart, useRef, onMounted } = owl | 
				
			|||
 | 
				
			|||
export class DynamicDashboardChart extends Component { | 
				
			|||
    setup() { | 
				
			|||
        this.doAction = this.props.doAction.doAction | 
				
			|||
        this.chartRef = useRef("chart") | 
				
			|||
        onWillStart(async () => { | 
				
			|||
            await loadJS("https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js") | 
				
			|||
        }) | 
				
			|||
        onMounted(()=> this.renderChart()) | 
				
			|||
    } | 
				
			|||
    renderChart(){ | 
				
			|||
        if (this.props.widget.graph_type){ | 
				
			|||
            const x_axis = this.props.widget.x_axis | 
				
			|||
            const y_axis = this.props.widget.y_axis | 
				
			|||
            const data = [] | 
				
			|||
            for (let i = 0; i < x_axis.length; i++) { | 
				
			|||
                const value = { key: x_axis[i], value: y_axis[i] } | 
				
			|||
                data.push(value); | 
				
			|||
            } | 
				
			|||
            new Chart( | 
				
			|||
                this.chartRef.el, | 
				
			|||
                    { | 
				
			|||
                      type: this.props.widget.graph_type || 'bar', | 
				
			|||
                          data: { | 
				
			|||
                            labels: data.map(row => row.key), | 
				
			|||
                                datasets: [ | 
				
			|||
                                  { | 
				
			|||
                                    label: this.props.widget.measured_field, | 
				
			|||
                                    data: data.map(row => row.value), | 
				
			|||
                                    backgroundColor: data.map((_, index) => getColor(index)), | 
				
			|||
                                    hoverOffset : 4 | 
				
			|||
                                  } | 
				
			|||
                                ] | 
				
			|||
                          }, | 
				
			|||
                    } | 
				
			|||
              ); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
    async getConfiguration(){ | 
				
			|||
        var id = this.props.widget.id | 
				
			|||
        await this.doAction({ | 
				
			|||
              type: 'ir.actions.act_window', | 
				
			|||
              res_model: 'dashboard.block', | 
				
			|||
              res_id: id, | 
				
			|||
              view_mode: 'form', | 
				
			|||
              views: [[false, "form"]] | 
				
			|||
          }); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
DynamicDashboardChart.template = xml ` | 
				
			|||
<div style="padding-bottom:30px" t-att-class="this.props.widget.cols +' col-4 block'" t-att-data-id="this.props.widget.id"> | 
				
			|||
    <div class="card"> | 
				
			|||
        <div class="card-header"> | 
				
			|||
            <div class="row"> | 
				
			|||
                <div class="col"> | 
				
			|||
                        <h3><t t-esc="this.props.widget.name"/></h3> | 
				
			|||
                </div> | 
				
			|||
                <div class="col"> | 
				
			|||
                    <div style="float:right;"><i title="Configuration" class="fa fa-cog block_setting fa-2x cursor-pointer" t-on-click="getConfiguration"/></div> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
        </div> | 
				
			|||
        <div class="card-body" id="in_ex_body_hide"> | 
				
			|||
            <div class="row"> | 
				
			|||
                <div class="col-md-12 chart_canvas"> | 
				
			|||
                    <div id="chart_canvas"> | 
				
			|||
                        <canvas t-ref="chart"/> | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
        </div> | 
				
			|||
    </div> | 
				
			|||
</div> | 
				
			|||
` | 
				
			|||
 | 
				
			|||
 | 
				
			|||
@ -1,52 +0,0 @@ | 
				
			|||
/** @odoo-module */ | 
				
			|||
 | 
				
			|||
import { registry} from '@web/core/registry'; | 
				
			|||
const { Component, xml } = owl | 
				
			|||
export class DynamicDashboardTile extends Component { | 
				
			|||
    setup() { | 
				
			|||
        super.setup(...arguments); | 
				
			|||
        this.action = this.props.doAction | 
				
			|||
    } | 
				
			|||
    async getRecord() { | 
				
			|||
        var model_name = this.props.widget.model_name | 
				
			|||
        if (model_name){ | 
				
			|||
            await this.action.doAction({ | 
				
			|||
              type: 'ir.actions.act_window', | 
				
			|||
              res_model: model_name, | 
				
			|||
              view_mode: 'tree', | 
				
			|||
              views: [[false, "tree"]], | 
				
			|||
              domain: this.props.widget.domain, | 
				
			|||
          }); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
    async getConfiguration() { | 
				
			|||
        var id = this.props.widget.id | 
				
			|||
        await this.action.doAction({ | 
				
			|||
            type: 'ir.actions.act_window', | 
				
			|||
            res_model: 'dashboard.block', | 
				
			|||
            res_id: id, | 
				
			|||
            view_mode: 'form', | 
				
			|||
            views: [[false, "form"]] | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
DynamicDashboardTile.template = xml`<div class="col-sm-12 col-md-12 col-lg-3 tile block"
 | 
				
			|||
             t-att-data-id="this.props.widget.id"> | 
				
			|||
            <div  draggable="true" t-att-style="'background: ' + this.props.widget.tile_color " class="tile-container d-flex justify-content-around align-items-center  position-relative w-100 h-auto my-3"> | 
				
			|||
                <a t-on-click="getConfiguration" class="block_setting position-absolute tile-container__setting-icon cursor-pointer"> | 
				
			|||
                    <i class="fa fa-cog"></i> | 
				
			|||
                </a> | 
				
			|||
                <div t-on-click="getRecord" class="d-flex cursor-pointer"> | 
				
			|||
                <div t-att-style="'color: ' + this.props.widget.icon_color " class="tile-container__icon-container bg-white  d-flex justify-content-center align-items-center"> | 
				
			|||
                    <i t-att-class="this.props.widget.icon" aria-hidden="true"></i> | 
				
			|||
                </div> | 
				
			|||
                <div class="tile-container__status-container" t-att-style="'color: ' + this.props.widget.text_color "> | 
				
			|||
                    <h2 class="status-container__title" t-att-style="'color: ' + this.props.widget.text_color "><t  t-esc="this.props.widget.name"/></h2> | 
				
			|||
                    <div class="status-container__figures d-flex flex-wrap align-items-baseline"> | 
				
			|||
                        <h3 class="mb-0 mb-md-1 mb-lg-0 mr-1" t-att-style="'color: ' + this.props.widget.text_color "><t t-esc="this.props.widget.value"/></h3> | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
        </div>` | 
				
			|||
 | 
				
			|||
@ -0,0 +1,391 @@ | 
				
			|||
/** @odoo-module **/ | 
				
			|||
import { registry } from "@web/core/registry"; | 
				
			|||
import { loadJS } from '@web/core/assets'; | 
				
			|||
import { DynamicDashboardTile} from './dynamic_dashboard_tile'; | 
				
			|||
import { DynamicDashboardChart} from './dynamic_dashboard_chart'; | 
				
			|||
import { useService } from "@web/core/utils/hooks"; | 
				
			|||
const { Component, useRef, mount, onWillStart, onMounted} = owl; | 
				
			|||
 | 
				
			|||
export class OdooDynamicDashboard extends Component { | 
				
			|||
    // Setup function to run when the template of the class OdooDynamicDashboard renders
 | 
				
			|||
    setup() { | 
				
			|||
        this.ThemeSelector = useRef('ThemeSelector'); | 
				
			|||
        this.action = useService("action"); | 
				
			|||
        this.orm = useService("orm"); | 
				
			|||
        this.dialog = useService("dialog"); | 
				
			|||
        this.actionId = this.props.actionId | 
				
			|||
        this.rpc = useService("rpc"); | 
				
			|||
        onWillStart(async () => { | 
				
			|||
            await loadJS("https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js") | 
				
			|||
            await loadJS("https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js") | 
				
			|||
            await loadJS("https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js") | 
				
			|||
        }) | 
				
			|||
        onMounted(()=>{ | 
				
			|||
            this.renderDashboard(); | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    onChangeTheme(){ | 
				
			|||
        /* Function for changing color of the theme of the dashboard */ | 
				
			|||
        $(".container").attr('style', this.ThemeSelector.el.value + 'min-height:-webkit-fill-available;') | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    ResizeDrag() { | 
				
			|||
    /* Function for resizing and dragging the div resize-drag */ | 
				
			|||
        $('.items .resize-drag').each(function(index, element) { | 
				
			|||
            interact(element).resizable({ | 
				
			|||
                edges: { left: true, right: true, bottom: true, top: true }, | 
				
			|||
                listeners: { | 
				
			|||
                    move (event) { | 
				
			|||
                        var target = event.target | 
				
			|||
                        var x = (parseFloat(target.getAttribute('data-x')) || 0) | 
				
			|||
                        var y = (parseFloat(target.getAttribute('data-y')) || 0) | 
				
			|||
                        // update the element's style
 | 
				
			|||
                        target.style.width = event.rect.width + 'px' | 
				
			|||
                        target.style.height = event.rect.height + 'px' | 
				
			|||
                        // translate when resizing from top or left edges
 | 
				
			|||
                        x += event.deltaRect.left | 
				
			|||
                        y += event.deltaRect.top | 
				
			|||
                    } | 
				
			|||
                }, | 
				
			|||
                modifiers: [ | 
				
			|||
                    // keep the edges inside the parent
 | 
				
			|||
                    interact.modifiers.restrictEdges({ | 
				
			|||
                        outer: 'parent' | 
				
			|||
                    }), | 
				
			|||
                    // minimum size
 | 
				
			|||
                    interact.modifiers.restrictSize({ | 
				
			|||
                        min: { width: 100, height: 50 } | 
				
			|||
                    }) | 
				
			|||
                ], | 
				
			|||
                inertia: true | 
				
			|||
            }).draggable({ | 
				
			|||
                listeners: {move: dragMoveListener}, | 
				
			|||
                inertia: true, | 
				
			|||
                modifiers: [ | 
				
			|||
                    interact.modifiers.restrictRect({ | 
				
			|||
                        restriction: 'parent', | 
				
			|||
                        endOnly: true | 
				
			|||
                    }) | 
				
			|||
                ] | 
				
			|||
            }) | 
				
			|||
 | 
				
			|||
            function dragMoveListener (event) { | 
				
			|||
                var target = event.target | 
				
			|||
                // keep the dragged position in the data-x/data-y attributes
 | 
				
			|||
                var x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx | 
				
			|||
                var y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy | 
				
			|||
                // translate the element
 | 
				
			|||
                target.style.transform = 'translate(' + x + 'px, ' + y + 'px)' | 
				
			|||
                // update the posiion attributes
 | 
				
			|||
                target.setAttribute('data-x', x) | 
				
			|||
                target.setAttribute('data-y', y) | 
				
			|||
            } | 
				
			|||
            // this function is used later in the resizing
 | 
				
			|||
            window.dragMoveListener = dragMoveListener | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async renderDashboard(){ | 
				
			|||
    /* Function for rendering the dashboard */ | 
				
			|||
        var self = this; | 
				
			|||
        $("#save_layout").hide(); | 
				
			|||
        await this.orm.call('dashboard.theme', 'get_records', [[]]).then(function (response) { | 
				
			|||
            response.forEach((ev) => { | 
				
			|||
                const options = document.createElement("option"); | 
				
			|||
                options.value = ev.style; | 
				
			|||
                options.text = ev.name; | 
				
			|||
                self.ThemeSelector.el.append(options) | 
				
			|||
            }); | 
				
			|||
        }) | 
				
			|||
        await this.orm.call("dashboard.block", "get_dashboard_vals", [[], this.actionId]).then( function (response){ | 
				
			|||
            for (let i = 0; i < response.length; i++) { | 
				
			|||
                if (response[i].type === 'tile'){ | 
				
			|||
                    mount(DynamicDashboardTile, $('.items')[0], { props: { | 
				
			|||
                        widget: response[i], doAction: self.action, dialog:self.dialog, orm: self.orm | 
				
			|||
                    }}); | 
				
			|||
                } | 
				
			|||
                else{ | 
				
			|||
                    mount(DynamicDashboardChart, $('.items')[0], { props: { | 
				
			|||
                        widget: response[i], doAction: self.action, rpc: self.rpc, dialog:self.dialog, orm: self.orm | 
				
			|||
                    }}); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    editLayout(ev) { | 
				
			|||
    /* Function for editing the layout , it enables resizing and dragging functionality */ | 
				
			|||
        $('.items .resize-drag').each(function(index, element) { | 
				
			|||
            interact(element).draggable(true) | 
				
			|||
            interact(element).resizable(true) | 
				
			|||
        }); | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        $("#edit_layout").hide(); | 
				
			|||
        $("#save_layout").show(); | 
				
			|||
        this.ResizeDrag() | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    saveLayout(ev){ | 
				
			|||
    /* Function for saving the layout */ | 
				
			|||
        var self = this; | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        $("#edit_layout").show(); | 
				
			|||
        $("#save_layout").hide(); | 
				
			|||
        var data_list = [] | 
				
			|||
        $('.items .resize-drag').each(function(index, element) { | 
				
			|||
            interact(element).draggable(false) | 
				
			|||
            interact(element).resizable(false) | 
				
			|||
            data_list.push({ | 
				
			|||
                'id' : element.dataset['id'], | 
				
			|||
                'data-x': element.dataset['x'], | 
				
			|||
                'data-y': element.dataset['y'], | 
				
			|||
                'height': element.clientHeight, | 
				
			|||
                'width': element.clientWidth, | 
				
			|||
            }) | 
				
			|||
        }); | 
				
			|||
        self.orm.call('dashboard.block','get_save_layout', [[], data_list]).then( function (response){ | 
				
			|||
            window.location.reload(); | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    changeViewMode(ev){ | 
				
			|||
    /* Function for changing the mode of the view */ | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        const currentMode = $(".mode").attr("mode"); | 
				
			|||
        if (currentMode == "light"){ | 
				
			|||
            $('.theme').attr('style','display: none;') | 
				
			|||
            $(".container").attr('style', 'background-color: #383E45;min-height:-webkit-fill-available; !important') | 
				
			|||
            $(".mode").attr("mode", "dark") | 
				
			|||
            $(".bi-moon-stars-fill").attr('class', 'bi bi-cloud-sun-fill view-mode-icon') | 
				
			|||
            $(".bi-cloud-sun-fill").attr('style', 'color:black;margin-left:10px;') | 
				
			|||
            $(".mode").attr('style','display: none !important'); | 
				
			|||
            $("#search-input-chart").attr('style', 'background-color: white; !important') | 
				
			|||
            $("#search-button").attr('style', 'background-color: #BB86FC; !important') | 
				
			|||
            $("#dropdownMenuButton").attr('style', 'background-color: #03DAC5;margin-top:-4px; !important') | 
				
			|||
            $("#text_add").attr('style', 'color: black; !important') | 
				
			|||
            $(".date-label").attr('style', 'color: black;font-family:monospace; !important') | 
				
			|||
            $(".block_setting").attr('style', 'color: white; !important') | 
				
			|||
            $(".block_delete").attr('style', 'color: white; !important') | 
				
			|||
            $(".block_image").attr('style', 'color: #03DAC5; !important') | 
				
			|||
            $(".block_pdf").attr('style', 'color: #03DAC5; !important') | 
				
			|||
            $(".block_csv").attr('style', 'color: #03DAC5; !important') | 
				
			|||
            $(".block_xlsx").attr('style', 'color: #03DAC5; !important') | 
				
			|||
        } | 
				
			|||
        else { | 
				
			|||
            $('.theme').attr('style','display: block;') | 
				
			|||
            $(".container").attr('style', this.ThemeSelector.el.value + 'min-height:-webkit-fill-available;') | 
				
			|||
            $(".mode").attr("mode", "light") | 
				
			|||
            $(".bi-cloud-sun-fill").attr('class', 'bi bi-moon-stars-fill view-mode-icon') | 
				
			|||
            $(".view-mode-icon").attr('style', 'color:black;margin-left:10px; !important') | 
				
			|||
            $(".mode").attr('style','display: none !important'); | 
				
			|||
            $(".mode").attr('style','color: white !important'); | 
				
			|||
            $("#search-input-chart").attr('style', 'background-color: none; !important') | 
				
			|||
            $("#search-button").attr('style', 'background-color: none; !important') | 
				
			|||
            $("#dropdownMenuButton").attr('style', 'background-color: none;margin-top:-4px; !important') | 
				
			|||
            $("#text_add").attr('style', 'color: white; !important') | 
				
			|||
            $(".date-label").attr('style', 'color: black; !important;font-family:monospace; !important') | 
				
			|||
            $(".block_setting").attr('style', 'color: black; !important') | 
				
			|||
            $(".block_delete").attr('style', 'color: black; !important') | 
				
			|||
            $(".block_image").attr('style', 'color: black; !important') | 
				
			|||
            $(".block_pdf").attr('style', 'color: black; !important') | 
				
			|||
            $(".block_csv").attr('style', 'color: black; !important') | 
				
			|||
            $(".block_xlsx").attr('style', 'color: black; !important') | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
    onClickAdd(event){ | 
				
			|||
    /* For enabling the toggle button */ | 
				
			|||
        event.stopPropagation(); | 
				
			|||
        event.preventDefault(); | 
				
			|||
        $(".dropdown-addblock").toggle() | 
				
			|||
    } | 
				
			|||
    onClickAddItem(event){ | 
				
			|||
    /* Function for adding tiles and charts */ | 
				
			|||
        event.stopPropagation(); | 
				
			|||
        event.preventDefault(); | 
				
			|||
        self = this; | 
				
			|||
        var type = event.target.getAttribute('data-type'); | 
				
			|||
        if (type == 'graph'){ | 
				
			|||
            var chart_type = event.target.getAttribute('data-chart_type'); | 
				
			|||
        } | 
				
			|||
        if (type == 'tile'){ | 
				
			|||
            var randomColor = '#' + ('000000' + Math.floor(Math.random() * 16777216).toString(16)).slice(-6); | 
				
			|||
            this.action.doAction({ | 
				
			|||
                type: 'ir.actions.act_window', | 
				
			|||
                res_model: 'dashboard.block', | 
				
			|||
                view_mode: 'form', | 
				
			|||
                views: [[false, 'form']], | 
				
			|||
                context: { | 
				
			|||
                    'form_view_initial_mode': 'edit', | 
				
			|||
                    'default_name': 'New Tile', | 
				
			|||
                    'default_type': type, | 
				
			|||
                    'default_height': '155px', | 
				
			|||
                    'default_width': '300px', | 
				
			|||
                    'default_tile_color': randomColor, | 
				
			|||
                    'default_text_color': '#FFFFFF', | 
				
			|||
                    'default_val_color': '#F3F3F3', | 
				
			|||
                    'default_fa_icon': 'fa fa-bar-chart', | 
				
			|||
                    'default_client_action_id': parseInt(self.actionId) | 
				
			|||
                } | 
				
			|||
            }) | 
				
			|||
        } | 
				
			|||
        else{ | 
				
			|||
            this.action.doAction({ | 
				
			|||
                type: 'ir.actions.act_window', | 
				
			|||
                res_model: 'dashboard.block', | 
				
			|||
                view_mode: 'form', | 
				
			|||
                views: [[false, 'form']], | 
				
			|||
                context: { | 
				
			|||
                    'form_view_initial_mode': 'edit', | 
				
			|||
                    'default_name': 'New ' + chart_type, | 
				
			|||
                    'default_type': type, | 
				
			|||
                    'default_height': '565px', | 
				
			|||
                    'default_width': '588px', | 
				
			|||
                    'default_graph_type': chart_type, | 
				
			|||
                    'default_fa_icon': 'fa fa-bar-chart', | 
				
			|||
                    'default_client_action_id': parseInt(self.actionId) | 
				
			|||
                }, | 
				
			|||
            }) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    dateFilter(){ | 
				
			|||
    /* Function for filtering the data based on the creation date */ | 
				
			|||
        $(".items").empty(); | 
				
			|||
        var start_date = $("#start-date").val(); | 
				
			|||
        var end_date = $("#end-date").val(); | 
				
			|||
        var self = this; | 
				
			|||
        if (!start_date){ | 
				
			|||
            start_date = "null" | 
				
			|||
        } | 
				
			|||
        if (!end_date){ | 
				
			|||
            end_date = "null" | 
				
			|||
        } | 
				
			|||
        this.orm.call("dashboard.block", "get_dashboard_vals", [[], this.actionId, start_date, end_date]).then( function (response){ | 
				
			|||
            for (let i = 0; i < response.length; i++) { | 
				
			|||
                if (response[i].type === 'tile'){ | 
				
			|||
                    mount(DynamicDashboardTile, $('.items')[0], { props: { | 
				
			|||
                        widget: response[i], doAction: self.action, dialog:self.dialog, orm: self.orm | 
				
			|||
                    }}); | 
				
			|||
                } | 
				
			|||
                else{ | 
				
			|||
                    mount(DynamicDashboardChart, $('.items')[0], { props: { | 
				
			|||
                        widget: response[i], doAction: self.action, rpc: self.rpc, dialog:self.dialog, orm: self.orm | 
				
			|||
                    }}); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async clickSearch(){ | 
				
			|||
    /* Function for searching the blocks with their names */ | 
				
			|||
        var input = $("#search-input-chart").val(); | 
				
			|||
        await this.rpc('/custom_dashboard/search_input_chart', {'search_input': input}).then(function (response) { | 
				
			|||
            var blocks = $(".items .resize-drag"); | 
				
			|||
            blocks.each(function(index, element){ | 
				
			|||
                var dataId = $(element).data('id'); | 
				
			|||
                if (response.includes(dataId)){ | 
				
			|||
                    $(element).css("visibility", "visible"); | 
				
			|||
                } | 
				
			|||
                else{ | 
				
			|||
                    $(element).css("visibility", "hidden"); | 
				
			|||
                } | 
				
			|||
            }) | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    showViewMode(ev){ | 
				
			|||
    /* Function for showing the mode text */ | 
				
			|||
        const currentMode = $(".mode").attr("mode"); | 
				
			|||
        if (currentMode == "light"){ | 
				
			|||
            $(".mode").text("Dark Mode") | 
				
			|||
            $(".mode").attr('style','display: inline-block !important; color: black !important'); | 
				
			|||
        } | 
				
			|||
        else{ | 
				
			|||
            $(".mode").text("Light Mode") | 
				
			|||
            $(".mode").attr('style','display: inline-block !important; color: black !important'); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
    hideViewMode(ev){ | 
				
			|||
    /* Function for hiding the mode text */ | 
				
			|||
        $(".mode").fadeOut(2000); | 
				
			|||
    } | 
				
			|||
    clearSearch(){ | 
				
			|||
    /* Function for clearing the search input */ | 
				
			|||
        $("#search-input-chart").val(''); | 
				
			|||
        var blocks = $(".items .resize-drag"); | 
				
			|||
        blocks.each(function(index, element){ | 
				
			|||
            $(element).css("visibility", "visible"); | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async printPdf() { | 
				
			|||
    /* Function for printing whole dashboard in pdf format */ | 
				
			|||
        var elements = $('.items .resize-drag') | 
				
			|||
        var newElement = document.createElement('div'); | 
				
			|||
        newElement.className = 'pdf'; | 
				
			|||
        elements.each(function(index, elem){ | 
				
			|||
            newElement.appendChild(elem); | 
				
			|||
        }); | 
				
			|||
        for (var x=0; x< $(newElement)[0].children.length; x++){ | 
				
			|||
            $($(newElement)[0].children[x])[0].style.transform = "" | 
				
			|||
        } | 
				
			|||
        var opt = { | 
				
			|||
            margin:       0.3, | 
				
			|||
            filename:     'Dashboard.pdf', | 
				
			|||
            image:        { type: 'jpeg', quality: 1 }, | 
				
			|||
            html2canvas:  { scale: 1 }, | 
				
			|||
            jsPDF:        { unit: 'mm', format: 'a3', orientation: 'portrait' } | 
				
			|||
        }; | 
				
			|||
        html2pdf().set(opt).from(newElement).save().then(()=>{ | 
				
			|||
            window.location.reload() | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async createPDF(){ | 
				
			|||
    /* Function for getting pdf data in string format */ | 
				
			|||
        var elements = $('.items .resize-drag') | 
				
			|||
        var newElement = document.createElement('div'); | 
				
			|||
        newElement.className = 'pdf'; | 
				
			|||
        elements.each(function(index, elem){ | 
				
			|||
            newElement.appendChild(elem); | 
				
			|||
        }); | 
				
			|||
        for (var x=0; x< $(newElement)[0].children.length; x++){ | 
				
			|||
            $($(newElement)[0].children[x])[0].style.transform = "" | 
				
			|||
        } | 
				
			|||
        var opt = { | 
				
			|||
            margin:       0.3, | 
				
			|||
            filename:     'Dashboard.pdf', | 
				
			|||
            image:        { type: 'jpeg', quality: 1 }, | 
				
			|||
            html2canvas:  { scale: 1 }, | 
				
			|||
            jsPDF:        { unit: 'mm', format: 'a3', orientation: 'portrait' } | 
				
			|||
        }; | 
				
			|||
        var pdf = html2pdf().set(opt).from(newElement).toPdf() | 
				
			|||
        var pdfOutput = await pdf.output('datauristring'); | 
				
			|||
        console.log(pdfOutput) | 
				
			|||
        return pdfOutput | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async sendMail(){ | 
				
			|||
        /* Function for creating pdf and sending mail to the selected users */ | 
				
			|||
        /* This function calls the createPDF() function and returns the pdf datas */ | 
				
			|||
        var created_pdf = await this.createPDF(); | 
				
			|||
        var base64code = created_pdf.split(',')[1]; | 
				
			|||
        this.action.doAction({ | 
				
			|||
            type: 'ir.actions.act_window', | 
				
			|||
            name: 'SEND MAIL', | 
				
			|||
            res_model: 'dashboard.mail', | 
				
			|||
            view_mode: 'form', | 
				
			|||
            views: [[false, 'form']], | 
				
			|||
            target: 'new', | 
				
			|||
            context: { | 
				
			|||
                'default_base64code': base64code, | 
				
			|||
            } | 
				
			|||
        }) | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
OdooDynamicDashboard.template = "owl.OdooDynamicDashboard" | 
				
			|||
registry.category("actions").add("OdooDynamicDashboard", OdooDynamicDashboard) | 
				
			|||
@ -0,0 +1,215 @@ | 
				
			|||
/** @odoo-module **/ | 
				
			|||
import { loadJS } from '@web/core/assets'; | 
				
			|||
import { getColor } from "@web/core/colors/colors"; | 
				
			|||
import { _t } from "@web/core/l10n/translation"; | 
				
			|||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; | 
				
			|||
const { Component, xml, onWillStart, useRef, onMounted } = owl | 
				
			|||
 | 
				
			|||
export class DynamicDashboardChart extends Component { | 
				
			|||
    // Setup function of the class DynamicDashboardChart
 | 
				
			|||
    setup() { | 
				
			|||
        this.doAction = this.props.doAction.doAction; | 
				
			|||
        this.chartRef = useRef("chart"); | 
				
			|||
        this.dialog = this.props.dialog; | 
				
			|||
        onWillStart(async () => { | 
				
			|||
            await loadJS("https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js") | 
				
			|||
            await loadJS("https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js") | 
				
			|||
            await loadJS("https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js") | 
				
			|||
        }) | 
				
			|||
        onMounted(()=> this.renderChart()) | 
				
			|||
    } | 
				
			|||
    // Function to export the chart in pdf, image, xlsx and csv format
 | 
				
			|||
    exportItem(ev){ | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        var type = $(ev.currentTarget).attr('data-type'); | 
				
			|||
        var canvas = $($($(ev.currentTarget)[0].offsetParent)[0].children[0].lastChild).find("#canvas")[0] | 
				
			|||
        var dataTitle = $(canvas).attr('data-title') | 
				
			|||
        var bgCanvas = document.createElement("canvas"); | 
				
			|||
        bgCanvas.width = canvas.width; | 
				
			|||
        bgCanvas.height = canvas.height; | 
				
			|||
        var bgCtx = bgCanvas.getContext("2d"); | 
				
			|||
        bgCtx.fillStyle = "white"; | 
				
			|||
        bgCtx.fillRect(0, 0, canvas.width, canvas.height); | 
				
			|||
        bgCtx.drawImage(canvas, 0, 0); | 
				
			|||
        var imgData = bgCanvas.toDataURL("image/png"); | 
				
			|||
        if (type === 'png') { | 
				
			|||
            var downloadLink = document.createElement('a'); | 
				
			|||
            downloadLink.href = imgData; | 
				
			|||
            downloadLink.download = `${dataTitle}.png`; | 
				
			|||
            document.body.appendChild(downloadLink); | 
				
			|||
            downloadLink.click(); | 
				
			|||
            document.body.removeChild(downloadLink); | 
				
			|||
        } | 
				
			|||
        if (type === 'pdf') { | 
				
			|||
            var pdf = new jsPDF(); | 
				
			|||
            pdf.addImage(imgData, 'PNG', 0, 0); | 
				
			|||
            pdf.save(`${dataTitle}.pdf`); | 
				
			|||
        } | 
				
			|||
        if (type === 'xlsx'){ | 
				
			|||
            var rows = []; | 
				
			|||
            var items = $('.resize-drag'); | 
				
			|||
            for (let i = 0; i < items.length; i++) { | 
				
			|||
                if ($(items[i]).attr('data-id') === $(ev.currentTarget).attr('data-id')) { | 
				
			|||
                    rows.push(this.props.widget.x_axis); | 
				
			|||
                    rows.push(this.props.widget.y_axis); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
            // Prepare the workbook
 | 
				
			|||
            const workbook = new ExcelJS.Workbook(); | 
				
			|||
            const worksheet = workbook.addWorksheet('My Sheet'); | 
				
			|||
            for(let i = 0; i < rows.length; i++){ | 
				
			|||
                worksheet.addRow(rows[i]); | 
				
			|||
            } | 
				
			|||
            const image = workbook.addImage({ | 
				
			|||
              base64: imgData, | 
				
			|||
              extension: 'png', | 
				
			|||
            }); | 
				
			|||
            worksheet.addImage(image, { | 
				
			|||
              tl: { col: 0, row: 4 }, | 
				
			|||
              ext: { width: canvas.width, height: canvas.height } | 
				
			|||
            }); | 
				
			|||
            // Save workbook to a file
 | 
				
			|||
            workbook.xlsx.writeBuffer() | 
				
			|||
            .then((buffer) => { | 
				
			|||
                // Create a Blob object from the buffer
 | 
				
			|||
                let blob = new Blob([buffer], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); | 
				
			|||
                let link = document.createElement('a'); | 
				
			|||
                link.href = window.URL.createObjectURL(blob); | 
				
			|||
                link.setAttribute("download", `${dataTitle}.xlsx`); | 
				
			|||
                document.body.appendChild(link); | 
				
			|||
                link.click(); | 
				
			|||
                document.body.removeChild(link); | 
				
			|||
            }) | 
				
			|||
        } | 
				
			|||
        if (type === 'csv') { | 
				
			|||
            var rows = []; | 
				
			|||
            var items = $('.resize-drag') | 
				
			|||
            for (let i = 0; i < items.length; i++) { | 
				
			|||
                if ($(items[i]).attr('data-id') === $(ev.currentTarget).attr('data-id')) { | 
				
			|||
                    rows.push(this.props.widget.x_axis); | 
				
			|||
                    rows.push(this.props.widget.y_axis); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
            let csvContent = "data:text/csv;charset=utf-8,"; | 
				
			|||
            rows.forEach(function (rowArray) { | 
				
			|||
                let row = rowArray.join(","); | 
				
			|||
                csvContent += row + "\r\n"; | 
				
			|||
            }); | 
				
			|||
            var link = document.createElement("a"); | 
				
			|||
            link.setAttribute("href", encodeURI(csvContent)); | 
				
			|||
            link.setAttribute("download", `${dataTitle}.csv`); | 
				
			|||
            document.body.appendChild(link); | 
				
			|||
            link.click(); | 
				
			|||
            document.body.removeChild(link); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
    // Function to get the configuration of the chart
 | 
				
			|||
    async getConfiguration(ev){ | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        var id = this.props.widget.id | 
				
			|||
        await this.doAction({ | 
				
			|||
            type: 'ir.actions.act_window', | 
				
			|||
            res_model: 'dashboard.block', | 
				
			|||
            res_id: id, | 
				
			|||
            view_mode: 'form', | 
				
			|||
            views: [[false, "form"]] | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
    // Function to remove the chart
 | 
				
			|||
    async removeTile(ev){ | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        this.dialog.add(ConfirmationDialog, { | 
				
			|||
            title: _t("Delete Confirmation"), | 
				
			|||
            body: _t("Are you sure you want to delete this item?"), | 
				
			|||
            confirmLabel: _t("YES, I'M SURE"), | 
				
			|||
            cancelLabel: _t("NO, GO BACK"), | 
				
			|||
            confirm: async () => { | 
				
			|||
                await this.props.orm.unlink("dashboard.block", [this.props.widget.id]); | 
				
			|||
                location.reload(); | 
				
			|||
            }, | 
				
			|||
            cancel: () => {}, | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
    // Function to render the chart
 | 
				
			|||
    renderChart(){ | 
				
			|||
        if (this.props.widget.graph_type){ | 
				
			|||
            const x_axis = this.props.widget.x_axis | 
				
			|||
            const y_axis = this.props.widget.y_axis | 
				
			|||
            const data = [] | 
				
			|||
            for (let i = 0; i < x_axis.length; i++) { | 
				
			|||
                const value = { key: x_axis[i], value: y_axis[i] } | 
				
			|||
                data.push(value); | 
				
			|||
            } | 
				
			|||
            new Chart( | 
				
			|||
                this.chartRef.el, | 
				
			|||
                    { | 
				
			|||
                        type: this.props.widget.graph_type || 'bar', | 
				
			|||
                        data: { | 
				
			|||
                            labels: data.map(row => row.key), | 
				
			|||
                            datasets: [ | 
				
			|||
                                { | 
				
			|||
                                    label: this.props.widget.measured_field || 'Data', | 
				
			|||
                                    data: data.map(row => row.value), | 
				
			|||
                                    backgroundColor: data.map((_, index) => getColor(index)), | 
				
			|||
                                    hoverOffset : 4 | 
				
			|||
                                } | 
				
			|||
                            ] | 
				
			|||
                        }, | 
				
			|||
                    } | 
				
			|||
              ); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
DynamicDashboardChart.template = xml` | 
				
			|||
    <div class="resize-drag block card" | 
				
			|||
        t-att-data-x="this.props.widget.data_x" | 
				
			|||
        t-att-data-y="this.props.widget.data_y" | 
				
			|||
        t-att-style="'height:'+this.props.widget.height+'; width:'+ this.props.widget.width+ '; transform: translate('+ this.props.widget.translate_x +', '+ this.props.widget.translate_y +');'" | 
				
			|||
        t-att-data-id="this.props.widget.id"> | 
				
			|||
        <div class="card-body mt-1" id="in_ex_body_hide"> | 
				
			|||
 | 
				
			|||
            <div class="block_edit block_setting" t-on-click="(ev) => this.getConfiguration(ev)"> | 
				
			|||
                <i title="Configuration" | 
				
			|||
                    class="fa fa-pencil block_setting chart-edit"/> | 
				
			|||
            </div> | 
				
			|||
 | 
				
			|||
            <div class="block_edit block_image" data-type="png" t-on-click="(ev) => this.exportItem(ev)"> | 
				
			|||
                <i title="Save As Image" | 
				
			|||
                    class="bi bi-image block_image chart-image"/> | 
				
			|||
            </div> | 
				
			|||
 | 
				
			|||
            <div class="block_edit block_pdf" data-type="pdf" t-on-click="(ev) => this.exportItem(ev)"> | 
				
			|||
                <i title="Export to PDF" | 
				
			|||
                    class="bi bi-file-earmark-pdf block_pdf chart-pdf"/> | 
				
			|||
            </div> | 
				
			|||
 | 
				
			|||
            <div class="block_edit block_csv" t-att-data-id="this.props.widget.id" data-type="csv" t-on-click="(ev) => this.exportItem(ev)"> | 
				
			|||
                <i title="Export to CSV" | 
				
			|||
                    class="bi bi-filetype-csv block_csv chart-csv"/> | 
				
			|||
            </div> | 
				
			|||
 | 
				
			|||
            <div class="block_edit block_xlsx" t-att-data-id="this.props.widget.id" data-type="xlsx" t-on-click="(ev) => this.exportItem(ev)"> | 
				
			|||
                <i title="Export to XLSX" | 
				
			|||
                    class="fa fa-file-excel-o block_xlsx chart-xlsx"/> | 
				
			|||
            </div> | 
				
			|||
 | 
				
			|||
            <div class="block_edit block_delete" t-on-click="(ev) => this.removeTile(ev)"> | 
				
			|||
                <i title="Delete" | 
				
			|||
                    class="fa fa-times block_delete chart-setting"/> | 
				
			|||
            </div> | 
				
			|||
            <h3 class="chart_title"> | 
				
			|||
                <t t-esc="this.props.widget.name"/> | 
				
			|||
            </h3> | 
				
			|||
            <div class="row"> | 
				
			|||
                <div class="col-md-12 chart_canvas" id="chart_canvas" | 
				
			|||
                    t-att-data-id="this.props.widget.id"> | 
				
			|||
                    <canvas id="canvas" t-ref="chart" t-att-data-title="this.props.widget.name"/> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
        </div> | 
				
			|||
    </div> | 
				
			|||
                ` | 
				
			|||
@ -0,0 +1,89 @@ | 
				
			|||
/** @odoo-module **/ | 
				
			|||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; | 
				
			|||
import { _t } from "@web/core/l10n/translation"; | 
				
			|||
const { Component, xml } = owl; | 
				
			|||
 | 
				
			|||
export class DynamicDashboardTile extends Component { | 
				
			|||
    // Setup function of the class DynamicDashboardTile
 | 
				
			|||
    setup() { | 
				
			|||
        this.doAction = this.props.doAction.doAction; | 
				
			|||
        this.dialog = this.props.dialog; | 
				
			|||
        this.orm = this.props.orm; | 
				
			|||
    } | 
				
			|||
    // Function to get the configuration of the tile
 | 
				
			|||
    async getConfiguration(ev){ | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        var id = this.props.widget.id | 
				
			|||
        await this.doAction({ | 
				
			|||
              type: 'ir.actions.act_window', | 
				
			|||
              res_model: 'dashboard.block', | 
				
			|||
              res_id: id, | 
				
			|||
              view_mode: 'form', | 
				
			|||
              views: [[false, "form"]] | 
				
			|||
          }); | 
				
			|||
    } | 
				
			|||
    // Function to remove the tile
 | 
				
			|||
    async removeTile(ev){ | 
				
			|||
        ev.stopPropagation(); | 
				
			|||
        ev.preventDefault(); | 
				
			|||
        this.dialog.add(ConfirmationDialog, { | 
				
			|||
            title: _t("Delete Confirmation"), | 
				
			|||
            body: _t("Are you sure you want to delete this item?"), | 
				
			|||
            confirmLabel: _t("YES, I'M SURE"), | 
				
			|||
            cancelLabel: _t("NO, GO BACK"), | 
				
			|||
            confirm: async () => { | 
				
			|||
                await this.orm.unlink("dashboard.block", [this.props.widget.id]); | 
				
			|||
                location.reload(); | 
				
			|||
            }, | 
				
			|||
            cancel: () => {}, | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
    // Function for getting records by double click
 | 
				
			|||
    async getRecords(){ | 
				
			|||
        var model_name = this.props.widget.model_name; | 
				
			|||
        if (model_name){ | 
				
			|||
            await this.doAction({ | 
				
			|||
              type: 'ir.actions.act_window', | 
				
			|||
              res_model: model_name, | 
				
			|||
              view_mode: 'tree', | 
				
			|||
              views: [[false, "tree"]], | 
				
			|||
              domain: this.props.widget.domain, | 
				
			|||
          }); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
DynamicDashboardTile.template = xml ` | 
				
			|||
    <div class="resize-drag tile" | 
				
			|||
        t-on-dblclick="getRecords" | 
				
			|||
        t-att-data-id="this.props.widget.id" | 
				
			|||
        t-att-data-x="this.props.widget.data_x" | 
				
			|||
        t-att-data-y="this.props.widget.data_y" | 
				
			|||
        t-att-style="this.props.widget.color+this.props.widget.text_color+ 'height:'+this.props.widget.height+';width:'+this.props.widget.width + '; transform: translate('+ this.props.widget.translate_x +', '+ this.props.widget.translate_y +');'"> | 
				
			|||
        <div t-att-style="this.props.widget.color+this.props.widget.text_color" | 
				
			|||
            class="d-flex align-items-center  w-100  my-3"> | 
				
			|||
            <a class="block_setting tile_edit tile-container__setting-icon" style="color:black;" t-on-click="(ev) => this.getConfiguration(ev)" > | 
				
			|||
                <i class="fa fa-edit"/> | 
				
			|||
            </a> | 
				
			|||
            <a class="block_delete tile_edit tile-container__delete-icon" style="color:black;" t-on-click="(ev) => this.removeTile(ev)"> | 
				
			|||
                <i class="fa fa-times"/> | 
				
			|||
            </a> | 
				
			|||
            <div t-att-style="this.props.widget.icon_color" | 
				
			|||
                 class="tile-container__icon-container bg-white d-flex justify-content-center align-items-center"> | 
				
			|||
                <i t-att-class="this.props.widget.icon"/> | 
				
			|||
            </div> | 
				
			|||
            <div t-att-style="this.props.widget.text_color" | 
				
			|||
                 class="tile-container__status-container"> | 
				
			|||
                <h2 t-att-style="this.props.widget.text_color" | 
				
			|||
                    class="status-container__title"> | 
				
			|||
                    <t t-esc="this.props.widget.name"/> | 
				
			|||
                </h2> | 
				
			|||
                <div class="status-container__figures d-flex flex-wrap align-items-baseline"> | 
				
			|||
                    <h3 class="mb-0 mb-md-1 mb-lg-0 mr-1" | 
				
			|||
                        t-att-style="this.props.widget.val_color"> | 
				
			|||
                        <t t-esc="this.props.widget.value"/> | 
				
			|||
                    </h3> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
        </div> | 
				
			|||
    </div>` | 
				
			|||
@ -0,0 +1,208 @@ | 
				
			|||
 | 
				
			|||
:root { | 
				
			|||
  /* Colors */ | 
				
			|||
  --green: #00C689; | 
				
			|||
  --blue: #3DA5F4; | 
				
			|||
  --red: #F1536E; | 
				
			|||
  --yellow: #FDA006; | 
				
			|||
  /*Fonts*/ | 
				
			|||
  --primary-font: 'Roboto', sans-serif; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
html .o_web_client > .o_action_manager { | 
				
			|||
  overflow: auto; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bg-green { | 
				
			|||
  background-color: var(--green); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bg-blue { | 
				
			|||
  background-color: var(--blue); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bg-red { | 
				
			|||
  background-color: var(--red); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.bg-yellow { | 
				
			|||
  background-color: var(--yellow); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.text-color-yellow { | 
				
			|||
  color: var(--yellow); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.text-color-green { | 
				
			|||
  color: var(--green); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.text-color-blue { | 
				
			|||
  color: var(--blue); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.text-color-red { | 
				
			|||
  color: var(--red); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.text-color-yellow { | 
				
			|||
  color: var(--yellow); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.tile-container__icon-container { | 
				
			|||
  border-radius: 50%; | 
				
			|||
  width: 4.75rem; | 
				
			|||
  height: 4.75rem; | 
				
			|||
  font-size: 28px; | 
				
			|||
  margin-left: 15px; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.tile-container__status-container { | 
				
			|||
  margin-left: 2em; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.status-container__title { | 
				
			|||
  font-family: var(--primary-font); | 
				
			|||
  font-weight: 500; | 
				
			|||
  font-size: 1.5rem; | 
				
			|||
  line-height: 1.5rem; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.status-container__figures { | 
				
			|||
  font-family: var(--primary-font); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.status-container__figures > h3 { | 
				
			|||
  font-weight: 700; | 
				
			|||
  font-size: 1.5rem; | 
				
			|||
  line-height: 1.813rem; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.tile-container__setting-icon { | 
				
			|||
  top: 0.638rem; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.dark-theme { | 
				
			|||
    /* Add dark theme styles here */ | 
				
			|||
    .block_edit { | 
				
			|||
    color: #b7b7b7 !important; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
.theme_icon:hover { | 
				
			|||
text-shadow: 0px 0px 5px #9388ff; | 
				
			|||
} | 
				
			|||
.add_block{ | 
				
			|||
  color: #dfdfdf; | 
				
			|||
} | 
				
			|||
  #ExportMenu { | 
				
			|||
    color: #626262; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .chart_title { | 
				
			|||
    color: #dfdfdf; | 
				
			|||
    margin: -0.5em 0 2em 0; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .btn-search_edit, { | 
				
			|||
    color: #313131; | 
				
			|||
    border: none; | 
				
			|||
    background-color: #979797 !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  #search-input-chart { | 
				
			|||
    color: #ffffff; | 
				
			|||
    border: 1px solid #4e4e4e; | 
				
			|||
    background-color: #2A2A2A !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  #search-clear { | 
				
			|||
    color: #1f1f1f !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  div.navbar { | 
				
			|||
    color: #909090; | 
				
			|||
    background-color: #2A2A2A !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .navbar-collapse { | 
				
			|||
    margin-bottom: 12px !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  h3 { | 
				
			|||
    color: #909090; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  div.dropdown-addblock { | 
				
			|||
    color: #909090; | 
				
			|||
    background-color: #2A2A2A !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  #dropdownMenuButton { | 
				
			|||
    color: #313131; | 
				
			|||
    background-color: #979797 !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  div.card-body { | 
				
			|||
    border-radius: 0.5em !important; | 
				
			|||
    background-color: #2a2a2a !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .o_kanban_record { | 
				
			|||
    background-color: #e8e8e8 !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .o_kanban_renderer { | 
				
			|||
    background-color: #e8e8e8 !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .oe_module_name { | 
				
			|||
    background-color: #ffffff !important; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  background-color: #1C1B1B; | 
				
			|||
  @media (max-width: 767px) { | 
				
			|||
    .navbar-light .navbar-toggler { | 
				
			|||
      color: #A7A7A72D; | 
				
			|||
      border-color: #F6F6F621; | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
.btn-align-items{ | 
				
			|||
    width: 134px; | 
				
			|||
    font-size: small; | 
				
			|||
    border-radius: revert; | 
				
			|||
    height: 33px; | 
				
			|||
} | 
				
			|||
#edit_layout{ | 
				
			|||
    background-color: #0c8444; | 
				
			|||
} | 
				
			|||
#save_layout{ | 
				
			|||
    background-color: #b53c5d; | 
				
			|||
    width: 134px; | 
				
			|||
    height: 33px; | 
				
			|||
    font-size: small; | 
				
			|||
    border-radius: revert; | 
				
			|||
} | 
				
			|||
#search-button{ | 
				
			|||
    width: 69px; | 
				
			|||
    margin-left: 5px; | 
				
			|||
} | 
				
			|||
#search-input-chart{ | 
				
			|||
    width: 206px; | 
				
			|||
    height: 34px; | 
				
			|||
    border: 1px solid black; | 
				
			|||
} | 
				
			|||
.search-clear{ | 
				
			|||
    margin-left: -80px; | 
				
			|||
} | 
				
			|||
label input{ | 
				
			|||
    appearance: none; | 
				
			|||
} | 
				
			|||
.mode{ | 
				
			|||
    padding-left: 7px; | 
				
			|||
    font-family: 'odoo_ui_icons'; | 
				
			|||
    display: none; | 
				
			|||
} | 
				
			|||
.view-mode-icon{ | 
				
			|||
    font-size: x-large; | 
				
			|||
} | 
				
			|||
@ -1,109 +0,0 @@ | 
				
			|||
.card{ | 
				
			|||
    border: none; | 
				
			|||
    border-radius: 0px; | 
				
			|||
    box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; | 
				
			|||
    -webkit-box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; | 
				
			|||
    -moz-box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; | 
				
			|||
} | 
				
			|||
.card-header{ | 
				
			|||
    background-color: transparent; | 
				
			|||
    border: none; | 
				
			|||
} | 
				
			|||
:root { | 
				
			|||
            /* Colors */ | 
				
			|||
            --green: #00C689; | 
				
			|||
            --blue: #3DA5F4; | 
				
			|||
            --red: #F1536E; | 
				
			|||
            --yellow: #FDA006; | 
				
			|||
            /*Fonts*/ | 
				
			|||
            --primary-font: 'Roboto', sans-serif; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        html .o_web_client > .o_action_manager { | 
				
			|||
        overflow:auto; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .bg-green { | 
				
			|||
            background-color: var(--green); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .bg-blue { | 
				
			|||
            background-color: var(--blue); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .bg-red { | 
				
			|||
            background-color: var(--red); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .bg-yellow { | 
				
			|||
            background-color: var(--yellow); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .text-color-yellow { | 
				
			|||
            color: var(--yellow); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .text-color-green { | 
				
			|||
            color: var(--green); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .text-color-blue { | 
				
			|||
            color: var(--blue); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .text-color-red { | 
				
			|||
            color: var(--red); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .text-color-yellow { | 
				
			|||
            color: var(--yellow); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .tile-container { | 
				
			|||
            padding: 3.2rem 1.5rem; | 
				
			|||
            border-radius: 2rem; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .tile-container__icon-container { | 
				
			|||
            border-radius: 50%; | 
				
			|||
            width: 4.75rem; | 
				
			|||
            height: 4.75rem; | 
				
			|||
            font-size: 28px; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .status-container__title { | 
				
			|||
            font-family: var(--primary-font); | 
				
			|||
            font-weight: 500; | 
				
			|||
            font-size: 1.5rem; | 
				
			|||
            line-height: 1.5rem; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .status-container__figures { | 
				
			|||
            font-family: var(--primary-font); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .status-container__figures>h3 { | 
				
			|||
            font-weight: 700; | 
				
			|||
            font-size: 1.5rem; | 
				
			|||
            line-height: 1.813rem; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .tile-container__setting-icon { | 
				
			|||
            top: 0.638rem; | 
				
			|||
            right: 1rem; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
// Main Navbar Dropdown menu location override | 
				
			|||
.o_menu_systray > .o_user_menu > .o-dropdown--menu{ | 
				
			|||
    left: auto !important; | 
				
			|||
    right: 0px !important; | 
				
			|||
} | 
				
			|||
.button-container{ | 
				
			|||
    padding-top: 5px; | 
				
			|||
} | 
				
			|||
.add_block{ | 
				
			|||
    margin-left: 6px; | 
				
			|||
} | 
				
			|||
.tile-container__status-container{ | 
				
			|||
    padding-left: 7px; | 
				
			|||
} | 
				
			|||
@ -1,21 +1,129 @@ | 
				
			|||
<?xml version="1.0" encoding="UTF-8"?> | 
				
			|||
<templates id="template" xml:space="preserve"> | 
				
			|||
    <t t-name="owl.dynamic_dashboard" owl="1"> | 
				
			|||
        <div class="container"> | 
				
			|||
            <div class="button-container"> | 
				
			|||
                <button class="btn btn-primary" data-type="tile" | 
				
			|||
                        type="button" t-on-click="_onClick_add_block">Add Block | 
				
			|||
    <!--DASHBOARD VIEW WITH NAVIGATION-BAR, INTERACTJS TEMPLATE--> | 
				
			|||
    <t t-name="owl.OdooDynamicDashboard" owl="1"> | 
				
			|||
        <div class="container" style="min-height:-webkit-fill-available;"> | 
				
			|||
            <div class="navbar navbar-expand-md navbar-light mb-4 navbar-style border-bottom" | 
				
			|||
                 role="navigation"> | 
				
			|||
                <button class="navbar-toggler" id="dropdownNavbar" type="button" | 
				
			|||
                        data-toggle="collapse" | 
				
			|||
                        data-target="#navbarCollapse" | 
				
			|||
                        aria-controls="navbarCollapse" aria-expanded="false" | 
				
			|||
                        aria-label="Toggle navigation"> | 
				
			|||
                    <span class="navbar-toggler-icon"/> | 
				
			|||
                </button> | 
				
			|||
                <button class="btn btn-primary add_block" data-type="graph" | 
				
			|||
                        type="button" t-on-click="_onClick_add_block">Add Graph | 
				
			|||
                </button> | 
				
			|||
            </div> | 
				
			|||
            <div class="o_dynamic_dashboard row"> | 
				
			|||
                <div class="o_dynamic_tile row"> | 
				
			|||
                <div class="collapse navbar-collapse" | 
				
			|||
                     aria-labelledby="dropdownNavbar"> | 
				
			|||
                    <ul class="navbar-nav mr-auto"> | 
				
			|||
                        <label class="navbar-items dropdown drop-down-add"> | 
				
			|||
                            <button class="btn btn-align-items dropdown-add-items dropdown-toggle" | 
				
			|||
                                    style="margin-top:-4px;" | 
				
			|||
                                    type="selection" id="dropdownMenuButton" | 
				
			|||
                                    data-toggle="dropdown" aria-haspopup="true" | 
				
			|||
                                    aria-expanded="false" | 
				
			|||
                                    t-on-click="onClickAdd"> | 
				
			|||
                                <i class="fa fa-plus-circle"/> | 
				
			|||
                                <span id="text_add">⠀Add Items</span> | 
				
			|||
                            </button> | 
				
			|||
                            <div class="dropdown-menu dropdown-addblock" | 
				
			|||
                                 aria-labelledby="dropdownMenuButton"> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="tile" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Tile</a> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="graph" data-chart_type="bar" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Bar Chart</a> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="graph" data-chart_type="doughnut" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Doughnut Chart</a> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="graph" | 
				
			|||
                                   data-chart_type="line" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Line Chart</a> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="graph" data-chart_type="pie" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Pie Chart</a> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="graph" | 
				
			|||
                                   data-chart_type="polarArea" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Polar Area Chart</a> | 
				
			|||
                                <a class="dropdown-item add_block" | 
				
			|||
                                   data-type="graph" | 
				
			|||
                                   data-chart_type="radar" | 
				
			|||
                                   t-on-click="(ev) => this.onClickAddItem(ev)">Radar Chart</a> | 
				
			|||
                            </div> | 
				
			|||
                        </label> | 
				
			|||
                    </ul> | 
				
			|||
                </div> | 
				
			|||
                <label class="navbar-items layout-switch" | 
				
			|||
                       style="padding-top:20px;" | 
				
			|||
                       id="edit-layout-label"> | 
				
			|||
                    <button class="navbar-items btn-search_edit btn-align-items btn btn-primary my-2 mx-2 my-sm-0" | 
				
			|||
                            type="button" | 
				
			|||
                            id="edit_layout" | 
				
			|||
                            t-on-click="(ev) => this.editLayout(ev)">Edit Layout</button> | 
				
			|||
                    <button class="navbar-items btn-search_edit btn btn-primary my-2 mx-2 my-sm-0" | 
				
			|||
                            type="button" | 
				
			|||
                            id="save_layout" | 
				
			|||
                            t-on-click="(ev) => this.saveLayout(ev)">Save Layout</button> | 
				
			|||
                    <label for="view_mode" | 
				
			|||
                           t-on-mouseover="(ev) => this.showViewMode(ev)" | 
				
			|||
                           t-on-mouseout="(ev) => this.hideViewMode(ev)" | 
				
			|||
                           t-on-click="(ev) => this.changeViewMode(ev)" | 
				
			|||
                           style="margin-left: 6px;"> | 
				
			|||
                        <input type="checkbox" id="view_mode"/> | 
				
			|||
                        <span><i class="bi bi-moon-stars-fill view-mode-icon" style="margin-left:10px"/></span> | 
				
			|||
                    </label> | 
				
			|||
                    <span class="mode" mode="light">Dark mode</span> | 
				
			|||
                </label> | 
				
			|||
 | 
				
			|||
                <div class="search-group" style="margin-right: 30px;padding-top:20px;"> | 
				
			|||
                    <!-- Search Bar --> | 
				
			|||
                    <div class="navbar-items btn-group search-box"> | 
				
			|||
                        <input class="form-control mr-sm-2" type="text" placeholder="Search" id="search-input-chart" aria-label="Search"/> | 
				
			|||
                        <span id="searchclear" t-on-click="clearSearch"> | 
				
			|||
                            <i class="fa fa-times search-clear" | 
				
			|||
                              style="margin-left:-25px;margin-top:9px;"/> | 
				
			|||
                        </span> | 
				
			|||
                    </div> | 
				
			|||
                    <button class="btn btn-outline-success my-2 my-sm-0" | 
				
			|||
                            t-on-click="clickSearch" | 
				
			|||
                            style="margin-left:10px;" | 
				
			|||
                            type="button">Search | 
				
			|||
                    </button> | 
				
			|||
                </div> | 
				
			|||
                        <!-- Date Inputs --> | 
				
			|||
                <div class="date-inputs" | 
				
			|||
                     style="position: absolute; right: 34.5em; font-size: smaller;font-family: monospace; padding-top:inherit; "> | 
				
			|||
                    <label for="start-date" class="date-label" | 
				
			|||
                           style="color: black;">Start Date:</label> | 
				
			|||
                            <input type="date" id="start-date" name="start-date" | 
				
			|||
                                   t-on-change="dateFilter" | 
				
			|||
                                   style="color: black; border: 1px solid #4e4e4e; background-color: white; padding: 5px 10px; border-radius: 5px;"/> | 
				
			|||
                            <label for="end-date" class="date-label" | 
				
			|||
                                   style="color: black; margin-left: 10px;">End Date:</label> | 
				
			|||
                            <input type="date" id="end-date" name="end-date" | 
				
			|||
                                   t-on-change="dateFilter" | 
				
			|||
                                   style="color: black; border: 1px solid #4e4e4e; background-color: white; padding: 5px 10px; border-radius: 5px;"/> | 
				
			|||
                </div> | 
				
			|||
                <div class="o_dynamic_graph w3-container row"> | 
				
			|||
                <div class="o-dropdown dropdown theme"> | 
				
			|||
                    <select class="form-select" | 
				
			|||
                            t-ref="ThemeSelector" | 
				
			|||
                            t-on-change="onChangeTheme"> | 
				
			|||
                        <option value="0">Select Theme</option> | 
				
			|||
                    </select> | 
				
			|||
                </div> | 
				
			|||
                <div class="dashboard_pdf" t-on-click="printPdf"> | 
				
			|||
                    <i class="bi bi-filetype-pdf" style="font-size:24px;"/> | 
				
			|||
                </div> | 
				
			|||
                <div class="dashboard_mail" t-on-click="sendMail"> | 
				
			|||
                    <i class="bi bi-envelope-fill" style="font-size:24px;"/> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
            <div class="all_items" style="display:grid;"> | 
				
			|||
                <div class="items"/> | 
				
			|||
            </div> | 
				
			|||
                <!--CONTAINER FOR CONTENT GENERATION :TILE & CHART(FROM DynamicDashboardTile & DynamicDashboardChart--> | 
				
			|||
        </div> | 
				
			|||
    </t> | 
				
			|||
</templates> | 
				
			|||
 | 
				
			|||
@ -1,46 +0,0 @@ | 
				
			|||
<?xml version="1.0" encoding="utf-8"?> | 
				
			|||
<odoo> | 
				
			|||
    <!--    Form view of the model dashboard menu--> | 
				
			|||
    <record id="dashboard_menu_view_form" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.menu.view.form</field> | 
				
			|||
        <field name="model">dashboard.menu</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <form> | 
				
			|||
                <sheet> | 
				
			|||
                    <group> | 
				
			|||
                        <group> | 
				
			|||
                            <field name="name"/> | 
				
			|||
                            <field name="parent_id"/> | 
				
			|||
                            <field name="group_ids" widget="many2many_tags" invisible="1"/> | 
				
			|||
                            <field name="client_action_id" invisible="1"/> | 
				
			|||
                        </group> | 
				
			|||
                    </group> | 
				
			|||
                </sheet> | 
				
			|||
            </form> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
    <!--   Tree view of the model dashboard menu --> | 
				
			|||
    <record id="dashboard_menu_view_tree" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.menu.view.tree</field> | 
				
			|||
        <field name="model">dashboard.menu</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <tree> | 
				
			|||
                <field name="name"/> | 
				
			|||
                <field name="parent_id"/> | 
				
			|||
            </tree> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
    <!--  Action for the model dashboard menu  --> | 
				
			|||
    <record id="dashboard_menu_action" model="ir.actions.act_window"> | 
				
			|||
        <field name="name">Dashboard Menu</field> | 
				
			|||
        <field name="type">ir.actions.act_window</field> | 
				
			|||
        <field name="res_model">dashboard.menu</field> | 
				
			|||
        <field name="view_mode">tree,form</field> | 
				
			|||
    </record> | 
				
			|||
    <!--    Menu item to show the configuration in module dynamic dashboard--> | 
				
			|||
    <menuitem name="Configuration" id="menu_dynamic_dashboard_configuration" parent="odoo_dynamic_dashboard.menu_dashboard" | 
				
			|||
              sequence="3"/> | 
				
			|||
    <!--    Menu item to show the dynamic menus in module dynamic dashboard--> | 
				
			|||
    <menuitem name="Dashboards" id="menu_dynamic_dashboard_menu" parent="odoo_dynamic_dashboard.menu_dynamic_dashboard_configuration" | 
				
			|||
              sequence="3" action="dashboard_menu_action"/> | 
				
			|||
</odoo> | 
				
			|||
@ -0,0 +1,66 @@ | 
				
			|||
<?xml version="1.0" encoding="utf-8"?> | 
				
			|||
<odoo> | 
				
			|||
    <!--Kanban view of the dashboard menu--> | 
				
			|||
    <record id="dashboard_menu_view_kanban" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.menu.view.kanban</field> | 
				
			|||
        <field name="model">dashboard.menu</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <kanban> | 
				
			|||
                <field name="name"/> | 
				
			|||
                <templates> | 
				
			|||
                    <t t-name="kanban-box"> | 
				
			|||
                        <div t-attf-class="oe_kanban_global_click"> | 
				
			|||
                            <center> | 
				
			|||
                                <h3 class="my-2 ms-3"> | 
				
			|||
                                    Name: | 
				
			|||
                                    <field name="name"/> | 
				
			|||
                                </h3> | 
				
			|||
                            </center> | 
				
			|||
                            <div class="row"> | 
				
			|||
                                <hr class="mt4 mb4"/> | 
				
			|||
                                <div class="col-6 text-center"> | 
				
			|||
                                    <strong>Parent:</strong> | 
				
			|||
                                </div> | 
				
			|||
                                <div class="col-6 text-center"> | 
				
			|||
                                    <field name="menu_id"/> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                    </t> | 
				
			|||
                </templates> | 
				
			|||
            </kanban> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
    <!--Form view of the dashboard menu--> | 
				
			|||
    <record id="dashboard_menu_view_form" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.menu.view.form</field> | 
				
			|||
        <field name="model">dashboard.menu</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <form> | 
				
			|||
                <sheet> | 
				
			|||
                    <group> | 
				
			|||
                        <group> | 
				
			|||
                            <field name="name" class="oe_inline"/> | 
				
			|||
                            <field name="menu_id" class="oe_inline"/> | 
				
			|||
                            <field name="group_ids" widget="many2many_tags" | 
				
			|||
                                   invisible="1"/> | 
				
			|||
                            <field name="client_action_id" invisible="1"/> | 
				
			|||
                        </group> | 
				
			|||
                    </group> | 
				
			|||
                </sheet> | 
				
			|||
            </form> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
 | 
				
			|||
    <!--    Action specified for the dashboard menu--> | 
				
			|||
    <record id="dashboard_menu_action" model="ir.actions.act_window"> | 
				
			|||
        <field name="name">Dashboards Menu</field> | 
				
			|||
        <field name="type">ir.actions.act_window</field> | 
				
			|||
        <field name="res_model">dashboard.menu</field> | 
				
			|||
        <field name="view_mode">kanban,form</field> | 
				
			|||
    </record> | 
				
			|||
    <!--Menu Item of the model Dashboard Menu--> | 
				
			|||
    <menuitem name="Dashboard Menu" id="dashboard_menu_view_action" | 
				
			|||
              parent="odoo_dynamic_dashboard.menu_dashboard" | 
				
			|||
              sequence="10" action="dashboard_menu_action"/> | 
				
			|||
</odoo> | 
				
			|||
@ -0,0 +1,56 @@ | 
				
			|||
<?xml version="1.0" encoding="utf-8"?> | 
				
			|||
<odoo> | 
				
			|||
    <!--Form view of the dashboard block theme--> | 
				
			|||
    <record id="dashboard_theme_view_form" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.theme.view.form</field> | 
				
			|||
        <field name="model">dashboard.theme</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <form> | 
				
			|||
                <sheet> | 
				
			|||
                    <group> | 
				
			|||
                        <div> | 
				
			|||
                            <field name="name" class="oe_inline" | 
				
			|||
                                   style="font-size: 30px;" | 
				
			|||
                                   placeholder="Theme Name" required="1"/> | 
				
			|||
                        </div> | 
				
			|||
                    </group> | 
				
			|||
                    <group> | 
				
			|||
                        <field name="color_x" widget="color"/> | 
				
			|||
                        <field name="color_y" widget="color"/> | 
				
			|||
                        <field name="color_z" widget="color"/> | 
				
			|||
                    </group> | 
				
			|||
                    <notebook> | 
				
			|||
                        <page string="Color Gradient"> | 
				
			|||
                            <field name="body" type="html" readonly="1"/> | 
				
			|||
                            <field name="style" invisible="1"/> | 
				
			|||
                        </page> | 
				
			|||
                    </notebook> | 
				
			|||
                </sheet> | 
				
			|||
            </form> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
 | 
				
			|||
    <!--Tree view of the dashboard block theme--> | 
				
			|||
    <record id="dashboard_theme_view_tree" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.theme.view.tree</field> | 
				
			|||
        <field name="model">dashboard.theme</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <tree> | 
				
			|||
                <field name="name"/> | 
				
			|||
            </tree> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
    <!--    Action specified for the dashboard block theme --> | 
				
			|||
    <record id="dashboard_theme_action" model="ir.actions.act_window"> | 
				
			|||
        <field name="name">Dashboard Theme</field> | 
				
			|||
        <field name="type">ir.actions.act_window</field> | 
				
			|||
        <field name="res_model">dashboard.theme</field> | 
				
			|||
        <field name="view_mode">tree,form</field> | 
				
			|||
    </record> | 
				
			|||
    <!--Menu Item for the model Dashboard Blocks--> | 
				
			|||
    <menuitem name="Configuration" id="odoo_dynamic_configuration" | 
				
			|||
              parent="odoo_dynamic_dashboard.menu_dashboard"/> | 
				
			|||
    <menuitem name="Dashboard Theme" id="dashboard_theme_menu" | 
				
			|||
              parent="odoo_dynamic_dashboard.odoo_dynamic_configuration" | 
				
			|||
              sequence="45" action="dashboard_theme_action"/> | 
				
			|||
</odoo> | 
				
			|||
@ -1,13 +0,0 @@ | 
				
			|||
<?xml version="1.0" encoding="utf-8"?> | 
				
			|||
<odoo> | 
				
			|||
    <!--    Action to show the dashboard--> | 
				
			|||
    <record id="dynamic_dashboard_action" model="ir.actions.client"> | 
				
			|||
        <field name="name">Dashboard</field> | 
				
			|||
        <field name="tag">owl.dynamic_dashboard</field> | 
				
			|||
    </record> | 
				
			|||
    <!--    Menu Item to show the dashboard module--> | 
				
			|||
    <menuitem name="Dashboard" id="menu_dashboard" sequence="0" web_icon="odoo_dynamic_dashboard,static/description/icon.png"/> | 
				
			|||
    <!--    Menu Item to show the dashboards menu in the dashboard module--> | 
				
			|||
    <menuitem name="Dashboards" id="menu_dynamic_dashboard" parent="odoo_dynamic_dashboard.menu_dashboard" | 
				
			|||
              sequence="1" action="dynamic_dashboard_action"/> | 
				
			|||
</odoo> | 
				
			|||
@ -0,0 +1,14 @@ | 
				
			|||
<?xml version="1.0" encoding="utf-8"?> | 
				
			|||
<odoo> | 
				
			|||
    <!--    The client action record to view the Dashboard--> | 
				
			|||
    <record id="dashboard_view_action" model="ir.actions.client"> | 
				
			|||
        <field name="name">Dashboard</field> | 
				
			|||
        <field name="tag">OdooDynamicDashboard</field> | 
				
			|||
    </record> | 
				
			|||
    <menuitem name="Dashboards" id="menu_dashboard" sequence="-1" | 
				
			|||
              web_icon="odoo_dynamic_dashboard,static/description/icon.png"/> | 
				
			|||
    <menuitem name="Dashboard" action="dashboard_view_action" | 
				
			|||
              id="dashboard_root_menu" | 
				
			|||
              parent="odoo_dynamic_dashboard.menu_dashboard" | 
				
			|||
              sequence="0"/> | 
				
			|||
</odoo> | 
				
			|||
@ -0,0 +1,75 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
from odoo import fields, models | 
				
			|||
 | 
				
			|||
 | 
				
			|||
class DashboardMail(models.TransientModel): | 
				
			|||
    _name = 'dashboard.mail' | 
				
			|||
    _description = 'Dashboard Mail' | 
				
			|||
 | 
				
			|||
    user_ids = fields.Many2many('res.users', string="Users", | 
				
			|||
                                domain="[('id','!=', uid)]", | 
				
			|||
                                help="Select User") | 
				
			|||
    base64code = fields.Char(string='Base 64', help='Base64 Code of the pdf') | 
				
			|||
 | 
				
			|||
    def send_mail(self): | 
				
			|||
        """ | 
				
			|||
        Function for sending mail to the selected users | 
				
			|||
        """ | 
				
			|||
        for user in self.user_ids: | 
				
			|||
            mail_content = ( | 
				
			|||
                           'Hi %s, <br/> ' | 
				
			|||
                           'I hope this mail finds you well. I am pleased to share the <b>Dashboard Report</b> with you.<br/>' | 
				
			|||
                           'Please find the attachment<br/>') % user.name | 
				
			|||
            mail_values = { | 
				
			|||
                'subject': 'Dashboard Report', | 
				
			|||
                'author_id': self.env.user.partner_id.id, | 
				
			|||
                'body_html': mail_content, | 
				
			|||
                'email_to': user.email, | 
				
			|||
            } | 
				
			|||
            mail_id = self.env['mail.mail'].create(mail_values) | 
				
			|||
            attachment_values = { | 
				
			|||
                'name': 'Dashboard.pdf', | 
				
			|||
                'datas': self.base64code, | 
				
			|||
                'type': 'binary', | 
				
			|||
                'res_model': 'mail.mail', | 
				
			|||
                'res_id': mail_id.id, | 
				
			|||
            } | 
				
			|||
            attachment_id = self.env['ir.attachment'].create(attachment_values) | 
				
			|||
            mail_id.write({ | 
				
			|||
                'attachment_ids': [(4, attachment_id.id)] | 
				
			|||
            }) | 
				
			|||
            mail_id.send() | 
				
			|||
 | 
				
			|||
        return { | 
				
			|||
            'type': 'ir.actions.client', | 
				
			|||
            'tag': 'reload', | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    def cancel_mail(self): | 
				
			|||
        """ | 
				
			|||
        Function for refreshing the page while clicking cancel | 
				
			|||
        """ | 
				
			|||
        return { | 
				
			|||
            'type': 'ir.actions.client', | 
				
			|||
            'tag': 'reload', | 
				
			|||
        } | 
				
			|||
@ -0,0 +1,22 @@ | 
				
			|||
<?xml version="1.0" encoding="UTF-8" ?> | 
				
			|||
<odoo> | 
				
			|||
<!--    Sending dashboard pdf to users --> | 
				
			|||
    <record id="dashboard_mail_view_form" model="ir.ui.view"> | 
				
			|||
        <field name="name">dashboard.mail.view.form</field> | 
				
			|||
        <field name="model">dashboard.mail</field> | 
				
			|||
        <field name="arch" type="xml"> | 
				
			|||
            <form string="Sent Mail"> | 
				
			|||
                <group> | 
				
			|||
                    <field name="user_ids" widget="many2many_tags"/> | 
				
			|||
                    <field name="base64code" invisible="1"/> | 
				
			|||
                </group> | 
				
			|||
                <footer> | 
				
			|||
                    <button name="send_mail" string="SEND" | 
				
			|||
                            class="btn-primary" type="object"/> | 
				
			|||
                    <button string="Cancel" class="btn-secondary" | 
				
			|||
                            name="cancel_mail" type="object"/> | 
				
			|||
                </footer> | 
				
			|||
            </form> | 
				
			|||
        </field> | 
				
			|||
    </record> | 
				
			|||
</odoo> | 
				
			|||