@ -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> |
## Module <odoo_dynamic_dashboard> |
||||
|
|
||||
#### 02.03.2024 |
#### 18.05.2024 |
||||
#### Version 17.0.1.0.0 |
#### 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"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||
<templates id="template" xml:space="preserve"> |
<templates id="template" xml:space="preserve"> |
||||
<t t-name="owl.dynamic_dashboard" owl="1"> |
<!--DASHBOARD VIEW WITH NAVIGATION-BAR, INTERACTJS TEMPLATE--> |
||||
<div class="container"> |
<t t-name="owl.OdooDynamicDashboard" owl="1"> |
||||
<div class="button-container"> |
<div class="container" style="min-height:-webkit-fill-available;"> |
||||
<button class="btn btn-primary" data-type="tile" |
<div class="navbar navbar-expand-md navbar-light mb-4 navbar-style border-bottom" |
||||
type="button" t-on-click="_onClick_add_block">Add Block |
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> |
||||
<button class="btn btn-primary add_block" data-type="graph" |
<div class="collapse navbar-collapse" |
||||
type="button" t-on-click="_onClick_add_block">Add Graph |
aria-labelledby="dropdownNavbar"> |
||||
</button> |
<ul class="navbar-nav mr-auto"> |
||||
</div> |
<label class="navbar-items dropdown drop-down-add"> |
||||
<div class="o_dynamic_dashboard row"> |
<button class="btn btn-align-items dropdown-add-items dropdown-toggle" |
||||
<div class="o_dynamic_tile row"> |
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> |
||||
<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> |
||||
|
<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> |
</div> |
||||
|
<!--CONTAINER FOR CONTENT GENERATION :TILE & CHART(FROM DynamicDashboardTile & DynamicDashboardChart--> |
||||
</div> |
</div> |
||||
</t> |
</t> |
||||
</templates> |
</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> |