@ -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> |