Tower: upload ks_dashboard_ninja 18.0.1.1.7 (was 18.0.1.1.7, via marketplace)

This commit is contained in:
2026-05-07 12:17:21 +00:00
parent e50acbac83
commit fd62a75b51
583 changed files with 54977 additions and 0 deletions

View File

@@ -0,0 +1,556 @@
/**@odoo-module **/
import { Component, useState} from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { useForwardRefToParent } from "@web/core/utils/hooks";
import { download } from "@web/core/network/download";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { KsDateFilter } from '@ks_dashboard_ninja/components/date_filter/date_filter';
import { ks_get_current_gridstack_config } from '@ks_dashboard_ninja/js/ks_global_functions'
import { DNFilter } from '@ks_dashboard_ninja/components/dn_filter/dn_filter';
import { isMobileOS } from "@web/core/browser/feature_detection";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { FormViewDialog} from '@web/views/view_dialogs/form_view_dialog';
import { useService } from "@web/core/utils/hooks";
import { eraseAllCookies } from '@ks_dashboard_ninja/js/ks_global_functions';
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class KsHeader extends Component{
static props = {
dashboard_data: {type:Object, optional:true },
mode : { type: String },
headerRootRef: { type: Function, optional: true },
}
static components = { Dropdown, DropdownItem, KsDateFilter, DNFilter }
static template = "ks_dashboard_ninja.Ks_dashboard_ninja_header"
setup(){
// super.setup()
this.ks_dashboard_data = this.props.dashboard_data
this.action = this.env.services.action
this.uiService = useService("ui");
this.action = useService("action");
this.notification = useService("notification");
this._rpc = rpc
this.ks_dashboard_id = this.ks_dashboard_data.ks_dashboard_id
this.isMobile = isMobileOS();
this.headerRootRef = useForwardRefToParent("headerRootRef");
this.items_length = this.ks_dashboard_data.ks_dashboard_items_ids.length
this.state = useState({
mode: this.props.mode , // types - [ "manager", "user", "mobile", "layout", "custom_date" ]
isDashboardBookmarked: this.ks_dashboard_data.is_bookmarked
});
this.dialogService = this.env.services.dialog
this.tempSelectedLayoutId = JSON.parse(JSON.stringify(this.ks_dashboard_data.ks_selected_board_id))
this.tempDashboardName = JSON.parse(JSON.stringify(this.ks_dashboard_data.name))
this.dropdowns = [
{ name: "Edit Layout", modes: ["manager", "custom_date"],
func: ()=>this.onKsEditLayoutClick(), svg: "ks_dashboard_ninja.header_edit_svg" },
{ name: "Bookmark Dashboard", modes: ["manager", "user", "custom_date"],
func: ()=>this.updateBookmark(), svg: "ks_dashboard_ninja.bookmark" },
{ name: "Capture Dashboard", modes: ["manager", "user", "custom_date"],
func: ()=> this.dashboardImageUpdate(), svg: "ks_dashboard_ninja.capture" },
{ name: "Settings", svg: "ks_dashboard_ninja.setting", modes: ["manager", "custom_date"],
dropdown_items: [ {name: "Dashboard Settings" , svg: "ks_dashboard_ninja.setting-2", func: (ev)=>this.ksOnDashboardSettingClick(ev), class : '', modes: ["manager", "custom_date"],},
{name: "Delete the Dashboard", svg: "ks_dashboard_ninja.trash_svg", func: this.ksOnDashboardDeleteClick.bind(this), class : '', modes: ["manager", "custom_date"],},
{name: "Create New Dashboard", svg: "ks_dashboard_ninja.add-square", func:()=>this.ksOnDashboardCreateClick(), class : '', modes: ["manager", "custom_date"],},
{name: "Generate Dashboard with AI", svg: "ks_dashboard_ninja.illustrator", func:()=>this.kscreateaidashboard(), class : '', modes: ["manager", "custom_date"],},
{name: "Duplicate Current Dashboard", svg: "ks_dashboard_ninja.copy", func:(ev)=>this.ksOnDashboardDuplicateClick(ev), class : '', modes: ["manager", "custom_date"],}], },
{ name: "More", svg: "ks_dashboard_ninja.more", modes: ["manager", "user", "custom_date"],
dropdown_items: [ {name: "Import Item" , svg: "ks_dashboard_ninja.download_svg", func: () => this.ksImportItemJson(), class : '', modes: ["manager", "custom_date"],},
{name: "Export Dashboard", svg: "ks_dashboard_ninja.document-upload", func:()=>this.ksOnDashboardExportClick(), class : '', modes: ["manager","user", "custom_date"],},
{name: "Import Dashboard", svg: "ks_dashboard_ninja.download_svg", func:()=>this.ksOnDashboardImportClick(), class : '', modes: ["manager", "custom_date"],}] },
]
this.header_mode_buttons = { "edit" : { buttons : [{ name: "Discard", callback: this._onDiscardLayoutChanges.bind(this), classes: 'dash-default-btn bg-white me-2', shouldVisible: true },
{ name: "Save as New Layout", callback: this.onSaveNewLayoutClick.bind(this), classes: 'dash-btn-red me-2 ks-bg-violet', shouldVisible: this.props.dashboard_data.multi_layouts },
{ name: "Save Layout", callback: this._onKsSaveLayoutClick.bind(this), classes: 'dash-btn-red', shouldVisible: true } ] },
"layout": { buttons : [{ name: "Set Default Layout", callback: this._ksSetLayoutAsDefault.bind(this), classes: 'dash-btn-red', shouldVisible: true},
{ name: "Discard", callback: this.discardLayoutSelection.bind(this), classes: 'dash-default-btn bg-white', shouldVisible: true}] } }
}
update_mode(mode){
this.state.mode = mode
}
dashboardImageUpdate(){
let image_element = document.querySelector('.ks_dashboard_main_content');
if(!document.querySelector('.ks_dashboard_main_content')?.childNodes.length){
image_element = document.querySelector('.main-box');
}
let self = this;
this.uiService.block();
let canvas = html2canvas(image_element, {
height: image_element.clientHeight + 186,
width: image_element.clientWidth,
windowWidth: image_element.scrollWidth,
windowHeight: image_element.scrollHeight,
scrollY: 0,
scrollX: 0,
x: image_element.scrollLeft,
y: image_element.scrollTop < 600 ? image_element.scrollTop < 50 ? image_element.scrollTop :
image_element.scrollTop - 150 : image_element.scrollTop - 650,
}).then((canvas) => {
let image = canvas.toDataURL("image/png");
self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/saveImage",{
model: 'ks_dashboard_ninja.board',
method: 'save_dashboard_image',
args: [[self.ks_dashboard_id]],
kwargs:{image: image},
}).then((result) => {
this.uiService.unblock();
});
});
this.notification.add(_t('Dashboard image updated successfully!'),{
title:_t("Dashboard Image Refreshed"),
type: 'success',
});
}
restoreController(){
let self = this;
let js_id = self.action.currentController.jsId
self.action.restore(js_id)
}
_onDiscardLayoutChanges(){
this.restoreController();
}
async updateBookmark(){
let updatedBookmarks = await this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/update_bookmarks",{
model: 'ks_dashboard_ninja.board',
method: 'update_bookmarks',
args: [[this.ks_dashboard_id]],
kwargs:{},
});
updatedBookmarks = updatedBookmarks[1]
this.state.isDashboardBookmarked = !this.state.isDashboardBookmarked
this.notification.add(_t(`Dashboard ${ updatedBookmarks ? "added to" : "removed from"} your bookmarks`),{
title:_t(`Bookmark ${ updatedBookmarks ? "Added" : "Removed"}`), type: 'success'});
}
onCreateNewChartClick() {
let self = this;
self.dialogService.add(FormViewDialog,{
resModel: 'ks_dashboard_ninja.item',
is_expand_icon_visible: true,
context: {
'ks_dashboard_id': self.ks_dashboard_data.ks_dashboard_id,
'ks_dashboard_item_type': 'ks_tile',
'form_view_ref': 'ks_dashboard_ninja.item_form_view',
'form_view_initial_mode': 'edit',
'ks_set_interval': self.ks_dashboard_data.ks_set_interval,
'ks_data_formatting':self.ks_dashboard_data.ks_data_formatting,
'ks_form_view' : true
},
onRecordSaved:()=>{
var js_id = self.env.services.action.currentController.jsId
self.env.services.action.restore(js_id)
},
size: "fs",
title: "Create New Chart"
});
}
onDashboardLayoutSelect(selected_board_id){
this.state.mode = 'layout'
this.tempSelectedLayoutId = selected_board_id
this.setLayoutGrid(selected_board_id);
}
setLayoutGrid(layout_id){
let grid_stack = this.env.gridStackRootRef.el.gridstack
let selected_layout_grid_config = this.ks_dashboard_data.ks_child_boards[layout_id][1];
selected_layout_grid_config = JSON.parse(selected_layout_grid_config);
Object.entries(selected_layout_grid_config).forEach((x,y)=>{
grid_stack.update($(this.env.gridStackRootRef.el).find(".grid-stack-item[gs-id=" + x[0] + "]")[0],{ x:x[1]['x'], y:x[1]['y'], w:x[1]['w'], h:x[1]['h'], autoPosition:false});
});
}
discardLayoutSelection(){
this.state.mode = this.ks_dashboard_data.ks_dashboard_manager ? "manager" : "user"
this.tempSelectedLayoutId = this.ks_dashboard_data.ks_selected_board_id
this.setLayoutGrid(this.ks_dashboard_data.ks_selected_board_id);
}
_ksSetLayoutAsDefault(){
let self = this;
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/update_child_board",{
model: 'ks_dashboard_ninja.board',
method: 'update_child_board',
args: ['update', self.ks_dashboard_id, {
"ks_selected_board_id": this.tempSelectedLayoutId ? this.tempSelectedLayoutId : this.ks_dashboard_data.ks_selected_board_id,
}],
kwargs:{},
}).then(function(result){
window.location.reload();
});
}
checkItemsPresence(){
if(!this.items_length){
this.notification.add(_t('No Items!'),{ title:_t("Create some items"), type: 'info'});
return true;
}
return false;
}
onKsEditLayoutClick(e) {
if(this.checkItemsPresence()) return;
let dashboard_data = this.ks_dashboard_data
this.tempDashboardName = dashboard_data.multi_layouts && dashboard_data.ks_child_boards ?
dashboard_data.ks_child_boards[dashboard_data.ks_selected_board_id]?.[0] : dashboard_data.name
this.env.gridStackRootRef.el.gridstack.setStatic(false);
this.env.update_dashboard_mode('edit');
this.env.gridStackRootRef?.el.gridstack?.enable();
this.state.mode = "edit"
}
_onKsSaveLayoutClick(){
let self = this;
let grid_stack = this.env.gridStackRootRef.el.gridstack
grid_stack.setStatic(true);
let dashboard_title = this.tempDashboardName
if (dashboard_title != false && dashboard_title != 0) {
let model = 'ks_dashboard_ninja.board';
let rec_id = self.ks_dashboard_data.ks_dashboard_id;
if(this.ks_dashboard_data.multi_layouts && this.ks_dashboard_data.ks_child_boards){
this.ks_dashboard_data.ks_child_boards[this.ks_dashboard_data.ks_selected_board_id][0] = dashboard_title;
if (this.ks_dashboard_data.ks_selected_board_id !== 'ks_default'){
rec_id = parseInt(this.ks_dashboard_data.ks_selected_board_id);
this.env.services.orm.write("ks_dashboard_ninja.child_board", [rec_id], { 'name': dashboard_title });
}
else{
this.ks_dashboard_data.name = this.tempDashboardName;
this.env.services.orm.write("ks_dashboard_ninja.board", [rec_id], { 'name': dashboard_title });
}
}
else{
self.ks_dashboard_data.name = dashboard_title;
this.env.services.orm.write("ks_dashboard_ninja.board", [rec_id], { 'name': dashboard_title });
}
}
if (this.ks_dashboard_data.ks_item_data) self._ksSaveCurrentLayout();
this.env.update_dashboard_mode('active')
grid_stack.disable();
grid_stack.commit();
this.state.mode = this.ks_dashboard_data.ks_dashboard_manager ? "manager" : "user"
}
_ksSaveCurrentLayout() {
let self = this;
let grid_config = ks_get_current_gridstack_config(this.env.gridStackRootRef.el);
let model = 'ks_dashboard_ninja.child_board';
let rec_id = self.ks_dashboard_data.ks_gridstack_config_id;
self.ks_dashboard_data.ks_gridstack_config = JSON.stringify(grid_config);
if(this.ks_dashboard_data.ks_selected_board_id && this.ks_dashboard_data.ks_child_boards){
this.ks_dashboard_data.ks_child_boards[this.ks_dashboard_data.ks_selected_board_id][1] = JSON.stringify(grid_config);
if (this.ks_dashboard_data.ks_selected_board_id !== 'ks_default'){
rec_id = parseInt(this.ks_dashboard_data.ks_selected_board_id)
}
}
if (!isMobileOS()) { // Do not save in Mobile view , due to column mode enable
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.child_board/write",{
model: model,
method: 'write',
args: [parseInt(rec_id), {
"ks_gridstack_config": JSON.stringify(grid_config)
}],
kwargs:{},
})
}
}
onSaveNewLayoutClick() {
let self = this;
let grid_stack = this.env.gridStackRootRef.el.gridstack
grid_stack.setStatic(true);
var dashboard_title = $('#ks_dashboard_title_input').val();
if (dashboard_title === "") {
self.notification.add(_t("Dashboard Name is required to save as New Layout"), { type: 'warning' });
} else{
if (!self.ks_dashboard_data.ks_child_boards){
self.ks_dashboard_data.ks_child_boards = {
'ks_default': [ this.ks_dashboard_data.name, self.ks_dashboard_data.ks_gridstack_config ]
}
}
this.ks_dashboard_data.name = dashboard_title;
let grid_config = ks_get_current_gridstack_config(this.env.gridStackRootRef.el);
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/update_child_board",{
model: 'ks_dashboard_ninja.board',
method: 'update_child_board',
args: ['create', self.ks_dashboard_id, {
"ks_gridstack_config": JSON.stringify(grid_config),
"ks_dashboard_ninja_id": self.ks_dashboard_id,
"name": dashboard_title,
"ks_active": true,
"company_id": self.ks_dashboard_data.ks_company_id,
}],
kwargs : {},
}).then(function(res_id){
self.ks_update_child_board_value(dashboard_title, res_id, grid_config),
// self._ksRenderActiveMode();
window.location.reload();
});
}
}
ks_update_child_board_value(dashboard_title, res_id, grid_config){
let self = this;
let child_board_id = res_id.toString();
self.ks_dashboard_data.ks_selected_board_id = child_board_id;
let update_data = {};
update_data[child_board_id] = [dashboard_title, JSON.stringify(grid_config)];
self.ks_dashboard_data.ks_child_boards = Object.assign(update_data, self.ks_dashboard_data.ks_child_boards);
}
ksOnDashboardSettingClick(ev){
let self = this;
let dashboard_id = this.ks_dashboard_id;
// TODO : Apply such functionlity that we donot have to give name of the name of the filter as string to erase cookies Also dont need to remove all cookies"
eraseAllCookies(this.ks_dashboard_id, ['PFilter', 'PFilterDataObj', 'Filter', 'CFilter', 'FilterDateData', 'ChartFilter', 'FFilter']);
let action = {
name: _t('Dashboard Settings'),type: 'ir.actions.act_window',
res_model: 'ks_dashboard_ninja.board', res_id: dashboard_id,
domain: [],context: {'create':false},
views: [
[false, 'form']
],view_mode: 'form',target: 'new',
}
// self.action.doAction(action)
self.action.doAction(action).then(function(result){
// self.eraseCookie('FilterOrderData' + self.ks_dashboard_id);
});
}
ksOnDashboardExportClick(){
// ev.preventDefault();
let self= this;
let dashboard_id = JSON.stringify(this.ks_dashboard_id);
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_dashboard_export", {
model: 'ks_dashboard_ninja.board',
method: "ks_dashboard_export",
args: [dashboard_id],
kwargs: {dashboard_id: dashboard_id}
}).then(function(result) {
var name = "dashboard_ninja";
var data = {"header": name, "dashboard_data":result,}
download({
data: { data:JSON.stringify(data) },
url: '/ks_dashboard_ninja/export/dashboard_json',
});
});
}
ksOnDashboardDeleteClick(ev){
let dashboard_id = this.ks_dashboard_id;
let self= this;
self.dialogService.add(ConfirmationDialog, {
body: _t("Are you sure you want to delete this dashboard ?"),
confirmLabel: _t("Delete Dashboard"),
title: _t("Delete Dashboard"),
confirm: () => {
this._rpc("/web/dataset/call_kw/ks.dashboard.delete.wizard/ks_delete_record", {
model: 'ks.dashboard.delete.wizard',
method: "ks_delete_record",
args: [dashboard_id],
kwargs: {dashboard_id: dashboard_id}
}).then((result)=>{
self.env.services.menu.reload();
let currentAppId = self.env.services.menu?.getCurrentApp()?.id;
self.env.services.menu.selectMenu(currentAppId).then(()=>{
self.notification.add(_t('Dashboard Deleted Successfully'),{
title:_t("Deleted"),
type: 'success',
});
});
});
},
});
}
ksOnDashboardCreateClick(){
var self= this;
var action = {
name: _t('Add New Dashboard'), type: 'ir.actions.act_window',
res_model: 'ks.dashboard.wizard', domain: [],
context: {}, views: [ [false, 'form']],
view_mode: 'form', target: 'new',
}
self.action.doAction(action)
}
ksOnDashboardDuplicateClick(){
let self= this;
let dashboard_id = this.ks_dashboard_id;
this._rpc('/web/dataset/call_kw/ks.dashboard.duplicate.wizard/DuplicateDashBoard', {
model: 'ks.dashboard.duplicate.wizard', method: "DuplicateDashBoard",
args: [this.ks_dashboard_id], kwargs: {}
}).then((result)=>{
self.action.doAction(result)
});
}
kscreateaidashboard(){
let self= this;
let action = {
name: _t('Generate Dashboard with AI'), type: 'ir.actions.act_window',
res_model: 'ks_dashboard_ninja.ai_dashboard',domain: [],
context: {'ks_dashboard_id': this.ks_dashboard_id},
views: [ [false, 'form']],
view_mode: 'form', target: 'new',
}
self.action.doAction(action)
}
kscreateaiitem(ev){
var self= this;
self.dialogService.add(FormViewDialog,{
resModel: 'ks_dashboard_ninja.arti_int', title: 'Generate items with AI',
is_expand_icon_visible: true,
context: {
'ks_dashboard_id': this.ks_dashboard_id, 'ks_form_view' : true,
'generate_dialog' : true, dialog_size: 'extra-large',
}
});
}
ksOnDashboardImportClick(){
let self = this;
let dashboard_id = this.ks_dashboard_id;
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_open_import", {
model: 'ks_dashboard_ninja.board',
method: 'ks_open_import',
args: [dashboard_id],
kwargs: {
dashboard_id: dashboard_id
}
}).then((result)=>{
self.action.doAction(result)
});
}
ksImportItemJson() {
var self = this;
$('.ks_input_import_item_button').click();
}
ksImportItem(e) {
var self = this;
var fileReader = new FileReader();
fileReader.onload = function() {
$('.ks_input_import_item_button').val('');
self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_import_item", {
model: 'ks_dashboard_ninja.board',
method: 'ks_import_item',
args: [self.ks_dashboard_id],
kwargs: {
file: fileReader.result,
dashboard_id: self.ks_dashboard_id
}
}).then(function(result) {
if (result === "Success") {
var js_id = self.action.currentController.jsId
self.action.restore(js_id)
}
});
};
fileReader.readAsText($('.ks_input_import_item_button').prop('files')[0]);
}
ks_gen_ai_analysis(ev){
var self = this;
this.state.dialog_header = false;
var ks_items = Object.values(self.ks_dashboard_data.ks_item_data);
var ks_items_explain = []
var ks_rest_items = []
if (ks_items.length>0){
ks_items.map((item)=>{
ks_items_explain.push({
name:item.name,
id:item.id,
ks_chart_data:item.ks_chart_data?{...JSON.parse(item.ks_chart_data),...{domains:[],previous_domain:[]}}:item.ks_chart_data,
ks_list_view_data: typeof item.ks_list_view_data === 'string' ? JSON.parse(item.ks_list_view_data) : item.ks_list_view_data,
item_type:item.ks_dashboard_item_type,
groupedby:item.ks_chart_relation_groupby_name,
subgroupedby:item.ks_chart_relation_sub_groupby_name,
stacked_bar_chart:item.ks_bar_chart_stacked,
count_type:item.ks_record_count_type,
count:item.ks_record_count,
model_name:item.ks_model_display_name,
kpi_data:item.ks_kpi_data
})
});
this.dialogService.add(ConfirmationDialog, {
body: _t("Do you agree that AI should be used to produce the explanation? It will take a few minutes to finish the process?"),
title:_t("Explain with AI"),
cancel: () => {},
confirmLabel: _t("Confirm"),
confirm: () => {
self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.arti_int/ks_generate_analysis",{
model: 'ks_dashboard_ninja.arti_int',
method: 'ks_generate_analysis',
args: [ks_items_explain,ks_rest_items,self.ks_dashboard_id],
kwargs:{},
}).then(function(result) {
if (result){
self.action.doAction({
type: "ir.actions.client",
name: _t("Explain with AI"),
target: "new",
tag: 'ks_dashboard_ninja',
params:{
ks_dashboard_id: self.ks_dashboard_id,
on_dialog: true,
explain_ai_whole: true,
explainWithAi: true,
dashboard_data: self.ks_dashboard_data,
},
context: {
dialog_size: 'extra-large'
}
},{
onClose: ()=>{
return self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.arti_int/ks_switch_default_dashboard",{
model: 'ks_dashboard_ninja.arti_int',
method: 'ks_switch_default_dashboard',
args: [self.ks_dashboard_id],
kwargs:{},
})
}
},
);
}
});
}
});
}else{
self.notification.add(_t('Please make few items to explain with AI'),{
title:_t("Failed"),
type: 'warning',
});
}
}
};

View File

@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="utf-8" ?>
<template>
<t t-name="ks_dashboard_ninja.Ks_dashboard_ninja_header" owl="1">
<section class="screen-info mb-2 container-fluid" t-ref="headerRootRef">
<div class="d-flex align-items-center justify-content-between info gap-1 flex-wrap w-100 ks_dashboard_top_menu-new">
<div class="d-flex align-items-center gap-2">
<div id="ks_dashboard_title" class="user-template-title dash-dd-2">
<t t-if="['manager', 'user', 'layout'].includes(state.mode)" t-call="ks_dashboard_ninja.ks_dn_layout_container"/>
<div class="form-input-box form-control ps-4" t-if="['edit'].includes(state.mode)">
<input id="ks_dashboard_title_input" type="text" maxlength="35"
t-att-value="tempDashboardName" t-model="this.tempDashboardName"/>
</div>
</div>
<t t-if="!isMobile" t-call="ks_dashboard_ninja.ks_header_dropdowns"/>
<KsDateFilter t-if="['manager', 'user', 'custom_date'].includes(state.mode) &amp;&amp; items_length"
dashboard_data="this.ks_dashboard_data" update_mode.bind="update_mode"/>
<DNFilter t-if="['manager', 'user'].includes(state.mode) &amp;&amp; !isMobile &amp;&amp; items_length"
dashboard_data="this.ks_dashboard_data"/>
<t t-if="ks_dashboard_data.ks_dashboard_manager">
<div class="ks_dashboard_top_settings dropdown d-none d-lg-block">
<input accept=".json " t-attf-id="file_#{_id}"
name="file" class="ks_input_import_item_button" type="file" style="display:none;"
t-on-change="ksImportItem"/>
</div>
</t>
</div>
<t t-if="['manager'].includes(state.mode) &amp;&amp; !isMobile">
<div class="new-features ks_dashboard_top_settings hide-in-edit d-flex align-items-center ms-auto">
<div class="d-lg-flex d-none">
<div class="dropdown dash-dd-2 magic-star-dd" title="AI Features">
<a class="text-decoration-none dropdown-toggle img-bg info me-lg-2 me-1" href="#"
role="button" data-bs-toggle="dropdown" aria-expanded="false">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.01757 5.03498C8.24044 4.43268 9.09232 4.43268 9.31519 5.03498L10.7982 9.04271C10.8683 9.23208 11.0176 9.38137 11.2069 9.45144L15.2147 10.9344C15.817 11.1573 15.817 12.0092 15.2147 12.2321L11.2069 13.7151C11.0176 13.7851 10.8683 13.9344 10.7982 14.1238L9.31519 18.1315C9.09232 18.7338 8.24044 18.7338 8.01757 18.1315L6.53457 14.1238C6.4645 13.9344 6.31521 13.7851 6.12585 13.7151L2.11811 12.2321C1.51581 12.0092 1.51581 11.1573 2.11811 10.9344L6.12585 9.45144C6.31521 9.38137 6.4645 9.23208 6.53457 9.04271L8.01757 5.03498Z" fill="" stroke="" stroke-width="1.25"/>
<path d="M17.6239 5L13.6239 5" stroke="" stroke-width="1.25" stroke-linecap="round"/>
<path d="M15.6244 7L15.6244 3" stroke="" stroke-width="1.25" stroke-linecap="round"/>
</svg>
</a>
<ul class="dropdown-menu py-0 ks-dropdown-menu">
<li>
<button class="feature-btn dropdown-item" t-on-click="kscreateaiitem" title="Generate the charts of a particular model using AI">
<span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" class="me-2">
<path d="M13.3996 4.62671C13.3996 4.98671 13.2063 5.31337 12.8996 5.48004L11.7396 6.10671L10.7529 6.63337L8.70628 7.74004C8.48628 7.86004 8.24628 7.92004 7.99961 7.92004C7.75294 7.92004 7.51294 7.86004 7.29294 7.74004L3.09961 5.48004C2.79294 5.31337 2.59961 4.98671 2.59961 4.62671C2.59961 4.26671 2.79294 3.94004 3.09961 3.77337L4.41294 3.06671L5.45961 2.50004L7.29294 1.51337C7.73294 1.27337 8.26628 1.27337 8.70628 1.51337L12.8996 3.77337C13.2063 3.94004 13.3996 4.26671 13.3996 4.62671Z" fill="#6789C6"/>
<path d="M6.59917 8.52664L2.69917 6.57997C2.39917 6.42664 2.0525 6.44664 1.76583 6.61997C1.47917 6.79331 1.3125 7.09997 1.3125 7.4333V11.12C1.3125 11.76 1.66583 12.3333 2.23917 12.62L6.13917 14.5666C6.2725 14.6333 6.41917 14.6666 6.56583 14.6666C6.73917 14.6666 6.9125 14.62 7.06583 14.52C7.3525 14.3466 7.51917 14.04 7.51917 13.7066V10.02C7.52583 9.38664 7.1725 8.8133 6.59917 8.52664Z" fill="#6789C6"/>
<path d="M14.686 7.43338V11.12C14.686 11.7534 14.3327 12.3267 13.7593 12.6134L9.85932 14.5667C9.72599 14.6334 9.57932 14.6667 9.43266 14.6667C9.25932 14.6667 9.08599 14.62 8.92599 14.52C8.64599 14.3467 8.47266 14.04 8.47266 13.7067V10.0267C8.47266 9.38671 8.82599 8.81338 9.39932 8.52671L10.8327 7.81338L11.8327 7.31338L13.2993 6.58004C13.5993 6.42671 13.946 6.44004 14.2327 6.62004C14.5127 6.79338 14.686 7.10004 14.686 7.43338Z" fill="#6789C6"/>
<path d="M11.7407 6.10667L10.7541 6.63333L4.41406 3.06667L5.46073 2.5L11.5807 5.95333C11.6474 5.99333 11.7007 6.04667 11.7407 6.10667Z" fill="#6789C6"/>
<path d="M11.834 7.31335V8.82669C11.834 9.10002 11.6073 9.32669 11.334 9.32669C11.0607 9.32669 10.834 9.10002 10.834 8.82669V7.81335L11.834 7.31335Z" fill="#6789C6"/>
</svg>
</span>
Generate with AI
</button>
</li>
<li>
<button class="feature-btn dropdown-item" t-on-click="ks_gen_ai_analysis">
<span>
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/convertshape.png" alt="convertshape"
class="img-fluid me-2" loading="lazy"/>
</span>
Explain with AI
</button>
</li>
<li>
<button class="feature-btn d-xl-none d-block dark dropdown-item" t-on-click="onCreateNewChartClick">
<span>
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/Graph 1.svg" alt="Graph" class="img-fluid me-1"
loading="lazy"/>
</span>
Create New Chart
</button>
</li>
</ul>
</div>
<button class="feature-btn dark d-xl-block d-none me-0" t-on-click="onCreateNewChartClick" title="Add the Charts to Dashboard">
<span>
<svg width="25" height="23" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg" class="">
<mask id="mask0_9094_3795" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="21">
<rect y="0.5" width="20" height="20" fill="white"/>
</mask>
<g mask="url(#mask0_9094_3795)">
<path d="M8.54411 5.40375L8.77611 8.85371L8.89128 10.5877C8.89253 10.766 8.92045 10.9432 8.97436 11.1135C9.11345 11.444 9.44811 11.654 9.81219 11.6393L15.3599 11.2764C15.6001 11.2725 15.8321 11.3623 16.0048 11.5262C16.1487 11.6628 16.2416 11.8415 16.2709 12.0336L16.2808 12.1503C16.0512 15.3292 13.7164 17.9807 10.5441 18.6651C7.37174 19.3495 4.11866 17.9037 2.55104 15.1125C2.0991 14.3016 1.81682 13.4104 1.72076 12.491C1.68064 12.2188 1.66297 11.9439 1.66793 11.6689C1.66297 8.26074 4.09 5.31426 7.48739 4.60394C7.89629 4.54026 8.29714 4.75673 8.46111 5.12974C8.50353 5.21612 8.53153 5.30864 8.54411 5.40375Z" fill="#04A9CC"/>
<path opacity="0.4" d="M18.3333 8.67687L18.3275 8.70403L18.3107 8.74352L18.313 8.85196C18.3043 8.99554 18.2488 9.13371 18.1533 9.24538C18.0537 9.36163 17.9177 9.44079 17.768 9.47154L17.6767 9.48404L11.276 9.89879C11.0631 9.91979 10.8511 9.85113 10.6928 9.70996C10.5608 9.59221 10.4765 9.43338 10.4527 9.26221L10.0231 2.87084C10.0156 2.84923 10.0156 2.82581 10.0231 2.80419C10.0289 2.62802 10.1065 2.46149 10.2384 2.34182C10.3702 2.22214 10.5456 2.15929 10.725 2.16731C14.5249 2.26398 17.7186 4.99645 18.3333 8.67687Z" fill="#04A9CC"/>
</g>
</svg>
</span>
<span class="ellipsis-content max-width-medium-130">Create New Chart</span>
</button>
</div>
</div>
</t>
<t t-if="['edit', 'layout'].includes(state.mode) &amp;&amp; !isMobile">
<t t-call="ks_dashboard_ninja.header_mode_buttons">
<t t-set="buttons_condition" t-value="['edit', 'layout'].includes(state.mode)"/>
<t t-set="buttons" t-value="header_mode_buttons[state.mode].buttons"/>
</t>
</t>
</div>
</section>
</t>
<t t-name="ks_dashboard_ninja.ks_header_dropdowns">
<t t-foreach="dropdowns" t-as="dropdown" t-key="dropdown_index">
<t t-if="dropdown.dropdown_items">
<Dropdown menuClass="'ks-dropdown-menu'" t-if="dropdown.modes.includes(state.mode)">
<t t-set-slot="content">
<DropdownItem
t-foreach="dropdown.dropdown_items"
t-as="dropdown_item" t-key="dropdown_item_index"
class="{ '': true }"
t-if="dropdown_item.modes.includes(state.mode)"
onSelected="dropdown_item.func">
<span class="me-2">
<t t-call="{{ dropdown_item.svg }}" />
</span>
<t t-esc="dropdown_item.name"/>
</DropdownItem>
</t>
<span class="img-bg info">
<t t-call="{{ dropdown.svg }}"/>
</span>
</Dropdown>
</t>
<t t-else="">
<DropdownItem class="{ 'img-bg info cursor-pointer': true,
'nav-active': dropdown.name === 'Bookmark Dashboard' &amp;&amp; state.isDashboardBookmarked }"
onSelected="dropdown.func" t-if="dropdown.modes.includes(state.mode)">
<t t-call="{{ dropdown.svg }}" />
</DropdownItem>
</t>
</t>
</t>
<t t-name="ks_dashboard_ninja.header_mode_buttons">
<div t-att-class=" 'd-flex gap-2 ' + btn_container_classes " t-if="buttons_condition">
<t t-foreach="buttons" t-as="button" t-key="button_index">
<button type="button" t-if="button.shouldVisible" t-att-class="button.classes" title="" t-on-click="button.callback">
<span><t t-out="button.name"/></span>
</button>
</t>
</div>
</t>
<t t-name="ks_dashboard_ninja.ks_dn_layout_container">
<t t-if="props.dashboard_data.multi_layouts &amp;&amp; props.dashboard_data.ks_child_boards">
<Dropdown menuClass="'ks-dropdown-menu'" disabled="isMobile || !items_length || !ks_dashboard_data.ks_dashboard_manager">
<t t-set-slot="content">
<DropdownItem t-foreach="ks_dashboard_data.ks_child_boards" t-as="child_board" t-key="child_board_index"
class="{ 'global-active': child_board === tempSelectedLayoutId }"
onSelected="() => { this.onDashboardLayoutSelect(child_board) } "
t-esc="ks_dashboard_data.ks_child_boards[child_board][0]"/>
</t>
<div class=" bg-transparent">
<span class="ellipsis-content max-width-medium-25vw" t-esc="ks_dashboard_data.ks_child_boards[tempSelectedLayoutId][0]"
t-att-class="isMobile || !items_length || !ks_dashboard_data.ks_dashboard_manager ? ' ks-dropdown-no--caret': ''"/>
</div>
</Dropdown>
</t>
<t t-else="">
<span id="ks_dashboard_title_label" class="ks_am_element dash-dd-2 ellipsis-content max-width-medium-25vw"
t-if="['manager', 'user'].includes(state.mode)" t-att-data-tooltip="props.dashboard_data.name">
<t t-esc="props.dashboard_data.name"/>
</span>
</t>
</t>
</template>

View File

@@ -0,0 +1,295 @@
.ks_body_class {
.screen-info {
background-color: $color-nav-bg;
height: 56px;
display: flex;
align-items: center;
padding: 0 40px;
@include max-992 {
padding: 0 20px;
}
@include max-575 {
padding: 0 16px;
}
// .ks_dashboard_top_menu-new {}
.user-template-title {
.ks_body_class .ks_am_element.dash-dd-2 {
font-size: $font-16;
color: $color-black;
font-weight: $f-w-400;
line-height: 20px;
}
span {
position: relative;
}
// &::after {
// content: '';
// display: block;
// position: absolute;
// height: 20px;
// width: 20px;
// right: 2px;
// background-image: url(../../images/icons/down-arrow.svg);
// background-repeat: no-repeat;
// top: 43%;
// transition: all 350ms ease-in-out;
// }
}
}
}
.ks_body_class .img-bg {
display: flex;
justify-content: center !important;
padding: 0 !important;
align-items: center !important;
background-color: $color-secondary-bg;
border: 0.5px solid $color-E5E7EB;
border-radius: 50% !important;
height: 40px;
width: 40px;
button{
border: none;
background:transparent;
}
&.info {
background-color: $color-white !important;
}
&.hover-item {
background-color: $color-bg-main;
&.kpi-tile-img {
background-color: $color-white !important;
margin: 0;
display: flex !important;
justify-content: center;
align-items: center;
&:hover {
transition: 0.2s linear;
transform: scale(1.1);
cursor: pointer !important;
}
svg {
fill: none !important;
}
}
&:hover {
background-color: $color-white !important;
}
}
&.dropdown-toggle::after {
display: none !important;
}
&.info svg {
fill: none !important;
stroke: $color-black !important;
@include max-992 {
width: 16px;
height: 16px;
}
}
&.info.active, &.info.nav-active,
&.info:hover {
border: 0.5px solid transparent !important;
background-color: $color-6789C6 !important;
svg {
stroke: $color-white !important;
}
}
@include max-992 {
height: 30px;
width: 30px;
& img {
height: 16px;
width: 16px;
}
}
// &.more-img {
// svg {
// fill: $color-black !important;
// stroke: none !important;
// }
//
// &.active,
// &:hover {
// svg {
// fill: $color-white !important;
// stroke: none !important;
// }
// }
// }
}
.ks_body_class .ks_chart_heading {
width: 75% !important;
}
.ks_body_class #ks_dashboard_title_input {
width: 450px;
}
.ks_body_class .ks_dashboard_layout_event {
span.df_selection_text {
padding: 0 6px !important;
}
&.ks_layout_selected {
span {
&::before {
left: -12px !important;
}
}
}
}
.ks_body_class .magic-star-dd {
.feature-btn {
// border-bottom: 1px solid $color-E5E7EB;
border-radius: 0;
border: none;
}
.img-bg {
border-radius: 10px !important;
display: flex;
justify-content: center;
align-items: center;
background-color: $color-FFF5F5 !important;
min-height: 45px !important;
width: 42px !important;
svg {
path {
fill: $color-ABC8E7;
stroke: $color-6789C6;
}
}
&:hover {
border: 1px solid $color-6789C6 !important;
background-color: $color-FFF5F5 !important;
// svg {
// path {
// fill: $color-white;
// stroke: $color-white;
// }
// }
}
}
}
.img-bg.info svg.fill-black-stroke-none{
fill: $color-black !important;
stroke: none !important;
&.active,
&:hover {
fill: $color-white !important;
stroke: none !important;
}
}
#ks_dashboard_title_label {
padding: 12px 15px;
border: 0.5px solid #E5E7EB;
color: #241C1D;
border-radius: 10px;
font-size: 16px;
font-weight: 400;
// text-overflow: ellipsis;
white-space: nowrap;
// overflow: hidden;
// max-width: 187px;
display: block;
}
button.ks-bg-violet {
background-color: $color-6789C6 !important;
border: 1px solid $color-6789C6 !important;
&:hover {
background-color: $color-597ebe !important;
}
}
.feature-btn {
font-size: $font-16;
font-weight: 500;
line-height: 21px;
text-align: left;
color: $color-paragraph;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid $color-E5E7EB;
outline: none;
background-color: $color-white;
display: flex;
align-items: center;
@include minmax1260 {
font-size: 11px;
}
&:hover {
background-color: $color-secondary-bg;
}
&:focus {
border: 1px solid $color-E7495E;
}
&.light {
background-color: $color-FFF5F5;
@include max-575 {
padding: 6px 10px;
font-size: $font-8;
& img {
height: 12px;
width: 12px;
object-fit: cover;
}
}
}
&.dark {
background-color: $color-D9F1FD;
&:hover {
background-color: #c4e4f3 !important;
}
}
}

View File

@@ -0,0 +1,310 @@
/** @odoo-module **/
import { Component, onMounted, useRef } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks";
import { FormViewDialog } from '@web/views/view_dialogs/form_view_dialog';
import { download } from "@web/core/network/download";
import { BlockUI } from "@web/core/ui/block_ui";
import { ks_get_current_gridstack_config } from '@ks_dashboard_ninja/js/ks_global_functions';
import { isMobileOS } from "@web/core/browser/feature_detection";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class KsItemButton extends Component{
static props = { item_data : { type: Object, optional:true },
item_classes : { type: String, optional:true },
itemRootRef : { type: Object },
}
static template = "ks_dashboard_ninja.ks_chart_buttons";
setup(){
this.common_classes = 'ks_dashboard_item_button_container ks_dropdown_container ks_dashboard_item_header ks_dashboard_item_header_hover chart_button_container d-flex '
this.ks_company = this.env.services.company.currentCompany.name
this.isMobile = isMobileOS()
this.item_data = this.props.item_data
this.ks_button_color = this._ks_get_rgba_format(this.item_data.ks_button_color)
this.id = this.props.item_data.id
this.rootRef = useRef('rootRef')
this.ksChartColorOptions = ["default", "dark", "moonrise", "material"]
this.chart_list = ['ks_bar_chart', 'ks_horizontalBar_chart', 'ks_line_chart', 'ks_area_chart', 'ks_pie_chart',
'ks_doughnut_chart', 'ks_polarArea_chart', 'ks_radialBar_chart', 'ks_scatter_chart', 'ks_funnel_chart',
'ks_bullet_chart', 'ks_flower_view', 'ks_radar_view']
this.isExportListVisible = [ ...this.chart_list, 'ks_map_view', 'ks_list_view'].includes(this.item_data.ks_dashboard_item_type)
this.store = useService("mail.store");
this.showButtons = !this.env.inDialog
this.selectedDashboardId = this.item_data.ks_dashboard_list[0]['id']
this.setItemDescription(this.item_data.ks_info)
}
_ks_get_rgba_format(val){
let rgba = val.split(',')[0].match(/[A-Za-z0-9]{2}/g);
rgba = rgba.map(function(v) {
return parseInt(v, 16)
}).join(",");
return "rgba(" + rgba + "," + val.split(',')[1] + ")";
}
setItemDescription(item_description){
let item_description_list = item_description.replace?.(/\\n/g, '\n').split?.('\n').filter(element => element !== '');
this.item_data.ks_info = item_description_list?.join?.(' ') ?? false
this.ks_item_description_list = item_description_list ?? false
}
handleDropdowns(ev){
let targetDropdown = ev.target.closest('.dropdown-toggle')
this.rootRef.el.querySelectorAll('.dropdown-toggle').forEach((dropdown) => {
targetDropdown !== dropdown ? Dropdown.getInstance(dropdown)?.hide() : ''
})
}
onEditItemTypeClick() {
var self = this;
self.env.services.dialog.add(FormViewDialog,{
resModel: 'ks_dashboard_ninja.item',
title: 'Edit Chart',
resId : self.id,
is_expand_icon_visible: true,
context: {
'form_view_ref': 'ks_dashboard_ninja.item_form_view',
'form_view_initial_mode': 'edit',
'ks_form_view' :true
},
onRecordSaved: () => {
var js_id = self.env.services.action.currentController.jsId
self.env.services.action.restore(js_id)
},
onRecordDiscarded: () => {
},
size: 'fs'
});
}
async ksChartExportXlsCsv(e) {
let chart_id = this.id;
let name = this.item_data.name;
let context = this.env.getContext();
let data = {}
if (this.item_data.ks_dashboard_item_type === 'ks_list_view'){
let params = this.env.ksGetParamsForItemFetch(chart_id);
data = {
"header": name,
"chart_data": typeof this.item_data.ks_list_view_data === 'string' ? this.item_data.ks_list_view_data : JSON.stringify(this.item_data.ks_list_view_data),
"ks_item_id": chart_id,
"ks_export_boolean": true,
"context": context,
'params': params,
}
}else{
data = {
"header": name,
"chart_data": this.item_data.ks_chart_data,
}
}
const blockUI = new BlockUI();
await download({
url: '/ks_dashboard_ninja/export/' + e.currentTarget.dataset.format,
data: {
data: JSON.stringify(data)
},
complete: () => unblockUI,
error: (error) => self.call('crash_manager', 'rpc_error', error),
});
}
ksChartExportPdf (e){
var self = this;
var chart_id = this.id;
var name = this.item_data.name;
var base64_image;
base64_image = $($(e.target).parentsUntil(".grid-stack-item").slice(-1)[0]).find('.ks_chart_card_body')[0]
var $ks_el = $($($(self.props.itemRootRef.el).find(".grid-stack-item[gs-id=" + chart_id + "]")).find('.ks_chart_card_body'));
var ks_height = $ks_el.height()
html2canvas(base64_image, {useCORS: true, allowTaint: false}).then(function(canvas){
var ks_image = canvas.toDataURL("image/png");
var ks_image_def = {
content: [{
image: ks_image,
width: 500,
height: ks_height > 750 ? 750 : ks_height,
}],
images: {
bee: ks_image
}
};
pdfMake.createPdf(ks_image_def).download(name + '.pdf');
})
}
ksChartExportimage(e){
var self = this;
var chart_id = this.id;
var name = this.item_data.name;
var base64_image
base64_image = $($(e.target).parentsUntil(".grid-stack-item").slice(-1)[0]).find(".ks_chart_card_body")[0]
html2canvas(base64_image,{useCORS: true, allowTaint: false}).then(function(canvas){
var ks_image = canvas.toDataURL("image/png");
const link = document.createElement('a');
link.href = ks_image;
link.download = name + 'png'
document.body.appendChild(link);
link.click()
document.body.removeChild(link);
})
}
async ksItemExportJson(e) {
var itemId = this.id;
var name = this.item_data.name;
var data = { 'header': name, item_id: itemId, }
const blockUI = new BlockUI();
await download({
url: '/ks_dashboard_ninja/export/item_json',
data: { data: JSON.stringify(data) },
complete: () => unblockUI,
error: (error) => self.call('crash_manager', 'rpc_error', error),
});
}
async openChatWizard(ev){
ev.stopPropagation();
let internal_chat_thread;
let channelId = await rpc("/web/dataset/call_kw/discuss.channel/getId",{
model: 'discuss.channel',
method: 'ks_chat_wizard_channel_id',
args: [[]],
kwargs:{
item_id: this.id,
dashboard_id: this.item_data.ks_dashboard_id,
dashboard_name: this.item_data.ks_dashboard_name,
item_name: this.item_data.name,
}
})
// FIXME : Dont close all chat popover windows . only close those ones belong belongs to dashboard
this.store.chatHub.opened?.forEach?.( (visibleChatWindow) => {
visibleChatWindow.close?.()
})
//
if(channelId) internal_chat_thread = await this.store.Thread.getOrFetch({ model: "discuss.channel", id: channelId})
if(internal_chat_thread){
if(internal_chat_thread.name) internal_chat_thread.name = this.item_data.ks_dashboard_name + ' - ' + this.item_data.name
internal_chat_thread.open()
}
}
onKsDuplicateItemClick(e) {
var self = this;
var ks_item_id = this.id;
var dashboard_id = parseInt(this.selectedDashboardId);
var dashboard_name = this.item_data.ks_dashboard_list.filter( (dashboard) => dashboard.id === dashboard_id)[0]?.name;
rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/copy",{
model: 'ks_dashboard_ninja.item',
method: 'copy',
args: [ks_item_id, {
'ks_dashboard_ninja_board_id': dashboard_id
}],
kwargs:{},
}).then(function(result) {
self.env.services.notification.add(_t('Selected item is duplicated to ' + dashboard_name + ' .'),{
title:_t("Item Duplicated"), type: 'success', });
var js_id = self.env.services.action.currentController.jsId
self.env.services.action.restore(js_id)
})
}
onKsMoveItemClick(e) {
let self = this;
let dashboard_id = parseInt(this.selectedDashboardId);
let dashboard_name = this.item_data.ks_dashboard_list.filter( (dashboard) => dashboard.id === dashboard_id)[0]?.name;
rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/write",{
model: 'ks_dashboard_ninja.item',
method: 'write',
args: [this.id, {
'ks_dashboard_ninja_board_id': dashboard_id
}],
kwargs:{}
}).then(function(result) {
self.env.services.notification.add(_t('Selected item is moved to ' + dashboard_name + ' .'), {
title:_t("Item Moved"), type: 'success', });
let js_id = self.env.services.action.currentController.jsId
self.env.services.action.restore(js_id)
});
}
onKsDeleteItemClick(e) {
let self = this;
let item = $($(e.currentTarget).parentsUntil('.grid-stack').slice(-1)[0])
this.env.services.dialog.add(ConfirmationDialog, {
body: _t("Are you sure that you want to remove this item?"),
confirmLabel: _t("Delete Item"),
title: _t("Delete Dashboard Item"),
confirm: () => {
self.ks_delete_item(self.id , item);
},
cancel: () => {},
});
}
ks_delete_item(id, item) {
let self = this;
let dashboard_data = self.env.getDashboardDataAsObj(['ks_item_data'])
rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/unlink", {
model: 'ks_dashboard_ninja.item',
method: 'unlink',
args: [id],
kwargs:{}
}).then(function(result) {
// Clean Item Remove Process. // TODO
// self.ks_remove_update_interval(); // IMPORTANT
delete self.props.item_data;
self.env.gridStackRootRef.el.gridstack?.removeWidget(item);
if (Object.keys(dashboard_data.ks_item_data).length > 0) {
self._ksSaveCurrentLayout();
}
let js_id = self.env.services.action.currentController.jsId
self.env.services.action.restore(js_id)
});
}
_ksSaveCurrentLayout() {
let self = this;
let grid_config = ks_get_current_gridstack_config(this.env.gridStackRootRef.el);
let dashboard_data = self.env.getDashboardDataAsObj(['ks_gridstack_config_id'])
let model = 'ks_dashboard_ninja.child_board';
let rec_id = dashboard_data.ks_gridstack_config_id;
if (!isMobileOS()) {
rpc("/web/dataset/call_kw/ks_dashboard_ninja.child_board/write",{
model: model,
method: 'write',
args: [rec_id, {
"ks_gridstack_config": JSON.stringify(grid_config)
}],
kwargs:{},
})
}
}
ksRenderChartColorOptions(e) {
let self = this;
// FIXME : Correct this later.
this.__owl__.parent.component.ksRenderChartColorOptions(e);
}
}

View File

@@ -0,0 +1,31 @@
.ks_dashboard_menu_container {
&.form-control.form-input-box.encapsulated-form-arrow {
width: auto !important;
&::after {
top: 41% !important;
right: 13px !important;
}
select {
font-size: $font-14;
/* width: 100%; */
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
li {
justify-content: center;
.dash-btn-red {
font-size: $font-12 !important;
font-weight: $f-w-600;
min-height: 32px !important;
height: 100%;
margin-right: 8px;
padding: 0 12px !important;
}
}
}

View File

@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="ks_dashboard_ninja.ks_chart_buttons">
<div t-att-class=" common_classes + props.item_classes"
t-if="showButtons" t-att-data-item_id="id" t-on-click.stop="handleDropdowns" t-ref="rootRef">
<t t-if="item_data.ksIsDashboardManager">
<div class="ks_chart_inner_buttons" t-if="chart_list.includes(item_data.ks_dashboard_item_type) &amp;&amp; !isMobile">
<button title="Color Palette" data-bs-toggle="dropdown"
class="ks_dashboard_item_action ks_dashboard_color_option dropdown-toggle o-no-caret img-bg hover-item me-2"
type="button" aria-expanded="true">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/brush.svg" alt="create" class="img-fluid" loading="lazy"/>
</button>
<ul role="menu" class="dropdown-menu ks-dropdown-menu dropdown-menu-right ks_color_pallate"
t-att-data-item-id="id"
t-att-data-chart-type="chart_type" t-att-data-chart-family="chart_family">
<t t-foreach="ksChartColorOptions" t-as="color_option" t-key="color_option_index">
<li t-att-class="'ks_li_'+color_option">
<span t-att-class="color_option + ' ks_chart_color_options'"
t-att-data-chart-color="color_option" t-on-click="ksRenderChartColorOptions">
<t t-esc="color_option"/>
</span>
</li>
</t>
</ul>
</div>
<div class="ks_chart_inner_buttons">
<button title="Move/Duplicate" data-bs-toggle="dropdown" t-if="!isMobile"
class="ks_dashboard_item_action dropdown-toggle img-bg hover-item me-2" type="button"
aria-expanded="true" t-att-style="'color:'+ ks_button_color + ';'">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.59351 3.92004H3.40682C2.26682 3.92004 1.3335 4.85337 1.3335 5.99337V13.5667C1.3335 14.5334 2.02683 14.9467 2.87349 14.4734L5.49349 13.0134C5.77349 12.86 6.22683 12.86 6.50016 13.0134L9.12016 14.4734C9.96682 14.9467 10.6602 14.5334 10.6602 13.5667V5.99337C10.6668 4.85337 9.73351 3.92004 8.59351 3.92004Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6668 5.99337V13.5667C10.6668 14.5334 9.9735 14.94 9.12683 14.4734L6.50684 13.0134C6.22684 12.86 5.77349 12.86 5.49349 13.0134L2.87349 14.4734C2.02683 14.94 1.3335 14.5334 1.3335 13.5667V5.99337C1.3335 4.85337 2.26682 3.92004 3.40682 3.92004H8.59351C9.73351 3.92004 10.6668 4.85337 10.6668 5.99337Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.6668 3.4067V10.98C14.6668 11.9467 13.9735 12.3534 13.1268 11.8867L10.6668 10.5134V5.99337C10.6668 4.85337 9.73351 3.92004 8.59351 3.92004H5.3335V3.4067C5.3335 2.2667 6.26682 1.33337 7.40682 1.33337H12.5935C13.7335 1.33337 14.6668 2.2667 14.6668 3.4067Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<ul role="menu" class="ks_dashboard_menu_container form-input-box form-control encapsulated-form-arrow dropdown-menu ks-dropdown-menu dropdown-menu-right">
<li class="ks_md_heading m-2">
<span>Select Dashboard</span>
</li>
<li class="m-2" t-on-click.stop="() => {}">
<select class="o_input o_group_selector o_add_group ks_dashboard_select" t-model="this.selectedDashboardId">
<t t-foreach="item_data.ks_dashboard_list" t-as="ks_dashboard"
t-key="ks_dashboard_index">
<option t-att-value="ks_dashboard['id']">
<t t-esc="ks_dashboard['name']"/>
</option>
</t>
</select>
</li>
<li class="m-2">
<button class="dash-btn-red o_apply_group o_add_group ks_duplicate_item" t-on-click="onKsDuplicateItemClick"
tabindex="-1" type="button">Duplicate
</button>
<button class="dash-btn-red o_apply_group o_add_group ks_move_item"
tabindex="-1" type="button" t-on-click="onKsMoveItemClick">Move
</button>
</li>
</ul>
</div>
<button title="Quick Customize" t-if="!isMobile" t-on-click="onEditItemTypeClick"
class="ks_dashboard_quick_edit_action_popup d-sm-block d-none img-bg hover-item me-2"
type="button" t-att-data-item-id="id" t-att-style="'color:'+ ks_button_color + ';'">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none">
<path d="M9.16675 1.66663H7.50008C3.33341 1.66663 1.66675 3.33329 1.66675 7.49996V12.5C1.66675 16.6666 3.33341 18.3333 7.50008 18.3333H12.5001C16.6667 18.3333 18.3334 16.6666 18.3334 12.5V10.8333" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.3666 2.51663L6.7999 9.0833C6.5499 9.3333 6.2999 9.82497 6.2499 10.1833L5.89157 12.6916C5.75823 13.6 6.3999 14.2333 7.30823 14.1083L9.81657 13.75C10.1666 13.7 10.6582 13.45 10.9166 13.2L17.4832 6.6333C18.6166 5.49997 19.1499 4.1833 17.4832 2.51663C15.8166 0.849966 14.4999 1.3833 13.3666 2.51663Z" stroke="currentColor" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.425 3.45837C12.9834 5.45004 14.5417 7.00837 16.5417 7.57504" stroke="currentColor" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</t>
<div class="ks_chart_inner_buttons dropdown">
<button title="Info" data-bs-toggle="dropdown"
class="ks_item_description dropdown-toggle o-no-caret img-bg hover-item me-2"
type="button" t-att-style="'color:'+ ks_button_color + ';'"
aria-expanded="true">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00004 14.6667C11.6667 14.6667 14.6667 11.6667 14.6667 8.00004C14.6667 4.33337 11.6667 1.33337 8.00004 1.33337C4.33337 1.33337 1.33337 4.33337 1.33337 8.00004C1.33337 11.6667 4.33337 14.6667 8.00004 14.6667Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5.33337V8.66671" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99646 10.6666H8.00245" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div role="menu" class="dropdown-menu ks-dropdown-menu dropdown-menu-right" style="width:20rem">
<div class="ks_chart_export_menu">
<div class="ks_chart_export_menu_header" style="margin-left:-10px">
<span>Info</span>
</div>
<div class="ks_info" style="margin-left:10px">
<span>Company: <t t-esc="ks_company"/></span>
</div>
<div class="ks_info" style="margin-left:10px">
<t t-if="item_data.ks_info">
<t t-foreach="ks_item_description_list" t-as="ks_description" t-key="ks_description_index">
<span><t t-esc="ks_description"/></span> <br/>
</t>
</t>
</div>
</div>
</div>
</div>
<div t-if="isExportListVisible" class="ks_chart_inner_buttons ks_dashboard_more_action" t-att-data-item-id="id">
<button title="Export Chart" data-bs-toggle="dropdown"
class="ks_dashboard_item_action dropdown-toggle o-no-caret img-bg hover-item me-2"
type="button"
aria-expanded="true">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/document-upload.svg" alt="document-upload"
class="img-fluid" loading="lazy"/>
</button>
<div role="menu" class="dropdown-menu ks-dropdown-menu dropdown-menu-right">
<!--Dynamic Rendering-->
<t t-call="ksMoreChartOptions"/>
</div>
</div>
<div t-if="item_data.ksIsDashboardManager" class="ks_chart_inner_buttons ks_dashboard_more_action">
<button title="More" data-bs-toggle="dropdown" type="button" aria-expanded="true"
class="ks_dashboard_item_action dropdown-toggle img-bg hover-item me-2" t-att-style="'color:'+ ks_button_color + ';'">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.2916 15.8333C12.2916 17.1 11.2666 18.125 9.99992 18.125C8.73325 18.125 7.70825 17.1 7.70825 15.8333C7.70825 14.5667 8.73325 13.5417 9.99992 13.5417C11.2666 13.5417 12.2916 14.5667 12.2916 15.8333ZM8.95825 15.8333C8.95825 16.4083 9.42492 16.875 9.99992 16.875C10.5749 16.875 11.0416 16.4083 11.0416 15.8333C11.0416 15.2583 10.5749 14.7917 9.99992 14.7917C9.42492 14.7917 8.95825 15.2583 8.95825 15.8333Z" fill="currentColor"/>
<path d="M12.2916 4.16671C12.2916 5.43337 11.2666 6.45837 9.99992 6.45837C8.73325 6.45837 7.70825 5.43337 7.70825 4.16671C7.70825 2.90004 8.73325 1.87504 9.99992 1.87504C11.2666 1.87504 12.2916 2.90004 12.2916 4.16671ZM8.95825 4.16671C8.95825 4.74171 9.42492 5.20837 9.99992 5.20837C10.5749 5.20837 11.0416 4.74171 11.0416 4.16671C11.0416 3.59171 10.5749 3.12504 9.99992 3.12504C9.42492 3.12504 8.95825 3.59171 8.95825 4.16671Z" fill="currentColor"/>
<path d="M12.2916 9.99996C12.2916 11.2666 11.2666 12.2916 9.99992 12.2916C8.73325 12.2916 7.70825 11.2666 7.70825 9.99996C7.70825 8.73329 8.73325 7.70829 9.99992 7.70829C11.2666 7.70829 12.2916 8.73329 12.2916 9.99996ZM8.95825 9.99996C8.95825 10.575 9.42492 11.0416 9.99992 11.0416C10.5749 11.0416 11.0416 10.575 11.0416 9.99996C11.0416 9.42496 10.5749 8.95829 9.99992 8.95829C9.42492 8.95829 8.95825 9.42496 8.95825 9.99996Z" fill="currentColor"/>
</svg>
</button>
<div role="menu" class="ks_chart_export_menu dropdown-menu ks-dropdown-menu dropdown-menu-right">
<div t-if="!isExportListVisible" class="ks_chart_json_export ks_chart_export_menu_item d-flex align-items-center"
t-att-data-item-id="id" data-format="chart_xls" t-on-click="ksItemExportJson">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/document-upload.svg" alt="document-upload" class="img-fluid" loading="lazy"/>
<span>Export Item</span>
</div>
<div class="d-flex g-10 align-items-center">
<button class="ks_dashboard_item_delete img-bg hover-item me-2 kpi-tile-img" title="Remove Item" type="button" t-on-click="onKsDeleteItemClick">
<svg width="16" height="16" viewBox="0 0 16 16" class="me-1" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665" fill="#241C1D"/>
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.66699 3.31337L5.81366 2.44004C5.92033 1.80671 6.00033 1.33337 7.12699 1.33337H8.87366C10.0003 1.33337 10.087 1.83337 10.187 2.44671L10.3337 3.31337" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5669 6.09338L12.1336 12.8067C12.0603 13.8534 12.0003 14.6667 10.1403 14.6667H5.86026C4.00026 14.6667 3.94026 13.8534 3.86693 12.8067L3.43359 6.09338" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.88672 11H9.10672" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.33301 8.33337H9.66634" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
</svg> Remove
</button>
</div>
<div class="d-flex g-10 align-items-center" t-if="!isMobile &amp;&amp; item_data.ks_dashboard_item_type !== 'ks_to_do'">
<button id='ks_ai_item_exp_dash' class="img-bg hover-item me-2 kpi-tile-img"
t-on-click="_onButtonClick" title="AI provides the insights of the item">
<img src="/ks_dashboard_ninja/static/images/favorite-chart.svg" alt="create" class="img-fluid" loading="lazy"/>
Explain With AI
</button>
</div>
<div class="d-flex g-10 align-items-center" t-if="!isMobile">
<button class="ks_dashboard_item_chatter_wizard img-bg hover-item me-2 kpi-tile-img" title="Channel" type="button" t-on-click="openChatWizard">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/messages.svg" alt="messages" class="img-fluid" loading="lazy"/>
Chat
</button>
</div>
</div>
</div>
</div>
</t>
<t t-name="ksMoreChartOptions">
<div class="ks_chart_export_menu">
<div class="ks_chart_xls_csv_export ks_chart_export_menu_item" t-on-click="ksChartExportXlsCsv"
t-att-data-chart-id="id" t-att-data-format="item_data.ks_dashboard_item_type === 'ks_list_view' ? 'list_xls' : 'chart_xls'">
<i class="fa fa-file-excel-o"/>
<span>Export to Excel</span>
</div>
<div class="ks_chart_xls_csv_export ks_chart_export_menu_item" t-on-click="ksChartExportXlsCsv"
t-att-data-chart-id="id" t-att-data-format="item_data.ks_dashboard_item_type === 'ks_list_view' ? 'list_csv' : 'chart_csv'">
<i class="fa fa-file-code-o"/>
<span>Export to CSV</span>
</div>
<div t-if="item_data.ks_dashboard_item_type != 'ks_list_view'" t-att-data-chart-id="id"
class="ks_chart_pdf_export ks_chart_export_menu_item" t-on-click="ksChartExportPdf">
<i class="fa fa-file-pdf-o"/>
<span>Save as PDF</span>
</div>
<div t-if="item_data.ks_dashboard_item_type != 'ks_list_view'" t-att-data-chart-id="id"
class="ks_chart_export_menu_item ks_chart_image_export" t-on-click="ksChartExportimage">
<i class="fa fa-file-image-o"/>
<span>Save as Image</span>
</div>
<div class="ks_chart_json_export ks_chart_export_menu_item" t-on-click="ksItemExportJson"
t-att-data-item-id="item_id" data-format="chart_xls">
<i class="fa fa-file-code-o"/>
<span>Export Item</span>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,149 @@
/** @odoo-module **/
import { Dialog } from "@web/core/dialog/dialog";
import { _t } from "@web/core/l10n/translation";
import { Component, useState, useRef, useChildSubEnv } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { Ksdashboardgraph } from '@ks_dashboard_ninja/components/ks_dashboard_graphs/ks_dashboard_graphs'
import { Ksdashboardtile } from '@ks_dashboard_ninja/components/ks_dashboard_tile_view/ks_dashboard_tile';
import { Ksdashboardtodo } from '@ks_dashboard_ninja/components/ks_dashboard_to_do_item/ks_dashboard_to_do';
import { Ksdashboardkpiview } from '@ks_dashboard_ninja/components/ks_dashboard_kpi_view/ks_dashboard_kpi';
import { ks_render_graphs, ksrenderfunnelchart } from "@ks_dashboard_ninja/js/charts_render_global_functions";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class CustomDialog extends Component {
setup() {
super.setup();
this.dialogService = useService("dialog");
this.graphRef = useRef("graph_div");
this.ks_question = useRef("ks_question")
this.user = user.name;
this.name_title = this.user.split(' ').length>1 ? this.user.split(' ')[0].charAt(0)+this.user.split(' ')[1].charAt(0):this.user.split(' ')[0].charAt(0);
this.dashboard_container = useRef("ks_gridstack_container");
this._rpc = rpc
this.notification = useService("notification");
this.ks_dashboard_data = this.props.dashboard_data
this.state = useState({
explain_ai: true,
newItem: this.props.item.ks_dashboard_item_type,
chart_ks_dashboard_id: this.props.item.ks_dashboard_id,
toggleState: true,
confirm_notification: false,
messages: [ { sender: 'ai', text: 'Hello I am AI Assistant, How may i help you ?' } ],
confirm_notification: false,
switch_layout: false,
});
useChildSubEnv({
ksGetParamsForItemFetch: (item_id) => this.props.getDomainParams(item_id),
getContext : this.props.getDashboardContext,
})
}
switch_bar_chart(new_item) {
this.ks_item = Object.assign({},this.props.item)
this.ks_item['ks_dashboard_item_type'] = new_item
$(this.graphRef.el).find('.ks_chart_card_body').remove();
if(new_item == 'ks_funnel_chart'){
ksrenderfunnelchart.bind(this)($(this.graphRef.el), this.ks_item, 'dashboard_view');
}
else{
ks_render_graphs.bind(this)($(this.graphRef.el), this.ks_item, this.props.dashboard_data?.zooming_enabled, 'dashboard_view');
}
this.state.newItem = new_item;
this.state.switch_layout = true;
}
ks_switch_layout() {
var self =this
let selectedItem = this.state.newItem;
let item_data = this.props.item
let item_id = this.props.item.id
let chart_id = this.state.chart_ks_dashboard_id;
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/write",{
model: 'ks_dashboard_ninja.item',
method: 'write',
args: [item_id, {
'ks_dashboard_item_type': selectedItem,
...( (selectedItem === 'ks_funnel_chart' && item_data.ks_chart_measure_field.length ) && {
'ks_funnel_record_field' : item_data.ks_chart_measure_field[0] })
}],
kwargs:{}
}).then(function(result) {
self.props.item.ks_dashboard_item_type = self.state.newItem
self.props.current_graph.item.ks_dashboard_item_type = selectedItem;
self.props.current_graph.ksFetchUpdateItem(item_id, chart_id, {})
})
.then(() => {
self.state.confirm_notification = JSON.parse(JSON.stringify(true));
});
}
ks_key_check(ev){
if (ev.keyCode == 13){
this.ks_send_request(ev);
}
}
ks_send_request(ev){
let self = this;
ev.stopPropagation();
let user_question= $(this.ks_question.el).val()
let user_obj = {sender:"user",text:user_question}
this.state.messages = [...this.state.messages,user_obj,{sender:'ai',text:'loading'}]
$(this.ks_question.el).val('')
rpc('/web/dataset/call_kw/ks_dashboard_ninja.arti_int/ks_gen_chat_res',{
model:'ks_dashboard_ninja.arti_int',
method:'ks_gen_chat_res',
args:[],
kwargs:{ks_question:user_question}
}).then((result)=>{
if (result['Answer']){
let answer = result['Answer'].split('\n').join('')
self.state.messages.pop()
self.state.messages.push({sender:'ai',text:answer})
}else{
self.state.messages.pop()
self.state.messages.push({sender:'ai',text:'AI unable to Generate Response'})
}
})
}
closeNotification(){
this.state.confirm_notification = false;
}
onChartCanvasClick_funnel(){}
}
CustomDialog.props = {
title: { type: String, optional:true},
ks_dashboard_manager: { type: Boolean, optional: true },
ks_dashboard_items: Object,
ks_dashboard_data: Object,
ks_dashboard_item_type: { type: String, optional: true },
ksdatefilter: { type: String, optional: true },
pre_defined_filter: { type:Object, optional:true},
custom_filter: { type:Object, optional:true},
dashboard_data: Object,
item: { type: Object, optional: true },
ks_speak: { type: Function, optional: true },
ksDateFilterSelection: { type: String, optional: true },
close: { type: Function, optional: true },
hideButtons: { type: Number, optional: true },
update_graph: { type: Function, optional: true },
current_graph: { type: Object, optional: true },
getDomainParams: { type: Function, optional: true },
getDashboardContext: { type: Function, optional: true }
}
CustomDialog.components = { Dialog, Ksdashboardgraph, Ksdashboardtile, Ksdashboardkpiview, Ksdashboardtodo };
CustomDialog.template = "ks_dashboard_ninja.CustomDialog";

View File

@@ -0,0 +1,155 @@
.ks-common-tab-pills {
.nav {
border-bottom: 1px solid $color-E5E7EB !important;
.nav-link {
font-size: $font-14;
font-weight: $f-w-400;
line-height: 24px;
letter-spacing: 0.01em;
text-align: left;
color: $color-paragraph !important;
padding: 16px !important;
&:hover {
transition: 0.2s linear;
transform: scale(1.1);
cursor: pointer !important;
}
svg {
stroke: $color-black;
}
&.active {
color: $color-E7495E !important;
background-color: $color-white;
border-bottom: 2px solid $color-E7495E;
border-radius: 0 !important;
transition: 0.2s linear;
transform: scale(1.1);
cursor: pointer !important;
svg {
stroke: $color-E7495E;
}
}
}
}
}
.share-switch {
.bg-F5F8FB {
background-color: $color-bg-main;
}
}
.expalin-content-box {
// border: 1px solid $color-E5E7EB;
// background-color: $color-bg-main;
min-height: 581px;
border-radius: 20px;
position: relative;
.title {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 22.4px;
text-align: left;
color: $color-black;
@include max-992 {
font-size: $font-12;
}
}
.description-box {
max-height: 500px;
overflow: auto;
@include custom-scrollbar;
@include max-992 {
max-height: calc(100vh - 387px);
}
p {
font-size: 12px;
font-weight: 400;
line-height: 19.2px;
text-align: left;
color: $color-black;
}
}
}
.chart-insight-tooltip-container {
position: fixed;
bottom: 30px;
left: 0;
right: 0;
width: 100%;
z-index: 30;
.tooltip-box {
box-shadow: 0px 4px 40px 0px #DBCACA80;
border-radius: 10px;
padding: 20px;
background-color: $color-white;
.tooltip-content {
margin-right: 20px;
height: 69;
width: 100px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.tooltip-title {
font-size: $font-24;
font-weight: $f-w-500;
line-height: 33.6px;
text-align: left;
color: $color-paragraph;
}
}
}
.ks-chart-insight {
border: 1.63px solid $color-E5E7EB;
min-height: 579px;
box-shadow: 0px 6.05px 30.23px 0px #EEEEEE80;
border-radius: 30px;
padding: 8px;
h4 {
font-size: $font-20;
font-weight: $f-w-500;
line-height: 48.38px;
text-align: left;
color: $color-05004E;
@include max-992 {
font-size: $font-14;
}
}
.chart-img {
height: 462px;
width: 100%;
img {
height: 100%;
width: 100%;
object-fit: contain;
}
}
}

View File

@@ -0,0 +1,290 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.CustomDialog">
<Dialog title="'Chart Insights'" footer="false">
<div class="container-fluid">
<div class="row" style="min-height: 60vh;">
<div class="col-6 d-flex flex-column align-items-stretch chat_explain_ai">
<t t-if="props.ks_dashboard_item_type === 'ks_tile'">
<Ksdashboardtile item="props.item"
dashboard_data="props.dashboard_data"
ksdatefilter="props.ksdatefilter"
pre_defined_filter="props.pre_defined_filter"
custom_filter="props.custom_filter"
hideButtons="0"
on_dialog="true"
onItemClick="() => {} "
itemsToUpdateList="[]"
ks_speak="props.ks_speak"/>
</t>
<t t-elif="props.ks_dashboard_item_type === 'ks_kpi'">
<Ksdashboardkpiview item="props.item"
dashboard_data="props.dashboard_data"
ksdatefilter="props.ksdatefilter"
pre_defined_filter="props.pre_defined_filter"
custom_filter="props.custom_filter"
hideButtons="0"
onItemClick="() => {} "
on_dialog="true"
itemsToUpdateList="[]"
ks_speak="props.ks_speak"/>
</t>
<t t-elif="props.ks_dashboard_item_type === 'ks_to_do'">
<Ksdashboardtodo item="props.item"
dashboard_data="props.dashboard_data"
hideButtons="0"
/>
</t>
<t t-else="">
<t t-if="props.item.ks_dashboard_item_type != 'ks_list_view'">
<div class="ks-common-tab-pills">
<ul class="nav nav-pills mb-3 border-bottom border-2" id="pills-tab" role="tablist">
<t t-set="chart_name" t-value="props.ks_dashboard_item_type"/>
<li class="nav-item" role="presentation">
<button t-attf-class="nav-link text-primary fw-semibold position-relative #{chart_name =='ks_bar_chart'?'show active':''}"
id="pills-home-tab" data-bs-toggle="pill" data-bs-target="#pills-one" type="button"
role="tab" aria-controls="pills-home" aria-selected="true"
t-on-click="()=>this.switch_bar_chart('ks_bar_chart')">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 18.3334H17.5" stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M4.66665 6.9834H3.33333C2.875 6.9834 2.5 7.3584 2.5 7.81673V15.0001C2.5 15.4584 2.875 15.8334 3.33333 15.8334H4.66665C5.12498 15.8334 5.49998 15.4584 5.49998 15.0001V7.81673C5.49998 7.3584 5.12498 6.9834 4.66665 6.9834Z"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M10.6666 4.32507H9.33333C8.875 4.32507 8.5 4.70007 8.5 5.15841V15.0001C8.5 15.4584 8.875 15.8334 9.33333 15.8334H10.6666C11.125 15.8334 11.5 15.4584 11.5 15.0001V5.15841C11.5 4.70007 11.125 4.32507 10.6666 4.32507Z"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M16.6666 1.66675H15.3333C14.875 1.66675 14.5 2.04175 14.5 2.50008V15.0001C14.5 15.4584 14.875 15.8334 15.3333 15.8334H16.6666C17.125 15.8334 17.5 15.4584 17.5 15.0001V2.50008C17.5 2.04175 17.125 1.66675 16.6666 1.66675Z"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
<li class="nav-item" role="presentation">
<button t-attf-class="nav-link text-primary fw-semibold position-relative #{chart_name =='ks_area_chart'?'show active':''}"
id="pills-profile-tab" data-bs-toggle="pill" data-bs-target="#pills-two"
type="button" role="tab" aria-controls="pills-profile" aria-selected="false"
t-on-click="()=>this.switch_bar_chart('ks_area_chart')">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M7.50033 18.3333L12.5003 18.3333C16.667 18.3333 18.3337 16.6666 18.3337 12.5L18.3337 7.49996C18.3337 3.33329 16.667 1.66663 12.5003 1.66663L7.50033 1.66663C3.33366 1.66663 1.66699 3.33329 1.66699 7.49996L1.66699 12.5C1.66699 16.6666 3.33366 18.3333 7.50033 18.3333Z"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M6.1084 12.075L8.09173 9.50005C8.37507 9.13338 8.90007 9.06672 9.26673 9.35005L10.7917 10.55C11.1584 10.8334 11.6834 10.7667 11.9667 10.4084L13.8917 7.92505"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
<li class="nav-item" role="presentation">
<button t-attf-class="nav-link text-primary fw-semibold position-relative #{chart_name =='ks_pie_chart'?'show active':''}"
id="pills-profile-tab" data-bs-toggle="pill" data-bs-target="#pills-three"
type="button" role="tab" aria-controls="pills-profile" aria-selected="false"
t-on-click="()=>this.switch_bar_chart('ks_pie_chart')">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M15.2667 10.0002C17.4333 10.0002 18.3333 9.16686 17.5333 6.43352C16.9917 4.59186 15.4083 3.00852 13.5667 2.46686C10.8333 1.66686 10 2.56686 10 4.73352V7.13352C10 9.16686 10.8333 10.0002 12.5 10.0002H15.2667Z"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M16.6672 12.2498C15.8922 16.1081 12.1922 18.9081 7.98384 18.2248C4.82551 17.7164 2.28384 15.1748 1.76718 12.0164C1.09218 7.82476 3.87551 4.12476 7.71718 3.34143"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
<li class="nav-item" role="presentation">
<button t-attf-class="nav-link text-primary fw-semibold position-relative #{chart_name =='ks_radialBar_chart'?'show active':''}"
id="pills-profile-tab" data-bs-toggle="pill" data-bs-target="#pills-four"
type="button" role="tab" aria-controls="pills-profile" aria-selected="false"
t-on-click="()=>this.switch_bar_chart('ks_radialBar_chart')">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.35033 4.97496C2.29199 6.37496 1.66699 8.11663 1.66699 9.99996C1.66699 14.6 5.40033 18.3333 10.0003 18.3333C14.6003 18.3333 18.3337 14.6 18.3337 9.99996C18.3337 5.39996 14.6003 1.66663 10.0003 1.66663"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M4.16699 9.99996C4.16699 13.225 6.77533 15.8333 10.0003 15.8333C13.2253 15.8333 15.8337 13.225 15.8337 9.99996C15.8337 6.77496 13.2253 4.16663 10.0003 4.16663"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M10 13.3333C11.8417 13.3333 13.3333 11.8416 13.3333 9.99996C13.3333 8.15829 11.8417 6.66663 10 6.66663"
stroke="" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</li>
<li class="nav-item" role="presentation">
<button t-attf-class="nav-link text-primary fw-semibold position-relative #{chart_name =='ks_funnel_chart'?'show active':''}"
id="pills-profile-tab" data-bs-toggle="pill" data-bs-target="#pills-five"
type="button" role="tab" aria-controls="pills-profile" aria-selected="false"
t-on-click="()=>this.switch_bar_chart('ks_funnel_chart')">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4.50065 1.75H15.5007C16.4173 1.75 17.1673 2.5 17.1673 3.41667V5.25C17.1673 5.91667 16.7507 6.75 16.334 7.16667L12.7507 10.3333C12.2507 10.75 11.9173 11.5833 11.9173 12.25V15.8333C11.9173 16.3333 11.584 17 11.1673 17.25L10.0007 18C8.91732 18.6667 7.41732 17.9167 7.41732 16.5833V12.1667C7.41732 11.5833 7.08398 10.8333 6.75065 10.4167L3.58398 7.08333C3.16732 6.66667 2.83398 5.91667 2.83398 5.41667V3.5C2.83398 2.5 3.58398 1.75 4.50065 1.75Z"
stroke="" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M9.10833 1.75L5 8.33333" stroke="" stroke-width="1.25"
stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</li>
</ul>
</div>
</t>
<div class="flex-grow-1 ks-chart-insight position-relative" t-ref="graph_div">
<Ksdashboardgraph item="props.item"
dashboard_data="props.dashboard_data"
ksdatefilter="props.ksdatefilter"
pre_defined_filter="props.pre_defined_filter"
custom_filter="props.custom_filter"
hideButtons="0" onItemClick="() => {} " itemsToUpdateList="[]"
ks_speak="props.ks_speak"
/>
</div>
</t>
</div>
<div id="explain_ai" class="col-6 d-flex1 flex-column" style="min-height: 100%;">
<t t-if="state.switch_layout">
<t t-if="props.ks_dashboard_item_type != 'ks_to_do' and props.ks_dashboard_item_type != 'ks_kpi' and props.ks_dashboard_item_type != 'ks_tile' and props.item.ks_dashboard_item_type != 'ks_list_view' and props.item.ks_dashboard_item_type != state.newItem">
<div class="share-switch d-flex justify-content-end align-items-center p-3">
<button class="dash-btn-red d-flex justify-content-center align-items-center me-2"
t-on-click="()=>this.ks_switch_layout()" title="Switch chart on Dashboard">
<span class="me-2">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M10.7673 1.88333L16.1923 4.80833C16.8257 5.14999 16.8257 6.12499 16.1923 6.46666L10.7673 9.39166C10.284 9.64999 9.71732 9.64999 9.23398 9.39166L3.80898 6.46666C3.17565 6.12499 3.17565 5.14999 3.80898 4.80833L9.23398 1.88333C9.71732 1.62499 10.284 1.62499 10.7673 1.88333Z"
stroke="#FFF" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M3.00768 8.44161L8.04935 10.9666C8.67435 11.2833 9.07435 11.9249 9.07435 12.6249V17.3916C9.07435 18.0833 8.34935 18.5249 7.73268 18.2166L2.69102 15.6916C2.06602 15.3749 1.66602 14.7333 1.66602 14.0333V9.26661C1.66602 8.57494 2.39102 8.13327 3.00768 8.44161Z"
stroke="#FFF" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M16.9924 8.44161L11.9508 10.9666C11.3258 11.2833 10.9258 11.9249 10.9258 12.6249V17.3916C10.9258 18.0833 11.6508 18.5249 12.2674 18.2166L17.3091 15.6916C17.9341 15.3749 18.3341 14.7333 18.3341 14.0333V9.26661C18.3341 8.57494 17.6091 8.13327 16.9924 8.44161Z"
stroke="#FFF" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<span>Switch Layout</span>
</button>
</div>
</t>
</t>
<div class="expalin-content-box">
<div class="d-flex justify-content-between align-items-center px-3 pt-3">
<div class="title"><t t-esc="state.explain_ai? '': 'Chat with AI'"/></div>
</div>
<t t-if="state.explain_ai">
<div class="explain-ai-box px-3">
<div class="description-box mt-4">
<t t-esc="props.item.ks_ai_analysis"/>
</div>
</div>
</t>
<t t-else="">
<div class="chat-ai-box px-3">
<div class="chat-sec mt-4">
<t t-foreach="state.messages" t-as="message" t-key="message_index">
<t t-if="message.sender == 'ai'">
<div class="left">
<div class="d-flex align-items-center mb-3">
<span class="ai-icon me-2">
<img src="/ks_dashboard_ninja/static/images/ai-icon.png" alt="ai-assistant" class="img-fluid"
loading="lazy"/>
</span>
<span class="title">AI Assistant</span>
</div>
<div class="answers">
<t t-if="message.text == 'loading'">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="background:#e7495e;"></span>
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="margin-left:5px;background:#e7495e;"></span>
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="margin-left:5px;background:#e7495e;"></span>
</t>
<t t-else="">
<t t-esc="message.text"/>
</t>
</div>
</div>
</t>
<t t-else="">
<div class="right my-4 py-2">
<div class="d-flex align-items-center mb-3 justify-content-end">
<span class="title"><t t-esc="this.user"/></span>
<span
class="user-icon ms-2 d-flex justify-content-center align-items-center">
<t t-esc="this.name_title"/>
</span>
</div>
<div class="questions text-end">
<t t-esc="message.text"/>
</div>
</div>
</t>
</t>
</div>
<div class="typer-box">
<form class="typer d-flex align-items-end">
<textarea name="" id="" placeholder="Chat with AI..." t-ref="ks_question" t-on-keyup="(ev)=>this.ks_key_check(ev)"/>
<button class="chat-logo" t-on-click="ks_send_request">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M5.54977 4.73999L11.9173 2.61749C14.7748 1.66499 16.3273 3.22499 15.3823 6.08249L13.2598 12.45C11.8348 16.7325 9.49477 16.7325 8.06977 12.45L7.43977 10.56L5.54977 9.92999C1.26727 8.50499 1.26727 6.17249 5.54977 4.73999Z"
stroke="white" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M7.58203 10.2375L10.267 7.54504L7.58203 10.2375Z"
fill="#241C1D" />
<path d="M7.58203 10.2375L10.267 7.54504" stroke="white"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</form>
</div>
</div>
</t>
</div>
</div>
</div>
<t t-if="state.confirm_notification">
<div class="container chart-insight-tooltip-container">
<div class="d-flex align-items-center mx-auto tooltip-box">
<div class="tooltip-content">
<img src="/ks_dashboard_ninja/static/images/demo-tooltip-img.png" alt="tootltip" class="img-fluid" loading="lazy"/>
</div>
<h5 class="tooltip-title mb-0">Your Chart has been Successfully Updated</h5>
<div class="ms-auto cursor-pointer" t-on-click="closeNotification">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z"
stroke="#241C1D" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M9.16992 14.83L14.8299 9.17004" stroke="#241C1D" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M14.8299 14.83L9.16992 9.17004" stroke="#241C1D" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</div>
</div>
</div>
</t>
</div>
</Dialog>
</t>
</templates>

View File

@@ -0,0 +1,70 @@
/**@odoo-module **/
import {Component,useState,useRef,onPatched} from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class KschatwithAI extends Component{
setup(){
this.user = user.name;
this.name_title = this.user.split(' ').length>1 ? this.user.split(' ')[0].charAt(0)+this.user.split(' ')[1].charAt(0):this.user.split(' ')[0].charAt(0);
this.state = useState({ messages:[{sender:'ai',text:'Hello I am AI Assistant, How may i help you ?',frame:false}]})
this.ks_question = useRef("ks_question")
onPatched(()=>{
$(".chat-sec")[0].scrollTop = $(".chat-sec")[0].scrollHeight
});
this.send_request = true
}
ks_key_check(ev){
if (ev.keyCode == 13){
if (this.send_request == true){
this.ks_send_request(ev)
}
}
}
ks_send_request(ev){
let self = this;
ev.stopPropagation();
ev.preventDefault();
let user_question= $(this.ks_question.el).val()
if (user_question.length>1 && this.send_request){
let user_obj = {sender:"user",text:user_question,frame:false}
this.state.messages = [...this.state.messages,user_obj,{sender:'ai',text:'loading',frame:false}]
$(this.ks_question.el).val('')
this.send_request = false
rpc('/web/dataset/call_kw/ks_dashboard_ninja.arti_int/ks_gen_chat_res',{
model:'ks_dashboard_ninja.arti_int',
method:'ks_gen_chat_res',
args:[],
kwargs:{ks_question:user_question}
}).then((result)=>{
if (result['Answer']){
let answer = result['Answer'].split('\n').join('')
if (answer.indexOf('Summary')!= -1){
answer = answer.slice(0,answer.indexOf('Summary'))
}
else{
answer = answer
}
self.state.messages.pop()
let frame = JSON.parse(result['Dataframe']).length>5? JSON.parse(result['Dataframe']):false;
self.state.messages.push({sender:'ai',text:answer,frame})
this.send_request = true
}else{
self.state.messages.pop()
self.state.messages.push({sender:'ai',text:'AI unable to Generate Response'})
this.send_request = true
}
})
}else{
$(this.ks_question.el).val('')
let user_obj = {sender:"user",text:user_question,frame:false}
let res_obj = {sender:'ai',text:'Either you have not asked any question or AI Unable to generate response',frame:false}
this.state.messages = [...this.state.messages,user_obj,res_obj]
}
}
};
KschatwithAI.components = { Dialog };
KschatwithAI.template = "Kschatwithai"

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="Kschatwithai">
<div id="chatid" class="chart-sec-modal message_with_ai">
<Dialog title="'Chat with AI'" footer="false" size="'md'">
<div class="chat-ai-box px-3">
<div class="chat-sec mt-4">
<t t-foreach="state.messages" t-as="message" t-key="message_index">
<t t-if="message.sender == 'ai'">
<div class="left">
<div class="d-flex align-items-center mb-3">
<span class="ai-icon me-2">
<img src="/ks_dashboard_ninja/static/images/ai-icon.png" alt="ai-assistant" class="img-fluid"
loading="lazy"/>
</span>
<span class="title">Ksolves Assistant</span>
</div>
<div class="answers">
<t t-if="message.text == 'loading'">
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="background:#e7495e;"></span>
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="margin-left:5px;background:#e7495e;"></span>
<span class="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" style="margin-left:5px;background:#e7495e;"></span>
</t>
<t t-else="">
<t t-if="!message.frame">
<t t-esc="message.text"/>
</t>
</t>
</div>
<t t-if="message.frame">
<p class="answers">Here is your generated table</p>
<div class="chart-table-wrapper">
<table class="table table-striped mb-0">
<thead>
<tr>
<t t-foreach="Object.keys(message.frame[0])" t-as="header" t-key="header_index">
<th>
<t t-esc="header"/>
</th>
</t>
</tr>
</thead>
<tbody>
<t t-foreach="message.frame" t-as="value" t-key="value_index">
<tr>
<t t-foreach="Object.keys(value)" t-as="item" t-key="item_index">
<td>
<t t-esc="value[item]"/>
</td>
</t>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</div>
</t>
<t t-else="">
<div class="right my-4 py-2">
<div class="d-flex align-items-center mb-3 justify-content-end">
<span class="title"><t t-esc="this.user"/></span>
<span
class="user-icon ms-2 d-flex justify-content-center align-items-center">
<t t-esc="this.name_title"/>
</span>
</div>
<div class="questions text-end">
<t t-esc="message.text"/>
</div>
</div>
</t>
</t>
</div>
<div class="typer-box">
<form class="typer d-flex align-items-end">
<textarea name="" id="" placeholder="Chat with AI..." t-ref="ks_question" t-on-keyup="(ev)=>this.ks_key_check(ev)"></textarea>
<button class="chat-logo" t-on-click="ks_send_request">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M5.54977 4.73999L11.9173 2.61749C14.7748 1.66499 16.3273 3.22499 15.3823 6.08249L13.2598 12.45C11.8348 16.7325 9.49477 16.7325 8.06977 12.45L7.43977 10.56L5.54977 9.92999C1.26727 8.50499 1.26727 6.17249 5.54977 4.73999Z"
stroke="white" stroke-width="1.5" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M7.58203 10.2375L10.267 7.54504L7.58203 10.2375Z"
fill="#241C1D" />
<path d="M7.58203 10.2375L10.267 7.54504" stroke="white"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</form>
</div>
</div>
</Dialog>
</div>
</t>
</templates>

View File

@@ -0,0 +1,347 @@
/** @odoo-module **/
import { Component, useState, onWillStart, onWillUpdateProps } from "@odoo/owl";
import { useLoadFieldInfo } from "@web/core/model_field_selector/utils";
import { getDomainDisplayedOperators } from "@web/core/domain_selector/domain_selector_operator_editor";
import { Domain } from "@web/core/domain";
import { useService, useBus } from "@web/core/utils/hooks";
import { treeFromDomain, domainFromTree } from "@web/core/tree_editor/condition_tree";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { getOperatorLabel } from "@web/core/tree_editor/tree_editor_operator_editor";
import { getDefaultValue, getValueEditorInfo } from "@web/core/tree_editor/tree_editor_value_editors";
import { getExpressionDisplayedOperators } from "@web/core/expression_editor/expression_editor_operator_editor";
import { KsDropDown } from "@ks_dashboard_ninja/components/custom_filter/ks_dropdown";
import { _t } from "@web/core/l10n/translation";
import { disambiguate } from "@web/core/tree_editor/utils";
import { condition, connector } from "@web/core/tree_editor/condition_tree";
import { useGetTreeDescription } from "@web/core/tree_editor/utils";
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class CustomFilter extends Component{
static props = {
domain: { type: String, optional: true },
update: { type: Function, optional: true },
remove: { type: Function, optional: true },
removeAllFilters: { type: Boolean, optional: true },
options: { type: Object, optional: true },
filter_info: { type: Object, optional: true },
};
static defaultProps = {
update: () => {},
domain: `[]`,
filter_info: {},
};
setup(){
this.notification = useService("notification");
this.loadFieldInfo = useLoadFieldInfo();
this.getDomainTreeDescription = useGetTreeDescription();
this.state = useState({
filtersRowList: [],
toggleState: true
});
this.customDomainFacets = {}
this.groupId = 0
this.domain = this.props.domain
this.filter_info = this.props.filter_info
onWillStart(() => this.willStart());
onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps))
}
onPropsUpdated(nextProps){
if(nextProps.removeAllFilters){
this.clearFacets_cookies()
this.clearRows();
}
}
willStart(){
this.setObjFromCookies();
const filters_list = Object.values(this.props.filter_info)
this.loadFieldDefs_and_Labels(filters_list);
const defaultOperator = getDomainDisplayedOperators(filters_list[0])[0]
this.defaultFilterRowObject = {
id: filters_list[0].id,
operator: defaultOperator,
value: getDefaultValue(filters_list[0], defaultOperator),
}
this.state.filtersRowList.push(JSON.parse(JSON.stringify(this.defaultFilterRowObject)));
}
setObjFromCookies(){
let customDomainFacets_from_cky = getObjectFromCookie('CFilter' + this.props.options.ks_dashboard_id)
this.customDomainFacets = customDomainFacets_from_cky?.lastAppliedFilters ?? {}
this.groupId = customDomainFacets_from_cky?.lastGroupId ?? 0
}
rowFilter(filter_id){
let filter = this.props.filter_info[filter_id]
return filter.name + ' ( ' + filter.model_name + ' ) '
}
rowOperator(row_index){
return getOperatorLabel(this.state.filtersRowList[row_index].operator)
}
// Remove Data from python , we load field defs here
async loadFieldDefs_and_Labels(filters_list) {
const promises = [];
const filter_labels = [];
const fieldDefs = {};
for (const filter of filters_list) {
filter_labels.push({ filter_name: filter.name + ' ( ' + filter.model_name + ' ) ', filter_id: filter.id})
if (typeof filter.field_name === "string") {
promises.push(
this.loadFieldInfo(filter.model, filter.field_name).then(({ fieldDef }) => {
fieldDefs[filter.id] = fieldDef;
})
);
}
}
await Promise.all(promises);
this.fieldDefs = fieldDefs;
this.filterLabels = filter_labels
}
getOperatorInfo(filter_id) {
const fieldDef = this.fieldDefs[filter_id];
const operators = getDomainDisplayedOperators(fieldDef);
const operatorList = operators.map((operator) => ({
operator: operator,
label: getOperatorLabel(operator),
}));
return operatorList;
}
getActiveOption(options, active){
const foundOption = options.find(option => option[0] === active);
return foundOption ? foundOption[1] : "Select";
}
getValueInfo(filter_row) {
const fieldDef = this.fieldDefs?.[filter_row.id] || this.filter_info[filter_row.id];
let valueInfo = getValueEditorInfo(fieldDef, filter_row.operator);
if(valueInfo.component?.name === 'Select' || valueInfo.component?.name === 'List'){
const options = fieldDef.selection || [];
const params = {activeOption: this.getActiveOption(options, filter_row.value)}
let KsSelectComponent = this.getKsSelectComponent(options, params)
if(fieldDef.type === "boolean"){
if (["is", "is_not"].includes(filter_row.operator)) {
const boolOptions = [
[true, _t("set")],
[false, _t("not set")],
];
const boolParams = {activeOption: this.getActiveOption(boolOptions, filter_row.value)}
return this.getKsSelectComponent(boolOptions, boolParams)
}
const boolOptions2 = [
[true, _t("True")],
[false, _t("False")],
];
const boolParams2 = {activeOption: this.getActiveOption(boolOptions2, filter_row.value)}
return this.getKsSelectComponent(boolOptions2, boolParams2)
}
if(valueInfo.component?.name === 'List'){
let editorInfo = getValueEditorInfo(fieldDef, "=", {
addBlankOption: true,
startEmpty: true,
});
if(editorInfo.component?.name === 'Select'){
editorInfo = KsSelectComponent
valueInfo.extractProps = ({ value, update }) => {
if (!disambiguate(value)) {
const { stringify } = editorInfo;
editorInfo.stringify = (val) => stringify(val, false);
}
return {
value,
update,
editorInfo,
};
}
}
return valueInfo;
}
return KsSelectComponent;
}
return valueInfo;
}
getKsSelectComponent(options, params){
const getOption = (value) => options.find(([v]) => v === value) || null;
return {
component: KsDropDown,
extractProps: ({ value, update }) => ({
value,
update,
options,
addBlankOption: params.addBlankOption,
activeOption: params.activeOption || "Select"
}),
isSupported: (value) => Boolean(getOption(value)),
defaultValue: () => options[0]?.[0] ?? false,
stringify: (value, disambiguate) => {
const option = getOption(value);
return option ? option[1] : disambiguate ? formatValue(value) : String(value);
},
message: _t("Value not in selection"),
};
}
getDefaultOperator(fieldDef) {
return getExpressionDisplayedOperators(fieldDef)[0];
}
onUpdateFilter(filter_id, row_index){
const fieldDef = this.fieldDefs[filter_id];
const operator = this.getDefaultOperator(fieldDef)
this.state.filtersRowList[row_index] = {
id: filter_id,
operator: operator,
value: getDefaultValue(fieldDef, operator),
}
}
addFilterRow(ev){
const node = JSON.parse(JSON.stringify(this.defaultFilterRowObject));
this.state.filtersRowList.push(node);
}
deleteRow(row_index){
this.state.filtersRowList.splice(row_index, 1);
}
clearRows(ev){
const node = JSON.parse(JSON.stringify(this.defaultFilterRowObject));
this.state.filtersRowList = [node];
}
onUpdateValue(row_index, updated_value){
let field_type = this.props?.filter_info?.[this.state.filtersRowList[row_index]?.id].type
let operator = this.state.filtersRowList[row_index]?.operator
if(field_type && ['many2many', 'many2one', 'one2many'].includes(field_type)){
if (Array.isArray(updated_value)) {
updated_value = updated_value.flatMap(item => {
if(item === "%UID") {
return user.userId;
} else if (item === "%MYCOMPANY") {
return ["in", "not in"].includes(operator) ? this.env.services.company.activeCompanyIds : this.env.services.company.activeCompanyIds[0];
} else {
return item;
}
});
}
else if(typeof updated_value === 'string'){
if(updated_value === "%UID") {
updated_value = user.userId;
} else if (updated_value === "%MYCOMPANY") {
updated_value = this.env.services.company.activeCompanyIds[0];
}
}
}
this.state.filtersRowList[row_index].value = updated_value;
}
onUpdateOperator(operator, row_index){
const fieldDef = this.fieldDefs[this.state.filtersRowList[row_index].id];
this.state.filtersRowList[row_index].operator = operator;
this.state.filtersRowList[row_index].value = getDefaultValue(fieldDef, operator, this.state.filtersRowList[row_index].value);
}
clearFacets_cookies(ev){
eraseCookie('CFilter' + this.props.options.ks_dashboard_id)
this.customDomainFacets = {}
}
async makeLabelsAndDomain(models_domain){
await Promise.all(
Object.entries(models_domain).map(async ([model, model_domain]) => {
model_domain.label = await this.getDomainTreeDescription(model, model_domain.domain);
model_domain.domain = new Domain(domainFromTree(model_domain.domain)).toList();
})
);
}
async applyFilters(ev) {
eraseCookie('CFilter' + this.props.options.ks_dashboard_id)
const filterRowList = this.state.filtersRowList;
const models_domain = {};
for (const filter_row of filterRowList) {
const filter = this.filter_info[filter_row.id];
const { model, field_name, model_name } = filter;
const {operator, value} = filter_row
if (!models_domain[model]) {
models_domain[model] = {
domain: connector('|'),
label: 'Custom Filter',
model_name
};
}
models_domain[model].domain.children.push(condition(field_name, operator, value));
}
await this.makeLabelsAndDomain(models_domain);
let domainsToUpdate = await this.validate_and_ReturnDomainsToUpdate(models_domain);
this.clearRows();
setObjectInCookie('CFilter' + this.props.options.ks_dashboard_id,
{ lastAppliedFilters: this.customDomainFacets, lastGroupId: this.groupId}, 1)
this.props.update(domainsToUpdate);
}
async validate_and_ReturnDomainsToUpdate(models_domain){
let domainsToUpdate = {}
for(const [model, model_domain] of Object.entries(models_domain)){
let domain;
let isValid;
try {
const evalContext = { ...user.context };
domain = new Domain(model_domain.domain).toList(evalContext);
} catch {
isValid = false;
}
if (isValid === undefined) {
isValid = await rpc("/web/domain/validate", { model: model, domain, });
}
if (!isValid) {
this.notification.add(_t("Domain is invalid. Please correct it"), {
type: "danger" });
return;
}
this.updateFacets(model, model_domain);
domainsToUpdate[model] = { [ `custom_filter_${this.groupId++}`] : { label: model_domain.label, domain: model_domain.domain } }
}
return domainsToUpdate;
}
updateFacets(model, model_domain){
this.customDomainFacets[model] = this.customDomainFacets[model] || { groups: {}, model_name: model_domain.model_name }
this.customDomainFacets[model].groups[`custom_filter_${this.groupId}`] = { label: model_domain.label, domain: model_domain.domain }
}
onRemoveFacet(model, group){
eraseCookie('CFilter' + this.props.options.ks_dashboard_id)
delete this.customDomainFacets[model].groups[group];
if(!Object.values(this.customDomainFacets[model].groups).length) delete this.customDomainFacets[model];
setObjectInCookie('CFilter' + this.props.options.ks_dashboard_id,
{ lastAppliedFilters: this.customDomainFacets, lastGroupId: this.groupId}, 1)
this.props.remove([model], [group]);
this.state.toggleState = !this.state.toggleState
}
}
CustomFilter.template = "ks_dashboard_ninja.custom_filter"
CustomFilter.components = { Dropdown, DropdownItem }

View File

@@ -0,0 +1,160 @@
.custom-dash-collapse{
.o_input{
button{
border: none;
background: transparent;
}
border: none;
}
.dropdown-toggle.d-flex.encapsulated-link{
overflow: auto;
max-height: 75px;
}
.dash-default-btn{
svg{
fill: transparent !important;
}
}
.table-dlt-btn{
&:hover{
border-color: #E7495E !important;
svg{
stroke: #E7495E !important;
}
}
}
.o-dropdown{
.ks-nav-link{
button{
min-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
}
}
.ks-nav-link{
button{
background-color: transparent;
border: none;
}
}
.dash-dd-2{
.selcted-opt.opted{
background-color: #F5F8FB;
padding-right: 22px;
}
.dropdown-toggle{
&:after{
display: none;
}
}
}
.encapsulated-cross {
position: absolute;
top: 2px;
}
}
.table-custom {
display: flex;
flex-direction: column;
overflow: auto;
max-height: calc(100vh - 483px);
@include custom-scrollbar;
.table-row {
display: flex;
align-items: center;
margin-bottom: 11px; // Adjust the spacing between rows if necessary
.table-cell {
// padding: 0px 11px 11px 0;
flex-shrink: 0; // Prevents the cell from shrinking smaller than its content
width: auto; // Allows the cell to take space based on its content
& .o_multi_record_selector .o_record_autocomplete_with_caret {
min-width: auto !important;
&::after {
display: none !important;
}
}
.table-dlt-btn {
border: 0.81px solid $color-D1D5DB;
padding: 10px 14px;
border-radius: 6px;
display: block;
background-color: transparent;
svg {
stroke: $color-black;
fill: none;
}
&.active,
&:focus {
svg {
stroke: $color-danger;
}
}
}
.table-dd {
.dropdown-menu {
width: 100%;
}
.dropdown-toggle {
border: 0.81px solid $color-D1D5DB;
padding: 12px 11px;
border-radius: 6px;
font-size: $font-14;
font-weight: $f-w-400;
line-height: 18.52px;
text-align: left;
color: $color-black;
display: block;
&::after {
margin-left: 8.2em;
@include max-992 {
display: inline-block;
}
}
}
}
}
}
}
@media only screen and (max-width: 1600px) {
.custom-dash-collapse{
.o-dropdown{
.ks-nav-link{
button{
min-width: 80px;
}
}
}
}
}
@media only screen and (max-width: 1600px) {
.custom-dash-collapse{
.o-dropdown{
.ks-nav-link{
button{
min-width: 60px;
}
}
}
}
}

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.custom_filter">
<t t-call="ks_dn.facets_container">
<t t-set="filter_data" t-value="customDomainFacets"/>
<t t-set="remove" t-value="onRemoveFacet.bind(this)"/>
</t>
<div class="d-flex align-items-center mb-3 mt-1">
<h4 class="mb-2">Custom Filters</h4>
<button class="clear-all-btn ms-auto" t-on-click="clearRows">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/close-circle.svg" alt="cross-icon"
class="img-fluid me-1" loading="lazy"/>
Clear All
</button>
</div>
<div>
<div class="table-custom mb-2">
<t t-foreach="state.filtersRowList" t-as="filter_row" t-key="filter_row_index">
<div class="table-row gap-3">
<t t-call="ks_dn.customFilter.filter.select">
<t t-set="row" t-value="filter_row"/>
<t t-set="row_index" t-value="filter_row_index"/>
</t>
<t t-call="ks_dn.customFilter.operator.select">
<t t-set="row" t-value="filter_row"/>
<t t-set="row_index" t-value="filter_row_index"/>
</t>
<t t-call="ks_dn.customFilter.value.select">
<t t-set="row" t-value="filter_row"/>
<t t-set="row_index" t-value="filter_row_index"/>
</t>
<div t-att-class=" filter_row_index === 0 ? 'table-cell d-none' : 'table-cell'" t-on-click="() => this.deleteRow(filter_row_index)">
<button class="table-dlt-btn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665"
stroke="" stroke-linecap="round" stroke-linejoin="round" />
<path d="M5.6665 3.31325L5.81317 2.43992C5.91984 1.80659 5.99984 1.33325 7.1265 1.33325H8.87317C9.99984 1.33325 10.0865 1.83325 10.1865 2.44659L10.3332 3.31325"
stroke="" stroke-linecap="round" stroke-linejoin="round" />
<path d="M12.5664 6.09351L12.1331 12.8068C12.0598 13.8535 11.9998 14.6668 10.1398 14.6668H5.85977C3.99977 14.6668 3.93977 13.8535 3.86644 12.8068L3.43311 6.09351"
stroke="" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.88672 11H9.10672" stroke="" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.3335 8.33325H9.66683" stroke="" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</div>
</t>
</div>
<div class="btn-box d-flex justify-content-end align-items-center">
<button class="dash-default-btn bg-white" t-on-click="addFilterRow">
<t t-call="ks_dashboard_ninja.add_circle_svg"/> Add a condition
</button>
<button class="dash-btn-red ms-2" t-on-click="applyFilters">
Apply Filter
</button>
</div>
</div>
</t>
<t t-name="ks_dn.customFilter.filter.select">
<div class="table-cell">Where</div>
<Dropdown menuClass="'table-cell ks-dropdown-menu'">
<t t-set-slot="content">
<DropdownItem
t-foreach="filterLabels"
t-as="filterLabel"
t-key="filterLabel.filter_id"
class="{ '': true }"
onSelected="() => this.onUpdateFilter(filterLabel.filter_id, row_index)"
t-esc="filterLabel.filter_name"/>
</t>
<button class="text-decoration-none bg-transparent " role="button" aria-expanded="false">
<t t-out="rowFilter(row.id)"/>
</button>
</Dropdown>
</t>
<t t-name="ks_dn.customFilter.operator.select">
<Dropdown menuClass="'table-cell ks-dropdown-menu'">
<t t-set-slot="content">
<DropdownItem
t-foreach="getOperatorInfo(row.id)"
t-as="operator"
t-key="operator_index"
class="{ '': true }"
t-esc="operator.label"
onSelected="() => this.onUpdateOperator(operator.operator, row_index)"
/>
</t>
<button class="text-decoration-none bg-transparent " href="#" role="button" aria-expanded="false">
<t t-out="rowOperator(row_index)"/>
</button>
</Dropdown>
</t>
<t t-name="ks_dn.customFilter.value.select">
<t t-call="web.TreeEditor.Editor">
<t t-set="_classes" t-value="'overflow-hidden table-cell form-control form-input-box'"/>
<t t-set="info" t-value="getValueInfo(filter_row)" />
<t t-set="value" t-value="filter_row.value" />
<t t-set="update" t-value="(value) => this.onUpdateValue(row_index, value)"/>
</t>
</t>
<t t-name="ks_dn.no-filter-view">
<div class="no-data-avilable">
<div class="d-flex align-items-center justify-content-center flex-column">
<div class="no-data-img">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/no-data.png" alt="no-data-available" loading="lazy" class="img-fluid"/>
</div>
<div class="title mb-1">
No Filter Available
</div>
<p>
No custom filter, Create some from dashboard settings
</p>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,41 @@
/** @odoo-module **/
import { Component, xml, useEffect } from "@odoo/owl";
import { Select } from "@web/core/tree_editor/tree_editor_components";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
export class KsDropDown extends Select{
setup(){
super.setup();
}
deserialize(value){
return JSON.parse(value);
}
}
KsDropDown.template = xml`<Dropdown menuClass="'o_input pe-3 text-truncate ks-dropdown-menu'">
<t t-set-slot="content">
<DropdownItem
t-foreach="props.options"
t-as="option"
t-key="serialize(option[0])"
class="{ '': true }"
t-esc="option[1]"
onSelected="() => this.props.update(this.deserialize(serialize(option[0])))"
/>
</t>
<button class="text-decoration-none" href="#" role="button" aria-expanded="false">
<t t-out="props.activeOption"/>
</button>
</Dropdown>`
KsDropDown.components = { Dropdown, DropdownItem }
KsDropDown.props = [ ...Select.props, "activeOption"]

View File

@@ -0,0 +1,158 @@
/**@odoo-module **/
import { Component, useState, useRef } from "@odoo/owl"
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { parseDateTime, parseDate, formatDate, formatDateTime } from "@web/core/l10n/dates";
import { localization } from "@web/core/l10n/localization";
import { _t } from "@web/core/l10n/translation";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
import { isMobileOS } from "@web/core/browser/feature_detection";
const { DateTime } = luxon;
export class KsDateFilter extends Component{
setup(){
this.ks_dashboard_data = this.props.dashboard_data
this.notification = this.env.services.notification
this.isMobile = isMobileOS();
this.ks_date_filter_selections = {
'l_none': _t('All Time'),
'l_day': _t('Today'),
't_week': _t('This Week'),
'td_week': _t('Week To Date'),
't_month': _t('This Month'),
'td_month': _t('Month to Date'),
't_quarter': _t('This Quarter'),
'td_quarter': _t('Quarter to Date'),
't_year': _t('This Year'),
'td_year': _t('Year to Date'),
'n_day': _t('Next Day'),
'n_week': _t('Next Week'),
'n_month': _t('Next Month'),
'n_quarter': _t('Next Quarter'),
'n_year': _t('Next Year'),
'ls_day': _t('Last Day'),
'ls_week': _t('Last Week'),
'ls_month': _t('Last Month'),
'ls_quarter': _t('Last Quarter'),
'ls_year': _t('Last Year'),
'l_week': _t('Last 7 days'),
'l_month': _t('Last 30 days'),
'l_quarter': _t('Last 90 days'),
'l_year': _t('Last 365 days'),
'ls_past_until_now': _t('Past Till Now'),
'ls_pastwithout_now': _t('Past Excluding Today'),
'n_future_starting_now': _t('Future Starting Now'),
'n_futurestarting_tomorrow': _t('Future Starting Tomorrow'),
'l_custom': _t('Custom Filter'),
};
this.ksDateFilterSelection = false
this.ksDateFilterStartDateObj = this.ks_dashboard_data.ks_dashboard_start_date ? parseDateTime(this.ks_dashboard_data.ks_dashboard_start_date) : DateTime.now();
this.ksDateFilterEndDateObj = this.get_initial_date(this.ks_dashboard_data.ks_dashboard_end_date)
this.rootRef = useRef("rootRef")
this.items = Object.keys(this.ks_dashboard_data.ks_dashboard_items_ids)
this.state = useState({
ks_current_filter : this.ksDateFilterSelection,
is_show_date_fields : false
})
this.state.ks_current_filter = this.ks_dashboard_data.ks_date_filter_selection
this.custom_date_filter_buttons = [ { name: "Apply", callback: this.onApplyClick.bind(this), classes: 'dash-default-btn bg-white me-2', shouldVisible: true },
{ name: "Clear", callback: this._onKsClearDateValues.bind(this), classes: 'dash-btn-red', shouldVisible: true } ]
}
get_initial_date(date){
if(date) return parseDateTime(date);
return this.ks_dashboard_data.ks_default_end_time ? DateTime.now().endOf('day') : DateTime.now();
}
_ksOnDateFilterMenuSelect(selected_filter_id) {
this.env.services.ui.block();
eraseCookie('FilterDateData' + this.ks_dashboard_data.ks_dashboard_id);
this.state.ks_current_filter = selected_filter_id
if (this.state.ks_current_filter !== 'l_custom'){
this._onKsApplyDateFilter(this.state.ks_current_filter, false, false)
}
else{
this.ksDateFilterStartDateObj = this.ks_dashboard_data.ks_dashboard_start_date ? parseDateTime(this.ks_dashboard_data.ks_dashboard_start_date) : DateTime.now()
this.ksDateFilterEndDateObj = this.get_initial_date(this.ks_dashboard_data.ks_dashboard_end_date)
this.state.is_show_date_fields = true
}
this.props.update_mode(this.state.ks_current_filter !== 'l_custom' ? "manager" : "custom_date")
this.env.services.ui.unblock();
}
_onKsApplyDateFilter(selected_filter_id, start_date, end_date) {
let self = this;
if(!['l_none', 'l_custom'].includes(selected_filter_id))
setObjectInCookie('FilterDateData' + this.ks_dashboard_data.ks_dashboard_id,
{'filter_selection': this.state.ks_current_filter, date_range: { start_date, end_date }}, 1);
self.ksDateFilterSelection = this.state.ks_current_filter;
if (this.state.ks_current_filter !== "l_custom") {
this.env.ks_update_date_filter_state(selected_filter_id, false, false);
this.props.update_mode(this.ks_dashboard_data.ks_dashboard_manager ? "manager" : "user");
} else {
if (start_date && end_date) {
if ( start_date <= end_date ) {
start_date = start_date.toString()
end_date = end_date.toString()
if (start_date === "Invalid date" || end_date === "Invalid date"){
this.notification.add(_t("Invalid Date"), { type: "warning"});
}else{
setObjectInCookie('FilterDateData' + this.ks_dashboard_data.ks_dashboard_id,
{'filter_selection': this.state.ks_current_filter, date_range: { start_date, end_date }}, 1);
this.state.is_show_date_fields = false
this.env.ks_update_date_filter_state(selected_filter_id, start_date, end_date)
this.props.update_mode(this.ks_dashboard_data.ks_dashboard_manager ? "manager" : "user");
}
} else {
this.notification.add(_t("Start date should be less than end date."), { type: "warning"});
}
} else {
let notification_text = !start_date && !end_date ? "Please enter start date and end date." : `Please enter ${ !start_date ? "start" : "end" } date`;
this.notification.add(_t(notification_text), { type: "warning"});
}
}
}
change_start_date(args){
this.ksDateFilterStartDateObj = args
}
change_end_date(args){
this.ksDateFilterEndDateObj = args
}
onApplyClick(){
this._onKsApplyDateFilter('l_custom', this.ksDateFilterStartDateObj, this.ksDateFilterEndDateObj)
}
_onKsClearDateValues() {
eraseCookie('FilterDateData' + this.ks_dashboard_data.ks_dashboard_id);
this.state.ks_current_filter = 'l_none'
this.state.is_show_date_fields = false
this.env.ks_update_date_filter_state('l_none', false, false)
this.props.update_mode(this.ks_dashboard_data.ks_dashboard_manager ? "manager" : "user");
}
showDateFilterFields(){
this.state.is_show_date_fields = true
this.props.update_mode('custom_date');
}
} ;
KsDateFilter.props = {
dashboard_data : { type : Object, optional : true },
update_mode : { type : Function, optional : true }
}
KsDateFilter.components = { Dropdown, DateTimeInput, DropdownItem }
KsDateFilter.template = "ks_dashboard_ninja.Ks_date_filter"

View File

@@ -0,0 +1,39 @@
.ks_custom_date_filter .dropdown-menu{
@include custom-scrollbar;
}
.custom-date-range-selector {
&.magic-star-dd {
.img-bg {
background-color: $color-white !important;
svg {
path {
fill: #6789C6 !important;
stroke: none !important;
}
}
}
}
}
.ks_date_input_fields {
input {
border: 0.81px solid #D1D5DB !important;
border-radius: 6px;
padding: 12px 10px !important;
font-size: 14px;
font-weight: 400;
line-height: 18.52px;
text-align: left;
color: #241C1D;
background-color: $color-white;
&#ks_btn_middle_child,
&#ks_btn_last_child {
border-radius: 6px !important;
padding: 12px 10px !important;
}
}
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="ks_dashboard_ninja.Ks_date_filter" owl="1">
<div t-att-class="this.items.length > 0 ?'ks_dashboard_link ks_am_content_element dash-dd-2 ks_custom_date_filter gap-2': 'ks_hide'"
t-ref="rootRef">
<t t-call="ks_dashboard_ninja.ks_date_filter_dropdown"/>
<div class="custom-date-range-selector cursor-pointer magic-star-dd"
t-if="state.ks_current_filter === 'l_custom' &amp;&amp; !state.is_show_date_fields &amp;&amp; !isMobile" t-on-click="showDateFilterFields">
<span class="img-bg">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.1662 2.37325V1.33325C11.1662 1.05992 10.9395 0.833252 10.6662 0.833252C10.3928 0.833252 10.1662 1.05992 10.1662 1.33325V2.33325H5.83284V1.33325C5.83284 1.05992 5.60617 0.833252 5.33284 0.833252C5.0595 0.833252 4.83284 1.05992 4.83284 1.33325V2.37325C3.03284 2.53992 2.1595 3.61325 2.02617 5.20659C2.01284 5.39992 2.17284 5.55992 2.3595 5.55992H13.6395C13.8328 5.55992 13.9928 5.39325 13.9728 5.20659C13.8395 3.61325 12.9662 2.53992 11.1662 2.37325Z" fill="#6789C6"/>
<path d="M13.3333 6.56006H2.66667C2.3 6.56006 2 6.86006 2 7.22673V11.3334C2 13.3334 3 14.6667 5.33333 14.6667H10.6667C13 14.6667 14 13.3334 14 11.3334V7.22673C14 6.86006 13.7 6.56006 13.3333 6.56006ZM6.14 12.1401C6.10667 12.1667 6.07333 12.2001 6.04 12.2201C6 12.2467 5.96 12.2667 5.92 12.2801C5.88 12.3001 5.84 12.3134 5.8 12.3201C5.75333 12.3267 5.71333 12.3334 5.66667 12.3334C5.58 12.3334 5.49333 12.3134 5.41333 12.2801C5.32667 12.2467 5.26 12.2001 5.19333 12.1401C5.07333 12.0134 5 11.8401 5 11.6667C5 11.4934 5.07333 11.3201 5.19333 11.1934C5.26 11.1334 5.32667 11.0867 5.41333 11.0534C5.53333 11.0001 5.66667 10.9867 5.8 11.0134C5.84 11.0201 5.88 11.0334 5.92 11.0534C5.96 11.0667 6 11.0867 6.04 11.1134C6.07333 11.1401 6.10667 11.1667 6.14 11.1934C6.26 11.3201 6.33333 11.4934 6.33333 11.6667C6.33333 11.8401 6.26 12.0134 6.14 12.1401ZM6.14 9.80673C6.01333 9.92673 5.84 10.0001 5.66667 10.0001C5.49333 10.0001 5.32 9.92673 5.19333 9.80673C5.07333 9.68006 5 9.50673 5 9.33339C5 9.16006 5.07333 8.98673 5.19333 8.86006C5.38 8.67339 5.67333 8.61339 5.92 8.72006C6.00667 8.75339 6.08 8.80006 6.14 8.86006C6.26 8.98673 6.33333 9.16006 6.33333 9.33339C6.33333 9.50673 6.26 9.68006 6.14 9.80673ZM8.47333 12.1401C8.34667 12.2601 8.17333 12.3334 8 12.3334C7.82667 12.3334 7.65333 12.2601 7.52667 12.1401C7.40667 12.0134 7.33333 11.8401 7.33333 11.6667C7.33333 11.4934 7.40667 11.3201 7.52667 11.1934C7.77333 10.9467 8.22667 10.9467 8.47333 11.1934C8.59333 11.3201 8.66667 11.4934 8.66667 11.6667C8.66667 11.8401 8.59333 12.0134 8.47333 12.1401ZM8.47333 9.80673C8.44 9.83339 8.40667 9.86006 8.37333 9.88673C8.33333 9.91339 8.29333 9.93339 8.25333 9.94673C8.21333 9.96673 8.17333 9.98006 8.13333 9.98672C8.08667 9.99339 8.04667 10.0001 8 10.0001C7.82667 10.0001 7.65333 9.92673 7.52667 9.80673C7.40667 9.68006 7.33333 9.50673 7.33333 9.33339C7.33333 9.16006 7.40667 8.98673 7.52667 8.86006C7.58667 8.80006 7.66 8.75339 7.74667 8.72006C7.99333 8.61339 8.28667 8.67339 8.47333 8.86006C8.59333 8.98673 8.66667 9.16006 8.66667 9.33339C8.66667 9.50673 8.59333 9.68006 8.47333 9.80673ZM10.8067 12.1401C10.68 12.2601 10.5067 12.3334 10.3333 12.3334C10.16 12.3334 9.98667 12.2601 9.86 12.1401C9.74 12.0134 9.66667 11.8401 9.66667 11.6667C9.66667 11.4934 9.74 11.3201 9.86 11.1934C10.1067 10.9467 10.56 10.9467 10.8067 11.1934C10.9267 11.3201 11 11.4934 11 11.6667C11 11.8401 10.9267 12.0134 10.8067 12.1401ZM10.8067 9.80673C10.7733 9.83339 10.74 9.86006 10.7067 9.88673C10.6667 9.91339 10.6267 9.93339 10.5867 9.94673C10.5467 9.96673 10.5067 9.98006 10.4667 9.98672C10.42 9.99339 10.3733 10.0001 10.3333 10.0001C10.16 10.0001 9.98667 9.92673 9.86 9.80673C9.74 9.68006 9.66667 9.50673 9.66667 9.33339C9.66667 9.16006 9.74 8.98673 9.86 8.86006C9.92667 8.80006 9.99333 8.75339 10.08 8.72006C10.2 8.66673 10.3333 8.65339 10.4667 8.68006C10.5067 8.68673 10.5467 8.70006 10.5867 8.72006C10.6267 8.73339 10.6667 8.75339 10.7067 8.78006C10.74 8.80673 10.7733 8.83339 10.8067 8.86006C10.9267 8.98673 11 9.16006 11 9.33339C11 9.50673 10.9267 9.68006 10.8067 9.80673Z" fill="#6789C6"/>
</svg>
</span>
</div>
<t t-if="state.ks_current_filter == 'l_custom' &amp;&amp; state.is_show_date_fields">
<div class="ks_date_input_fields d-flex gap-2">
<div class="form-input-box form-control start-date">
<DateTimeInput value="ksDateFilterStartDateObj" type="'datetime'" rounding="1"
id="'ks_btn_middle_child'" placeholder="'Start Date...'" onChange.bind="change_start_date"/>
</div>
<div class="form-input-box form-control start-date">
<DateTimeInput value="ksDateFilterEndDateObj" type="'datetime'" rounding="1"
id="'ks_btn_last_child'" placeholder="'End Date...'" onChange.bind="change_end_date"/>
</div>
</div>
<div class="ks_date_apply_clear_print gap-2">
<!--Apply and Clear buttons will only be shown when Date filter : Custom-->
<button type='button' class='dash-default-btn bg-white ' t-on-click="onApplyClick" data-tooltip="Apply">
<svg xmlns="http://www.w3.org/2000/svg" width="20" class="me-lg-2 me-0" height="20" viewBox="0 0 20 20" fill="none" stroke="#FFFFFF">
<path d="M18.0249 12.2917C18.0249 13.0334 17.8249 13.7334 17.4583 14.3334C16.7749 15.4751 15.5166 16.25 14.0666 16.25C13.2833 16.25 12.5499 16.0167 11.9333 15.6084C11.4166 15.2917 10.9916 14.8501 10.6833 14.3334C10.3166 13.7334 10.1083 13.0334 10.1083 12.2917C10.1083 10.1084 11.8833 8.33337 14.0666 8.33337C14.3666 8.33337 14.6583 8.3667 14.9333 8.43337C16.7083 8.82504 18.0249 10.4084 18.0249 12.2917Z" stroke="#241C1D" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5253 12.2917L13.5003 13.2667L15.6086 11.3167" stroke="#241C1D" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.2413 3.34995V5.19995C17.2413 5.87495 16.8163 6.71663 16.3996 7.14163L14.9329 8.43329C14.6579 8.36662 14.3663 8.33329 14.0663 8.33329C11.8829 8.33329 10.1079 10.1083 10.1079 12.2916C10.1079 13.0333 10.3163 13.7333 10.6829 14.3333C10.9913 14.85 11.4163 15.2916 11.9329 15.6083V15.8916C11.9329 16.4 11.5996 17.075 11.1746 17.325L9.9996 18.0833C8.90794 18.7583 7.39127 18 7.39127 16.65V12.1916C7.39127 11.6 7.0496 10.8416 6.71627 10.425L3.51625 7.05829C3.09958 6.63329 2.75793 5.87497 2.75793 5.37497V3.43329C2.75793 2.42496 3.51628 1.66663 4.44128 1.66663H15.5579C16.4829 1.66663 17.2413 2.42495 17.2413 3.34995Z" stroke="#241C1D" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button type='button' class='dash-default-btn bg-white clear-dashboard-date-filter' t-on-click="_onKsClearDateValues" data-tooltip="Clear">
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00016 14.6666C11.6668 14.6666 14.6668 11.6666 14.6668 7.99992C14.6668 4.33325 11.6668 1.33325 8.00016 1.33325C4.3335 1.33325 1.3335 4.33325 1.3335 7.99992C1.3335 11.6666 4.3335 14.6666 8.00016 14.6666Z" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.11328 9.88661L9.88661 6.11328" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.88661 9.88661L6.11328 6.11328" stroke="#241C1D" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</t>
</div>
</t>
<t t-name="ks_dashboard_ninja.ks_date_filter_dropdown">
<Dropdown menuClass="'ks-dropdown-menu'">
<t t-set-slot="content">
<DropdownItem t-foreach="ks_date_filter_selections" t-as="date_selection" t-key="date_selection_index"
class="{ 'global-active': state.ks_current_filter === date_selection , 'd-none': date_selection === 'l_custom' &amp;&amp; isMobile}"
onSelected="() => this._ksOnDateFilterMenuSelect(date_selection)">
<t t-esc="ks_date_filter_selections[date_selection]"/>
</DropdownItem>
</t>
<span class="ellipsis-content d-block max-width-medium-6vw" t-esc="ks_date_filter_selections[state.ks_current_filter]"/>
</Dropdown>
</t>
</templates>

View File

@@ -0,0 +1,147 @@
/** @odoo-module **/
import { Component, useState, onWillStart } from "@odoo/owl";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { PreDefinedFilter } from "@ks_dashboard_ninja/components/predefined_filter/predefined_filter";
import { FavFilterWizard } from "@ks_dashboard_ninja/components/favourite_filter/favourite_filter";
import { FavouriteFilter } from "@ks_dashboard_ninja/components/favourite_filter/favourite_filter";
import { Domain } from "@web/core/domain";
import { CustomFilter } from '@ks_dashboard_ninja/components/custom_filter/custom_filter';
import { ModelDrivenFilterApplicator } from '@ks_dashboard_ninja/components/model_driven_filter_applicator/model_driven_filter_applicator';
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
/*
* TODO : Applying filters , updating facets , removing facets,
and applying domain have some functionalities common , can be reviewed
there is a possibility to make common methods and reduce some lines of code
*/
export class DNFilter extends Component{
static props = {
dashboard_data: { type: Object, optional: true },
};
static defaultProps = {
};
static components = { PreDefinedFilter, Dropdown, CustomFilter, FavouriteFilter, ModelDrivenFilterApplicator }
setup(){
this.dashboard_domain_data = {}
this.isShowPredefinedFilter = Object.keys(this.props.dashboard_data.ks_dashboard_pre_domain_filter).length
this.isShowCustomFilter = Object.keys(this.props.dashboard_data.ks_dashboard_custom_domain_filter).length
this.state = useState({
dashboard_favourite_filter: this.props.dashboard_data.ks_dashboard_favourite_filter,
removeAllFilters: false,
filters_count: 0,
})
onWillStart( this.willStart );
}
willStart(){
this.setObjFromCookies();
this.state.filters_count = this.filters_count
}
get options(){
return {
ks_dashboard_id : this.props.dashboard_data.ks_dashboard_id,
}
}
get filters_count(){
let count = 0;
Object.values(this.dashboard_domain_data).forEach( (model_domain_data) => {
Object.keys(model_domain_data.sub_domains).forEach( (group_key_name) => {
group_key_name.startsWith('FF') ? count : ++count;
})
})
return count;
}
get hasActiveFilter(){
return Object.keys(this.dashboard_domain_data).length ;
}
setObjFromCookies(){
let dashboard_domain_data_from_cky = getObjectFromCookie('Filter' + this.props.dashboard_data.ks_dashboard_id)
this.dashboard_domain_data = dashboard_domain_data_from_cky ?? {}
}
update_sub_domains(modelDomainsToUpdate){
this.state.removeAllFilters = false
Object.keys(modelDomainsToUpdate).forEach( (model) => {
this.dashboard_domain_data[model] ??= { sub_domains: {}}
Object.keys(modelDomainsToUpdate[model]).forEach( (group) => {
this.dashboard_domain_data[model].sub_domains[group] ??= {}
this.dashboard_domain_data[model].sub_domains[group] = modelDomainsToUpdate[model][group].domain
})
})
this.update(Object.keys(modelDomainsToUpdate))
}
applyFavouriteFilter(modelDomainsToUpdate){
eraseCookie('Filter' + this.props.dashboard_data.ks_dashboard_id)
delete this.dashboard_domain_data
this.state.removeAllFilters = true
this.dashboard_domain_data = modelDomainsToUpdate
this.state.filters_count = this.filters_count
setObjectInCookie('Filter' + this.props.dashboard_data.ks_dashboard_id, this.dashboard_domain_data, 1)
this.env.replace_dashboard_filters(modelDomainsToUpdate)
}
remove_sub_domains(models, groups){
models.forEach( (model) => {
groups.forEach( ( group) => {
delete this.dashboard_domain_data[model].sub_domains[group]
})
})
this.update(models)
// this.pruneEmptyFilterGroups(models)
}
update(models){
eraseCookie('Filter' + this.props.dashboard_data.ks_dashboard_id)
let domainsToUpdate = {};
for( let model of models){
let domain = Domain.and([ ...Object.values(this.dashboard_domain_data[model].sub_domains) ]).toList();
this.dashboard_domain_data[model].domain = domain
domainsToUpdate[model] = { domain, sub_domains: this.dashboard_domain_data[model].sub_domains }
}
setObjectInCookie('Filter' + this.props.dashboard_data.ks_dashboard_id, this.dashboard_domain_data, 1)
this.state.filters_count = this.filters_count
this.env.update_dashboard_filters(domainsToUpdate)
}
onSaveAsFavouriteClick(){
this.env.services.dialog.add(FavFilterWizard,{
save_favourite_filter: this.save_favourite_filter.bind(this),
dashboard_favourite_filter: this.state.dashboard_favourite_filter,
dashboard_data: this.props.dashboard_data,
dashboard_domain_data: this.dashboard_domain_data
});
}
save_favourite_filter(ks_filter_name, ks_filter_obj){
this.state.dashboard_favourite_filter[ks_filter_name] = ks_filter_obj
}
delete_favourite_filter(ks_filter_name){
delete this.state.dashboard_favourite_filter[ks_filter_name]
}
hideFilterTab() {
Collapse.getInstance('#collapseExample').hide()
}
}
DNFilter.template = "ks_dashboard_ninja.dn_filter"

View File

@@ -0,0 +1,59 @@
.ks_body_class .filters-amount {
background-color: $color-E7495E;
font-size: 8px;
font-weight: 400;
line-height: 16px;
text-align: left;
color: $color-white;
height: 17px;
width: 17px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.ks_body_class .filter {
svg {
stroke: $color-paragraph;
fill: none;
}
}
.ks_body_class .filter.active {
svg {
fill: $color-6789C6;
stroke: $color-white;
}
}
.ks_body_class .favorite-btn {
border: 0.81px solid $color-E5E7EB;
border-radius: 5px;
font-size: $font-12;
font-weight: $f-w-500;
line-height: 16.8px;
text-align: left;
color: $color-1E1E1E;
padding: 10px 8px;
background-color: $color-white;
}
.ks_body_class .o_searchview_facet_label {
span {
display: flex;
align-items: center;
justify-content: center;
}
}
.custom-dash-collapse {
left: -250px;
}
.custom-filter-tab, .predefined-filter-tab {
overflow: auto;
max-height: 50vh;
@include custom-scrollbar;
}

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.dn_filter" owl="1">
<div class="d-flex justify-content-center align-items-center gap-2 filters_section_root">
<div class="dropdown dash-dd-2 filter filters_section d-lg-block d-none"
t-att-class="state.filters_count ? 'active' : ''">
<a class="text-decoration-none d-flex dropdown-toggle align-items-center" data-bs-toggle="collapse"
data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
<span class="d-flex align-items-center">
<svg width="20" class="me-lg-2 me-0" height="20" viewBox="0 0 20 20" fill=""
xmlns="http://www.w3.org/2000/svg">
<path
d="M18.0249 12.2917C18.0249 13.0334 17.8249 13.7334 17.4583 14.3334C16.7749 15.4751 15.5166 16.25 14.0666 16.25C13.2833 16.25 12.5499 16.0167 11.9333 15.6084C11.4166 15.2917 10.9916 14.8501 10.6833 14.3334C10.3166 13.7334 10.1083 13.0334 10.1083 12.2917C10.1083 10.1084 11.8833 8.33337 14.0666 8.33337C14.3666 8.33337 14.6583 8.3667 14.9333 8.43337C16.7083 8.82504 18.0249 10.4084 18.0249 12.2917Z"
stroke="" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M12.5253 12.2917L13.5003 13.2667L15.6086 11.3167" stroke=""
stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" />
<path
d="M17.2413 3.34995V5.19995C17.2413 5.87495 16.8163 6.71663 16.3996 7.14163L14.9329 8.43329C14.6579 8.36662 14.3663 8.33329 14.0663 8.33329C11.8829 8.33329 10.1079 10.1083 10.1079 12.2916C10.1079 13.0333 10.3163 13.7333 10.6829 14.3333C10.9913 14.85 11.4163 15.2916 11.9329 15.6083V15.8916C11.9329 16.4 11.5996 17.075 11.1746 17.325L9.9996 18.0833C8.90794 18.7583 7.39127 18 7.39127 16.65V12.1916C7.39127 11.6 7.0496 10.8416 6.71627 10.425L3.51625 7.05829C3.09958 6.63329 2.75793 5.87497 2.75793 5.37497V3.43329C2.75793 2.42496 3.51628 1.66663 4.44128 1.66663H15.5579C16.4829 1.66663 17.2413 2.42495 17.2413 3.34995Z"
stroke="" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</span>
<span class="d-lg-block d-none ellipsis-content max-width-medium-4vw">Filters</span>
<div class="ms-1 d-flex justify-content-center filters-amount" t-if="state.filters_count" t-esc="state.filters_count"/>
</a>
<div class="collapse custom-dash-collapse" id="collapseExample">
<div class="card card-body">
<ul class="nav nav-pills mb-3 border-bottom border-2" id="pills-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link text-primary fw-semibold position-relative show active" id="pills-home-tab"
data-bs-toggle="pill" data-bs-target="#pills-home"
type="button" role="tab" aria-controls="pills-home" aria-selected="true">Predefined Filter
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link text-primary fw-semibold position-relative" id="pills-profile-tab"
data-bs-toggle="pill" data-bs-target="#pills-profile" type="button" role="tab"
title="Create domain and apply filter" aria-controls="pills-profile"
aria-selected="false">Custom Filter
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link text-primary fw-semibold position-relative" id="pills-charts-filter-tab"
data-bs-toggle="pill" data-bs-target="#pills-charts-filter" type="button" role="tab"
title="Create domain and apply filter" aria-controls="pills-charts-filter"
aria-selected="false"> Charts Filter
</button>
</li>
<div class="position-relative ms-auto encapsulated-bottom">
<button class="favorite-btn me-3 d-flex align-items-center justify-content-center" t-on-click="onSaveAsFavouriteClick">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/ranking.png" alt="ranking"
class="img-fluid me-1" loading="lazy"/>
Save as Favourite
</button>
</div>
<div t-on-click="hideFilterTab" class="position-relative d-flex align-items-center me-2 encapsulated-bottom">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/close-circle.svg" alt="cross-icon" style="height: 20px; width: 20px;"
class="img-fluid me-2" loading="lazy"/>
</div>
</ul>
<div class="tab-content" id="pills-tabContent">
<!-- Home tab pane (default visible tab) -->
<div class="tab-pane fade show active predefined-filter-tab" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab">
<PreDefinedFilter filters_data="props.dashboard_data.ks_dashboard_pre_domain_filter" domain="'[]'" options="options"
update.bind="update_sub_domains" remove.bind="remove_sub_domains" removeAllFilters="state.removeAllFilters"/>
</div>
<!-- Profile tab pane -->
<div class="tab-pane fade custom-filter-tab" id="pills-profile" role="tabpanel" aria-labelledby="pills-profile-tab">
<t t-if="isShowCustomFilter">
<CustomFilter filter_info="props.dashboard_data.ks_dashboard_custom_domain_filter" options="options"
update.bind="update_sub_domains" domain="'[]'" remove.bind="remove_sub_domains" removeAllFilters="state.removeAllFilters"/>
</t>
<t t-else="">
<t t-call="ks_dn.no-filter-view"/>
</t>
</div>
<div class="tab-pane fade charts-filter-tab" id="pills-charts-filter" role="tabpanel" aria-labelledby="pills-charts-filter-tab">
<ModelDrivenFilterApplicator options="options" update.bind="update_sub_domains"
remove.bind="remove_sub_domains" removeAllFilters="state.removeAllFilters"/>
</div>
</div>
</div>
</div>
</div>
<FavouriteFilter t-if="Object.keys(state.dashboard_favourite_filter).length"
favourite_filters_data="props.dashboard_data.ks_dashboard_favourite_filter" options="options"
onDelete.bind="delete_favourite_filter" update.bind="applyFavouriteFilter" remove.bind="remove_sub_domains"/>
</div>
</t>
</templates>

View File

@@ -0,0 +1,145 @@
/** @odoo-module **/
import { Component, useState, onWillStart } from "@odoo/owl";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { session } from "@web/session";
import { _t } from "@web/core/l10n/translation";
import { Dialog } from "@web/core/dialog/dialog";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
import { useService } from "@web/core/utils/hooks";
export class FavFilterWizard extends Component{
static template = "ks_dashboard_ninja.FavFilterWizard"
static components = { Dialog }
setup(){
this.notification = useService("notification")
// this.favoriteFilterId = 0
}
save_favourite_filter(ev){
let self = this;
let ks_filter_name = $('#favourite_filter_name').val();
let ks_is_fav_filter_shared = $('#favFilterShareBool').prop('checked')
if (!ks_filter_name.length){
this.notification.add(_t("A name for your favorite filter is required."), {
type: "warning",
});
}else{
var ks_saved_fav_filters = Object.keys(self.props.dashboard_favourite_filter)
const favourite = ks_saved_fav_filters.find(item => item == ks_filter_name)
if (favourite?.length){
this.notification.add(_t("A filter with same name already exists."), {
type: "warning",
});
}
else{
let domains_to_save = JSON.stringify(self.props.dashboard_domain_data)
let newFavFilter = {name: ks_filter_name,
ks_dashboard_board_id: self.props.dashboard_data.ks_dashboard_id,
ks_filter: domains_to_save,
ks_access_id: ks_is_fav_filter_shared ? false : user.userId}
rpc("/web/dataset/call_kw/ks_dashboard_ninja.favourite_filters/create", {
model: 'ks_dashboard_ninja.favourite_filters',
method: 'create',
args: [newFavFilter],
kwargs: {}
}).then(function(result){
newFavFilter.id = result
newFavFilter.ks_filter = self.props.dashboard_domain_data
self.props.save_favourite_filter(ks_filter_name, newFavFilter)
self.props.close();
});
}
}
}
}
export class FavouriteFilter extends Component{
static props = {
favourite_filters_data: { type: Object, optional: true },
onDelete: { type: Function, optional: true },
update: { type: Function, optional: true },
remove: { type: Function, optional: true },
options: { type: Object, optional: true },
};
static defaultProps = {
};
static template = "ks_dashboard_ninja.favourite_filter"
static components = { Dropdown, DropdownItem }
setup(){
// this.dashboard_domain_data = {}
this.state = useState({ current_active: ''})
onWillStart( () => { this.setObjFromCookies() })
}
setObjFromCookies(){
let current_active_from_cky = getObjectFromCookie('FFilter' + this.props.options.ks_dashboard_id)
this.state.current_active = current_active_from_cky ?? ''
}
onFilterSelect(filterName){
filterName !== this.state.current_active ? this.applyFavFilter(filterName) : this.removeFavFilter(filterName)
}
applyFavFilter(filterName){
this.state.current_active = filterName
eraseCookie('FFilter' + this.props.options.ks_dashboard_id)
let domainsToUpdate = {}
let filterToBeApplied = this.props.favourite_filters_data[filterName].ks_filter
Object.keys(filterToBeApplied).forEach( (model) => {
let domain = filterToBeApplied[model].domain
domainsToUpdate[model] = { domain: domain , sub_domains: { [`FF_${filterName}`]: domain } }
})
setObjectInCookie('FFilter' + this.props.options.ks_dashboard_id, this.state.current_active, 1)
this.props.update(domainsToUpdate)
}
removeFavFilter(filterName){
eraseCookie('FFilter' + this.props.options.ks_dashboard_id)
this.state.current_active = ''
this.props.remove(Object.keys(this.props.favourite_filters_data[filterName].ks_filter) , [`FF_${filterName}`])
}
onDeleteBtnClick(filterName){
// var ks_filter_domain = this.props.favourite_filters_data[filterName].filter;
let ks_access_id = this.props.favourite_filters_data[filterName].ks_access_id;
// var ks_remove_filter_models = Object.keys(ks_filter_domain)
// const ks_items_to_update_remove = self.ks_dashboard_data.ks_dashboard_items_ids.filter((item) =>
// ks_remove_filter_models.includes(self.ks_dashboard_data.ks_item_model_relation[item][0])|| ks_remove_filter_models.includes(self.ks_dashboard_data.ks_item_model_relation[item][1])
// );
this.env.services.dialog.add(ConfirmationDialog, {
body: _t(`This filter is will be removed${ks_access_id ? '' : 'for everybody'} if you continue.`),
confirmLabel: _t("Delete Filter"),
title: _t("Delete Filter"),
confirm: () => {
this.delete_fav_filter(filterName)
}
})
}
delete_fav_filter(filterName){
let self = this;
// this.isFavFilter = false;
rpc("/web/dataset/call_kw/ks_dashboard_ninja.favourite_filters/unlink", {
model: 'ks_dashboard_ninja.favourite_filters',
method: 'unlink',
args: [Number(this.props.favourite_filters_data[filterName].id)],
kwargs: {}
}).then(function(result) {
self.removeFavFilter(filterName)
self.props.onDelete(filterName)
});
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="ks_dashboard_ninja.FavFilterWizard">
<Dialog title="'Create Favourite Filter'" size="'md'" contentClass="'favFilterDialog'">
<div>
<label for="favourite_filter_name" class="mb-2 input-label">Filter Name</label>
<input type="text" id="favourite_filter_name" class="form-control form common-input mb-3" placeholder="Enter Filter Name (eg. Order States, SaleOrder)" maxlength="35"/>
<input type="checkbox" id="favFilterShareBool" class="form-check-input common-checkbox "/>
<label for="favFilterShareBool" class="ms-2 ps-1 label-checkbox">Show with all users</label>
</div>
<t t-set-slot="footer">
<button class="dash-btn-red o-default-button" t-on-click="save_favourite_filter">Save</button>
<button class="dash-default-btn" t-on-click="props.close">Close</button>
</t>
</Dialog>
</t>
<t t-name="ks_dashboard_ninja.favourite_filter" owl="1">
<Dropdown menuClass="'ks-dropdown-menu'">
<t t-set-slot="content">
<DropdownItem t-foreach="Object.values(props.favourite_filters_data)" t-as="favourite_filter" t-key="favourite_filter_index"
class="{ 'd-flex justify-content-between ': true, ' global-active': state.current_active === favourite_filter.name}"
onSelected="() => this.onFilterSelect(favourite_filter.name)">
<span><t t-esc="favourite_filter.name"/></span>
<button class="dadsh-dlt-btn" t-on-click.stop="() => this.onDeleteBtnClick(favourite_filter.name)">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 2.98999C8.835 2.82499 7.16 2.73999 5.49 2.73999C4.5 2.73999 3.51 2.78999 2.52 2.88999L1.5 2.98999"
stroke="#EC2D30" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" />
<path d="M4.25 2.485L4.36 1.83C4.44 1.355 4.5 1 5.345 1H6.655C7.5 1 7.565 1.375 7.64 1.835L7.75 2.485"
stroke="#EC2D30" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" />
<path d="M9.4252 4.57007L9.1002 9.60507C9.0452 10.3901 9.0002 11.0001 7.6052 11.0001H4.3952C3.0002 11.0001 2.9552 10.3901 2.9002 9.60507L2.5752 4.57007"
stroke="#EC2D30" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" />
<path d="M5.16504 8.25H6.83004" stroke="#EC2D30" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" />
<path d="M4.75 6.25H7.25" stroke="#EC2D30" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</DropdownItem>
</t>
<div class="">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.0836 6.75843L12.1836 8.95843C12.3336 9.25843 12.7336 9.55843 13.067 9.60843L15.0586 9.94177C16.3336 10.1584 16.6336 11.0751 15.717 11.9918L14.167 13.5501C13.9086 13.8084 13.7586 14.3168 13.842 14.6834L14.2836 16.6084C14.6336 18.1251 13.8253 18.7168 12.4836 17.9251L10.617 16.8168C10.2753 16.6168 9.7253 16.6168 9.38364 16.8168L7.50864 17.9168C6.16697 18.7084 5.35864 18.1168 5.70864 16.6001L6.1503 14.6751C6.23364 14.3168 6.08364 13.8084 5.8253 13.5418L4.28364 12.0001C3.36697 11.0834 3.66697 10.1584 4.94197 9.9501L6.93364 9.61677C7.26697 9.55843 7.66697 9.26677 7.81697 8.96677L8.91697 6.76677C9.50864 5.56677 10.492 5.56677 11.0836 6.75843Z" fill="#6789C6" />
<path d="M5 8.12508C4.65833 8.12508 4.375 7.84175 4.375 7.50008V1.66675C4.375 1.32508 4.65833 1.04175 5 1.04175C5.34167 1.04175 5.625 1.32508 5.625 1.66675V7.50008C5.625 7.84175 5.34167 8.12508 5 8.12508Z" fill="#6789C6" />
<path d="M15 8.12508C14.6583 8.12508 14.375 7.84175 14.375 7.50008V1.66675C14.375 1.32508 14.6583 1.04175 15 1.04175C15.3417 1.04175 15.625 1.32508 15.625 1.66675V7.50008C15.625 7.84175 15.3417 8.12508 15 8.12508Z" fill="#6789C6" />
<path d="M10 3.95841C9.65833 3.95841 9.375 3.67508 9.375 3.33341V1.66675C9.375 1.32508 9.65833 1.04175 10 1.04175C10.3417 1.04175 10.625 1.32508 10.625 1.66675V3.33341C10.625 3.67508 10.3417 3.95841 10 3.95841Z" fill="#6789C6" />
</svg>
</div>
</Dropdown>
</t>
</templates>

View File

@@ -0,0 +1,3 @@
// .ks_date_filter_selected{
//
// }

View File

@@ -0,0 +1,858 @@
/** @odoo-module **/
import { Component, onWillStart, useState ,useEffect,onMounted, onPatched, onWillUpdateProps,useRef, onWillUnmount, markup, onError } from "@odoo/owl";
import { onAudioEnded, convert_data_to_utc } from '@ks_dashboard_ninja/js/ks_global_functions';
import { KsItemButton } from '@ks_dashboard_ninja/components/chart_buttons/chart_buttons';
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
import { useBus, useService } from "@web/core/utils/hooks";
import { formatFloat } from "@web/core/utils/numbers";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation";
import { localization } from "@web/core/l10n/localization";
import {formatDate,formatDateTime} from "@web/core/l10n/dates";
import { parseDateTime, parseDate } from "@web/core/l10n/dates";
import { renderToElement, renderToString } from "@web/core/utils/render";
import { isMobileOS } from "@web/core/browser/feature_detection";
import { ks_render_graphs, ksrenderfunnelchart, ksrendermapview } from "@ks_dashboard_ninja/js/charts_render_global_functions";
import { Domain } from "@web/core/domain";
import { domainFromTree } from "@web/core/tree_editor/condition_tree";
import { condition, connector } from "@web/core/tree_editor/condition_tree";
export class Ksdashboardgraph extends Component{
setup(){
this.markup = markup;
this.chart_container = {};
this.dialogService = useService("dialog");
this.actionService = useService("action");
this._rpc = rpc
this.state = useState({item_data:"",list_view_data:"", update_chart: 0})
onMounted(() => this._update_view());
onPatched(() => this.update_list_view());
this.item = this.props.item
this.item.ksIsDashboardManager = this.props.dashboard_data.ks_dashboard_manager
this.item.ks_dashboard_list = this.props.dashboard_data.ks_dashboard_list
this.ks_dashboard_id = this.props.item.ks_dashboard_id
this.ks_dashboard_data = this.props.dashboard_data
if (this.item.ks_dashboard_item_type == 'ks_list_view'){
this.prepare_list()
}else{
this.prepare_item(this.item);
}
this.ks_gridstack_container = useRef("ks_gridstack_container");
this.aiAudioRef = useRef("aiAudioRef");
this.ks_list_view = useRef("ks_list_view");
var update_interval = this.props.dashboard_data.ks_set_interval
this.ks_ai_analysis = this.ks_dashboard_data.ks_ai_explain_dash
if (this.item.ks_ai_analysis && this.item.ks_ai_analysis){
var ks_analysis = this.item.ks_ai_analysis.split('ks_gap')
this.ks_ai_analysis_1 = ks_analysis[0]
this.ks_ai_analysis_2 = ks_analysis[1]
}
onWillUpdateProps((nextprops)=>{
if (nextprops.itemsToUpdateList.length){
if (nextprops.itemsToUpdateList?.includes(this.item.id)){
this.ksFetchUpdateItem(this.item.id, this.ks_dashboard_id, nextprops.dashboard_data.context)
}
}
})
onWillUnmount( () => {
this.aiAudioRef.el?.removeEventListener('ended', onAudioEnded)
})
}
get isMobile() {
return isMobileOS();
}
ksFetchUpdateItem(item_id, dash_id, context, domain = this.env.ksGetParamsForItemFetch(item_id)) {
this.root?.dispose();
this.env.services.ui.block();
var self = this;
context = self.env.getContext();
rpc("/web/dataset/call_kw",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_item',
args: [
[parseInt(item_id)], dash_id,domain
],
kwargs: { context },
})
.then((new_item_data) => {
if(new_item_data[item_id].ks_list_view_data){
new_item_data[item_id].ks_list_view_data = convert_data_to_utc(new_item_data[item_id].ks_list_view_data)
}
this.ks_dashboard_data.ks_item_data[item_id] = new_item_data[item_id];
this.item = this.ks_dashboard_data.ks_item_data[item_id] ;
// done this to render updated items on play button
this.__owl__.parent.component.ks_dashboard_data.ks_item_data[this.item.id] = new_item_data[item_id]
if (this.item.ks_dashboard_item_type =="ks_funnel_chart"){
$(this.ks_gridstack_container.el).find(".card-body").remove()
ksrenderfunnelchart.bind(this)($(this.ks_gridstack_container.el),this.item, 'dashboard_view');
}else if(this.item.ks_dashboard_item_type =="ks_list_view"){
this.prepare_list()
if (this.intial_count < this.item.ks_pagination_limit ) {
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}else{
$(this.ks_list_view.el).find('.ks_load_next').removeClass('ks_event_offer_list');
}
}else if(this.item.ks_dashboard_item_type == ("ks_map_view")){
$(this.ks_gridstack_container.el).find(".card-body").remove()
ksrendermapview.bind(this)($(this.ks_gridstack_container.el),this.item, 'dashboard_view')
}else{
$(this.ks_gridstack_container.el).find(".card-body").remove()
ks_render_graphs.bind(this)($(this.ks_gridstack_container.el),this.item, this.props.dashboard_data.zooming_enabled, 'dashboard_view')
}
this.env.services.ui.unblock();
//
});
}
_update_view(){
let self = this
if(this.item.ks_dashboard_item_type == 'ks_list_view'){
if (this.item.ks_pagination_limit < this.intial_count) {
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}
if (this.intial_count < this.item.ks_pagination_limit ) {
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}
if (this.item.ks_record_data_limit === this.item.ks_pagination_limit){
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}
if (this.intial_count == 0){
$(this.ks_list_view.el).find('.ks_pager').addClass('d-none');
}
if (this.item.ks_pagination_limit==0){
$(this.ks_list_view.el).find('.ks_pager_name').addClass('d-none');
}
if (this.item.ks_data_calculation_type === 'query' || this.item.ks_list_view_type === "ungrouped"){
$('.ks_list_canvas_click').removeClass('ks_list_canvas_click');
}
}else{
if (this.item.ks_data_calculation_type === 'query'){
$(this.ks_gridstack_container.el).find(".ks_dashboard_item_chart_info").addClass('d-none');
}
$(this.ks_gridstack_container.el).addClass('ks_dashboarditem_id');
$(this.ks_gridstack_container.el).find(".ks_dashboard_item_button_container").addClass("ks_funnel_item_container")
if (this.item.ks_dashboard_item_type =="ks_funnel_chart"){
ksrenderfunnelchart.bind(this)($(this.ks_gridstack_container.el),this.item, 'dashboard_view')
}else if(this.item.ks_dashboard_item_type == ("ks_map_view")){
ksrendermapview.bind(this)($(this.ks_gridstack_container.el),this.item, 'dashboard_view')
}else{
ks_render_graphs.bind(this)($(this.ks_gridstack_container.el),this.item, this.props.dashboard_data.zooming_enabled, 'dashboard_view')
}
}
this.aiAudioRef.el?.addEventListener('ended', onAudioEnded)
}
update_list_view(){
if(this.item.ks_dashboard_item_type == 'ks_list_view'){
if (this.item.ks_pagination_limit < this.intial_count) {
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}
if (this.intial_count < this.item.ks_pagination_limit ) {
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}
if (this.item.ks_record_data_limit === this.item.ks_pagination_limit){
$(this.ks_list_view.el).find('.ks_load_next').addClass('ks_event_offer_list');
}
if (this.intial_count == 0){
$(this.ks_list_view.el).find('.ks_pager').addClass('d-none');
}
if (this.intial_count != 0){
$(this.ks_list_view.el).find('.ks_pager').removeClass('d-none');
}
if (this.item.ks_pagination_limit==0){
$(this.ks_list_view.el).find('.ks_pager_name').addClass('d-none');
}
if (this.item.ks_data_calculation_type === 'query' || this.item.ks_list_view_type === "ungrouped"){
$('.ks_list_canvas_click').removeClass('ks_list_canvas_click');
}
}
}
prepare_list() {
var self = this;
if (this.item.ks_info){
var ks_description = this.item.ks_info.replace?.(/\\n/g, '\n').split?.('\n');
var ks_description = ks_description.filter(element => element !== '')
}else {
var ks_description = false;
}
if (typeof(this.item.ks_list_view_data) == 'string'){
var list_view_data = JSON.parse(this.item.ks_list_view_data)
}else{
var list_view_data = this.item.ks_list_view_data
}
var data_rows = list_view_data.data_rows
var length = data_rows ? data_rows.length: false;
var item_id = this.item.id
this.ks_info = ks_description?.join?.(' ') ?? false;
this.ks_chart_title = this.item.name
this.ks_breadcrumb = this.item.ks_action_name
this.item_id = item_id
this.ksIsDashboardManager= self.ks_dashboard_data.ks_dashboard_manager,
this.ksIsUser = true,
this.ks_dashboard_list = self.ks_dashboard_data.ks_dashboard_list,
this.count = '1-' + length
this.offset = 1
this.intial_count = length
this.ks_company= this.item.ks_company
this.calculation_type = this.ks_dashboard_data.ks_item_data[this.item_id].ks_data_calculation_type
this.self = this
if (this.item.ks_list_view_type === "ungrouped" && list_view_data) {
if (list_view_data.date_index) {
var index_data = list_view_data.date_index;
for (var i = 0; i < index_data.length; i++) {
for (var j = 0; j < list_view_data.data_rows.length; j++) {
var index = index_data[i]
var date = list_view_data.data_rows[j]["data"][index]
if (date) {
if( list_view_data.fields_type[index] === 'date'){
let parsedDate = parseDate(date,{format: localization.dateFormat});
list_view_data.data_rows[j]["data"][index] = formatDate(parsedDate, { format: localization.dateFormat })
} else{
let parsedDate = parseDateTime(date,{format: localization.dateTimeFormat});
list_view_data.data_rows[j]["data"][index] = formatDateTime(parsedDate, { format: localization.dateTimeFormat })
}
}else{
// list_view_data.data_rows[j]["data"][index] = "";
}
}
}
}
}
if (list_view_data) {
for (var i = 0; i < list_view_data.data_rows.length; i++) {
for (var j = 0; j < list_view_data.data_rows[0]["data"].length; j++) {
if (typeof(list_view_data.data_rows[i].data[j]) === "number" || list_view_data.data_rows[i].data[j]) {
if (typeof(list_view_data.data_rows[i].data[j]) === "number") {
list_view_data.data_rows[i].data[j] = formatFloat(list_view_data.data_rows[i].data[j], Float64Array, {digits:[0, self.item.ks_precision_digits]})
}
} else {
// list_view_data.data_rows[i].data[j] = "";
}
}
}
}
this.state.list_view_data = list_view_data
this.list_type = this.item.ks_list_view_type
this.ks_pager = true
this.tmpl_list_type = self.ks_dashboard_data.ks_item_data[this.item_id].ks_list_view_type
this.isDrill = this.ks_dashboard_data.ks_item_data[this.item_id]['isDrill']
this.ks_show_records = this.item.ks_show_records
// this.item.$el = $ks_gridstack_container;
}
prepare_item(item) {
var self = this;
var isDrill = item.isDrill ? item.isDrill : false;
this.chart
var chart_id = item.id;
this.ksColorOptions = ["default","dark","moonrise","material"]
var funnel_title = item.name;
if (item.ks_info){
var ks_description = item.ks_info.replace?.(/\\n/g, '\n').split?.('\n');
var ks_description = ks_description.filter(element => element !== '')
}else {
var ks_description = false;
}
this.ks_chart_title= funnel_title,
this.ksIsDashboardManager= self.ks_dashboard_data.ks_dashboard_manager,
this.ksIsUser = true,
this.ks_dashboard_list = self.ks_dashboard_data.ks_dashboard_list,
this.chart_id = chart_id,
this.ks_info = ks_description?.join?.(' ') ?? false,
this.ksChartColorOptions = this.ksColorOptions,
this.ks_company = item.ks_company,
this.ks_dashboard_item_type = item.ks_dashboard_item_type,
this.ks_breadcrumb = item.ks_action_name
}
onChartCanvasClick(evt, column_index = false, row_data = false, column_field_type = false) {
var self = this;
this.ksUpdateDashboard = {};
if(this.env.inDialog) return ;
var item_id = $(evt.target).parent().data().itemId;
var chart_title = '#'+this.item.name
var item_data = self.ks_dashboard_data.ks_item_data[item_id]
if (self.ks_dashboard_data.ks_item_data[item_id].max_sequnce) {
var sequence = item_data.sequnce ? item_data.sequnce : 0
var domain = $(evt.target).parent().data().domain;
if ($(evt.target).parent().data().last_seq !== sequence) {
self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/ks_fetch_drill_down_data",{
model: 'ks_dashboard_ninja.item',
method: 'ks_fetch_drill_down_data',
args: [item_id, domain, sequence],
kwargs : {},
}).then((result) => {
if (result.ks_list_view_data) {
var chart_id_name = '#item'+'_' +'-1'
var id_name = '#'+result.ks_action_name + '_' + (result.sequence-1)
if (self.ks_dashboard_data.ks_item_data[item_id].domains) {
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_list_view_data).previous_domain;
} else {
self.ks_dashboard_data.ks_item_data[item_id]['domains'] = {}
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_list_view_data).previous_domain;
}
self.ks_dashboard_data.ks_item_data[item_id]['isDrill'] = true;
self.ks_dashboard_data.ks_item_data[item_id]['sequnce'] = result.sequence;
self.ks_dashboard_data.ks_item_data[item_id]['ks_list_view_data'] = result.ks_list_view_data;
self.ks_dashboard_data.ks_item_data[item_id]['ks_list_view_type'] = result.ks_list_view_type;
self.ks_dashboard_data.ks_item_data[item_id]['ks_dashboard_item_type'] = 'ks_list_view';
self.ks_dashboard_data.ks_item_data[item_id]['sequnce'] = result.sequence;
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_more_action").addClass('d-none');
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_chart_heading").addClass("d-none")
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_list_view_heading").addClass("d-none")
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").empty();
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_search_plus").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_search_minus").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").removeClass('d-none');
// $($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_title).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_pager").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_action_export").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_quick_edit_action_popup").removeClass('d-sm-block ');
var item_data = self.ks_dashboard_data.ks_item_data[item_id]
var list_view_data = JSON.parse(item_data['ks_list_view_data'])
var $container = renderToElement('ks_dashboard_ninja.ks_new_list_view_table',{
list_view_data,item_id:self.item_id,self, markup, state : { list_view_data }
})
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").append($container);
} else {
self.ks_dashboard_data.ks_item_data[item_id]['ks_chart_data'] = result.ks_chart_data;
self.ks_dashboard_data.ks_item_data[item_id]['sequnce'] = result.sequence;
self.ks_dashboard_data.ks_item_data[item_id]['ks_dashboard_item_type'] = result.ks_chart_type;
self.ks_dashboard_data.ks_item_data[item_id]['isDrill'] = true;
var chart_id_name = '#item'+'_' +'-1'
var id_name = '#'+result.ks_action_name + '_' + (result.sequence-1)
if (self.ks_dashboard_data.ks_item_data[item_id].domains) {
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_chart_data).previous_domain;
} else {
self.ks_dashboard_data.ks_item_data[item_id]['domains'] = {}
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_chart_data).previous_domain;
}
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_more_action").addClass('d-none');
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_list_view_heading").addClass("d-none")
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_id_name).removeClass('d-none');
// $($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_title).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_chart_info").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_color_option").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_search_plus").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_search_minus").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_pager").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_quick_edit_action_popup").removeClass('d-sm-block ');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_action_export").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").empty();
var item_data = self.ks_dashboard_data.ks_item_data[item_id]
if(item_data.ks_dashboard_item_type == 'ks_funnel_chart'){
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".card-body").remove();
ksrenderfunnelchart.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]"), item_data, 'dashboard_view');
}else{
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".card-body").remove();
ks_render_graphs.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]"), item_data, self.props.dashboard_data.zooming_enabled, 'dashboard_view');
}
}
});
}
}
else{
/**
* This is the logic for adding dashboard filter from chart clicks
*/
if(!isMobileOS()){
if(['many2one', 'selection'].includes(column_field_type)){
let field_name = self.item.ks_list_view_data.fields_technical_name[column_index]
let model_name = self.item.ks_list_view_data.model
let model_display_name = self.item.ks_model_display_name
if(field_name && model_name && model_display_name)
this.env.bus.trigger("APPLY: Dashboard Filter" , { model_display_name, model_name, field_name,
operator : '=', value : row_data[0] ?? row_data })
}
}
}
evt.stopPropagation();
}
async onChartCanvasClick_funnel(evt, item_id, item){
var self = this;
if(this.env.inDialog || self.env.mode === 'edit') return ;
this.ksUpdateDashboard = {};
var domain = [];
var partner_id;
var final_active;
// var myChart = self.chart_container[item_id];
var index;
var item_data = self.ks_dashboard_data.ks_item_data[item_id];
var groupBy = JSON.parse(item_data["ks_chart_data"])['groupby'];
var labels = JSON.parse(item_data["ks_chart_data"])['labels'];
var domains = JSON.parse(item_data["ks_chart_data"])['domains'];
var sequnce = item_data.sequnce ? item_data.sequnce : 0;
// $(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_breadcrumb").removeClass("d-none")
var chart_title = '#'+item.name
// if (item.ks_dashboard_item_type == "ks_bullet_chart" || item.ks_dashboard_item_type === "ks_funnel_chart" || item.ks_dashboard_item_type === "ks_flower_view" || item.ks_dashboard_item_type === "ks_radialBar_chart"){
if (evt.target.dataItem){
var activePoint = evt.target.dataItem.dataContext;
}
if (activePoint) {
if (activePoint.category){
for (let i=0 ; i<labels.length ; i++){
if (labels[i] == activePoint.category){
index = i
}
}
domain = domains[index]
}
else if (activePoint.stage){
for (let i=0 ; i<labels.length ; i++){
if (labels[i] == activePoint.stage){
index = i
}
}
domain = domains[index]
}
if (typeof domain === 'object' && domain !== null && !Array.isArray(domain)) {
domain = domain[evt.target.dataItem.component._settings.name]
}
if (item_data.max_sequnce != 0 && sequnce < item_data.max_sequnce) {
self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/ks_fetch_drill_down_data",{
model: 'ks_dashboard_ninja.item',
method: 'ks_fetch_drill_down_data',
args: [item_id, domain, sequnce],
kwargs : {},
}).then((result) => {
self.ks_dashboard_data.ks_item_data[item_id]['sequnce'] = result.sequence;
self.ks_dashboard_data.ks_item_data[item_id]['isDrill'] = true;
if (result.ks_chart_data) {
var chart_id_name = '#item'+'_' +'-1'
var id_name = '#'+result.ks_action_name + '_' + (result.sequence-1)
self.ks_dashboard_data.ks_item_data[item_id]['ks_dashboard_item_type'] = result.ks_chart_type;
self.ks_dashboard_data.ks_item_data[item_id]['ks_chart_data'] = result.ks_chart_data;
if (self.ks_dashboard_data.ks_item_data[item_id].domains) {
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_chart_data).previous_domain;
} else {
self.ks_dashboard_data.ks_item_data[item_id]['domains'] = {}
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_chart_data).previous_domain;
}
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_chart_heading").addClass("d-none")
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_chart_info").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_color_option").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_quick_edit_action_popup").removeClass('d-sm-block ');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_more_action").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").empty();
var item_data = self.ks_dashboard_data.ks_item_data[item_id]
if(item_data.ks_dashboard_item_type == 'ks_funnel_chart'){
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".card-body").remove();
ksrenderfunnelchart.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]"),item_data, 'dashboard_view');
}else{
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".card-body").remove();
ks_render_graphs.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]"), item_data, self.props.dashboard_data.zooming_enabled, 'dashboard_view');
}
} else {
if ('domains' in self.ks_dashboard_data.ks_item_data[item_id]) {
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_list_view_data).previous_domain;
} else {
self.ks_dashboard_data.ks_item_data[item_id]['domains'] = {}
self.ks_dashboard_data.ks_item_data[item_id]['domains'][result.sequence] = JSON.parse(result.ks_list_view_data).previous_domain;
}
var chart_id_name = '#item'+'_' +'-1'
var id_name = '#'+result.ks_action_name + '_' + (result.sequence-1)
self.ks_dashboard_data.ks_item_data[item_id]['isDrill'] = true;
self.ks_dashboard_data.ks_item_data[item_id]['sequnce'] = result.sequence;
self.ks_dashboard_data.ks_item_data[item_id]['ks_list_view_data'] = result.ks_list_view_data;
self.ks_dashboard_data.ks_item_data[item_id]['ks_list_view_type'] = result.ks_list_view_type;
self.ks_dashboard_data.ks_item_data[item_id]['ks_dashboard_item_type'] = 'ks_list_view';
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_chart_heading").addClass("d-none")
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(id_name).removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_chart_info").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_color_option").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").empty();
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_quick_edit_action_popup").removeClass('d-sm-block ');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_more_action").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").addClass('table-responsive');
var item_data = self.ks_dashboard_data.ks_item_data[item_id]
self.item = item_data
self.prepare_list();
var list_view_data = JSON.parse(item_data['ks_list_view_data'])
var $container = renderToElement('ks_dashboard_ninja.ks_new_list_view_table',{
list_view_data, self, item_id:self.item_id, markup, state: { list_view_data }
})
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").append($container);
}
});
} else {
if (item_data.action) {
var action = Object.assign({}, item_data.action);
if (action.view_mode.includes('tree')) action.view_mode = action.view_mode.replace('tree', 'list');
for (var i = 0; i < action.views.length; i++) action.views[i][1].includes('tree') ? action.views[i][1] = action.views[i][1].replace('tree', 'list') : action.views[i][1];
action['domain'] = domain || [];
action['search_view_id'] = [action.search_view_id, 'search']
} else {
var action = {
name: _t(item_data.name),
type: 'ir.actions.act_window',
res_model: item_data.ks_model_name,
domain: domain || [],
context: {
'group_by': groupBy ? groupBy:false ,
},
views: [
[false, 'list'],
[false, 'form']
],
view_mode: 'list',
target: 'current',
}
}
if (item_data.ks_show_records && action) {
self.actionService.doAction(action, {
on_reverse_breadcrumb: self.on_reverse_breadcrumb,
});
}
}
}
}
ksOnDrillUp(e) {
var self = this;
var item_id = e.currentTarget.dataset.itemId;
var item_data = self.ks_dashboard_data.ks_item_data[item_id];
var domain;
var chart_name = '#'+item_data.name
var sequence = parseInt(e.currentTarget.dataset.sequence)
var chart_id_name = '#item'+'_' +'-1'
if(item_data) {
if ('domains' in item_data) {
domain = item_data['domains'][sequence+1] ? item_data['domains'][sequence+1] : []
var sequnce = sequence;
if (sequnce >= 0) {
self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/ks_fetch_drill_down_data",{
model: 'ks_dashboard_ninja.item',
method: 'ks_fetch_drill_down_data',
args: [item_id, domain, sequnce],
kwargs:{}
}).then((result) => {
self.ks_dashboard_data.ks_item_data[item_id]['ks_chart_data'] = result.ks_chart_data;
self.ks_dashboard_data.ks_item_data[item_id]['sequnce'] = result.sequence;
var id_name = '#'+result.ks_action_name + '_' + sequence;
var ks_breadcrumb_elements = $($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(id_name).nextAll();
if (result.ks_chart_type) {
self.ks_dashboard_data.ks_item_data[item_id]['ks_dashboard_item_type'] = result.ks_chart_type;
self.item.ks_dashboard_item_type = result.ks_chart_type
}
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").empty();
if (result.ks_chart_data) {
var item_data = self.ks_dashboard_data.ks_item_data[item_id];
ks_breadcrumb_elements.each(function(index,item){
$(item).addClass("d-none")
})
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_chart_info").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_color_option").removeClass('d-none')
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".card-body").remove()
if (result.ks_chart_type == "ks_funnel_chart"){
ksrenderfunnelchart.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]"),item_data, 'dashboard_view');
}else{
ks_render_graphs.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]"), item_data, self.props.dashboard_data.zooming_enabled, 'dashboard_view');
}
} else {
self.ks_dashboard_data.ks_item_data[item_id]['ks_list_view_data'] = result.ks_list_view_data;
self.ks_dashboard_data.ks_item_data[item_id]['ks_list_view_type'] = result.ks_list_view_type;
self.ks_dashboard_data.ks_item_data[item_id]['ks_dashboard_item_type'] = 'ks_list_view';
self.item.ks_dashboard_item_type = 'ks_list_view';
var item_data = self.ks_dashboard_data.ks_item_data[item_id]
ks_breadcrumb_elements.each(function(index,item){
$(item).addClass("d-none")
})
self.prepare_list(item_data);
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_pager").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_chart_info").addClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_color_option").addClass('d-none')
var list_view_data = JSON.parse(item_data['ks_list_view_data'])
var $container = renderToElement('ks_dashboard_ninja.ks_new_list_view_table',{
list_view_data,item_id:self.item_id,self, markup, state: { list_view_data }
})
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".card-body").append($container);
}
});
} else {
var ks_breadcrumb_elements = $($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_id_name).nextAll();
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(chart_id_name).addClass('d-none');
ks_breadcrumb_elements.each(function(index,item){
$(item).addClass("d-none")
})
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_chart_heading").removeClass("d-none")
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").find(".ks_list_view_heading").removeClass("d-none")
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").addClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_chart_info").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_color_option").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_quick_edit_action_popup").addClass('d-sm-block ');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_more_action").removeClass('d-none');
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_action_export").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_search_plus").removeClass('d-none')
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_search_minus").removeClass('d-none')
self.ksFetchChartItem(item_id)
var updateValue = self.ks_dashboard_data.ks_set_interval;
}
} else {
if(!domain){
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_id + "]").children()[0]).find(".ks_dashboard_item_drill_up").addClass('d-none');
}
}
}
e.stopPropagation();
}
ksFetchChartItem(id) {
var self = this;
var item_data = self.ks_dashboard_data.ks_item_data[id];
return self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_fetch_item",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_item',
args: [
[item_data.id], self.ks_dashboard_data.ks_dashboard_id, {}
],
kwargs:{},
}).then((new_item_data) => {
this.ks_dashboard_data.ks_item_data[id] = new_item_data[id];
this.ks_dashboard_data.ks_item_data[id]['ks_dashboard_item_type'] = new_item_data[id].ks_dashboard_item_type
this.item.ks_dashboard_item_type = new_item_data[id].ks_dashboard_item_type
$($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + id + "]").children()[0]).find(".card-body").empty();
var item_data = self.ks_dashboard_data.ks_item_data[id]
if (item_data.ks_list_view_data) {
self.actionService.doAction({
type: "ir.actions.client",
tag: "reload",
});
}else if(item_data.ks_dashboard_item_type == 'ks_funnel_chart'){
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]").find(".card-body").remove();
var name = item_data.name ?item_data.name : item_data.ks_model_display_name;
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]").find('.ks_chart_heading').prop('title',name)
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]").find('.ks_chart_heading').text(name)
ksrenderfunnelchart.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]"),item_data, 'dashboard_view');
}else{
$(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]").find(".card-body").remove();
ks_render_graphs.bind(this)($(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + id + "]"), item_data, self.props.dashboard_data.zooming_enabled, 'dashboard_view');
}
});
}
ksRenderChartColorOptions(e) {
var self = this;
if (!$(e.currentTarget).parent().hasClass('global-active')) {
// FIXME : Correct this later.
var $parent = $(e.currentTarget).parent().parent();
$parent.find('.global-active').removeClass('global-active')
$(e.currentTarget).parent().addClass('global-active')
var item_data = self.ks_dashboard_data.ks_item_data[$parent.data().itemId];
var chart_data = JSON.parse(item_data.ks_chart_data);
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/write",{
model: 'ks_dashboard_ninja.item',
method: 'write',
args: [$parent.data().itemId, {
"ks_chart_item_color": e.currentTarget.dataset.chartColor
}],
kwargs:{}
}).then(() => {
self.ks_dashboard_data.ks_item_data[$parent.data().itemId]['ks_chart_item_color'] = e.target.dataset.chartColor;
$(".grid-stack-item[gs-id=" + item_data.id + "]").find(".card-body").remove();
if (item_data.ks_dashboard_item_type == 'ks_funnel_chart'){
ksrenderfunnelchart.bind(this)($(self.ks_gridstack_container.el), item_data, 'dashboard_view');
}else{
ks_render_graphs.bind(this)($(self.ks_gridstack_container.el),item_data, self.props.dashboard_data.zooming_enabled, 'dashboard_view');
}
})
}
}
ksLoadMoreRecords(e) {
var self = this;
var ks_intial_count = e.target.parentElement.dataset.prevOffset;
var ks_offset = e.target.parentElement.dataset.next_offset;
var itemId = e.currentTarget.dataset.itemId;
var offset = self.ks_dashboard_data.ks_item_data[itemId].ks_pagination_limit;
var context = self.ks_dashboard_data['context']
var params;
params = self.env.ksGetParamsForItemFetch(parseInt(itemId));
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_get_list_view_data_offset",{
model: 'ks_dashboard_ninja.board',
method: 'ks_get_list_view_data_offset',
args: [parseInt(itemId), {
ks_intial_count: ks_intial_count,
offset: ks_offset,
}, parseInt(self.ks_dashboard_data.ks_dashboard_id), params],
kwargs:{context: self.env.getContext()}
}).then(function(result) {
var item_data = self.ks_dashboard_data.ks_item_data[itemId];
if(result.ks_list_view_data){
result.ks_list_view_data = convert_data_to_utc(result.ks_list_view_data)
}
var item_view = $(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]");
self.item.ks_list_view_data = result.ks_list_view_data;
self.prepare_list();
$(e.target).parents('.ks_pager').find('.ks_value').text(result.offset + "-" + result.next_offset);
e.target.parentElement.dataset.next_offset = result.next_offset;
e.target.parentElement.dataset.prevOffset = result.offset;
$(e.target.parentElement).find('.ks_load_previous').removeClass('ks_event_offer_list');
if (result.next_offset < parseInt(result.offset) + (offset - 1) || result.next_offset == item_data.ks_record_count || result.next_offset === result.limit){
$(e.target).addClass('ks_event_offer_list');
}
});
}
ksLoadPreviousRecords(e) {
var self = this;
var itemId = e.currentTarget.dataset.itemId;
var offset = self.ks_dashboard_data.ks_item_data[itemId].ks_pagination_limit;
var ks_offset = parseInt(e.target.parentElement.dataset.prevOffset) - (offset + 1) ;
var ks_intial_count = e.target.parentElement.dataset.next_offset;
var context = self.ks_dashboard_data['context']
var params;
params = self.env.ksGetParamsForItemFetch(parseInt(itemId));
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_get_list_view_data_offset",{
model: 'ks_dashboard_ninja.board',
method: 'ks_get_list_view_data_offset',
args: [parseInt(itemId), {
ks_intial_count: ks_intial_count,
offset: ks_offset,
}, parseInt(self.ks_dashboard_data.ks_dashboard_id), params],
kwargs:{context: self.env.getContext()}
}).then(function(result) {
if(result.ks_list_view_data){
result.ks_list_view_data = convert_data_to_utc(result.ks_list_view_data)
}
var item_data = self.ks_dashboard_data.ks_item_data[itemId];
var item_view = $(".ks_dashboard_main_content").find(".grid-stack-item[gs-id=" + item_data.id + "]");
self.item.ks_list_view_data = result.ks_list_view_data;
self.prepare_list();
$(e.target).parents('.ks_pager').find('.ks_value').text(result.offset + "-" + result.next_offset);
e.target.parentElement.dataset.next_offset = result.next_offset;
e.target.parentElement.dataset.prevOffset = result.offset;
$(e.target.parentElement).find('.ks_load_next').removeClass('ks_event_offer_list');
if (result.offset === 1) {
$(e.target).addClass('ks_event_offer_list');
}
});
}
ksOnListItemInfoClick(e) {
var self = this;
var item_id = e.currentTarget.dataset.itemId;
var item_data = self.ks_dashboard_data.ks_item_data[item_id];
var action = {
name: _t(item_data.name),
type: 'ir.actions.act_window',
res_model: e.currentTarget.dataset.model,
domain: item_data.ks_domain || [],
views: [
[false, 'list'],
[false, 'form']
],
target: 'current',
}
if (e.currentTarget.dataset.listViewType === "ungrouped") {
action['view_mode'] = 'form';
action['views'] = [
[false, 'form']
];
action['res_id'] = parseInt(e.currentTarget.dataset.recordId);
} else {
if (e.currentTarget.dataset.listType === "date_type") {
var domain = JSON.parse(e.currentTarget.parentElement.parentElement.dataset.domain);
action['view_mode'] = 'list';
action['context'] = {
'group_by': e.currentTarget.dataset.groupby,
};
action['domain'] = domain;
} else if (e.currentTarget.dataset.listType === "relational_type") {
var domain = JSON.parse(e.currentTarget.parentElement.parentElement.dataset.domain);
action['view_mode'] = 'list';
action['context'] = {
'group_by': e.currentTarget.dataset.groupby,
};
action['domain'] = domain;
action['context']['search_default_' + e.currentTarget.dataset.groupby] = parseInt(e.currentTarget.dataset.recordId);
} else if (e.currentTarget.dataset.listType === "other") {
var domain = JSON.parse(e.currentTarget.parentElement.parentElement.dataset.domain);
action['view_mode'] = 'list';
action['context'] = {
'group_by': e.currentTarget.dataset.groupby,
};
action['context']['search_default_' + e.currentTarget.dataset.groupby] = parseInt(e.currentTarget.dataset.recordId);
action['domain'] = domain;
}
}
self.actionService.doAction(action)
}
};
Ksdashboardgraph.props = {
item: { type: Object, optional: true},
dashboard_data: { type: Object, optional: true},
ksdatefilter : { type: String ,optional: true},
pre_defined_filter :{ type:Object, optional: true},
custom_filter :{ type:Object, optional: true},
itemsToUpdateList : { type: Array, optional: true },
ks_speak:{ type: Function , optional: true},
hideButtons: { type: Number, optional: true },
generate_dialog: { type: Boolean, optional: true },
explain_ai_whole: { type: Boolean, optional: true },
onItemClick: { type: Function },
};
Ksdashboardgraph.template = "Ks_chart_list_container";
Ksdashboardgraph.components = { KsItemButton };

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="Ks_chart_list_container">
<t t-if="item.ks_dashboard_item_type == 'ks_list_view'">
<t t-call="ks_dashboard_ninja.Ksdashboardlistview"/>
</t>
<t t-else="">
<t t-call="Ks_gridstack_container"/>
</t>
</t>
<t t-name="Ks_gridstack_container">
<div class="grid-stack-item ks_tile_carousel ks_chart_container ks_dashboarditem_id" t-att-id="chart_id" t-ref="ks_gridstack_container">
<div class="grid-stack-item-content ks_dashboarditem_chart_container ks_border_radius ks_dashboard_item_hover dashboard-container d-flex flex-fill flex-column border-0"
t-att-title="ks_info" t-att-id="chart_id">
<t t-if="!env.isExplainDashboardWithAi">
<div class="dashboard-header position-relative py-3 mb-1 px-3 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center w-50">
<h4 class="ks_chart_heading text-capitalize text-start" t-att-title="ks_chart_title">
<t t-esc="ks_chart_title"/>
</h4>
<!-- <div >-->
<t t-if="ks_breadcrumb.length >= 1">
<nav class="ks_breadcrumb">
<ul>
<li class="d-none" t-att-id="'item'+'_'+'-1'">
<span t-att-data-sequence = '-1' t-att-data-item-id="chart_id" t-on-click="ksOnDrillUp">
<t t-esc="ks_chart_title"/>
</span>
</li>
<t t-foreach="ks_breadcrumb" t-as="chart_breadcrumb" t-key="chart_breadcrumb_index">
<li class="d-none" t-att-id="chart_breadcrumb['name'] + '_' + chart_breadcrumb_index">
<span t-att-data-sequence="chart_breadcrumb_index" t-att-data-item-id="chart_id" t-on-click="ksOnDrillUp">
<t t-esc="chart_breadcrumb['name']"/>
</span>
</li>
</t>
<!-- <t t-if="state.explain_ai">-->
</ul>
</nav>
</t>
</div>
<!-- </div>-->
<img src="/ks_dashboard_ninja/static/images/selected.svg" class="ks_img_display d-none" width="30"/>
<div class="select-btn d-none">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 18.3332C14.5834 18.3332 18.3334 14.5832 18.3334 9.99984C18.3334 5.4165 14.5834 1.6665 10.0001 1.6665C5.41675 1.6665 1.66675 5.4165 1.66675 9.99984C1.66675 14.5832 5.41675 18.3332 10.0001 18.3332Z"
stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.45825 9.99993L8.81659 12.3583L13.5416 7.6416" stroke="" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<KsItemButton item_data="this.item" itemRootRef="this.ks_gridstack_container"/>
</div>
</t>
<t t-if="ks_ai_analysis">
<div class="explain-ai pt-3">
<div class="container">
<div class="row ks_ai_explain_body">
<div class="col-xl-6 col-12">
<div class="charts-sec">
<h4 class="ks_chart_heading text-capitalize text-start" t-att-title="ks_chart_title">
<t t-esc="ks_chart_title"/>
</h4>
<div class="card-body ks_chart_card_body charts-sec"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<!-- <div class="ks_ai_explanation">-->
<p><t t-esc="ks_ai_analysis_1"/></p>
<p><t t-esc="ks_ai_analysis_2"/></p>
<!-- </div>-->
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24" loading="lazy"
class="img-fluid"/>
</div>
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div class="mt-1 card-body ks_chart_card_body"/>
</t>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,457 @@
/** @odoo-module **/
import { Component,useState ,onWillUpdateProps,useEffect,onMounted,useRef, onWillUnmount} from "@odoo/owl";
import {globalfunction } from '@ks_dashboard_ninja/js/ks_global_functions';
import { formatFloat } from "@web/core/utils/numbers";
import { formatInteger } from "@web/views/fields/formatters";
import { useService } from "@web/core/utils/hooks";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
import { _t } from "@web/core/l10n/translation";
import { onAudioEnded } from '@ks_dashboard_ninja/js/ks_global_functions';
import { isMobileOS } from "@web/core/browser/feature_detection";
import { KsItemButton } from '@ks_dashboard_ninja/components/chart_buttons/chart_buttons';
export class Ksdashboardkpiview extends Component{
file_type_magic_word= {'/': 'jpg','R': 'gif','i': 'png','P': 'svg+xml'}
setup(){
var self = this;
this._rpc = rpc
this.actionService = useService("action");
this.ks_kpi = useRef('ks_kpi')
this.aiAudioRef = useRef("aiAudioRef");
this.ksAllowItemClick = false;
this.state = useState({item_info_kpi1:{},item_info_kpi2:{},item_info_kpi3:{}})
onMounted(() => this._update_view());
this.item = this.props.item
this.ks_dashboard_data = this.props.dashboard_data
this.item.ksIsDashboardManager = this.props.dashboard_data.ks_dashboard_manager
this.item.ks_dashboard_list = this.props.dashboard_data.ks_dashboard_list
this.classname = ' encapsulated-kpi-tile ks_dashboard_kpi ks_dashboard_kpi_dashboard ks_dashboard_custom_srollbar ks_dashboarditem_id ks_dashboard_item_hover ks_db_item_preview_color_picker grid-stack-item-content'
this.ks_ai_analysis = this.props.dashboard_data.ks_ai_explain_dash
if (this.ks_ai_analysis){
this.reviewclass = 'ks_ai_explain_tile'
}else{
this.reviewclass = ''
}
if (this.item.ks_ai_analysis && this.item.ks_ai_analysis){
var ks_analysis = this.item.ks_ai_analysis.split('ks_gap')
this.ks_ai_analysis_1 = ks_analysis[0]
this.ks_ai_analysis_2 = ks_analysis[1]
}
this.prepareKpiData();
var update_interval = this.props.dashboard_data.ks_set_interval
onWillUpdateProps( async (nextprops) => {
if (nextprops?.itemsToUpdateList?.length){
if (nextprops.itemsToUpdateList?.includes(this.item.id)){
await this.ksFetchUpdateItem(this.item.id)
}
}
})
onWillUnmount( () => {
this.aiAudioRef.el?.removeEventListener('ended', onAudioEnded)
})
}
ksFetchUpdateItem(item_id) {
var self = this;
return rpc("/web/dataset/call_kw",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_item',
args: [
[parseInt(item_id)], self.ks_dashboard_data.ks_dashboard_id, self.env.ksGetParamsForItemFetch(self.item.id)
],
kwargs: { context: self.env.getContext() },
}).then(function(new_item_data) {
this.ks_dashboard_data.ks_item_data[item_id] = new_item_data[item_id];
this.item = this.ks_dashboard_data.ks_item_data[item_id] ;
this.__owl__.parent.component.ks_dashboard_data.ks_item_data[this.item.id] = new_item_data[item_id]
this.prepareKpiData()
this._update_view();
}.bind(this));
}
get isMobile() {
return isMobileOS();
}
_update_view(){
if(!this.kpi_data[1]){
if (this.field.ks_target_view === "Progress Bar" && this.field.ks_goal_enable) {
if(this.ks_kpi.el?.querySelector('#ks_progressbar')){
this.ks_kpi.el.querySelector('#ks_progressbar').value = this.target_deviation ? parseInt(this.target_deviation) : 0
}
}
if (this.field.ks_goal_enable) {
const targetDeviationElement = this.ks_kpi.el?.querySelector(".target_deviation");
if(targetDeviationElement)
targetDeviationElement.style.color = this.state.item_info_kpi1.target_arrow === 'up' ? 'green' : 'red';
}
var ks_valid_date_selection = ['l_day', 't_week', 't_month', 't_quarter', 't_year'];
if (this.field.ks_previous_period && String(this.state.item_info_kpi1.previous_period_data) && ks_valid_date_selection.indexOf(this.ks_date_filter_selection) >= 0) {
const preDeviationElement = this.ks_kpi.el?.querySelector(".pre_deviation");
if(preDeviationElement)
preDeviationElement.style.color = this.state.item_info_kpi1.pre_arrow === 'up' ? 'green' : 'red';
}
const ksTargetPreviousElement = this.ks_kpi.el?.querySelector('.ks_target_previous');
if (ksTargetPreviousElement?.children?.length !== 2)
ksTargetPreviousElement?.classList.add('justify-content-center');
}else{
if (this.field.ks_data_comparison === 'Sum') {
if(this.ks_kpi.el?.querySelector('.target_deviation')) {
const targetDeviationElement = this.ks_kpi.el.querySelector('.target_deviation');
if(targetDeviationElement)
targetDeviationElement.style.color = this.state.item_info_kpi2.ks_color;
}
if (this.field.ks_goal_enable && this.field.ks_target_view === "Progress Bar") {
const progressBarElement = this.ks_kpi.el.querySelector('#ks_progressbar');
const targetDeviation = (this.ks_target_deviation === Infinity || this.ks_target_deviation === -Infinity) ? 0 : this.ks_target_deviation;
if (!isNaN(targetDeviation) && targetDeviation >= 0 && targetDeviation <= 100) {
progressBarElement.value = parseInt(targetDeviation);
}
}
}
if (this.field.ks_data_comparison === 'Percentage') {
const targetDeviationElement = this.ks_kpi.el?.querySelector('.target_deviation');
if(targetDeviationElement){
targetDeviationElement.style.color = this.state.item_info_kpi2.ks_color;
}
if (this.field.ks_goal_enable && this.field.ks_target_view === "Progress Bar") {
const progressBarElement = this.ks_kpi.el.querySelector('#ks_progressbar');
if (this.state.item_info_kpi2.count) {
const countValue = (this.count === Infinity || this.count === -Infinity) ? 0 : this.count;
progressBarElement.value = parseInt(countValue);
} else {
progressBarElement.value = 0;
}
}
}
}
const dashboardItem = this.ks_kpi.el.querySelector('.ks_dashboarditem_id');
dashboardItem.style.backgroundColor = this.ks_rgba_background_color;
dashboardItem.style.color = this.ks_rgba_font_color;
this.aiAudioRef.el?.addEventListener('ended', onAudioEnded)
}
ksSum(count_1, count_2, item_info, field, target_1, kpi_data) {
var self = this;
var count = count_1 + count_2;
if (field.ks_multiplier_active){
item_info['count'] = globalfunction._onKsGlobalFormatter(count* field.ks_multiplier, field.ks_data_format, field.ks_precision_digits);
item_info['count_tooltip'] = formatFloat(count * field.ks_multiplier,{digits:[0,field.ks_precision_digits]});
}else{
item_info['count'] = globalfunction._onKsGlobalFormatter(count, field.ks_data_format, field.ks_precision_digits);
item_info['count_tooltip'] = formatFloat(parseFloat(count), Float64Array, {digits:[0,field.ks_precision_digits]});
}
if (field.ks_multiplier_active){
count = count * field.ks_multiplier;
}
var ks_selection = field.ks_unit_selection;
if (ks_selection === 'monetary') {
var ks_currency_id = field.ks_currency_id[0];
var ks_data = globalfunction.ks_monetary(item_info['count'], ks_currency_id);
item_info['count'] = ks_data;
} else if (ks_selection === 'custom') {
var ks_field = field.ks_chart_unit;
item_info['count']= ks_field+" "+item_info['count']
}
item_info['target_enable'] = field.ks_goal_enable;
item_info['ks_color'] = (target_1 - count) > 0 ? "red" : "green";
item_info.pre_arrow = (target_1 - count) > 0 ? "down" : "up";
item_info['ks_comparison'] = true;
this.target_deviation = (target_1 - count) > 0 ? Math.round(((target_1 - count) / target_1) * 100) : Math.round((Math.abs((target_1 - count)) / target_1) * 100);
this.ks_target_deviation = Math.round((count / target_1) * 100);
if (this.target_deviation !== Infinity) item_info.target_deviation = formatInteger(this.target_deviation) + "%";
else {
item_info.target_deviation = this.target_deviation;
item_info.pre_arrow = false;
}
var target_progress_deviation = target_1 == 0 ? 0 : Math.round((count / target_1) * 100);
item_info.target_progress_deviation = formatInteger(target_progress_deviation) + "%";
return item_info
}
ksPercentage(count_1, count_2, field, item_info, target_1, kpi_data) {
if (field.ks_multiplier_active){
count_1 = count_1 * field.ks_multiplier;
count_2 = count_2 * field.ks_multiplier;
}
if (field.ks_data_format=="exact"){
var count = (count_1 / count_2) * 100;
}
else{
var count = parseInt((count_1 / count_2) * 100);
}
if (field.ks_multiplier_active){
count = count * field.ks_multiplier;
}
if (field.ks_data_format=='exact'){
item_info['count'] = count ? formatFloat(count, {digits: [0, 2]}) + "%" : "0%";
}
else{
item_info['count'] = count ? formatInteger(count) + "%" : "0%";
}
item_info['count_tooltip'] = count ? count + "%" : "0%";
item_info.target_progress_deviation = item_info['count']
target_1 = target_1 > 100 ? 100 : target_1;
item_info.target = target_1 + "%";
item_info.pre_arrow = (target_1 - count) > 0 ? "down" : "up";
item_info['ks_color'] = (target_1 - count) > 0 ? "red" : "green";
item_info['target_enable'] = field.ks_goal_enable;
item_info['ks_comparison'] = false;
item_info.target_deviation = item_info.target > 100 ? 100 : item_info.target;
this.count = Math.round(count) ? Math.round(count) :0
return item_info
}
prepareKpiData() {
// if(!kpi_data) return
var self = this;
var field = this.item;
this.ks_date_filter_selection = field.ks_date_filter_selection;
if (field.ks_date_filter_selection === "l_none") this.ks_date_filter_selection = self.ks_dashboard_data.ks_date_filter_selection;
var ks_valid_date_selection = ['l_day', 't_week', 't_month', 't_quarter', 't_year'];
var kpi_data = JSON.parse(field.ks_kpi_data);
var count_1 = kpi_data[0] ? kpi_data[0].record_data: undefined;
var count_2 = kpi_data[1] ? kpi_data[1].record_data : undefined;
var target_1 = kpi_data[0] ? kpi_data[0].target : undefined;
var target_view = field.ks_target_view,
pre_view = field.ks_prev_view;
this.ks_rgba_background_color = self._ks_get_rgba_format(field.ks_background_color);
var ks_rgba_button_color = self._ks_get_rgba_format(field.ks_button_color);
this.ks_rgba_font_color = self._ks_get_rgba_format(field.ks_font_color);
if (field.ks_goal_enable) {
var diffrence = 0.0
if(field.ks_multiplier_active){
diffrence = (count_1 * field.ks_multiplier) - target_1
}else{
diffrence = count_1 - target_1
}
var acheive = diffrence >= 0 ? true : false;
diffrence = Math.abs(diffrence);
var deviation = Math.round((diffrence / target_1) * 100)
if (deviation !== Infinity) deviation = deviation ? formatInteger(deviation) + '%' : 0 + '%';
}
if (field.ks_previous_period && ks_valid_date_selection.indexOf(this.ks_date_filter_selection) >= 0) {
var previous_period_data = kpi_data[0].previous_period;
var pre_diffrence = (count_1 - previous_period_data);
if (field.ks_multiplier_active){
var previous_period_data = kpi_data[0].previous_period * field.ks_multiplier;
var pre_diffrence = (count_1 * field.ks_multiplier - previous_period_data);
}
var pre_acheive = pre_diffrence > 0 ? true : false;
pre_diffrence = Math.abs(pre_diffrence);
var pre_deviation = previous_period_data ? formatInteger(parseInt((pre_diffrence / previous_period_data) * 100)) + '%' : "100%"
}
if (this.item.ks_info){
var ks_description = this.item.ks_info.replace?.(/\\n/g, '\n').split?.('\n');
var ks_description = ks_description.filter(element => element !== '')?.join?.(' ') ?? false
}else {
var ks_description = false;
}
this.item['ksIsDashboardManager'] = self.ks_dashboard_data.ks_dashboard_manager;
this.item['ksIsUser'] = true;
// if (this.item.ks_tv_play){
// this.item['ksIsUser'] = false;
// }
var ks_icon_url;
if (field.ks_icon_select == "Custom") {
if (field.ks_icon[0]) {
ks_icon_url = 'data:image/' + (self.file_type_magic_word[field.ks_icon[0]] || 'png') + ';base64,' + field.ks_icon;
} else {
ks_icon_url = false;
}
}
var target_progress_deviation = String(Math.round((count_1 / target_1) * 100));
if(field.ks_multiplier_active){
var target_progress_deviation = String(Math.round(((count_1 * field.ks_multiplier) / target_1) * 100));
}
var ks_rgba_icon_color = self._ks_get_rgba_format(field.ks_default_icon_color)
var item_info = {
item: this.item,
id: field.id,
count_1: globalfunction.ksNumFormatter(kpi_data[0]['record_data'], 1),
count_1_tooltip: kpi_data[0]['record_data'],
count_2: kpi_data[1] ? String(kpi_data[1]['record_data']) : false,
name: field.name ? field.name : field.ks_model_id.data.display_name,
target_progress_deviation:target_progress_deviation,
icon_select: field.ks_icon_select,
default_icon: field.ks_default_icon,
icon_color: ks_rgba_icon_color,
target_deviation: deviation,
target_arrow: acheive ? 'up' : 'down',
ks_enable_goal: field.ks_goal_enable,
ks_previous_period: ks_valid_date_selection.indexOf(this.ks_date_filter_selection) >= 0 ? field.ks_previous_period : false,
target: globalfunction.ksNumFormatter(target_1, 1),
previous_period_data: previous_period_data,
pre_deviation: pre_deviation,
pre_arrow: pre_acheive ? 'up' : 'down',
target_view: field.ks_target_view,
pre_view: field.ks_prev_view,
ks_dashboard_list: self.ks_dashboard_data.ks_dashboard_list,
ks_icon_url: ks_icon_url,
ks_rgba_button_color:ks_rgba_button_color,
ks_info: ks_description,
}
this.previous_period_data = previous_period_data
this.target_deviation = parseInt(item_info.target_progress_deviation) ? parseInt(item_info.target_progress_deviation) : "0"
// if (item_info.target_deviation === Infinity) item_info.target_arrow = false;
item_info.target_progress_deviation = parseInt(item_info.target_progress_deviation) ? formatInteger(parseInt(item_info.target_progress_deviation)) : "0"
if (field.ks_multiplier_active){
item_info['count_1'] = globalfunction._onKsGlobalFormatter(kpi_data[0]['record_data'] * field.ks_multiplier, field.ks_data_format, field.ks_precision_digits);
item_info['count_1_tooltip'] = kpi_data[0]['record_data'] * field.ks_multiplier
}else{
item_info['count_1'] = globalfunction._onKsGlobalFormatter(kpi_data[0]['record_data'], field.ks_data_format, field.ks_precision_digits);
}
if (kpi_data[0].target){
item_info['target'] = globalfunction._onKsGlobalFormatter(kpi_data[0].target, field.ks_data_format, field.ks_precision_digits);
}
if (field.ks_unit){
if (field.ks_multiplier_active){
var ks_record_count = kpi_data[0]['record_data'] * field.ks_multiplier
}else{
var ks_record_count = kpi_data[0]['record_data']
}
var ks_selection = field.ks_unit_selection;
if (ks_selection === 'monetary') {
var ks_currency_id = field.ks_currency_id;
var ks_data = globalfunction._onKsGlobalFormatter(ks_record_count, field.ks_data_format, field.ks_precision_digits);
ks_data = globalfunction.ks_monetary(ks_data, ks_currency_id);
item_info['count_1'] = ks_data;
} else if (ks_selection === 'custom') {
var ks_field = field.ks_chart_unit;
item_info['count_1']= ks_field+" "+globalfunction._onKsGlobalFormatter(ks_record_count, field.ks_data_format, field.ks_precision_digits);
}else {
item_info['count_1']= globalfunction._onKsGlobalFormatter(ks_record_count, field.ks_data_format, field.ks_precision_digits);
}
}
this.kpi_data = kpi_data
this.field = field
this.state.item_info_kpi1 = this.state.item_info_kpi3 = item_info
if (kpi_data[1]) {
switch (this.field.ks_data_comparison) {
case "None":
if (field.ks_multiplier_active){
var count_tooltip = String(count_1 * field.ks_multiplier) + "/" + String(count_2 * field.ks_multiplier);
var count = String(globalfunction.ksNumFormatter(count_1 * field.ks_multiplier, 1)) + "/" + String(globalfunction.ksNumFormatter(count_2 * field.ks_multiplier, 1));
var data1 = globalfunction._onKsGlobalFormatter(count_1 * field.ks_multiplier, field.ks_data_format, field.ks_precision_digits);
var data2 = globalfunction._onKsGlobalFormatter(count_2 * field.ks_multiplier, field.ks_data_format, field.ks_precision_digits);
if (field.ks_unit){
var ks_selection = field.ks_unit_selection;
if (ks_selection === 'monetary') {
var ks_currency_id = field.ks_currency_id;
data1 = globalfunction.ks_monetary(data1, ks_currency_id);
data2 = globalfunction.ks_monetary(data2, ks_currency_id)
item_info['count'] = data1+"/"+data2;
} else if (ks_selection === 'custom') {
var ks_field = field.ks_chart_unit;
data1= ks_field+" "+data1
data2= ks_field+" "+data2
item_info['count']= data1+"/"+data2
}
}else {
item_info['count']=String(globalfunction._onKsGlobalFormatter(count_1*field.ks_multiplier, field.ks_data_format, field.ks_precision_digits)) + "/" + String(globalfunction._onKsGlobalFormatter(count_2*field.ks_multiplier, field.ks_data_format, field.ks_precision_digits));
}
}else{
var count_tooltip = String(count_1) + "/" + String(count_2);
var count = String(globalfunction.ksNumFormatter(count_1, 1)) + "/" + String(globalfunction.ksNumFormatter(count_2, 1));
var data1 = globalfunction._onKsGlobalFormatter(count_1 , field.ks_data_format, field.ks_precision_digits);
var data2 = globalfunction._onKsGlobalFormatter(count_2 , field.ks_data_format, field.ks_precision_digits);
if (field.ks_unit){
var ks_selection = field.ks_unit_selection;
if (ks_selection === 'monetary') {
var ks_currency_id = field.ks_currency_id;
data1 = globalfunction.ks_monetary(data1, ks_currency_id);
data2 = globalfunction.ks_monetary(data2, ks_currency_id)
item_info['count'] = data1+"/"+data2;
} else{
var ks_field = field.ks_chart_unit;
data1= ks_field+" "+data1
data2= ks_field+" "+data2
item_info['count']= data1+"/"+data2
}
}else {
item_info['count']=String(globalfunction._onKsGlobalFormatter(count_1, field.ks_data_format, field.ks_precision_digits)) + "/" + String(globalfunction._onKsGlobalFormatter(count_2, field.ks_data_format, field.ks_precision_digits));
}
}
item_info['count_tooltip'] = count_tooltip;
item_info['target_enable'] = false;
this.state.item_info_kpi2 = item_info
break;
case "Sum":
this.state.item_info_kpi2 = this.ksSum(count_1, count_2, item_info, field, target_1, kpi_data);
break;
case "Percentage":
this.state.item_info_kpi2 = this.ksPercentage(count_1, count_2, field, item_info, target_1, kpi_data);
break;
case "Ratio":
var gcd = self.ks_get_gcd(Math.round(count_1), Math.round(count_2));
if (this.item.ks_data_format == 'exact'){
if (count_1 && count_2) {
item_info['count_tooltip'] = count_1 / gcd + ":" + count_2 / gcd;
item_info['count'] = formatFloat(count_1 / gcd, Float64Array,{digits: [0, field.ks_precision_digits]}) + ":" + formatFloat(count_2 / gcd, Float64Array, {digits: [0, field.ks_precision_digits]});
} else {
item_info['count_tooltip'] = count_1 + ":" + count_2;
item_info['count'] = count_1 + ":" + count_2
}
}else{
if (count_1 && count_2) {
item_info['count_tooltip'] = count_1 / gcd + ":" + count_2 / gcd;
item_info['count'] = globalfunction.ksNumFormatter(count_1 / gcd, 1) + ":" + globalfunction.ksNumFormatter(count_2 / gcd, 1);
}else {
item_info['count_tooltip'] = (count_1) + ":" + count_2;
item_info['count'] = globalfunction.ksNumFormatter(count_1, 1) + ":" + globalfunction.ksNumFormatter(count_2, 1);
}
}
item_info['target_enable'] = false;
this.state.item_info_kpi2 = item_info
break;
}
}
}
_ks_get_rgba_format(val){
var rgba = val.split(',')[0].match(/[A-Za-z0-9]{2}/g);
rgba = rgba.map(function(v) {
return parseInt(v, 16)
}).join(",");
return "rgba(" + rgba + "," + val.split(',')[1] + ")";
}
get previewclass(){
var ks_valid_date_selection = ['l_day', 't_week', 't_month', 't_quarter', 't_year'];
if (this.field.ks_previous_period && String(this.state.item_info_kpi1.previous_period_data) && ks_valid_date_selection.indexOf(this.ks_date_filter_selection) >= 0 &&
(this.field.ks_goal_enable && this.field.ks_target_view === "Progress Bar")){
return 'ks_previous_period'
}else{ return ''}
}
ks_get_gcd(a, b) {
return (b == 0) ? a : this.ks_get_gcd(b, a % b);
}
};
Ksdashboardkpiview.props = {
item: { type: Object, optional:true},
dashboard_data: { type: Object, optional:true},
ksdatefilter :{type :String, optional:true},
pre_defined_filter:{type: Object, optional:true},
custom_filter :{type:Object, optional:true},
itemsToUpdateList : { type: Array, optional: true },
ks_speak:{type:Function , optional:true},
hideButtons: { type: Number, optional: true },
on_dialog: { type: Boolean, optional: true },
generate_dialog: { type: Boolean, optional: true },
onItemClick: { type: Function },
};
Ksdashboardkpiview.template = "Ksdashboardkpiview";
Ksdashboardkpiview.components = { KsItemButton };

View File

@@ -0,0 +1,381 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="Ksdashboardkpiview">
<t t-if="props.item.ks_model_id_2 == false and (props.item.ks_target_view == 'Number' || !props.item.ks_goal_enable)">
<t t-call="ks_kpi_template"/>
</t>
<t t-elif="props.item.ks_model_id_2 == false and (props.item.ks_target_view == 'Progress Bar' and props.item.ks_goal_enable)">
<t t-call="ks_kpi_template_3"/>
</t>
<t t-else="">
<t t-call="ks_kpi_template_2"/>
</t>
</t>
<t t-name="ks_kpi_template">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-class="'ks_item_click grid-stack-item ks_tile_carousel '+ previewclass + reviewclass" t-att-id="state.item_info_kpi1.item.id" t-ref="ks_kpi">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_kpi_temp_1"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_kpi_temp_1"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-class="'ks_item_click grid-stack-item ks_tile_carousel '+ previewclass + reviewclass" t-att-id="state.item_info_kpi1.item.id" t-ref="ks_kpi">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_kpi_temp_1"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_kpi_temp_1"/>
</t>
</div>
</t>
</t>
<t t-name="ks_kpi_template_3">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-class="'ks_item_click grid-stack-item ks_tile_carousel template-3-checking ' + reviewclass" t-att-id="state.item_info_kpi3.item.id" t-ref="ks_kpi">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_kpi_temp_3"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_kpi_temp_3"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-class="'ks_item_click grid-stack-item ks_tile_carousel template-3-checking ' + reviewclass" t-att-id="state.item_info_kpi3.item.id" t-ref="ks_kpi">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_kpi_temp_3"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_kpi_temp_3"/>
</t>
</div>
</t>
</t>
<t t-name="ks_kpi_template_2">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-class="'ks_item_click grid-stack-item ks_tile_carousel'+ reviewclass" t-att-id="state.item_info_kpi2.item.id" t-ref="ks_kpi">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_kpi_temp_2"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_kpi_temp_2"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-class="'ks_item_click grid-stack-item ks_tile_carousel '+ reviewclass" t-att-id="state.item_info_kpi2.item.id" t-ref="ks_kpi">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_kpi_temp_2"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_kpi_temp_2"/>
</t>
</div>
</t>
</t>
<t t-name="ks_kpi_temp_1">
<div t-att-class="ks_ai_analysis? classname + ' ks_ai_chart_body ks_ai_dashboard_item ks_explain_ai_view': classname"
t-att-title="state.item_info_kpi1.item.ks_info" t-att-id="state.item_info_kpi1.item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<div class="ks_dashboard_icon_l5 ks_dashboard_icon_color_picker dashboard-item-icon img-bg">
<t t-if="state.item_info_kpi1.icon_select=='Custom'">
<t t-if="state.item_info_kpi1.ks_icon_url">
<img t-att-src="state.item_info_kpi1.ks_icon_url"/>
</t>
</t>
<t t-elif="state.item_info_kpi1.icon_select=='Default'">
<span t-att-style="'color:'+ state.item_info_kpi1.icon_color + ';'"
t-att-class="'fa fa-' + state.item_info_kpi1.default_icon + ' fa-4x'"/>
</t>
</div>
<div class="flex-container">
<div class="ks_dashboard_item_main_body_l5 ks_kpi_main_body">
<div class="ks_dashboard_kpi_count_preview" t-att-title="state.item_info_kpi1.count_1_tooltip">
<t t-esc="state.item_info_kpi1.count_1"/>
</div>
</div>
<t t-if="state.item_info_kpi1.ks_previous_period">
<div class="var-prev" style="text-align: center;">
<div style="color: rgba(0, 0, 0, 0.61);">
<span>vs Prev</span>
</div>
<div>
<span class="pre_deviation " style="font-size:medium;">
<t t-esc="state.item_info_kpi1.pre_deviation"/>
<i t-att-class="'fa fa-arrow-'+ pre_arrow"/>
</span>
</div>
</div>
</t>
</div>
<div class="ks_dashboard_kpi_name_preview dashboard-item-name mb-3" t-att-title="state.item_info_kpi1.name">
<t t-esc="state.item_info_kpi1.name"/>
</div>
<div class="d-flex ks_target_previous dashboard-item-compare">
<t t-if="state.item_info_kpi1.ks_enable_goal">
<div class="w-100 d-flex justify-content-between align-items-center px-2">
<div style="color: rgba(0, 0, 0, 0.61); text-align:center;">
<span>vs Target</span>
</div>
<div>
<span class="target_deviation" style="font-size : medium;">
<t t-esc="state.item_info_kpi1.target_deviation"/>
<t t-if="state.item_info_kpi1.target_arrow">
<i t-att-class="'fa fa-arrow-'+ state.item_info_kpi1.target_arrow"/>
</t>
</span>
</div>
</div>
</t>
</div>
<!-- for ai dashboard-->
<img src="/ks_dashboard_ninja/static/images/selected.svg" class="ks_img_display d-none" width="30"/>
<div class="select-btn d-none">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.0001 18.3332C14.5834 18.3332 18.3334 14.5832 18.3334 9.99984C18.3334 5.4165 14.5834 1.6665 10.0001 1.6665C5.41675 1.6665 1.66675 5.4165 1.66675 9.99984C1.66675 14.5832 5.41675 18.3332 10.0001 18.3332Z"
stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.45825 9.99993L8.81659 12.3583L13.5416 7.6416" stroke="" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<!---->
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l6'" itemRootRef="this.ks_kpi"/>
</div>
</t>
<t t-name="ks_kpi_temp_2">
<div t-att-id="state.item_info_kpi2.id"
t-att-class="ks_ai_analysis? classname + ' ks_ai_chart_body ks_ai_dashboard_item ks_explain_ai_view': classname"
t-att-title="state.item_info_kpi2.item.ks_info" t-on-click.stop="() => this.props.onItemClick(item.id)">
<div class="ks_dashboard_icon_l5 ks_dashboard_icon_color_picker dashboard-item-icon img-bg">
<t t-if="state.item_info_kpi2.icon_select=='Custom'">
<t t-if="state.item_info_kpi2.ks_icon_url">
<img t-att-src="state.item_info_kpi2.ks_icon_url"/>
</t>
</t>
<t t-elif="state.item_info_kpi2.icon_select=='Default'">
<span t-att-style="'color:'+ state.item_info_kpi2.icon_color + ';'"
t-att-class="'fa fa-' + state.item_info_kpi2.default_icon + ' fa-4x'"/>
</t>
</div>
<div class="ks_dashboard_item_main_body_l5 ks_kpi_main_body">
<div class="ks_dashboard_kpi_count_preview dashboard-item-data" t-att-title="state.item_info_kpi2.count_tooltip">
<span>
<t t-esc="state.item_info_kpi2.count"/>
<t t-if="state.item_info_kpi2.target_view =='Progress Bar' and state.item_info_kpi2.target_enable">/
<t t-esc="state.item_info_kpi2.target"/>
</t>
</span>
</div>
</div>
<div class="ks_dashboard_kpi_name_preview dashboard-item-name mb-3">
<t t-esc="state.item_info_kpi2.name"/>
</div>
<t t-if="state.item_info_kpi2.ks_enable_goal and state.item_info_kpi2.target_enable">
<div>
<t t-if="state.item_info_kpi2.target_deviation and state.item_info_kpi2.target_view =='Number'">
<div class="dashboard-item-compare">
<div>
<span class="ks_kpi_target_grey">vs Target</span>
</div>
<div>
<span class="target_deviation">
<t t-esc="state.item_info_kpi2.target_deviation"/>
<t t-if="state.item_info_kpi2.pre_arrow">
<i t-att-class="'fa fa-arrow-'+ state.item_info_kpi2.pre_arrow"/>
</t>
</span>
</div>
</div>
</t>
<t t-if="state.item_info_kpi2.target_progress_deviation and state.item_info_kpi2.target_view =='Progress Bar'">
<div class="text-center ks_progress dashboard-item-compare">
<div class="w-100 text-start">
<progress id="ks_progressbar" value="0" max="100"/>
</div>
<div class="text-center">
<t t-esc="state.item_info_kpi2.target_progress_deviation"/>
</div>
</div>
</t>
</div>
</t>
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l6'" itemRootRef="this.ks_kpi" />
</div>
</t>
<t t-name="ks_kpi_temp_3">
<div t-att-id="state.item_info_kpi3.id"
t-att-class="ks_ai_analysis? classname + ' ks_ai_chart_body ks_ai_dashboard_item ks_explain_ai_view': classname"
t-att-title="state.item_info_kpi3.item.ks_info" t-on-click.stop="() => this.props.onItemClick(item.id)">
<div class="ks_dashboard_icon_l5 ks_dashboard_icon_color_picker dashboard-item-icon img-bg">
<t t-if="state.item_info_kpi3.icon_select=='Custom'">
<t t-if="state.item_info_kpi3.ks_icon_url">
<img t-att-src="state.item_info_kpi3.ks_icon_url"/>
</t>
</t>
<t t-elif="state.item_info_kpi3.icon_select=='Default'">
<span t-att-style="'color:'+ state.item_info_kpi3.icon_color + ';'"
t-att-class="'fa fa-' + state.item_info_kpi3.default_icon + ' fa-4x'"/>
</t>
</div>
<div class="flex-container">
<div class="ks_dashboard_item_main_body_l5 ks_kpi_main_body dashboard-item-data">
<div class="ks_dashboard_kpi_count_preview" t-att-title="count_1_tooltip">
<span class="ks_count">
<t t-esc="state.item_info_kpi3.count_1"/>
</span>
/
<span>
<t t-esc="state.item_info_kpi3.target"/>
</span>
</div>
</div>
<t t-if="item.ks_previous_period &amp;&amp; previous_period_data">
<div class="text-center var-prev">
<div>
<span>vs Prev</span>
</div>
<div class="d-flex flex-column">
<t t-esc="state.item_info_kpi3.previous_period_data"/>
<span class="pre_deviation">
<t t-esc="state.item_info_kpi3.pre_deviation"/>
<i t-att-class="'fa fa-arrow-'+ state.item_info_kpi3.pre_arrow"/>
</span>
</div>
</div>
</t>
</div>
<div class="ks_dashboard_kpi_name_preview dashboard-item-name mb-3">
<t t-esc="state.item_info_kpi3.name"/>
</div>
<div class="text-center ks_progress dashboard-item-compare">
<div class="w-100 text-start">
<progress id="ks_progressbar" value="0" max="100"/>
</div>
<div class="text-center">
<t t-esc="state.item_info_kpi3.target_progress_deviation"/>%
</div>
</div>
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l6'" itemRootRef="this.ks_kpi"/>
</div>
</t>
</templates>

View File

@@ -0,0 +1,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.Ksdashboardlistview">
<div class="grid-stack-item ks_tile_carousel ks_dashboarditem_id" t-att-id="item_id" t-ref="ks_list_view">
<div class="grid-stack-item-content ks_list_view_container ks_dashboarditem_chart_container ks_dashboard_item_hover card border-0"
t-att-title="ks_info" t-att-id="item_id">
<t t-if="!env.isExplainDashboardWithAi">
<div class="p-3 py-3 d-flex flex-row align-items-center justify-content-between">
<div class="d-flex align-items-center w-50">
<h6 class="m-0 font-weight-bold h3 mr-3 ks_list_view_heading text-capitalize text-start " t-att-title="ks_chart_title">
<t t-esc="ks_chart_title"/>
</h6>
<t t-if="ks_breadcrumb.length >= 1">
<nav class="ks_breadcrumb">
<ul>
<li class="d-none" t-att-id="'item'+'_'+'-1'">
<span t-att-data-sequence = '-1' t-att-data-item-id="item_id" t-on-click="ksOnDrillUp">
<t t-esc="ks_chart_title"/>
</span>
</li>
<t t-foreach="ks_breadcrumb" t-as="chart_breadcrumb" t-key="chart_breadcrumb_index">
<li class="d-none" t-att-id="chart_breadcrumb['name'] + '_' + chart_breadcrumb_index">
<span t-att-data-sequence="chart_breadcrumb_index" t-att-data-item-id="item_id" t-on-click="ksOnDrillUp">
<t t-esc="chart_breadcrumb['name']"/>
</span>
</li>
</t>
</ul>
</nav>
</t>
</div>
<!-- for ai dashboard-->
<img src="/ks_dashboard_ninja/static/images/selected.svg" class="ks_img_display d-none" width="30"/>
<div class="select-btn d-none">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.0001 18.3332C14.5834 18.3332 18.3334 14.5832 18.3334 9.99984C18.3334 5.4165 14.5834 1.6665 10.0001 1.6665C5.41675 1.6665 1.66675 5.4165 1.66675 9.99984C1.66675 14.5832 5.41675 18.3332 10.0001 18.3332Z"
stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.45825 9.99993L8.81659 12.3583L13.5416 7.6416" stroke="" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<!-- -->
<KsItemButton item_data="this.item" itemRootRef="ks_list_view"/>
<t t-if="ksIsDashboardManager and props.hideButtons">
</t>
</div>
</t>
<t t-if="ks_ai_analysis">
<div class="explain-ai pt-3">
<div class="container">
<div class="row ks_ai_explain_body">
<div class="col-xl-6 col-12">
<div class="charts-sec">
<h6 class="m-0 font-weight-bold h3 mr-3 ks_list_view_heading text-capitalize text-start text-black" t-att-title="ks_chart_title">
<t t-esc="ks_chart_title"/>
</h6>
<div name="ks_list_div" class="card-body table-responsive ks_list_card_body ks_list_item_table ks_list_explain">
<t t-call="ks_dashboard_ninja.ks_list_view_table"/>
</div>
<div class="d-flex justify-content-end">
<div class="ks_pager_name">
<t t-if="ks_pager" t-call="ks_pager_template"/>
</div>
</div>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<!-- <div class="ks_ai_explanation">-->
<p><t t-esc="ks_ai_analysis_1"/></p>
<p><t t-esc="ks_ai_analysis_2"/></p>
<!-- </div>-->
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div name="ks_list_div" class="card-body table-responsive ks_list_item_table">
<t t-call="ks_dashboard_ninja.ks_list_view_table"/>
</div>
<div class="d-flex justify-content-end">
<div class="ks_pager_name">
<t t-if="ks_pager" t-call="ks_pager_template"/>
</div>
</div>
</t>
</div>
</div>
</t>
<t t-name="ks_dashboard_ninja.ks_list_view_tmpl">
<t t-foreach="state.list_view_data['data_rows']" t-as="table_row" t-key="table_row_index">
<tr class="ks_tr" t-att-data-record-id="table_row['id']" t-att-data-domain="table_row['domain']"
t-att-data-item-Id="item_id"
t-att-data-sequence="table_row['sequence']" t-att-data-last_seq="table_row['last_seq']">
<t t-set="ks_rec_count" t-value="0"/>
<t t-foreach="table_row['data']" t-as="row_data" t-key="row_data_index">
<t t-if="table_row['ks_column_type']?.[ks_rec_count]=='html'">
<td class="ks_list_canvas_click">
<t t-out="markup(row_data)"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
<!-- TODO: No need of multiple if else , can be deduced to logic without if else , nedd to change the flow for this -->
<t t-elif="['many2one', 'selection'].includes(state.list_view_data['fields_type']?.[ks_rec_count])">
<td class="ks_list_canvas_click cursor-pointer"
t-on-click="(ev) => this.onChartCanvasClick(ev, row_data_index, row_data, state.list_view_data['fields_type']?.[ks_rec_count] )">
<t t-out="row_data ? row_data[1] : ''"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
<t t-elif="table_row['ks_column_type']?.[ks_rec_count] == 'boolean'">
<td class="ks_list_canvas_click" t-on-click="onChartCanvasClick">
<t t-out="row_data"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
<t t-else="">
<td class="ks_list_canvas_click" t-on-click="onChartCanvasClick">
<t t-esc="row_data !== false ? row_data : ''"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
</t>
<td class="ks_info">
<t t-if="ks_show_records">
<div id="ks_item_info" t-att-data-model="state.list_view_data['model']"
t-att-data-list-type="state.list_view_data['list_view_type']"
t-att-data-groupby="state.list_view_data['groupby']"
t-att-data-record-id="table_row['id']" t-att-data-item-id="item_id"
t-att-data-list-view-type="tmpl_list_type"
t-att-data-domain="table_row['domain']"
t-on-click="ksOnListItemInfoClick">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none">
<path d="M9.16675 1.66663H7.50008C3.33341 1.66663 1.66675 3.33329 1.66675 7.49996V12.5C1.66675 16.6666 3.33341 18.3333 7.50008 18.3333H12.5001C16.6667 18.3333 18.3334 16.6666 18.3334 12.5V10.8333" stroke="#241C1D" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.3666 2.51663L6.7999 9.0833C6.5499 9.3333 6.2999 9.82497 6.2499 10.1833L5.89157 12.6916C5.75823 13.6 6.3999 14.2333 7.30823 14.1083L9.81657 13.75C10.1666 13.7 10.6582 13.45 10.9166 13.2L17.4832 6.6333C18.6166 5.49997 19.1499 4.1833 17.4832 2.51663C15.8166 0.849966 14.4999 1.3833 13.3666 2.51663Z" stroke="#241C1D" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.425 3.45837C12.9834 5.45004 14.5417 7.00837 16.5417 7.57504" stroke="#241C1D" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</t>
</td>
</tr>
</t>
</t>
<t t-name="ks_dashboard_ninja.ks_list_view_table">
<t t-if="state.list_view_data &amp;&amp; state.list_view_data.label &amp;&amp; state.list_view_data.label.length">
<table id="ksListViewTable" class="table table-hover ks_list_view_layout_1"
t-att-data-model="state.list_view_data['model']">
<thead>
<t t-call="ks_list_view_header"/>
</thead>
<tbody class="ks_table_body">
<t t-call="ks_dashboard_ninja.ks_list_view_tmpl"/>
</tbody>
</table>
</t>
<t t-else="">
<div class="d-flex justify-content-center align-items-center h-100">
No Data Present
</div>
</t>
</t>
<t t-name="ks_list_view_header">
<tr>
<t t-foreach="state.list_view_data['label']" t-as="table_header" t-key="table_header_index">
<th>
<t t-esc="table_header"/>
</th>
</t>
<th/>
</tr>
</t>
<div t-name="ks_pager_template" class="ks_pager">
<span class="ks_counter">
<span class="ks_value">
<t t-esc="count"/>
</span>
</span>
<span class="btn-group" aria-atomic="true" t-att-data-next_offset="intial_count"
t-att-data-prev-offset="offset">
<button type="button"
class="fa fa-chevron-left btn ks_load_previous ks_event_offer_list"
t-att-data-item-id="item_id" title="Previous" t-on-click="ksLoadPreviousRecords"/>
<button type="button" class="fa fa-chevron-right btn ks_load_next"
t-att-data-item-id="item_id" title="Next" t-on-click="ksLoadMoreRecords"/>
</span>
</div>
</templates>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.ks_new_list_view_tmpl">
<t t-foreach="list_view_data['data_rows']" t-as="table_row" t-key="table_row_index">
<tr class="ks_tr" t-att-data-record-id="table_row['id']" t-att-data-domain="table_row['domain']"
t-att-data-item-Id="item_id"
t-att-data-sequence="table_row['sequence']" t-att-data-last_seq="table_row['last_seq']">
<t t-set="ks_rec_count" t-value="0"/>
<t t-foreach="table_row['data']" t-as="row_data" t-key="row_data_index">
<t t-if="table_row['ks_column_type'][ks_rec_count]=='html'">
<td class="ks_list_canvas_click">
<t t-out="markup(row_data)"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
<!-- TODO: No need of multiple if else , can be deduced to logic without if else , nedd to change the flow for this -->
<t t-elif="['many2one', 'selection'].includes(state.list_view_data['fields_type']?.[ks_rec_count])">
<td class="ks_list_canvas_click cursor-pointer"
t-on-click="(ev) => self.onChartCanvasClick(ev, row_data_index, row_data, state.list_view_data['fields_type'][ks_rec_count] )">
<!-- <t t-out="row_data"/>-->
<t t-raw="row_data ? row_data[1] : ''"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
<t t-elif="table_row['ks_column_type'][ks_rec_count] == 'boolean'">
<td class="ks_list_canvas_click" t-on-click="onChartCanvasClick">
<t t-out="row_data"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
<t t-else="">
<td class="ks_list_canvas_click" t-on-click="(ev)=>self.onChartCanvasClick(ev)">
<t t-esc="row_data !== false ? row_data : ''"/>
</td>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
</t>
<td class="ks_info">
<t t-if="ks_show_records ">
<div id="ks_item_info" t-att-data-model="list_view_data['model']"
t-att-data-list-type="list_view_data['list_view_type']"
t-att-data-groupby="list_view_data['groupby']"
t-att-data-record-id="table_row['id']" t-att-data-item-id="item_id"
t-att-data-list-view-type="tmpl_list_type"
t-att-data-domain="table_row['domain']"
t-on-click="ksOnListItemInfoClick">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 20 20" fill="none">
<path d="M9.16675 1.66663H7.50008C3.33341 1.66663 1.66675 3.33329 1.66675 7.49996V12.5C1.66675 16.6666 3.33341 18.3333 7.50008 18.3333H12.5001C16.6667 18.3333 18.3334 16.6666 18.3334 12.5V10.8333" stroke="#241C1D" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.3666 2.51663L6.7999 9.0833C6.5499 9.3333 6.2999 9.82497 6.2499 10.1833L5.89157 12.6916C5.75823 13.6 6.3999 14.2333 7.30823 14.1083L9.81657 13.75C10.1666 13.7 10.6582 13.45 10.9166 13.2L17.4832 6.6333C18.6166 5.49997 19.1499 4.1833 17.4832 2.51663C15.8166 0.849966 14.4999 1.3833 13.3666 2.51663Z" stroke="#241C1D" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.425 3.45837C12.9834 5.45004 14.5417 7.00837 16.5417 7.57504" stroke="#241C1D" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</t>
</td>
</tr>
</t>
</t>
<t t-name="ks_dashboard_ninja.ks_new_list_view_table">
<t t-if="list_view_data">
<table id="ksListViewTable" class="table table-hover ks_list_view_layout_1"
t-att-data-model="list_view_data['model']">
<thead>
<t t-call="ks_new_list_view_header"/>
</thead>
<tbody class="ks_table_body">
<t t-call="ks_dashboard_ninja.ks_new_list_view_tmpl"/>
</tbody>
</table>
</t>
<t t-else="">
<div class="d-flex justify-content-center align-items-center h-100">
No Data Present
</div>
</t>
</t>
<t t-name="ks_new_list_view_header">
<tr>
<t t-foreach="list_view_data['label']" t-as="table_header" t-key="table_header_index">
<th>
<t t-esc="table_header"/>
</th>
</t>
<th/>
</tr>
</t>
</templates>

View File

@@ -0,0 +1,556 @@
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { Component, onRendered, onWillStart, useState, onPatched, useChildSubEnv, useSubEnv,
onMounted, onWillRender, useRef, useEffect, onWillUnmount } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
import { useService, useBus } from "@web/core/utils/hooks";
import { localization } from "@web/core/l10n/localization";
import { session } from "@web/session";
import { download } from "@web/core/network/download";
import { useChildRef } from "@web/core/utils/hooks";
import { BlockUI } from "@web/core/ui/block_ui";
import { WebClient } from "@web/webclient/webclient";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { patch } from "@web/core/utils/patch";
import { isMobileOS } from "@web/core/browser/feature_detection";
import { loadBundle } from '@web/core/assets';
import { FormViewDialog} from '@web/views/view_dialogs/form_view_dialog';
import { renderToElement } from "@web/core/utils/render";
import { convert_data_to_utc, eraseAllCookies } from '@ks_dashboard_ninja/js/ks_global_functions'
import { KschatwithAI } from '@ks_dashboard_ninja/components/chatwithAI/ks_chat';
import { DateTimePicker } from "@web/core/datetime/datetime_picker";
import { DateTimeInput } from "@web/core/datetime/datetime_input";
const { DateTime } = luxon;
import {formatDate,formatDateTime} from "@web/core/l10n/dates";
import {parseDateTime,parseDate,} from "@web/core/l10n/dates";
import { KsHeader } from '@ks_dashboard_ninja/components/Header/Header'
import { KsItems } from "@ks_dashboard_ninja/components/ks_items/ks_items";
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
import { debounce } from "@bus/workers/websocket_worker_utils";
export class KsDashboardNinja extends Component {
setup() {
let self = this;
this.actionService = useService("action");
this.uiService = useService("ui");
this.dialogService = useService("dialog");
this.notification = useService("notification");
this._rpc = rpc
this.dialogService = useService("dialog");
this.user = user
this.headerRootRef = useChildRef();
this.gridStackRootRef = useChildRef();
// debugger;
this.header = this.headerRootRef
this.footer = useRef("ks_dashboard_footer");
this.main_body = useRef("ks_main_body");
this.reload_menu_option = {
reload:this.props.action.context.ks_reload_menu,
menu_id: this.props.action.context.ks_menu_id
};
this.ks_mode = 'active';
this.action_manager = parent;
this.name = "ks_dashboard";
this.ksIsDashboardManager = false;
this.dashboard_domain_data = {}
this.recent_searches = useState({ value : []})
this.file_type_magic_word = {
'/': 'jpg',
'R': 'gif',
'i': 'png',
'P': 'svg+xml',
};
this.ksAllowItemClick = true;
//Dn Filters Iitialization
this.date_format = localization.dateFormat
this.datetime_format = localization.dateTimeFormat
this.ks_date_filter_data;
// To make sure date filter show date in specific order.
this.ks_date_filter_selection_order = ['l_day', 't_week', 't_month', 't_quarter','t_year',
'td_week','td_month','td_quarter', 'td_year','n_day','n_week', 'n_month', 'n_quarter', 'n_year',
'ls_day','ls_week', 'ls_month', 'ls_quarter', 'ls_year', 'l_week', 'l_month', 'l_quarter', 'l_year',
'ls_past_until_now', 'ls_pastwithout_now','n_future_starting_now', 'n_futurestarting_tomorrow',
'l_custom'
];
this.ks_dashboard_id = this.props.action.params.ks_dashboard_id;
this.isReloadOnFirstCreate = this.props.action?.params?.isReloadOnFirstCreate ? true : false
this.explain_ai_whole = true;
this.explain_ai_whole = this.props.action.params.explain_ai_whole === undefined ? true : false;
var ks_fav_filter_remove = $('.ks_dn_fav_filters').find('.ks_fav_filters_checked');
if (ks_fav_filter_remove.length){
var ks_fav_filter = ks_fav_filter_remove.attr('fav-name');
ks_fav_filter_remove.removeClass('ks_fav_filters_checked');
this.ks_remove_favourite_filter(ks_fav_filter);
}
this.gridstackConfig = {};
this.grid = true;
this.state = useState({
ks_dashboard_name: '',
ks_multi_layout: false,
ks_dash_name: '',
ks_dashboard_manager :false,
date_selection_data: {},
date_selection_order :[],
ks_show_create_layout_option : true,
ks_show_layout :false,
ks_selected_board_id:false,
ks_child_boards:false,
ks_dashboard_data:{},
ks_dn_pre_defined_filters:[],
ks_dashboard_items:[],
update:false,
ksDateFilterSelection :'none',
pre_defined_filter :{},
ksDateFilterStartDate: DateTime.now(),
ksDateFilterEndDate:DateTime.now(),
stateToggle: false,
dialog_header: true,
should_loading: true,
itemsToUpdateList: []
})
this.ksChartColorOptions = ['default', 'cool', 'warm', 'neon'];
this.ksDateFilterSelection = false;
this.ksDateFilterStartDate = false;
this.ksDateFilterEndDate = false;
this.ksUpdateDashboard = {};
$("head").append('<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">');
if(this.props.action.context.ks_reload_menu){
this.trigger_up('reload_menu_data', { keep_open: true, scroll_to_bottom: true});
}
var context = {
ksDateFilterSelection: this.ksDateFilterSelection,
ksDateFilterStartDate: this.ksDateFilterStartDate,
ksDateFilterEndDate: this.ksDateFilterEndDate,
}
this.dn_state = {}
this.dn_state['user_context']=context
this.isFavFilter = false;
this.activeFavFilterName = "FavouriteFilter"
this.ks_speeches = [];
onWillStart(this.willStart);
onWillRender(this.dashboard_mount);
//
this.modelsListForAutoUpdate = []
this.env.services.bus_service.addChannel('ks_notification')
useEffect(() => {
this.ks_fetch_items_data();
}, ()=>[]
)
useEffect( () => {
if(this.ks_dashboard_data.ks_set_interval){
this._debounced_items_for_update = debounce(this.update_dashboard_items.bind(this), parseInt(this.ks_dashboard_data.ks_set_interval))
this.env.services.bus_service.subscribe('Update: Dashboard Items', (detail) => {
this.modelsListForAutoUpdate.push(detail.model)
this._debounced_items_for_update();});
return this.env.services.bus_service.removeEventListener('Update: Dashboard Items', (detail) => {
this.modelsListForAutoUpdate.push(detail.model)
this._debounced_items_for_update();})
}
}, ()=>[])
onMounted(() => {
$(".modal-footer").find("button").addClass('d-none')
let filterFacetCountTag = this.header.el.querySelector('.filters-amount')
if (document.body.classList.contains("ks_body_class")){
document.querySelector(".ks-zoom-view")?.classList.add("d-none")
}
});
onRendered(()=>{
if(this.isReloadOnFirstCreate){
this.isReloadOnFirstCreate = false;
if(this.props.action.params && this.props.action.params.isReloadOnFirstCreate)
this.props.action.params.isReloadOnFirstCreate = false
this.env.services.menu.reload();
this.notification.add(_t('New Dashboard is successfully created'),{
title:_t("New Dashboard"),
type: 'success',
});
}
})
// TODO: make items independent , graph component,etc should not be dependent on some other components,
// TODO: presently we are using many functions in env , can be removed to make component fully independent
useChildSubEnv({
getContext : this.getContext.bind(this),
ksGetParamsForItemFetch: (item_id) => this.ksGetParamsForItemFetch(item_id),
ks_update_date_filter_state: (selected_filter_id, start_date, end_date) => this.ks_update_date_filter_state(selected_filter_id, start_date, end_date),
update_dashboard_filters: (domainsToUpdate) => this.update_dashboard_filters(domainsToUpdate),
replace_dashboard_filters: (domain_data) => this.replace_dashboard_filters(domain_data),
onKsEditLayoutClick: () => this.onKsEditLayoutClick(),
isMobile: this.isMobile,
update_dashboard_mode: this.update_dashboard_mode.bind(this),
get mode(){ return self.ks_mode},
getDashboardDataAsObj: this.getDashboardDataAsObj.bind(this),
gridStackRootRef: this.gridStackRootRef,
isExplainDashboardWithAi: this.isExplainDashboardWithAi,
})
onWillUnmount(()=>{
document.querySelector(".ks-zoom-view")?.classList.remove("d-none")
})
}
get isMobile(){
return isMobileOS();
}
get isExplainDashboardWithAi(){
return this.props.action.params.explain_ai_whole ? true : false;
}
willStart(){
let self = this;
let def;
if (this.reload_menu_option.reload && this.reload_menu_option.menu_id) {
def = this.getParent().actionService.ksDnReloadMenu(this.reload_menu_option.menu_id);
}
this.setFilterObjFromCookies();
return $.when(def, loadBundle("ks_dashboard_ninja.ks_dashboard_lib")).then(function() {
return self.ks_fetch_data().then(function(){
});
});
}
update_dashboard_items(){
let itemsToUpdateList = []
this.modelsListForAutoUpdate?.forEach( (model_name) => {
itemsToUpdateList.push( ...this.ks_dashboard_data.ks_model_item_relation[model_name] || [])
})
this.state.itemsToUpdateList = JSON.parse(JSON.stringify(itemsToUpdateList))
this.modelsListForAutoUpdate = []
}
getDashboardDataAsObj(params){ // params - list of parameters to be get in child components , empty list return whole data
let data = {}
params.forEach( (param) => {
if(this.ks_dashboard_data[param]){
data[param] = this.ks_dashboard_data[param]
}
})
return Object.keys(data).length ? data : this.ks_dashboard_data;
}
setFilterObjFromCookies(){
let dashboard_domain_data_from_cky = getObjectFromCookie('Filter' + this.ks_dashboard_id)
this.dashboard_domain_data = dashboard_domain_data_from_cky ?? {}
}
makeCtxFromCookies(){
let dateFilterCookieObj = getObjectFromCookie('FilterDateData' + this.ks_dashboard_id);
let context = {}
if (dateFilterCookieObj != null){
context = {
ksDateFilterSelection: dateFilterCookieObj.filter_selection ?? false,
ksDateFilterStartDate: false, ksDateFilterEndDate: false,
}
if(context.ksDateFilterSelection === 'l_custom'){
try {
context.ksDateFilterStartDate = dateFilterCookieObj.date_range.start_date
context.ksDateFilterEndDate = dateFilterCookieObj.date_range.end_date
} catch (error) {
context.ksDateFilterStartDate = false
context.ksDateFilterEndDate = false
eraseCookie('FilterDateData' + this.ks_dashboard_id);
}
}
let ctx_to_be_added = { ...session.user_context, ...{ allowed_company_ids: this.env.services.company.activeCompanyIds }}
return Object.assign(context, ctx_to_be_added);
}
let ctx_to_be_added = { ...session.user_context, ...{ allowed_company_ids: this.env.services.company.activeCompanyIds }}
return Object.assign(context, ctx_to_be_added);
}
ks_fetch_data(){
let self = this;
return rpc("/web/dataset/call_kw",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_dashboard_data',
args: [ self.ks_dashboard_id ],
kwargs : { context: self.makeCtxFromCookies() },
}).then(function(result) {
if(self.props?.action?.params?.dashboard_data){
self.ks_dashboard_data = JSON.parse(JSON.stringify(self.props.action.params.dashboard_data))
}
else{
self.ks_dashboard_data = result;
}
self.ks_dashboard_item_length = self.ks_dashboard_data.ks_dashboard_items_ids.length
self.ks_dashboard_data.ks_ai_explain_dash = self.props.action.params.explainWithAi ? true : false
self.ks_dashboard_data['ks_dashboard_id'] = self.props.action.params.ks_dashboard_id
self.ks_dashboard_data['context'] = self.getContext()
self.ks_dashboard_data['ks_ai_dashboard'] = false
self.ks_favourite_filters = JSON.parse(JSON.stringify(self.ks_dashboard_data.ks_dashboard_favourite_filter))
});
}
async ks_fetch_items_data(){
let self = this;
let items_promises = []
let item_ids = self.ks_dashboard_data.ks_dashboard_items_ids
await Promise.all(
item_ids.map(async (item_id) => {
const itemData = await rpc("/web/dataset/call_kw",{
model: "ks_dashboard_ninja.board",
method: "ks_fetch_item",
args : [[item_id], self.ks_dashboard_id, self.ksGetParamsForItemFetch(item_id)],
kwargs: { context: self.getContext() }
})
if(itemData[item_id].ks_list_view_data){
itemData[item_id].ks_list_view_data = convert_data_to_utc(itemData[item_id].ks_list_view_data)
}
Object.assign(self.ks_dashboard_data.ks_item_data, itemData)
})
);
this.state.should_loading = false
}
getContext() {
let context = {
ksDateFilterSelection: this.ks_dashboard_data?.ks_date_filter_selection ?? false,
ksDateFilterStartDate: this.ks_dashboard_data?.ks_date_filter_selection === 'l_custom' ? this.ks_dashboard_data.ks_dashboard_start_date : false,
ksDateFilterEndDate: this.ks_dashboard_data?.ks_date_filter_selection === 'l_custom' ? this.ks_dashboard_data.ks_dashboard_end_date : false,
}
var ctx_to_be_added = { ...session.user_context, ...{ allowed_company_ids: this.env.services.company.activeCompanyIds }}
return Object.assign(context, ctx_to_be_added);
}
update_dashboard_mode(mode){
this.ks_mode = mode;
}
ksGetParamsForItemFetch(item_id) {
let self = this;
let model1 = self.ks_dashboard_data.ks_item_model_relation[item_id][0];
let model2 = self.ks_dashboard_data.ks_item_model_relation[item_id][1];
var ks_domain_1 = self.dashboard_domain_data[model1] && self.dashboard_domain_data[model1].domain || [];
var ks_domain_2 = self.dashboard_domain_data[model2] && self.dashboard_domain_data[model2].domain || [];
return {
ks_domain_1: ks_domain_1,
ks_domain_2: ks_domain_2,
}
}
update_dashboard_filters(domainsToUpdate){ // params:- domainsToUpdate ( Should be object with key as model_name )
let itemsToUpdateList = []
Object.keys(domainsToUpdate).forEach( (model) => {
this.dashboard_domain_data[model] ??= {}
this.dashboard_domain_data[model] = domainsToUpdate[model]
itemsToUpdateList.push( ...this.ks_dashboard_data.ks_model_item_relation[model] || [])
if(!Object.keys(this.dashboard_domain_data[model].sub_domains ?? {}).length){
delete this.dashboard_domain_data[model]
}
})
if(!Object.keys(this.dashboard_domain_data).length){
eraseAllCookies(this.ks_dashboard_id, ['PFilter', 'PFilterDataObj', 'Filter', 'CFilter'])
}
this.state.itemsToUpdateList = JSON.parse(JSON.stringify(itemsToUpdateList))
}
replace_dashboard_filters(domain_data){ // params:- domain_data ( Should be object with key as model_name, replace the existing domain obj )
let itemsToUpdateList = []
Object.keys(this.dashboard_domain_data).forEach( (model) => {
itemsToUpdateList.push( ...this.ks_dashboard_data.ks_model_item_relation[model] || [] )
})
Object.keys(domain_data).forEach( (model) => {
itemsToUpdateList.push( ...this.ks_dashboard_data.ks_model_item_relation[model] || [] )
})
this.dashboard_domain_data = domain_data
this.state.itemsToUpdateList = JSON.parse(JSON.stringify(itemsToUpdateList))
this.state.ksDateFilterSelection = 'none'
}
get ks_get_current_dashboard_data(){
return this.ks_dashboard_data
}
dashboard_mount(){
var self = this;
var items = Object.values(self.ks_dashboard_data.ks_item_data)
}
ksRenderDashboard(){
let self = this;
if (self.ks_dashboard_data.ks_child_boards) self.ks_dashboard_data.name = this.ks_dashboard_data.ks_child_boards[self.ks_dashboard_data.ks_selected_board_id][0];
self.ksRenderDashboardMainContent();
}
ksRenderDashboardMainContent(){
var self = this;
if (isMobileOS() && $('#ks_dn_layout_button :first-child').length > 0) {
$('.ks_am_element').append($('#ks_dn_layout_button :first-child')[0].innerText);
$(self.header.el).find("#ks_dn_layout_button").addClass("ks_hide");
}
if (Object.keys(self.ks_dashboard_data.ks_item_data).length) {
// todo implement below mentioned function
self._renderDateFilterDatePicker();
$(self.header.el).find('.ks_dashboard_link').removeClass("ks_hide");
} else if (!Object.keys(self.ks_dashboard_data.ks_item_data).length) {
$(self.header.el).find('.ks_dashboard_link').addClass("ks_hide");
}
}
_renderDateFilterDatePicker() {
var self = this;
$(self.header.el).find(".ks_dashboard_link").removeClass("ks_hide");
self._KsGetDateValues();
}
loadDashboardData(date = false){
var self = this;
$(self.header.el).find(".custom_date_filter_section").removeClass("ks_hide");
$(self.header.el).find(".ks_dashboard_top_settings").addClass("d-none");
$(self.header.el).find("#favFilterMain").addClass("ks_hide");
$(self.header.el).find(".filters_section").addClass("ks_hide");
}
_KsGetDateValues() {
var self = this;
var date_filter_selected = self.ks_dashboard_data.ks_date_filter_selection;
if (self.ksDateFilterSelection == 'l_none'){
var date_filter_selected = self.ksDateFilterSelection;
}
$(self.header.el).find('#' + date_filter_selected).addClass("ks_date_filter_selected global-filter");
if (self.ks_dashboard_data.ks_date_filter_selection === 'l_custom') {
var ks_end_date = self.ks_dashboard_data.ks_dashboard_end_date;
var ks_start_date = self.ks_dashboard_data.ks_dashboard_start_date;
var start_date = parseDateTime(ks_start_date, self.datetime_format)
var end_date = parseDateTime(ks_end_date, self.datetime_format)
self.state.ksDateFilterStartDate = start_date
self.state.ksDateFilterEndDate = end_date
$(self.header.el).find('.ks_date_input_fields').removeClass("ks_hide");
$(self.header.el).find('.ks_date_filter_dropdown').addClass("ks_btn_first_child_radius");
} else if (self.ks_dashboard_data.ks_date_filter_selection !== 'l_custom') {
$(self.header.el).find('.ks_date_input_fields').addClass("ks_hide");
}
}
ks_update_date_filter_state(selected_filter_id, ksDateFilterStartDate, ksDateFilterEndDate){
let self = this;
self.ks_dashboard_data.ks_date_filter_selection = selected_filter_id;
self.ks_dashboard_data.ks_dashboard_start_date = ksDateFilterStartDate
self.ks_dashboard_data.ks_dashboard_end_date = ksDateFilterEndDate
self.state.itemsToUpdateList = JSON.parse(JSON.stringify(this.ks_dashboard_data.ks_dashboard_items_ids))
}
ks_dashboard_item_action(e){
this.ksAllowItemClick = false;
}
stoppropagation(ev){
ev.stopPropagation();
this.ksAllowItemClick = false;
}
ksOnLayoutSelection(layout_id){
var self = this;
var selected_layout_name = this.ks_dashboard_data.ks_child_boards[layout_id][0];
var selected_layout_grid_config = this.ks_dashboard_data.ks_child_boards[layout_id][1];
this.gridstackConfig = JSON.parse(selected_layout_grid_config);
Object.entries(this.gridstackConfig).forEach((x,y)=>{
self.grid.update($(self.main_body.el).find(".grid-stack-item[gs-id=" + x[0] + "]")[0],{ x:x[1]['x'], y:x[1]['y'], w:x[1]['w'], h:x[1]['h'],autoPosition:false});
});
}
_onKsSaveLayoutClick(){
var self = this;
self._ksRenderActiveMode();
}
_ksRenderActiveMode(){
var self = this
if (self.grid && $('.grid-stack').data('gridstack')) {
$('.grid-stack').data('gridstack').disable();
}
}
ks_remove_update_interval(){
var self = this;
if (self.ksUpdateDashboard) {
Object.values(self.ksUpdateDashboard).forEach(function(itemInterval) {
clearInterval(itemInterval);
});
self.ksUpdateDashboard = {};
}
}
onKsEditLayoutClick(e) {
var self = this;
self.ksAllowItemClick = false;
self._ksRenderEditMode();
}
_ksRenderEditMode(){
let self = this;
self.ks_remove_update_interval();
}
}
KsDashboardNinja.components = { KsHeader, KsItems };
KsDashboardNinja.template = "ks_dashboard_ninja.KsDashboardNinjaHeader"
registry.category("actions").add("ks_dashboard_ninja", KsDashboardNinja);
const ks_dn_webclient ={
async loadRouterState(...args) {
var self = this;
const sup = await super.loadRouterState(...args);
const ks_reload_menu = async (id) => {
this.menuService.reload().then(() => {
self.menuService.selectMenu(id);
});
}
this.actionService.ksDnReloadMenu = ks_reload_menu;
return sup;
},
};
patch(WebClient.prototype, ks_dn_webclient)

View File

@@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.KsDashboardNinjaHeader" owl="1">
<!-- screen -->
<KsHeader dashboard_data="ks_get_current_dashboard_data" mode="ks_dashboard_data.ks_dashboard_manager ? 'manager' : 'user'"
headerRootRef="headerRootRef"/>
<main class="main-box" t-ref="ks_main_body">
<t t-call="ks_dashboard_ninja.ks_main_body_container"/>
</main>
</t>
<!-- Dashboard Main Body Container -->
<t t-name="ks_dashboard_ninja.ks_main_body_container" owl="1">
<div class="ks_dashboard_main_content explain-ai-main-content" t-att-class="ks_dashboard_item_length === 0 ? ' h-0' : ''" t-ref="ks_main_body">
<t t-if="ks_dashboard_item_length != 0">
<t t-if="!state.should_loading">
<t t-call="ks_dashboard_item_template"/>
</t>
<div t-else="state.should_loading">
<t t-call="ks_loading_template"/>
</div>
</t>
</div>
<t t-if="ks_dashboard_item_length === 0">
<t t-call="ksNoItemView"/>
</t>
</t>
<t t-name="ks_loading_template">
<div t-att-class="'o_blockUI fixed-top d-flex justify-content-center align-items-center flex-column vh-100'">
<div class="o_spinner mb-4">
<img src="/ks_dashboard_ninja/static/images/loader.gif" alt="Loading..."/>
</div>
<div class="o_message text-center px-4">Loading...</div>
</div>
</t>
<!-- Empty Dashboard View Layout -->
<t t-name="ksNoItemView">
<section class="overview-sec-dasboards container-fluid h-100">
<div t-if="!filteredDashboardsInfo" class="d-flex flex-column justify-content-center align-items-center h-100 no-data">
<div>
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/no-data.png" alt="no-data-files" class="img-fluid" loading="lazy"/>
</div>
<h3 class="mb-0">
No Data Found!!
</h3>
<p class="mb-3">Your Personal Dashboard is Empty!</p>
<button class="dash-btn-red d-flex align-items-center" t-if="state.ks_dashboard_manager &amp;&amp; !isMobile" t-on-click="onAddItemTypeClick">
<span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="transparent" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 12H16" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 16V8" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="ms-2">
Create New Chart
</span>
</button>
</div>
</section>
</t>
<t t-name="ksNoItemChartView">
<div class="overview-sec-dasboards container-fluid h-100 graph_text">
<div t-if="!filteredDashboardsInfo" class="d-flex flex-column justify-content-center align-items-center h-100 no-data">
<div>
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/no-data.png" alt="no-data-files" class="img-fluid" loading="lazy"/>
</div>
<h3 class="mb-0">
No Data Found!!
</h3>
</div>
</div>
</t>
<!--Item Layouts : -->
<t t-name="ks_dashboard_item_template">
<KsItems ks_data="ks_get_current_dashboard_data" gridStackRootRef="gridStackRootRef" domain_data_obj="dashboard_domain_data"
date_filter_data="state.ksDateFilterSelection" itemsToUpdateList="state.itemsToUpdateList"/>
</t>
<t t-name="ksQuickEditButtonContainer">
<div class="ks_dashboard_quick_edit_action grid-stack-item" t-att-gs-x="grid.x"
t-att-gs-y="grid.y" t-att-gs-w="grid.w" t-att-gs-h="grid.h">
<button title="Quick Customize" data-bs-toggle="dropdown"
class="ks_dashboard_item_action btn dropdown-toggle btn-xs o-no-caret btn"
type="button"
aria-expanded="true">
<i class="fa fa-cog"/>
</button>
<div role="menu" class="dropdown-menu ks-dropdown-menu ks_qe_dropdown_menu ">
</div>
</div>
</t>
<t t-name="ks_dn_layout_container_new">
<t t-if="ks_multi_layout">
<div class="ks_am_element dash-dd-2">
<button id="ks_dn_layout_button" class="o_dropdown_toggler_btn dashboard_name_bg dropdown-toggle"
data-bs-toggle="dropdown" aria-expanded="false">
<span>
<t t-esc="ks_child_boards[ks_selected_board_id][0]"/>
</span>
<span class="caret"/>
</button>
<ul id="ks_dashboard_layout_dropdown_container"
class="dropdown-menu ks_dashboard_custom_srollbar ks-dropdown-menu dropdown-max-height"
role="menu">
<t t-foreach="Object.keys(ks_child_boards)" t-as="layout_id" t-key="layout_id_index">
<li t-att-class="ks_selected_board_id === layout_id ? 'ks_dashboard_layout_event ks_layout_selected': 'ks_dashboard_layout_event'"
t-att-data-ks_layout_id="layout_id" t-on-click="(ev)=>self._ksOnDnLayoutMenuSelect(ev)">
<span class="df_selection_text">
<t t-esc="ks_child_boards[layout_id][0]"/>
</span>
</li>
</t>
</ul>
</div>
</t>
<t t-else="">
<span id="ks_dashboard_title_label" class="ks_am_element dash-dd-2">
<t t-esc="ks_dash_name"/>
</span>
</t>
</t>
<t t-name="no-data-view-without-button">
<div class="no-data-avilable">
<div class="d-flex align-items-center justify-content-center flex-column">
<div class="no-data-img">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/no-data.png" alt="no-data-available" loading="lazy"
class="img-fluid w-75"/>
</div>
<div class="title mb-1">
<t t-esc="header_text"/>
</div>
<p>
<t t-esc="body_text"/>
</p>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,222 @@
/** @odoo-module **/
import { Component, useState ,useEffect,onWillUpdateProps,useRef, onMounted, onWillUnmount} from "@odoo/owl";
import {globalfunction } from '@ks_dashboard_ninja/js/ks_global_functions';
import { loadBundle } from "@web/core/assets";
import { useService } from "@web/core/utils/hooks";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
import { _t } from "@web/core/l10n/translation";
import { onAudioEnded } from '@ks_dashboard_ninja/js/ks_global_functions';
import { isMobileOS } from "@web/core/browser/feature_detection";
import { KsItemButton } from '@ks_dashboard_ninja/components/chart_buttons/chart_buttons';
export class Ksdashboardtile extends Component{
file_type_magic_word= {'/': 'jpg','R': 'gif','i': 'png','P': 'svg+xml'}
setup(){
var self = this;
this._rpc = rpc
this.actionService = useService("action");
this.ks_tile = useRef('ks_tile');
this.ks_container_class = 'grid-stack-item';
this.aiAudioRef = useRef("aiAudioRef");
this.ks_inner_container_class = 'grid-stack-item-content';
this.state = useState({data_count:""})
this.item = this.props.item
this.file_type_magic_word= {'/': 'jpg','R': 'gif','i': 'png','P': 'svg+xml'}
this.ks_dashboard_data = this.props.dashboard_data
this.item.ksIsDashboardManager = this.props.dashboard_data.ks_dashboard_manager
this.item.ks_dashboard_list = this.props.dashboard_data.ks_dashboard_list
this.ks_ai_analysis = this.ks_dashboard_data.ks_ai_explain_dash
if (this.ks_ai_analysis){
this.ks_container_class = 'grid-stack-item ks_ai_explain_tile'
this.ks_inner_container_class = 'grid-stack-item-content ks_ai_dashboard_item'
}else{
this.ks_container_class = 'grid-stack-item'
this.ks_inner_container_class = 'encapsulated-kpi-tile grid-stack-item-content'
}
if (this.item.ks_ai_analysis && this.item.ks_ai_analysis){
var ks_analysis = this.item.ks_ai_analysis.split('ks_gap')
this.ks_ai_analysis_1 = ks_analysis[0]
this.ks_ai_analysis_2 = ks_analysis[1]
}
this.prepare_item();
var update_interval = this.props.dashboard_data.ks_set_interval
onWillUpdateProps(async(nextprops)=>{
if (nextprops.itemsToUpdateList.length){
if (nextprops.itemsToUpdateList?.includes(this.item.id)){
await this.ksFetchUpdateItem(this.item.id)
}
}
})
onMounted(()=>{
if (this.ks_ai_analysis){
$(this.ks_tile.el).find('.ks_dashboarditem_id').addClass('ks_ai_chart_body')
}
this.aiAudioRef.el?.addEventListener('ended', onAudioEnded)
})
onWillUnmount( () => {
this.aiAudioRef.el?.removeEventListener('ended', onAudioEnded)
})
}
get isMobile() {
return isMobileOS();;
}
ksFetchUpdateItem(item_id) {
var self = this;
return rpc("/web/dataset/call_kw/",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_item',
args: [
[parseInt(item_id)], self.ks_dashboard_data.ks_dashboard_id, self.env.ksGetParamsForItemFetch(self.item.id)
],
kwargs: { context: self.env.getContext() },
}).then(function(new_item_data) {
this.ks_dashboard_data.ks_item_data[item_id] = new_item_data[item_id];
this.item = this.ks_dashboard_data.ks_item_data[item_id] ;
this.__owl__.parent.component.ks_dashboard_data.ks_item_data[this.item.id] = new_item_data[item_id]
this.prepare_item()
}.bind(this));
}
ksStopClickPropagation(e){
this.ksAllowItemClick = false;
}
prepare_item() {
var self = this;
var ks_icon_url, item_view;
var ks_rgba_background_color, ks_rgba_font_color, ks_rgba_default_icon_color, ks_rgba_button_color;
var style_main_body, style_image_body_l2, style_domain_count_body, style_button_customize_body, style_button_delete_body;
if (this.item.ks_multiplier_active){
var ks_record_count = this.item.ks_record_count * this.item.ks_multiplier
if (this.item.ks_unit){
var ks_selection = this.item.ks_unit_selection;
if (ks_selection === 'monetary') {
var ks_currency_id = this.item.ks_currency_id;
var ks_data = globalfunction._onKsGlobalFormatter(ks_record_count, this.item.ks_data_format, this.item.ks_precision_digits);
ks_data = globalfunction.ks_monetary(ks_data, ks_currency_id);
var data_count = ks_data;
} else{
var ks_field = this.item.ks_chart_unit;
var data_count= ks_field+" "+globalfunction._onKsGlobalFormatter(ks_record_count, this.item.ks_data_format, this.item.ks_precision_digits);
}
}else {
var data_count= globalfunction._onKsGlobalFormatter(ks_record_count, this.item.ks_data_format, this.item.ks_precision_digits);
}
var count = ks_record_count;
}else{
var ks_record_count = this.item.ks_record_count
if (this.item.ks_unit){
var ks_selection = this.item.ks_unit_selection;
if (ks_selection === 'monetary') {
var ks_currency_id = this.item.ks_currency_id;
var ks_data = globalfunction._onKsGlobalFormatter(ks_record_count, this.item.ks_data_format, this.item.ks_precision_digits);
ks_data = globalfunction.ks_monetary(ks_data, ks_currency_id);
var data_count = ks_data;
} else{
var ks_field = this.item.ks_chart_unit;
var data_count= ks_field+" "+globalfunction._onKsGlobalFormatter(ks_record_count, this.item.ks_data_format, this.item.ks_precision_digits);
}
}else {
var data_count= globalfunction._onKsGlobalFormatter(ks_record_count, this.item.ks_data_format, this.item.ks_precision_digits);
}
var count = ks_record_count;
}
if (this.item.ks_icon_select == "Custom") {
if (this.item.ks_icon[0]) {
ks_icon_url = 'data:image/' + (self.file_type_magic_word[this.item.ks_icon[0]] || 'png') + ';base64,' + this.item.ks_icon;
} else {
ks_icon_url = false;
}
}
this.item.ksIsDashboardManager = self.ks_dashboard_data.ks_dashboard_manager;
this.item.ksIsUser = true;
this.ks_rgba_background_color = self._ks_get_rgba_format(this.item.ks_background_color);
this.ks_rgba_font_color = self._ks_get_rgba_format(this.item.ks_font_color);
this.ks_rgba_default_icon_color = self._ks_get_rgba_format(this.item.ks_default_icon_color);
this.ks_rgba_button_color = self._ks_get_rgba_format(this.item.ks_button_color);
if (this.item.ks_info){
var ks_description = this.item.ks_info.replace?.(/\\n/g, '\n').split?.('\n');
var ks_description = ks_description.filter(element => element !== '')?.join?.(' ') ?? false
}else {
var ks_description = false;
}
this.ks_icon_url = ks_icon_url
this.state.data_count = data_count
this.count = count
this.ks_info = ks_description
this.ks_dashboard_list= self.ks_dashboard_data.ks_dashboard_list
this.style_main_body = this._ksMainBodyStyle(this.ks_rgba_background_color, this.ks_rgba_font_color, this.item).background_style;
}
get style_image_body_l2(){
return this._ksMainBodyStyle(this.ks_rgba_background_color, this.ks_rgba_font_color, this.item).style_image_body_l2;
}
get style_main_body_l4(){
return "color : " + this.ks_rgba_font_color + ";border : solid;border-width : 1px;";
}
get style_image_body_l4(){
return this._ksMainBodyStyle(this.ks_rgba_background_color, this.ks_rgba_font_color, this.item).background_style;
}
_ks_get_rgba_format(val){
var rgba = val.split(',')[0].match(/[A-Za-z0-9]{2}/g);
rgba = rgba.map(function(v) {
return parseInt(v, 16)
}).join(",");
return "rgba(" + rgba + "," + val.split(',')[1] + ")";
}
_ksMainBodyStyle(ks_rgba_background_color, ks_rgba_font_color, tile){
var background_style = "background-color:" + ks_rgba_background_color + ";color : " + ks_rgba_font_color + ";";
var ks_rgba_dark_background_color_l2 = this._ks_get_rgba_format(this.ks_get_dark_color(tile.ks_background_color.split(',')[0], tile.ks_background_color.split(',')[1], -10));
var style_image_body_l2 = "background-color:" + ks_rgba_dark_background_color_l2 + ";";
return {
'background_style': background_style,
'style_image_body_l2': style_image_body_l2
};
}
ks_get_dark_color(color, opacity, percent) {
var num = parseInt(color.slice(1), 16),
amt = Math.round(2.55 * percent),
R = (num >> 16) + amt,
G = (num >> 8 & 0x00FF) + amt,
B = (num & 0x0000FF) + amt;
return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1) + "," + opacity;
}
};
Ksdashboardtile.props = {
item: { type: Object, optional:true},
dashboard_data: { type: Object, optional:true},
ksdatefilter : {type:String,optional:true},
pre_defined_filter :{type:Object, optional:true},
custom_filter :{type:Object, optional:true},
itemsToUpdateList :{ type: Array, optional:true },
ks_speak:{type:Function , optional:true},
hideButtons: { type: Number, optional: true },
on_dialog: { type: Boolean, optional: true },
generate_dialog: { type: Boolean, optional: true },
onItemClick: { type: Function },
};
Ksdashboardtile.template = "ksdashboardtile";
Ksdashboardtile.components = { KsItemButton };

View File

@@ -0,0 +1,592 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ksdashboardtile">
<t t-if="props.item.ks_layout == 'layout1'" t-call="ks_dashboard_item_layout1"/>
<t t-elif="props.item.ks_layout == 'layout2'" t-call="ks_dashboard_item_layout2"/>
<t t-elif="props.item.ks_layout == 'layout3'" t-call="ks_dashboard_item_layout3"/>
<t t-elif="props.item.ks_layout == 'layout4'" t-call="ks_dashboard_item_layout4"/>
<t t-elif="props.item.ks_layout == 'layout5'" t-call="ks_dashboard_item_layout5"/>
<t t-elif="props.item.ks_layout == 'layout6'" t-call="ks_dashboard_item_layout6"/>
<t t-else="" t-call="ks_dashboard_item_layout_default"/>
</t>
<t t-name="ks_dashboard_item_layout1">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-class="'ks_item_click ' + ks_container_class" t-att-id="item.id" t-ref="ks_tile">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_tile_layout_1"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_1"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-class="'ks_item_click ' + ks_container_class" t-att-id="item.id" t-ref="ks_tile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_tile_layout_1"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_1"/>
</t>
</div>
</t>
</t>
<t t-name="ks_tile_layout_1">
<div t-att-class="'ks_dashboarditem_id ks_tile_carousel ks_explain_ai_view encapsulated-tile-container ks_dashboard_item ks_dashboard_item_hover ' + ks_inner_container_class"
t-att-style="style_main_body"
t-att-title="item.ks_info"
t-att-id="item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<KsItemButton item_data="this.item" itemRootRef="ks_tile"/>
<!-- main-body-lay-out-1-->
<div class="ks_dashboard_item_main_body encapsulated-tile-1">
<div class="ks_dashboard_icon">
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
<img t-att-src="ks_icon_url"/>
</t>
</t>
<t t-elif="item.ks_icon_select=='Default'">
<span t-att-style="'color:'+ ks_rgba_default_icon_color + ';'"
t-att-class="'fa fa-' + item.ks_default_icon + ' fa-4x'"/>
</t>
<!--<img t-att-src="ks_icon_url"/>-->
</div>
<div class="ks_dashboard_item_info">
<div class="ks_dashboard_item_domain_count" t-att-title="count">
<t t-esc="state.data_count"/>
</div>
<div class="ks_dashboard_item_name" t-att-title="item.name">
<t t-esc="item.name"/>
</div>
</div>
</div>
<!-- main-body-lay-out-1-->
</div>
</t>
<t t-name="ks_dashboard_item_layout2">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_tile_layout_2"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24"
alt="voice-img" loading="lazy" class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_2"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-class="'ks_item_click ' + ks_container_class" t-att-id="item.id" t-ref="ks_tile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_tile_layout_2"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_2"/>
</t>
</div>
</t>
</t>
<t t-name="ks_tile_layout_2">
<div t-att-class="'ks_dashboarditem_id ks_tile_carousel encap-layout-2-box ks_explain_ai_view ks_dashboard_item_l2 ks_dashboard_item_hover ' + ks_inner_container_class"
t-att-style="style_main_body"
t-att-title="item.ks_info" t-att-id="item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<!-- main-body-lay-out-2-->
<div class="ks_dashboard_item_main_body_l2 ">
<div class="ks_dashboard_item_domain_count_l2 layout-2-count" t-att-title="count">
<t t-esc="state.data_count"/>
</div>
<div class="ks_dashboard_item_name_l2 layout-2-name" t-att-title="item.name">
<t t-esc="item.name"/>
</div>
</div>
<div class="ks_dashboard_icon_l2 layout-2-icon" t-att-style="style_image_body_l2">
<!--<img t-att-src="ks_icon_url"/>-->
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
<img t-att-src="ks_icon_url"/>
</t>
</t>
<t t-elif="item.ks_icon_select=='Default'">
<span t-att-style="'color:'+ ks_rgba_default_icon_color + ';'"
t-att-class="'fa fa-' + item.ks_default_icon"/>
</t>
</div>
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l6'" itemRootRef="ks_tile"/>
</div>
</t>
<t t-name="ks_dashboard_item_layout3">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_tile_layout_3"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_3"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_tile_layout_3"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_3"/>
</t>
</div>
</t>
</t>
<t t-name="ks_tile_layout_3">
<div t-att-class="'ks_dashboarditem_id ks_tile_carouse encapsulated-tile-container ks_explain_ai_view ks_dashboard_item ks_dashboard_item_hover '+ ks_inner_container_class"
t-att-style="'background-color: ' + ks_rgba_background_color + ' !important;'"
t-att-title="item.ks_info" t-att-id="item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<KsItemButton item_data="this.item" itemRootRef="ks_tile"/>
<!-- main-body-lay-out-3-->
<div class="ks_dashboard_item_main_body minimum-180 encapsulated-main-body">
<div class="ks_dashboard_icon_l3">
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
<img t-att-src="ks_icon_url"/>
</t>
</t>
<t t-elif="item.ks_icon_select=='Default'">
<span t-att-style="'color:'+ ks_rgba_default_icon_color + ';'"
t-att-class="'fa fa-' + item.ks_default_icon + ' fa-4x'"/>
</t>
</div>
<div class="ks_dashboard_item_info ks_dashboard_item_info_l3" t-att-style="'color:'+ ks_rgba_font_color + ' !important;'">
<div class="ks_dashboard_item_domain_count_l3" t-att-title="count" t-att-style="'color:'+ ks_rgba_font_color + ' !important;'">
<t t-esc="state.data_count"/>
</div>
<div class="ks_dashboard_item_name_l3" t-att-title="item.name">
<t t-esc="item.name"/>
</div>
</div>
</div>
<!-- main-body-lay-out-3-->
</div>
</t>
<t t-name="ks_dashboard_item_layout4">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_tile_layout_4"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_4"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_tile_layout_4"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_4"/>
</t>
</div>
</t>
</t>
<t t-name="ks_tile_layout_4">
<div t-att-class="'ks_dashboarditem_id ks_tile_carousel encapsulated-tile-container ks_explain_ai_view ks_dashboard_item_l2 ks_dashboard_item_hover '+ ks_inner_container_class"
t-att-style="'background-color: ' + ks_rgba_background_color + ' !important;'"
t-att-title="item.ks_info" t-att-id="item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<!-- main-body-lay-out-4-->
<div class="d-flex justify-content-between flex-column w-100">
<div class="ks_dashboard_icon_l4 layout-4-icon bg-white" t-att-style="style_image_body_l4">
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
<img t-att-src="ks_icon_url"/>
</t>
</t>
<t t-elif="item.ks_icon_select=='Default'">
<span t-att-style="'color:'+ ks_rgba_default_icon_color + ';'"
t-att-class="'fa fa-' + item.ks_default_icon + ' fa-4x'"/>
</t>
</div>
<div class="ks_dashboard_item_main_body_l2 layout-4-count ks_bg_white">
<div class="ks_dashboard_item_domain_count_l2 ms-0" t-att-title="count" t-att-style="'color: ' + ks_rgba_font_color + ' !important;'">
<t t-esc="state.data_count"/>
</div>
</div>
<div class="ks_dashboard_item_name_l2 layout-4-name ms-0" t-att-title="item.name" t-att-style="'color: ' + ks_rgba_font_color + ' !important;'">
<t t-esc="item.name"/>
</div>
</div>
<!-- main-body-lay-out-4-->
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l6'" itemRootRef="ks_tile"/>
</div>
</t>
<t t-name="ks_dashboard_item_layout5">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_tile_layout_5"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_5"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_tile_layout_5"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_5"/>
</t>
</div>
</t>
</t>
<t t-name="ks_tile_layout_5">
<div t-att-class="'ks_dashboarditem_id ks_tile_carousel encapsulated-tile-container ks_explain_ai_view ks_dashboard_item_l5 ks_dashboard_item_hover '+ ks_inner_container_class"
t-att-style="style_main_body"
t-att-title="item.ks_info" t-att-id="item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<!-- main-body-lay-out-5-->
<div class="ks_dashboard_icon_l5 dashboard-item-icon img-bg">
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
<img t-att-src="ks_icon_url"/>
</t>
</t>
<t t-elif="item.ks_icon_select=='Default'">
<span t-att-style="'color:'+ ks_rgba_default_icon_color + ';'"
t-att-class="'fa fa-' + item.ks_default_icon + ' fa-4x'"/>
</t>
</div>
<div class="ks_dashboard_item_main_body_l5 dashboard-item-data">
<div class="ks_dashboard_item_domain_count_l5" t-att-title="count">
<t t-esc="state.data_count"/>
</div>
</div>
<div class="ks_dashboard_item_name_l5 dashboard-item-name" t-att-title="item.name">
<t t-esc="item.name"/>
</div>
<!-- main-body-lay-out-5-->
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l2'" itemRootRef="ks_tile"/>
</div>
</t>
<t t-name="ks_dashboard_item_layout6">
<t t-if="env.inDialog">
<div class="explain-ai pt-3">
<div class="container">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<div class="row ks_ai_explain_body">
<t t-if="ks_ai_analysis">
<div class="col-xl-6 col-12">
<div class="charts-sec ks_explain_ai">
<t t-call="ks_tile_layout_6"/>
</div>
</div>
<div class="col-xl-6 col-12">
<div class="charts-data h-100">
<div class="charts-content-box">
<t t-call="ks_item_explanation"/>
</div>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<!-- <span class="fa fa-volume-up"/>-->
<audio t-ref="aiAudioRef"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_6"/>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-else="">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class" t-ref="ks_tile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_tile_layout_6"/>
<t t-call="ks_item_explanation"/>
</div>
</t>
<t t-else="">
<t t-call="ks_tile_layout_6"/>
</t>
</div>
</t>
</t>
<t t-name="ks_tile_layout_6">
<div t-att-class="'ks_dashboarditem_id ks_tile_carousel layout-6-box encapsulated-tile-container ks_explain_ai_view ks_dashboard_item_l2 ks_dashboard_item_hover '+ ks_inner_container_class"
t-att-style="style_main_body"
t-att-title="item.ks_info" t-att-id="item.id" t-on-click.stop="() => this.props.onItemClick(item.id)">
<!-- main-body-lay-out-6-->
<div class="d-flex flex-column w-100">
<div class="ks_dashboard_item_main_body_l2 layout-6-container">
<div class="ks_dashboard_icon_l2">
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
<img t-att-src="ks_icon_url"/>
</t>
</t>
<t t-elif="item.ks_icon_select=='Default'">
<span t-att-style="'color:'+ ks_rgba_default_icon_color + ';'"
t-att-class="'fa fa-' + item.ks_default_icon + ' fa-4x'"/>
</t>
</div>
<div class="ks_dashboard_item_domain_count_l2 layout-6-name ml-0" t-att-title="count">
<t t-esc="state.data_count"/>
</div>
</div>
<div class="ks_dashboard_item_name_l2 layout-6-tile-name" t-att-title="item.name">
<span class="ellipsis-content w-100"><t t-esc="item.name"/></span>
</div>
</div>
<!-- main-body-lay-out-6-->
<KsItemButton item_data="this.item" item_classes="'ks_dashboard_item_header_l6'" itemRootRef="ks_tile"/>
</div>
</t>
<t t-name="ks_dashboard_item_layout_default">
<div t-att-id="item.id" t-att-class="'ks_item_click ' + ks_container_class">
<div class="ks_dashboard_item ks_tile_carousel ks_explain_ai_view ks_dashboard_item_hover"
t-att-style="style_main_body">
<t t-if="item.ksIsDashboardManager and props.hideButtons and !props.on_dialog">
<div class="ks_dashboard_item_header ks_dashboard_item_header_hover">
<button type="button" title="Customize Item" class="ks_dashboard_item_customize">
<i class="fa fa-pencil"/>
</button>
<button type="button" title="Remove Item" class="ks_dashboard_item_delete">
<i class="fa fa-times"/>
</button>
</div>
</t>
<p>Layout Coming Soon</p>
</div>
</div>
</t>
<t t-name="ks_item_explanation">
<p><t t-esc="ks_ai_analysis_1"/></p>
<p><t t-esc="ks_ai_analysis_2"/></p>
<div class="voice-button" t-on-click="props.ks_speak" title="Text to speech">
<div class="voice-cricle">
<img src="/ks_dashboard_ninja/static/images/voice-cricle.svg" height="24" width="24" alt="voice-img" loading="lazy"
class="img-fluid"/>
</div>
<div class="comp-gif d-none">
<img src="/ks_dashboard_ninja/static/images/Comp.gif" alt="loading gif" height="24" width="24"
class="img-fluid"/>
</div>
<audio t-ref="aiAudioRef"/>
</div>
</t>
</templates>

View File

@@ -0,0 +1,298 @@
.encapsulated-tile-container {
padding: 12px 10px !important;
// border: none !important;
// tile-2
.layout-2-box {
padding: 0 !important;
.layout-2-count {
font-size: $font-24;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
margin-top: 20px;
margin-left: 10px;
}
.layout-2-icon {
border-bottom-left-radius: 16px;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 16px;
}
.layout-2-name {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 24px;
text-align: left;
margin-left: 10px;
margin-top: 4px;
margin-bottom: 14px;
}
}
.encapsulated-tile-1 {
display: flex;
flex-direction: column;
align-items: baseline;
.ks_dashboard_icon {
margin: 0 !important;
height: 80px;
width: 80px;
background: $color-white;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.ks_dashboard_item_info {
margin-left: 0 !important;
.ks_dashboard_item_domain_count {
font-family: Poppins;
font-size: $font-24;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
// color: $color-black;
margin-top: 8px;
}
.ks_dashboard_item_name {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 24px;
text-align: left;
// color: $color-black;
margin-top: 4px;
}
}
}
.ks_dashboard_item_button_container {
position: absolute;
right: 10px;
top: 10px;
}
// tile-3
.encapsulated-main-body {
display: flex;
flex-direction: column;
align-items: baseline;
height: 100%;
.ks_dashboard_icon_l3 {
position: absolute;
top: 0;
left: 0;
height: 80px;
overflow: hidden;
width: 80px;
background: $color-white;
border-bottom-right-radius: 100%;
display: flex;
justify-content: center;
align-items: center;
border-top-left-radius: 16px;
img {
width: 40px !important;
height: 40px !important;
position: relative;
right: 3px;
top: -5px;
}
span {
font-size: 31px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-67%, -59%);
}
}
.ks_dashboard_item_domain_count_l3 {
text-align: left;
font-size: $font-30;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
// color: $color-6789C6;
margin-bottom: 5px;
width: 100% !important;
}
.ks_dashboard_item_name_l3 {
text-align: left;
font-size: $font-16;
font-weight: $f-w-500;
line-height: 24px;
text-align: left;
width: 100% !important;
// color: $color-black;
}
.ks_dashboard_item_info_l3 {
flex: 1;
display: flex;
flex-direction: column;
align-items: baseline;
justify-content: end;
}
}
.layout-4-icon {
position: absolute;
bottom: 0;
right: 0;
height: 80px;
width: 80px;
border-top-left-radius: 100%;
border-bottom-right-radius: 16px;
// background-color: $color-white !important;
span {
font-size: 31px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-30%, -30%);
}
img {
width: 40px !important;
height: 40px !important;
position: relative;
right: -5px;
}
}
.layout-4-count {
font-size: $font-36;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
// color: $color-E7495E;
margin-left: 0;
background-color: transparent !important;
}
.layout-4-name {
font-size: $font-20;
font-weight: $f-w-500;
line-height: 30px;
text-align: left;
width: 70% !important;
// color: $color-black;
}
.layout-6-name {
font-size: $font-36;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
// color: $color-737791;
margin-bottom: 10px;
margin-left: 8px;
}
.layout-6-tile-name {
margin-left: 0;
min-height: 65px;
width: 100%;
background: $color-white;
padding: 16px;
font-size: $font-14;
font-weight: $f-w-500;
line-height: 18px;
text-align: left;
// color: $color-black;
display: flex;
align-items: center;
margin-bottom: 21px;
}
}
// layout-2-css
.encap-layout-2-box.encapsulated-kpi-tile.grid-stack-item-content {
padding: 0 !important;
display: flex;
flex-direction: column;
.layout-2-count {
font-size: $font-24;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
margin-top: 10px;
margin-left: 10px;
}
.layout-2-name {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 24px;
text-align: left;
margin-left: 10px;
margin-top: 4px;
margin-bottom: 14px;
}
.layout-2-icon {
border-bottom-left-radius: 16px;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 16px;
padding: 17px;
height: 100%;
width: 100%;
span {
font-size: 4rem !important;
}
}
}
.encapsulated-kpi-tile.layout-6-box.grid-stack-item-content {
padding: 0 !important;
.layout-6-container {
flex: 1;
width: 100% !important;
display: flex;
align-items: end;
.ks_dashboard_icon_l2 {
padding: 20px 0 10px 13px;
display: flex;
justify-content: flex-start;
width: fit-content;
img {
width: 40px !important;
height: 40px !important;
}
span {
font-size: 31px;
}
}
}
}

View File

@@ -0,0 +1,35 @@
/** @odoo-module */
import { Dialog } from "@web/core/dialog/dialog";
import { Component,useRef } from "@odoo/owl";
export class Todoeditdialog extends Component {
setup() {
}
_ks_click(ev){
this.props.confirm(event)
this.props.close()
}
}
Todoeditdialog.template = "Kseditdialog";
Todoeditdialog.props = {
close: Function,
confirm: { type: Function, optional: true },
ks_description: { type: String, optional: true },
}
Todoeditdialog.components = { Dialog };
export class addtododialog extends Component{
setup(){
}
ks_task_click(ev){
this.props.confirm(event)
this.props.close()
}
}
addtododialog.template = "Ksaddtaskdialog";
addtododialog.props = {
close: Function,
confirm: { type: Function, optional: true },
}
addtododialog.components = { Dialog };

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="Kseditdialog">
<Dialog size="'md'" title="'Edit Task'">
<div class="form-control form-input-box">
<input type='text' class='ks_description w-100' t-att-value="props.ks_description" placeholder='Task'>
</input>
</div>
<t t-set-slot="footer">
<button class=" dash-btn-red" t-on-click="_ks_click">Select</button>
<button class=" dash-default-btn" t-on-click="this.props.close">Close</button>
</t>
</Dialog>
</t>
<t t-name="Ksaddtaskdialog">
<Dialog size="'md'" title="'New Task'">
<div class="form-control form-input-box">
<input type='text' class='ks_section w-100' placeholder='Task'>
</input>
</div>
<t t-set-slot="footer">
<button class="dash-btn-red ks_create_task" t-on-click="ks_task_click">Select</button>
<button class="dash-default-btn" t-on-click="this.props.close">Close</button>
</t>
</Dialog>
</t>
</templates>

View File

@@ -0,0 +1,263 @@
/** @odoo-module **/
import { Component, onWillStart, useState ,onMounted, onWillRender,useRef,onWillPatch, onRendered } from "@odoo/owl";
import { globalfunction } from '@ks_dashboard_ninja/js/ks_global_functions';
import { loadBundle } from "@web/core/assets";
import { isMobileOS } from "@web/core/browser/feature_detection";
import { useService } from "@web/core/utils/hooks";
import { Todoeditdialog, addtododialog } from "@ks_dashboard_ninja/components/ks_dashboard_to_do_item/editdialog";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation";
import { KsItemButton } from '@ks_dashboard_ninja/components/chart_buttons/chart_buttons';
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class Ksdashboardtodo extends Component{
setup(){
super.setup();
this._rpc = rpc
this.dialogService = useService("dialog");
// this.mailChatService = useService("mail.chat_window");
// this.threadService = useService("mail.thread");
this.state = useState({to_do_view_data : ""})
this.todoRootRef = useRef("todoRootRef");
this.item = this.props.item
this.ks_dashboard_data = this.props.dashboard_data
this.item.ksIsDashboardManager = this.props.dashboard_data.ks_dashboard_manager
this.item.ks_dashboard_list = this.props.dashboard_data.ks_dashboard_list
this.prepare_item();
}
get isMobile() {
return isMobileOS();
}
ksFetchUpdateItem(item_id) {
var self = this;
return self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_fetch_item",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_item',
args: [
[parseInt(item_id)], self.ks_dashboard_data.ks_dashboard_id,{}
],
kwargs: { context: self.env.getContext() },
}).then(function(new_item_data) {
this.ks_dashboard_data.ks_item_data[item_id] = new_item_data[item_id];
this.item = this.ks_dashboard_data.ks_item_data[item_id] ;
this.prepare_item()
}.bind(this));
}
get ksIsDashboardManager(){
return this.ks_dashboard_data.ks_dashboard_manager;
}
get ksIsUser(){
return true;
}
get ks_dashboard_list(){
return this.ks_dashboard_data.ks_dashboard_list;
}
prepare_item() {
var self = this;
var item = self.item
self.ks_to_do_view_name = 'Test';
self.item_id = item.id;
self.list_to_do_data = JSON.parse(item.ks_to_do_data);
self.state.to_do_view_data = JSON.parse(item.ks_to_do_data);
self.ks_chart_title = item.name;
if (item.ks_info){
var ks_description = item.ks_info.replace?.(/\\n/g, '\n').split?.('\n');
var ks_description = ks_description.filter(element => element !== '')?.join?.(' ') ?? false
}else {
var ks_description = false;
}
self.ks_header_color = self._ks_get_rgba_format(item.ks_header_bg_color);
self.ks_font_color = self._ks_get_rgba_format(item.ks_font_color);
var ks_rgba_button_color = self._ks_get_rgba_format(item.ks_button_color);
self.ks_rgba_button_color = ks_rgba_button_color
self.ks_info = ks_description
self.ks_company = item.ks_company
}
_ks_get_rgba_format(val){
var rgba = val.split(',')[0].match(/[A-Za-z0-9]{2}/g);
rgba = rgba.map(function(v) {
return parseInt(v, 16)
}).join(",");
return "rgba(" + rgba + "," + val.split(',')[1] + ")";
}
_onKsEditTask(e){
var self = this;
var ks_description_id = e.currentTarget.dataset.contentId;
var ks_item_id = e.currentTarget.dataset.itemId;
var ks_section_id = e.currentTarget.dataset.sectionId;
var ks_description = $(e.currentTarget.parentElement.parentElement).find('.ks_description').attr('value');
var ks_result = this.dialogService.add(Todoeditdialog,{
ks_description :ks_description,
confirm: (event) => {
var content = $(event.currentTarget.parentElement.parentElement).find('.ks_description').val();
if (content.length === 0){
content = ks_description;
}
self.onSaveTask(content, parseInt(ks_description_id), parseInt(ks_item_id), parseInt(ks_section_id));
},
});
}
onSaveTask(content, ks_description_id, ks_item_id, ks_section_id){
var self = this;
rpc("/web/dataset/call_kw/ks_to.do.description/write",{
model: 'ks_to.do.description',
method: 'write',
args: [ks_description_id, {
"ks_description": content
}],
kwargs:{},
}).then(function() {
self.ksFetchUpdateItem(ks_item_id).then(function(){
$(".ks_li_tab[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_li_tab[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('show');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('show');
$(".header_add_btn[data-item-id=" + ks_item_id + "]").attr('data-section-id', ks_section_id);
});
});
}
_onKsDeleteContent(e){
var self = this;
var ks_description_id = e.currentTarget.dataset.contentId;
var ks_item_id = e.currentTarget.dataset.itemId;
var ks_section_id = e.currentTarget.dataset.sectionId;
this.dialogService.add(ConfirmationDialog, {
body: _t("Are you sure you want to remove this task?"),
confirm: () => {
self._rpc("/web/dataset/call_kw/ks_to.do.description/unlink",{
model: 'ks_to.do.description',
method: 'unlink',
args: [parseInt(ks_description_id)],
kwargs:{}
}).then(function(result) {
self.ksFetchUpdateItem(ks_item_id).then(function(){
$(".ks_li_tab[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_li_tab[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('show');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('show');
$(".header_add_btn[data-item-id=" + ks_item_id + "]").attr('data-section-id', ks_section_id);
});
});
},
cancel: () => {},
});
}
_onKsAddTask(e){
var self = this;
var ks_section_id = e.currentTarget.dataset.sectionId;
var ks_item_id = e.currentTarget.dataset.itemId;
var ks_result = this.dialogService.add(addtododialog,{
confirm: (event) => {
var content = $(event.currentTarget.parentElement.parentElement).find('.ks_section').val();
self._onCreateTask(content, parseInt(ks_section_id), parseInt(ks_item_id));
},
});
}
_onCreateTask(content, ks_section_id, ks_item_id){
var self = this;
this._rpc("/web/dataset/call_kw/ks_to.do.description/create",{
model: 'ks_to.do.description',
method: 'create',
args: [{
ks_to_do_header_id: ks_section_id,
ks_description: content,
}],
kwargs:{}
}).then(function() {
self.ksFetchUpdateItem(ks_item_id).then(function(){
$(".ks_li_tab[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_li_tab[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('show');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('show');
$(".header_add_btn[data-item-id=" + ks_item_id + "]").attr('data-section-id', ks_section_id);
});
});
}
ksOnToDoClick(ev){
ev.preventDefault();
var self= this;
var tab_id = $(ev.currentTarget).attr('href');
var $tab_section = $('#' + tab_id.substring(1));
$(ev.currentTarget).addClass("active");
$(ev.currentTarget).parent().siblings().each(function(){
$(this).children().removeClass("active");
});
$('#' + tab_id.substring(1)).siblings().each(function(){
$(this).removeClass("active");
$(this).addClass("fade");
});
$tab_section.removeClass("fade");
$tab_section.addClass("active");
$(ev.currentTarget).parent().parent().siblings().attr('data-section-id', $(ev.currentTarget).data().sectionId);
}
_onKsActiveHandler(e){
var self = this;
var ks_item_id = e.currentTarget.dataset.itemId;
var content_id = e.currentTarget.dataset.contentId;
var ks_task_id = e.currentTarget.dataset.contentId;
var ks_section_id = e.currentTarget.dataset.sectionId;
var ks_value = e.currentTarget.dataset.valueId;
if (ks_value== 'True'){
ks_value = false
}else{
ks_value = true
}
self.content_id = parseInt(content_id);
rpc("/web/dataset/call_kw/ks_to.do.description/write",{
model: 'ks_to.do.description',
method: 'write',
args: [parseInt(content_id), {
"ks_active": ks_value
}],
kwargs:{}
}).then(function() {
self.ksFetchUpdateItem(ks_item_id).then(function(){
$(".ks_li_tab[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_li_tab[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('active');
$(".ks_tab_section[data-item-id=" + ks_item_id + "]").removeClass('show');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('active');
$(".ks_tab_section[data-section-id=" + ks_section_id + "]").addClass('show');
$(".header_add_btn[data-item-id=" + ks_item_id + "]").attr('data-section-id', ks_section_id);
});
});
}
};
Ksdashboardtodo.props = {
item: { type: Object, Optional:true},
dashboard_data: { type: Object, Optional:true},
hideButtons: { type: Number, optional: true },
on_dialog: { type: Boolean, optional: true },
explain_ai_whole: { type: Boolean, optional: true }
};
Ksdashboardtodo.components = { Todoeditdialog, addtododialog, KsItemButton }
Ksdashboardtodo.template = "Ksdashboardtodo";

View File

@@ -0,0 +1,313 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="Ksdashboardtodo">
<t t-if="props.explain_ai_whole">
<div class="grid-stack-item ks_tile_carousel ks_dashboarditem_id ks_explain_todo" t-att-id="item_id" t-ref="todoRootRef">
<div class="grid-stack-item-content ks_list_view_container ks_dashboard_item_hover card border-0"
t-att-title="ks_info" t-att-id="item_id">
<div class="ks_card_header encapsulated-header-card" t-att-style="'background-color:'+ ks_header_color + ';' + 'color:'+ ks_font_color + '!important;'">
<div class="p-3 py-3 d-flex flex-row align-items-center justify-content-between ">
<div class="d-flex align-items-center w-50">
<h6 class="m-0 font-weight-bold h3 mr-3 ks_list_view_heading text-capitalize"
t-att-style="'color:'+ ks_font_color + ' !important' + ';'"
t-att-title="ks_chart_title">
<t t-esc="ks_chart_title"/>
</h6>
</div>
<KsItemButton item_data="this.item" itemRootRef="todoRootRef"/>
</div>
<div class="card-header">
<div class="nav-tabs-navigation">
<div class="nav-tabs-wrapper">
<t t-if="state.to_do_view_data['label']">
<ul class="nav nav-tabs" data-tabs="tabs">
<t t-set="ks_rec_count" t-value="0"/>
<t t-foreach="state.to_do_view_data['label']" t-as="table_header" t-key="table_header_index">
<li class="nav-item">
<t t-if="ks_rec_count==0">
<a class="nav-link active ks_li_tab" t-on-click="ksOnToDoClick"
t-att-style="'color:'+ ks_font_color + ' !important' + ';'"
t-att-data-item-id="item_id"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_rec_count]"
data-toggle="pill"
t-att-href="state.to_do_view_data['ks_link'][ks_rec_count]">
<t t-esc="table_header"/>
</a>
</t>
<t t-else="">
<a class="nav-link ks_li_tab" t-on-click="ksOnToDoClick"
t-att-data-item-id="item_id"
t-att-style="'color:'+ ks_font_color + ' !important' + ';'"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_rec_count]"
data-toggle="pill"
t-att-href="state.to_do_view_data['ks_link'][ks_rec_count]">
<t t-esc="table_header"/>
</a>
</t>
</li>
<t t-set="ks_rec_count" t-value="ks_rec_count+1"/>
</t>
</ul>
<button class="header_add_btn" t-on-click="_onKsAddTask"
t-att-data-item-id="item_id"
t-att-style="'color:'+ ks_rgba_button_color + ' !important' + ';'"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][0]">
<span class="fa fa-lg fa-plus-circle"/>
</button>
</t>
<t t-else="">
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="nav-item">
No Section Available.
</li>
</ul>
</t>
</div>
</div>
</div>
</div>
<div class="ks_to_do_card_body card-body table-responsive">
<t t-call="ks_to_do_dashboard_inner_body"/>
</div>
</div>
</div>
</t>
</t>
<t t-name="ks_to_do_dashboard_inner_body">
<div class="container-fluid p-0">
<div>
<div>
<div class="card">
<t t-if="state.to_do_view_data['label']">
<div class="card-body">
<div class="tab-content">
<t t-set="ks_section_count" t-value="0"/>
<t t-foreach="state.to_do_view_data['ks_href_id']" t-as="ks_href_id" t-key="ks_href_id_index">
<t t-if="ks_section_count===0">
<div class="tab-pane active ks_tab_section"
t-att-data-item-id="item_id"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
t-att-id="ks_href_id">
<t t-if="state.to_do_view_data['ks_content'][ks_href_id]">
<table class="table">
<t t-if="state.to_do_view_data['ks_content'][ks_href_id]">
<t t-set="ks_temp_count" t-value="0"/>
<t t-foreach="state.to_do_view_data['ks_content'][ks_href_id]"
t-as="table_row" t-key="table_row_index">
<tr>
<t t-if="!ks_tv_play">
<td class="ks_custom_check">
<div class="form-check">
<label class="form-check-label">
<t t-if="state.to_do_view_data['ks_content_active'][ks_href_id][ks_temp_count]=='True'">
<input class="form-check-input"
type="checkbox"
value=""/>
</t>
<t t-else="">
<input class="form-check-input"
type="checkbox" value=""
checked=""/>
</t>
<span class="form-check-sign ks_do_item_active_handler" t-on-click="_onKsActiveHandler"
t-att-data-item-id="item_id"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
t-att-data-value-id="state.to_do_view_data['ks_content_active'][ks_href_id][ks_temp_count]">
<span class="check"/>
</span>
</label>
</div>
</td>
</t>
<t t-if="state.to_do_view_data['ks_content_active'][ks_href_id][ks_temp_count]=='True'">
<td class="ks_description"
t-att-value="table_row"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]">
<t t-esc="table_row"/>
</td>
</t>
<t t-else="">
<td class="ks_description ks_do_item_line_through"
t-att-value="table_row"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]">
<t t-esc="table_row"/>
</td>
</t>
<t t-if="!ks_tv_play">
<td class="td-actions text-right">
<button type="button" rel="tooltip"
t-att-data-item-id="item_id"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
class="btn-link btn-sm ks_edit_content" t-on-click="_onKsEditTask"
data-original-title="Edit Task">
<!-- <i class="material-icons">Edit</i>-->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none">
<path d="M9.16675 1.66663H7.50008C3.33341 1.66663 1.66675 3.33329 1.66675 7.49996V12.5C1.66675 16.6666 3.33341 18.3333 7.50008 18.3333H12.5001C16.6667 18.3333 18.3334 16.6666 18.3334 12.5V10.8333" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M13.3666 2.51663L6.7999 9.0833C6.5499 9.3333 6.2999 9.82497 6.2499 10.1833L5.89157 12.6916C5.75823 13.6 6.3999 14.2333 7.30823 14.1083L9.81657 13.75C10.1666 13.7 10.6582 13.45 10.9166 13.2L17.4832 6.6333C18.6166 5.49997 19.1499 4.1833 17.4832 2.51663C15.8166 0.849966 14.4999 1.3833 13.3666 2.51663Z" stroke="currentColor" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M12.425 3.45837C12.9834 5.45004 14.5417 7.00837 16.5417 7.57504" stroke="currentColor" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</button>
<button type="button"
t-att-data-item-id="item_id"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]"
class="btn btn-danger btn-link btn-sm ks_delete_content" t-on-click="_onKsDeleteContent"
data-original-title="Remove">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665" fill="#4B5563"/>
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.66699 3.31337L5.81366 2.44004C5.92033 1.80671 6.00033 1.33337 7.12699 1.33337H8.87366C10.0003 1.33337 10.087 1.83337 10.187 2.44671L10.3337 3.31337" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5669 6.09338L12.1336 12.8067C12.0603 13.8534 12.0003 14.6667 10.1403 14.6667H5.86026C4.00026 14.6667 3.94026 13.8534 3.86693 12.8067L3.43359 6.09338" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.88672 11H9.10672" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.33301 8.33337H9.66634" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</td>
</t>
<t t-set="ks_temp_count" t-value="ks_temp_count+1"/>
</tr>
</t>
</t>
</table>
</t>
<t t-else="">
<span class="nav-tabs-title">No Tasks Available</span>
</t>
<t t-set="ks_section_count" t-value="ks_section_count+1"/>
</div>
</t>
<t t-else="">
<div class="tab-pane fade ks_tab_section"
t-att-data-item-id="item_id"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
t-att-id="ks_href_id">
<t t-if="state.to_do_view_data['ks_content'][ks_href_id]">
<table class="table">
<t t-if="state.to_do_view_data['ks_content'][ks_href_id]">
<t t-set="ks_temp_count" t-value="0"/>
<t t-foreach="state.to_do_view_data['ks_content'][ks_href_id]"
t-as="table_row" t-key="table_row_index">
<tr>
<t t-if="!ks_tv_play">
<td class="ks_custom_check">
<div class="form-check">
<label class="form-check-label">
<t t-if="state.to_do_view_data['ks_content_active'][ks_href_id][ks_temp_count]=='True'">
<input class="form-check-input"
type="checkbox"
value="True"/>
</t>
<t t-else="">
<input class="form-check-input"
type="checkbox"
value="False"
checked=""/>
</t>
<span class="form-check-sign ks_do_item_active_handler" t-on-click="_onKsActiveHandler"
t-att-data-item-id="item_id"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
t-att-data-value-id="state.to_do_view_data['ks_content_active'][ks_href_id][ks_temp_count]">
<span class="check"></span>
</span>
</label>
</div>
</td>
</t>
<t t-if="state.to_do_view_data['ks_content_active'][ks_href_id][ks_temp_count]=='True'">
<td class="ks_description"
t-att-value="table_row"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]">
<t t-esc="table_row"/>
</td>
</t>
<t t-else="">
<td class="ks_description ks_do_item_line_through"
t-att-value="table_row"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]">
<t t-esc="table_row"/>
</td>
</t>
<t t-if="!ks_tv_play">
<td class="td-actions text-right">
<button type="button" rel="tooltip"
t-att-data-item-id="item_id"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
class="btn-link btn-sm ks_edit_content" t-on-click="_onKsEditTask"
data-original-title="Edit Task">
<!-- <i class="material-icons">Edit</i>-->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none">
<path d="M9.16675 1.66663H7.50008C3.33341 1.66663 1.66675 3.33329 1.66675 7.49996V12.5C1.66675 16.6666 3.33341 18.3333 7.50008 18.3333H12.5001C16.6667 18.3333 18.3334 16.6666 18.3334 12.5V10.8333" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M13.3666 2.51663L6.7999 9.0833C6.5499 9.3333 6.2999 9.82497 6.2499 10.1833L5.89157 12.6916C5.75823 13.6 6.3999 14.2333 7.30823 14.1083L9.81657 13.75C10.1666 13.7 10.6582 13.45 10.9166 13.2L17.4832 6.6333C18.6166 5.49997 19.1499 4.1833 17.4832 2.51663C15.8166 0.849966 14.4999 1.3833 13.3666 2.51663Z" stroke="currentColor" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M12.425 3.45837C12.9834 5.45004 14.5417 7.00837 16.5417 7.57504" stroke="currentColor" stroke-width="1.25" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</button>
<button type="button"
t-att-data-item-id="item_id"
t-att-data-section-id="state.to_do_view_data['ks_section_id'][ks_section_count]"
t-att-data-content-id="state.to_do_view_data['ks_content_record_id'][ks_href_id][ks_temp_count]"
class="btn btn-danger btn-link btn-sm ks_delete_content" t-on-click="_onKsDeleteContent"
data-original-title="Remove">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665" fill="#4B5563"/>
<path d="M14 3.98665C11.78 3.76665 9.54667 3.65332 7.32 3.65332C6 3.65332 4.68 3.71999 3.36 3.85332L2 3.98665" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.66699 3.31337L5.81366 2.44004C5.92033 1.80671 6.00033 1.33337 7.12699 1.33337H8.87366C10.0003 1.33337 10.087 1.83337 10.187 2.44671L10.3337 3.31337" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.5669 6.09338L12.1336 12.8067C12.0603 13.8534 12.0003 14.6667 10.1403 14.6667H5.86026C4.00026 14.6667 3.94026 13.8534 3.86693 12.8067L3.43359 6.09338" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.88672 11H9.10672" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.33301 8.33337H9.66634" stroke="#4B5563" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</td>
</t>
<t t-set="ks_temp_count" t-value="ks_temp_count+1"/>
</tr>
</t>
</t>
</table>
</t>
<t t-else="">
<span class="nav-tabs-title">No Tasks Available</span>
</t>
<t t-set="ks_section_count" t-value="ks_section_count+1"/>
</div>
</t>
</t>
</div>
</div>
</t>
<t t-else="">
<div class="card-body">
<div class="tab-content">
<span class="nav-tabs-title">No Tasks Available</span>
</div>
</div>
</t>
</div>
</div>
</div>
</div>
</t>
</templates>

View File

@@ -0,0 +1,3 @@
.ks_body_class .nav-tabs-wrapper {
word-break: break-word;
}

View File

@@ -0,0 +1,210 @@
/** @odoo-module **/
import { Component, useState, useEffect, onMounted } from "@odoo/owl"
import { useForwardRefToParent } from "@web/core/utils/hooks";
import { Ksdashboardtile } from '../ks_dashboard_tile_view/ks_dashboard_tile';
import { Ksdashboardtodo } from '../ks_dashboard_to_do_item/ks_dashboard_to_do';
import { Ksdashboardkpiview } from '../ks_dashboard_kpi_view/ks_dashboard_kpi';
import { Ksdashboardgraph } from '../ks_dashboard_graphs/ks_dashboard_graphs';
import { isMobileOS } from "@web/core/browser/feature_detection";
import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class KsItems extends Component {
static props = {
ks_data : { type: Object, optional:true },
domain_data_obj : { type: Object, optional:true },
itemsToUpdateList : { type: Array, optional:true },
date_filter_data : { type: String, optional:true },
gridStackRootRef: { type: Function, optional: true },
}
static template = "Ks_items"
static components = { Ksdashboardtile, Ksdashboardtodo, Ksdashboardkpiview, Ksdashboardgraph }
setup(){
this.ks_dashboard_data = this.props.ks_data
this.on_dialog = false;
this.gridstackConfig = {};
this.actionService = useService('action');
this.gridStackRootRef = useForwardRefToParent("gridStackRootRef")
this.explain_ai_whole = true;
this.gridstack_options = {
staticGrid:true,
float: false,
cellHeight: 68,
styleInHead : true,
disableOneColumnMode: true,
};
if (isMobileOS()) {
this.gridstack_options.disableOneColumnMode = false
}
onMounted( () => this.onMount())
}
onMount(){
this.grid = GridStack.init(this.gridstack_options, $(".grid-stack")[0]);
if(!this.env.inDialog) this.grid_initiate();
}
grid_initiate(){
var self=this;
var $gridstackContainer = $(".grid-stack");
if($gridstackContainer.length){
var item = self.ksSortItems(self.ks_dashboard_data.ks_item_data)
if(this.ks_dashboard_data.ks_gridstack_config){
this.gridstackConfig = JSON.parse(this.ks_dashboard_data.ks_gridstack_config);
}
for (var i = 0; i < item.length; i++) {
var graphs = ['ks_scatter_chart','ks_bar_chart', 'ks_horizontalBar_chart', 'ks_line_chart', 'ks_area_chart', 'ks_doughnut_chart','ks_polarArea_chart','ks_pie_chart','ks_flower_view', 'ks_radar_view','ks_radialBar_chart','ks_map_view','ks_funnel_chart','ks_bullet_chart', 'ks_to_do', 'ks_list_view']
var $ks_preview = $('#' + item[i].id)
if ($ks_preview.length && !this.ks_dashboard_data.ks_ai_explain_dash) {
if (item[i].id in self.gridstackConfig) {
var min_width = graphs.includes(item[i].ks_dashboard_item_type) ? 3 : 2
self.grid.addWidget($ks_preview[0], {x:self.gridstackConfig[item[i].id].x, y:self.gridstackConfig[item[i].id].y, w:self.gridstackConfig[item[i].id].w, h: self.gridstackConfig[item[i].id].h, autoPosition:false, minW:min_width, maxW:null, minH:3, maxH:null, id:item[i].id});
} else if ( graphs.includes(item[i].ks_dashboard_item_type)) {
self.grid.addWidget($ks_preview[0], {x:0, y:0, w:5, h:6,autoPosition:true,minW:4,maxW:null,minH:3,maxH:null, id :item[i].id});
}else{
self.grid.addWidget($ks_preview[0], {x:0, y:0, w:2, h:2,autoPosition:true,minW:2,maxW:null,minH:3,maxH:2,id:item[i].id});
}
}else{
if (item[i].id in self.gridstackConfig) {
var min_width = graphs.includes(item[i].ks_dashboard_item_type)? 3:2
self.grid.addWidget($ks_preview[0], {x:self.gridstackConfig[item[i].id].x, y:self.gridstackConfig[item[i].id].y, w:12, h: 6, autoPosition:false, minW:min_width, maxW:null, minH:2, maxH:null, id:item[i].id});
} else {
self.grid.addWidget($ks_preview[0], {x:0, y:0, w:12, h:6,autoPosition:true,minW:4,maxW:null,minH:3,maxH:null, id :item[i].id});
}
}
}
this.grid.setStatic(true);
}
}
ksSortItems(ks_item_data) {
let items = []
let self = this;
var item_data = Object.assign({}, ks_item_data);
if (self.ks_dashboard_data.ks_gridstack_config) {
self.gridstackConfig = JSON.parse(self.ks_dashboard_data.ks_gridstack_config);
let a = Object.values(self.gridstackConfig);
let b = Object.keys(self.gridstackConfig);
for (var i = 0; i < a.length; i++) {
a[i]['id'] = b[i];
}
a.sort(function(a, b) {
return (35 * a.y + a.x) - (35 * b.y + b.x);
});
for (let i = 0; i < a.length; i++) {
if (item_data[a[i]['id']]) {
items.push(item_data[a[i]['id']]);
delete item_data[a[i]['id']];
}
}
}
return items.concat(Object.values(item_data));
}
_onKsItemClick(item_id){
let self = this;
let action ;
// To Handle only allow item to open when not clicking on item
if(self.env.mode === 'edit' || this.env.inDialog) return;
let item_data = self.ks_dashboard_data.ks_item_data[item_id];
if (item_data && item_data.ks_show_records && item_data.ks_data_calculation_type != 'query' && item_data.ks_model_name) {
if (item_data.action) {
action = Object.assign({}, item_data.action);
if (action.view_mode.includes('tree')) action.view_mode = action.view_mode.replace('tree', 'list');
for (var i = 0; i < action.views.length; i++) action.views[i][1].includes('tree') ? action.views[i][1] = action.views[i][1].replace('tree', 'list') : action.views[i][1];
action['domain'] = item_data.ks_domain || [];
action['search_view_id'] = [action.search_view_id, 'search']
} else {
action = {
name: _t(item_data.name),
type: 'ir.actions.act_window',
res_model: item_data.ks_model_name,
domain: item_data.ks_domain || "[]",
views: [
[false, 'list'],
[false, 'form']
],
view_mode: 'list',
target: 'current',
}
}
if(action){
self.actionService.doAction(action);
}
}
}
async speak_once(ev,item){
ev.preventDefault()
var ks_audio = ev.currentTarget;
let ks_speeches = []
ev.currentTarget.parentElement.querySelector('.voice-cricle').classList.toggle("d-none");
ev.currentTarget.parentElement.querySelector('.comp-gif').classList.toggle("d-none");
$(document.querySelectorAll('audio')).each((index,item)=>{
if ($(ks_audio).find('audio')[0] !== item && !item.paused){
item.pause()
item.parentElement?.querySelector('.voice-cricle')?.classList.toggle("d-none");
item.parentElement?.querySelector('.comp-gif')?.classList.toggle("d-none");
}
})
if (!ks_speeches.length){
if ($(ks_audio).find('audio').attr('src') && $(ks_audio).find('audio')[0].paused){
$(ks_audio).find('audio')[0].play();
$(ks_audio).find('.fa.fa-volume-up').removeClass('d-none');
$(ks_audio).find('.comp-gif').removeClass('d-none');
$(ks_audio).find('.voice-cricle').addClass('d-none');
$(ks_audio).find('.fa.fa-pause').remove();
}else if ($(ks_audio).find('audio').attr('src') && !$(ks_audio).find('audio')[0].paused){
$(ks_audio).find('audio')[0].pause();
$(ks_audio).find('.voice-cricle').removeClass('d-none');
$(ks_audio).find('.comp-gif').addClass('d-none');
$(ks_audio).find('.fa.fa-volume-up').addClass('d-none');
}else{
$(ks_audio).find('.comp-gif').removeClass('d-none');
$(ks_audio).find('.voice-cricle').addClass('d-none');
ks_speeches.push(rpc('/web/dataset/call_kw/ks_dashboard_ninja.arti_int/ks_generatetext_to_speech',{
model : "ks_dashboard_ninja.arti_int",
method:"ks_generatetext_to_speech",
args:[item.id],
kwargs:{}
}).then(function(result){
if (result){
$(ks_audio).find('span').removeClass('d-none')
var audio = (JSON.parse(result)).snd;
$(ks_audio).find('audio')[0].src="data:audio/wav;base64, "+audio;
$(ks_audio).find('audio')[0].play()
ks_speeches.pop()
}
else{
$(ks_audio).find('.comp-gif').addClass('d-none');
$(ks_audio).find('.voice-cricle').removeClass('d-none');
ks_speeches.pop()
}
}.bind(this))
)
return Promise.resolve(ks_speeches)
}
}
}
}

View File

@@ -0,0 +1,12 @@
.ks_body_class .grid-stack-item>.grid-stack-item-content {
box-shadow: none !important;
}
.ks_body_class .grid-stack-item.ui-draggable{
cursor: move;
.ks_dashboard_item_header_hover {
display: none !important;
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="Ks_items">
<div class="ks_dashboard_item_content grid-stack ks_dashboard_items_list m-3" gs-w="36" t-ref="gridStackRootRef"/>
<t t-call="items_renderer"/>
</t>
<t t-name="items_renderer">
<t t-foreach="Object.values(props.ks_data.ks_item_data)" t-as="item" t-key="item.id">
<t t-if="item.ks_dashboard_item_type === 'ks_tile'">
<Ksdashboardtile item="item" hideButtons="1" on_dialog="on_dialog" dashboard_data="props.ks_data"
ksdatefilter="props.date_filter_data" itemsToUpdateList="props.itemsToUpdateList"
onItemClick.bind="_onKsItemClick" ks_speak="(ev) => this.speak_once(ev, item)"/>
</t>
<t t-elif="item.ks_dashboard_item_type === 'ks_kpi'">
<Ksdashboardkpiview item="item" hideButtons="1" on_dialog="on_dialog" dashboard_data="props.ks_data"
ksdatefilter="props.date_filter_data" itemsToUpdateList="props.itemsToUpdateList"
onItemClick.bind="_onKsItemClick" ks_speak="(ev) => this.speak_once(ev, item)"/>
</t>
<t t-elif="item.ks_dashboard_item_type === 'ks_to_do'">
<Ksdashboardtodo item="item" hideButtons="1" dashboard_data="ks_dashboard_data" explain_ai_whole="explain_ai_whole"/>
</t>
<t t-else="">
<Ksdashboardgraph item="item" hideButtons="1" explain_ai_whole="explain_ai_whole" dashboard_data="props.ks_data"
ksdatefilter="props.date_filter_data" itemsToUpdateList="props.itemsToUpdateList" ks_speak="(ev) => this.speak_once(ev, item)"
onItemClick.bind="_onKsItemClick"/>
</t>
</t>
</t>
</templates>

View File

@@ -0,0 +1,103 @@
/** @odoo-module **/
import { Component, useState, onWillStart, onWillUpdateProps } from "@odoo/owl";
import { Domain } from "@web/core/domain";
import { useBus } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";
import { useGetTreeDescription } from "@web/core/tree_editor/utils";
import { treeFromDomain } from "@web/core/tree_editor/condition_tree";
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
export class ModelDrivenFilterApplicator extends Component {
/*
* This component is based on event bus
* Takes any model name , group [ domain with key ]
* Applies it to dashboard and maintains its history
*/
static template = "ks_dashboard_ninja.model_driven_filter_applicator"
static props = {
update: { type: Function },
remove: { type: Function },
removeAllFilters: { type: Boolean },
options: { type: Object, optional: true },
};
static defaultProps = {
options: {},
};
setup(){
this.getDomainTreeDescription = useGetTreeDescription();
this.state = useState({
'facets' : {}
})
if(!this.env.inDialog)
useBus(this.env.bus, 'APPLY: Dashboard Filter', ({ detail }) => this.addFacet(detail.model_display_name, detail.model_name, detail.field_name,
detail.operator, detail.value))
onWillStart( () => this.apply_cookies_data() )
onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps))
}
onPropsUpdated(nextProps){
if(nextProps.removeAllFilters){
eraseCookie('ChartFilter' + this.props.options.ks_dashboard_id)
this.clearFacets();
}
}
apply_cookies_data(){
let facets_data = getObjectFromCookie('ChartFilter' + this.props.options.ks_dashboard_id)
this.state.facets = facets_data ?? {}
}
clearFacets(){
this.state.facets = {}
}
async make_label(model, domain){
let label = await this.getDomainTreeDescription(model, treeFromDomain(domain));
return label;
}
async addFacet(model_display_name, model_name, field_name, operator, value){
let group_name = `${model_name + '_' + field_name + operator + value}`
let domain = new Domain([[field_name , operator, value]]).toList();
let facets_to_update = this.state.facets[model_name] || { groups: {}, model_name: model_display_name }
if(!facets_to_update.groups[group_name]){
let label = await this.make_label(model_name, domain)
facets_to_update.groups[group_name] = { label: label, domain: domain }
this.state.facets[model_name] = facets_to_update
this.apply_domain(model_name, group_name, domain);
}
}
onRemoveFacet(model, group){
/**
* This method remove the facet or domain showing through facet from the dashboard
*/
eraseCookie('ChartFilter' + this.props.options.ks_dashboard_id)
delete this.state.facets[model].groups[group];
if(!Object.values(this.state.facets[model].groups).length) delete this.state.facets[model];
setObjectInCookie('ChartFilter' + this.props.options.ks_dashboard_id, this.state.facets, 1)
this.props.remove([model], [group]);
}
apply_domain(model_name, group_name, domain){
/**
* This method create the domain and update to the dashboards domain data
*/
eraseCookie('ChartFilter' + this.props.options.ks_dashboard_id)
let domain_data_to_update = { [model_name]: { [group_name]: { domain: domain}}}
setObjectInCookie('ChartFilter' + this.props.options.ks_dashboard_id, this.state.facets, 1)
this.props.update(domain_data_to_update)
}
}

View File

@@ -0,0 +1,86 @@
.custom-dash-collapse{
.o_input{
button{
border: none;
background: transparent;
}
border: none;
}
.dropdown-toggle.d-flex.encapsulated-link{
overflow: auto;
max-height: 75px;
}
.dash-default-btn{
svg{
fill: transparent !important;
}
}
.table-dlt-btn{
&:hover{
border-color: #E7495E !important;
svg{
stroke: #E7495E !important;
}
}
}
.o-dropdown{
.ks-nav-link{
button{
min-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
}
}
.ks-nav-link{
button{
background-color: transparent;
border: none;
}
}
.dash-dd-2{
.selcted-opt.opted{
background-color: #F5F8FB;
padding-right: 22px;
}
.dropdown-toggle{
&:after{
display: none;
}
}
}
.encapsulated-cross {
position: absolute;
top: 2px;
}
}
@media only screen and (max-width: 1600px) {
.custom-dash-collapse{
.o-dropdown{
.ks-nav-link{
button{
min-width: 80px;
}
}
}
}
}
@media only screen and (max-width: 1600px) {
.custom-dash-collapse{
.o-dropdown{
.ks-nav-link{
button{
min-width: 60px;
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name='ks_dashboard_ninja.model_driven_filter_applicator'>
<t t-if="Object.values(state.facets).length" t-call="ks_dn.facets_container">
<t t-set="filter_data" t-value="state.facets"/>
<t t-set="remove" t-value="onRemoveFacet.bind(this)"/>
</t>
<t t-else="" t-call="no-data-view-without-button">
<t t-set="header_text" t-value="'No Filter Applied'"/>
<t t-set="body_text" t-value="'Apply from List Chart :) '"/>
</t>
</t>
</templates>

View File

@@ -0,0 +1,141 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component, onWillStart, useState, useEffect, onWillUnmount, onMounted } from "@odoo/owl";
//import { dnNavBarAddClasses } from "@ks_dashboard_ninja/js/dnNavBarExtend";
import { useService } from "@web/core/utils/hooks";
import { debounce } from "@web/core/utils/timing";
import { _t } from "@web/core/l10n/translation";
import { WarningDialog } from "@web/core/errors/error_dialogs";
import { isMobileOS } from "@web/core/browser/feature_detection";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
//import { rpc } from "@web/core/network/rpc";
//import { user } from "@web/core/user";
export class KSDashboardNinjaOverview extends Component{
setup(){
this.rpc = rpc
this.orm = useService("orm");
this.actionService = useService("action");
this.overviewTilesNames = [['All Dashboards', 'one'], ['All Charts', 'two'], ['Total Maps', 'three'],
['Bookmarked Dashboards', 'four'], ['All Lists', 'five']]
this.state = useState({
bookmarkedDashboards: false,
filter: localStorage.getItem('dashboardFilter') || "All Dashboards",
})
onWillStart(async () => {
this.data = await rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/fetch_dashboard_overview",{
model: 'ks_dashboard_ninja.board',
method: 'fetch_dashboard_overview',
args: [[]],
kwargs:{},
});
await rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_update_menu_id",{
model: 'ks_dashboard_ninja.board',
method: 'ks_update_menu_id_old_db',
args: [[]],
kwargs:{},
});
await this.env.services.menu.reload()
});
onWillUnmount(()=>{
document.querySelector(".ks-zoom-view")?.classList.remove("d-none")
})
onMounted(()=>{
if (document.body.classList.contains("ks_body_class")){
document.querySelector(".ks-zoom-view")?.classList.add("d-none")
}
});
}
get isMobile(){
return isMobileOS();
}
get filteredDashboards(){
if (this.state.filter === 'Bookmarked'){
let filteredData = Object.entries(this.data.dashboardsInfo).filter( (dashboard)=> dashboard[1].is_bookmarked);
return filteredData.length ? Object.fromEntries(filteredData) : false;
}
return this.data.overviewInfo[0] ? this.data.dashboardsInfo : false;
}
async viewDashboard(ev){
ev.preventDefault();
const dashboardId = parseInt(ev.currentTarget.dataset.dashboardId)
let clientActionId = await this.orm.silent.read(
'ks_dashboard_ninja.board',
[dashboardId],
['ks_dashboard_client_action_id']
);
clientActionId = clientActionId[0]?.ks_dashboard_client_action_id[0]
this.actionService.doAction({
type: "ir.actions.client",
tag: "ks_dashboard_ninja",
params:{
ks_dashboard_id: dashboardId
},
id: clientActionId
});
// if(menuId){
// let response = await this.env.services.menu.selectMenu(menuId);
// }
}
onFilterChange(ev){
this.state.filter = ev.target.text;
this.state.bookmarkedDashboards = !this.state.bookmarkedDashboards;
localStorage.setItem("dashboardFilter", this.state.filter);
if($('#overviewFilter'))
$('#overviewFilter').text(this.state.filter);
}
async updateBookmark(ev){
let dashboardId = parseInt(ev.currentTarget.dataset.dashboardId);
let unBookmarkSvg = $(`#unBookmark${dashboardId}`);
let bookmarkSvg = $(`#bookmark${dashboardId}`);
let bookmarkCountTag = $('[id="Bookmarked Dashboards"]');
if (unBookmarkSvg && bookmarkSvg){
unBookmarkSvg.toggleClass('d-none');
bookmarkSvg.toggleClass('d-none');
}
this.data.dashboardsInfo[dashboardId].is_bookmarked = !this.data.dashboardsInfo[dashboardId].is_bookmarked;
let updatedBookmarks = await this.rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/update_bookmarks",{
model: 'ks_dashboard_ninja.board',
method: 'update_bookmarks',
args: [[dashboardId]],
kwargs:{},
});
bookmarkCountTag.text(updatedBookmarks[0]);
}
createDashboard(ev){
let action = {
name: _t('Create Dashboard'),
type: 'ir.actions.act_window',
res_model: 'ks.dashboard.wizard',
domain: [],
context: {
},
views: [
[false, 'form']
],
view_mode: 'form',
target: 'new',
}
this.actionService.doAction(action);
}
}
KSDashboardNinjaOverview.template = "ks_dashboard_ninja.dashboardNinjaOverView";
registry.category("actions").add("dashboard_ninja", KSDashboardNinjaOverview);

View File

@@ -0,0 +1,175 @@
.overview-sec-one {
h1 {
font-size: $font-20;
font-weight: $f-w-500;
line-height: 28px;
color: $color-black;
margin-bottom: 0;
@include max-575 {
font-size: $font-12;
}
span {
font-weight: $f-w-600;
}
}
}
.overview-sec-tiles {
@media screen and (min-width:1200px) {
.col-xl-2 {
flex: 1;
width: auto;
}
}
.tile {
min-height: 113px;
border-radius: 16px;
padding: 14px;
&:hover {
box-shadow: 0px 4px 12px 0px #00000040;
transition: all 0.5s ease-in-out;
}
&.one {
background-color: $color-FFE2E5;
}
&.two {
background-color: $color-FFF4DE;
}
&.three {
background-color: $color-DCFCE7;
}
&.four {
background-color: $color-F3E8FF;
}
&.five {
background-color: $color-white;
}
.num {
font-size: $font-24;
font-weight: $f-w-500;
line-height: 33.6px;
color: $color-black;
}
p {
font-size: $font-15;
font-weight: $f-w-400;
line-height: 18px;
color: $color-black;
}
}
}
.overview-sec-dasboards {
.no-data {
max-height: calc(100% - 56px);
overflow: auto;
h3 {
font-size: $font-32;
font-weight: $f-w-500;
line-height: 44.8px;
color: $color-secondary-main;
}
p {
font-size: $font-20;
font-weight: $f-w-500;
line-height: 28px;
color: $color-black;
}
}
.data-added {
max-height: calc(100vh - 467px);
overflow: auto;
@include max-768 {
max-height: unset;
overflow: unset;
}
@include custom-scrollbar;
@media screen and (min-width:1200px) {
max-height: calc(100vh - 331px);
}
@media (max-width: 900px) and (orientation: landscape) {
max-height: none;
}
.dashboard-box {
box-shadow: 0px 4px 20px 0px #EEEEEE80;
border-radius: 10px;
background-color: $color-white;
padding: 20px;
min-height: 290px;
.dash-preview-box {
border-radius: 10px;
border: 0.5px solid $color-E5E7EB;
width: 100%;
overflow: hidden;
// height: 290px;
img {
width: 100%;
height: auto;
aspect-ratio: 16/9;
}
}
.dashboard-info {
.left {
h4 {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 22.4px;
color: $color-black;
}
p {
font-size: $font-14;
font-weight: $f-w-400;
line-height: 18px;
color: $color-black;
}
}
.right {
.marked {
.bookmark {
display: block;
cursor: pointer;
}
&.active {
.bookmarked {
display: block !important;
cursor: pointer;
}
.bookmark {
display: none;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,154 @@
<t t-name="ks_dashboard_ninja.dashboardNinjaOverView" owl="1">
<main class="main-box">
<section class="overview-sec-one mb-4 pb-1">
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center">
<h1>Welcome <span t-esc="data.user_name"/></h1>
<div class="d-flex align-items-center">
<!-- custom-dd-2-start -->
<div class="dropdown dash-dd-2">
<a t-ref="overviewFilter" class="text-decoration-none dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<t t-esc="state.filter"/>
</a>
<ul class="dropdown-menu ks-dropdown-menu">
<li><a class="dropdown-item" href="#" t-on-click="onFilterChange">All Dashboards</a></li>
<li><a class="dropdown-item" href="#" t-on-click="onFilterChange">Bookmarked</a></li>
</ul>
</div>
<!-- custom-dd-2-end -->
<button class="dash-btn-red d-flex align-items-center ms-3 d-none d-md-flex" t-if="data.isManager" t-on-click="createDashboard">
<span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="transparent" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 12H16" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 16V8" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="ms-1">
Add New Dashboard
</span>
</button>
</div>
</div>
</div>
</section>
<section class="overview-sec-tiles mb-4 pb-1 container-fluid">
<div class="d-flex align-items-center row-gap-3 row">
<t t-foreach="[0,1,2,3,4]" t-as="i" t-key="i">
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-12">
<div t-attf-class="tile {{overviewTilesNames[i][1]}} d-flex justify-content-between align-items-center">
<div>
<div t-attf-id="{{overviewTilesNames[i][0]}}" class="num mb-1">
<t t-esc="data.overviewInfo[i]"/>
</div>
<p class="mb-1"><t t-esc="overviewTilesNames[i][0]"/></p>
</div>
<div>
<img t-attf-src="/ks_dashboard_ninja/static/images/dashboardOverview/{{overviewTilesNames[i][0]}}.svg" alt="category" loading="lazy" class="img-fluid"/>
</div>
</div>
</div>
</t>
</div>
</section>
<section class="overview-sec-dasboards container-fluid">
<t t-set="filteredDashboardsInfo" t-value="filteredDashboards"/>
<!-- no-data-found -->
<div t-if="!filteredDashboardsInfo" class="d-flex flex-column justify-content-center align-items-center h-100 no-data">
<div>
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/no-data.png" alt="no-data-files" class="img-fluid" loading="lazy"/>
</div>
<h3 class="mb-0">
No Data Found!!
</h3>
<p class="mb-3">No Dashboards have been Found</p>
<button class="dash-btn-red d-flex align-items-center" t-if="data.isManager &amp;&amp; !isMobile" t-on-click="createDashboard">
<span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="transparent" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5 22 22 17.5 22 12C22 6.5 17.5 2 12 2C6.5 2 2 6.5 2 12C2 17.5 6.5 22 12 22Z" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 12H16" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 16V8" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="ms-2">
Add New Dashboard
</span>
</button>
</div>
<div class="row row-gap-34 data-added" t-if="filteredDashboardsInfo">
<t t-foreach="filteredDashboardsInfo" t-as="dashboard" t-key="dashboard">
<div class="col-xl-4 col-md-6 col-12">
<div class="dashboard-box flex-column d-flex align-items-center justify-content-center">
<div class="mb-2 dash-preview-box">
<img t-att-src="data.dashboardsInfo[dashboard].image" alt="dashboard-img" class="img-fluid"
loading="lazy"/>
</div>
<div class="d-flex w-100 justify-content-between align-items-center dashboard-info">
<div class="left w-50">
<h4 class="text-truncate"><t t-esc="data.dashboardsInfo[dashboard].name"/></h4>
<p><t t-esc="data.dashboardsInfo[dashboard].chartCount"/>
Charts</p>
</div>
<!-- -->
<div class="right w-50">
<div class="marked mb-2 d-flex justify-content-end align-items-center">
<div t-attf-id="unBookmark{{data.dashboardsInfo[dashboard].id}}" t-att-class="data.dashboardsInfo[dashboard].is_bookmarked ? 'd-none' : ''"
t-on-click="updateBookmark" t-att-data-dashboard-id="data.dashboardsInfo[dashboard].id"
title="Bookmark the dashboard">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" class="bookmark"
xmlns="http://www.w3.org/2000/svg">
<path d="M7.70831 7.54163C9.19165 8.08329 10.8083 8.08329 12.2916 7.54163"
stroke="#292D32" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round"/>
<path
d="M14.0169 1.66669H5.98357C4.20857 1.66669 2.76691 3.11669 2.76691 4.88335V16.625C2.76691 18.125 3.84191 18.7584 5.15857 18.0334L9.22524 15.775C9.65857 15.5334 10.3586 15.5334 10.7836 15.775L14.8502 18.0334C16.1669 18.7667 17.2419 18.1334 17.2419 16.625V4.88335C17.2336 3.11669 15.7919 1.66669 14.0169 1.66669Z"
stroke="#292D32" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round"/>
<path
d="M14.0169 1.66669H5.98357C4.20857 1.66669 2.76691 3.11669 2.76691 4.88335V16.625C2.76691 18.125 3.84191 18.7584 5.15857 18.0334L9.22524 15.775C9.65857 15.5334 10.3586 15.5334 10.7836 15.775L14.8502 18.0334C16.1669 18.7667 17.2419 18.1334 17.2419 16.625V4.88335C17.2336 3.11669 15.7919 1.66669 14.0169 1.66669Z"
stroke="#292D32" stroke-width="1.25" stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
</div>
<!-- bookmarked -->
<div t-attf-id="bookmark{{data.dashboardsInfo[dashboard].id}}" t-on-click="updateBookmark"
t-att-class="data.dashboardsInfo[dashboard].is_bookmarked ? '' : 'd-none'"
t-att-data-dashboard-id="data.dashboardsInfo[dashboard].id"
title="Bookmarked">
<svg width="16" height="18" viewBox="0 0 16 18" fill="none"
class="bookmarked cursor-pointer" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.0165 0.666687H3.98318C2.20818 0.666687 0.766518 2.11669 0.766518 3.88335V15.625C0.766518 17.125 1.84152 17.7584 3.15818 17.0334L7.22485 14.775C7.65818 14.5334 8.35818 14.5334 8.78318 14.775L12.8499 17.0334C14.1665 17.7667 15.2415 17.1334 15.2415 15.625V3.88335C15.2332 2.11669 13.7915 0.666687 12.0165 0.666687ZM10.5082 7.12502C9.69985 7.41669 8.84985 7.56669 7.99985 7.56669C7.14985 7.56669 6.29985 7.41669 5.49152 7.12502C5.16652 7.00835 4.99985 6.65002 5.11652 6.32502C5.24152 6.00002 5.59985 5.83335 5.92485 5.95002C7.26652 6.43335 8.74152 6.43335 10.0832 5.95002C10.4082 5.83335 10.7665 6.00002 10.8832 6.32502C10.9999 6.65002 10.8332 7.00835 10.5082 7.12502Z"
fill="#6789C6"/>
</svg>
</div>
<button class="img-bg bookmark-capture ms-lg-2 ms-1 d-md-block d-none"
t-att-data-dashboard-id="data.dashboardsInfo[dashboard].id" t-on-click="viewDashboard"
title="To update the Dashboard image, go to dashboard and update through camera icon. We can also scroll in the dashboard and capture only the visible part.">
<svg width="24" height="24" viewBox="0 0 24 24" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M6.75992 22H17.2399C19.9999 22 21.0999 20.31 21.2299 18.25L21.7499 9.99C21.8899 7.83 20.1699 6 17.9999 6C17.3899 6 16.8299 5.65 16.5499 5.11L15.8299 3.66C15.3699 2.75 14.1699 2 13.1499 2H10.8599C9.82992 2 8.62992 2.75 8.16992 3.66L7.44992 5.11C7.16992 5.65 6.60992 6 5.99992 6C3.82992 6 2.10992 7.83 2.24992 9.99L2.76992 18.25C2.88992 20.31 3.99992 22 6.75992 22Z" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 8H13.5" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 18C13.79 18 15.25 16.54 15.25 14.75C15.25 12.96 13.79 11.5 12 11.5C10.21 11.5 8.75 12.96 8.75 14.75C8.75 16.54 10.21 18 12 18Z" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<a class="dash-link d-flex align-items-center justify-content-end" t-att-data-dashboard-id="data.dashboardsInfo[dashboard].id"
t-on-click="viewDashboard" title="Click to view the Dashboard">
View Dashboard
<i class="fa fa-angle-right ms-1" aria-hidden="true"/>
</a>
</div>
</div>
</div>
</div>
</t>
</div>
</section>
</main>
</t>

View File

@@ -0,0 +1,167 @@
/** @odoo-module **/
import { Component, useState, onWillUpdateProps, onWillStart, onRendered } from "@odoo/owl";
import { useBus } from "@web/core/utils/hooks";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { useGetTreeDescription } from "@web/core/tree_editor/utils";
import { Domain } from "@web/core/domain";
import { treeFromDomain } from "@web/core/tree_editor/condition_tree";
import { setObjectInCookie, getObjectFromCookie, eraseCookie } from "@ks_dashboard_ninja/js/cookies";
export class PreDefinedFilterDropdown extends Component{
static template = "ks_dashboard_ninja.pre_defined_filter_dropdown"
static props = {
dropdown_items: { type: Array },
onDropDownSelect: { type: Function },
removeAllFilters: { type: Boolean, optional: true }
};
static components = { DropdownItem }
setup(){
this.state = useState({
searchText: '',
});
}
get dropdown_items(){
let searchedFilters = this.props.dropdown_items.filter(
(item) => item.name.toLowerCase().includes(this.state.searchText.toLowerCase()) || item.type === 'separator')
while(this.state.searchText && searchedFilters.length && searchedFilters[searchedFilters.length - 1].type === 'separator')
searchedFilters.pop();
while(this.state.searchText && searchedFilters.length && searchedFilters[0].type === 'separator') searchedFilters.shift();
return searchedFilters;
}
}
export class PreDefinedFilter extends Component{
static template = "ks_dashboard_ninja.pre_defined_filter"
static props = {
domain: { type: String, optional: true },
update: { type: Function, optional: true },
removeAllFilters: { type: Boolean, optional: true },
remove: { type: Function, optional: true },
options: { type: Object, optional: true }, // Tobe used for extra data provides to the component
filters_data: { type: Object, optional: true },
};
static defaultProps = {
};
static components = { Dropdown, PreDefinedFilterDropdown }
setup(){
this.getDomainTreeDescription = useGetTreeDescription();
this.defaultState = {"Select Filter": { model_name: 'Select Filter', groups: {}}}
this.state = useState({
activeFilterModels : {"Select Filter": { model_name: 'Select Filter', groups: {}}},
});
this.filter_id = 0
this.filters_data = this.props.filters_data;
onWillStart( () => {
this.setObjFromCookies();
this.filters_data_list = Object.values(this.filters_data).sort(function(a, b){return a.sequence - b.sequence})
})
onWillUpdateProps((nextProps) => this.onPropsUpdated(nextProps))
}
onPropsUpdated(nextProps){
if(nextProps.removeAllFilters)
this.clearFilters()
}
setObjFromCookies(){
let activeFilterModels_from_cky = getObjectFromCookie('PFilter' + this.props.options.ks_dashboard_id)
let filters_data_from_cky = getObjectFromCookie('PFilterDataObj' + this.props.options.ks_dashboard_id)
this.state.activeFilterModels = activeFilterModels_from_cky ?? {"Select Filter": { model_name: 'Select Filter', groups: {}}}
if(!activeFilterModels_from_cky) this.setActiveFilters()
this.filters_data = filters_data_from_cky ?? this.filters_data
}
setActiveFilters(){
Object.values(this.filters_data).forEach((filter)=> {
if(filter.active){
this.addFilter(filter.id, filter.categ, filter.model)
}
})
}
clearFilters(){
Object.values(this.filters_data).forEach( (filter) => { filter.active = false })
this.state.activeFilterModels = {"Select Filter": { model_name: 'Select Filter', groups: {}}}
eraseCookie('PFilter' + this.props.options.ks_dashboard_id)
eraseCookie('PFilterDataObj' + this.props.options.ks_dashboard_id)
}
onPredefinedFilterSelect(filterId, filterCategory, filterModel){
let isAppliedFilter = this.state.activeFilterModels[filterModel]?.groups?.[filterCategory]?.filters?.[filterId]
isAppliedFilter ? this.removeFilter(filterId, filterCategory, filterModel) : this.addFilter(filterId, filterCategory, filterModel)
}
async removeFilter(filterId, filterCategory, filterModel){
delete this.state.activeFilterModels[filterModel].groups[filterCategory].filters[filterId]
this.filters_data[filterId].active = false
await this.update(filterModel, filterCategory, filterId)
this.pruneEmptyFilterGroups(this.state.activeFilterModels, filterModel, filterCategory)
}
addFilter(filterId, filterCategory, filterModel){
if (this.state.activeFilterModels["Select Filter"]) {
delete this.state.activeFilterModels["Select Filter"];
}
this.filters_data[filterId].active = true
this.state.activeFilterModels[filterModel] ??= { model_name: this.filters_data[filterId].model_name, groups: {} };
this.state.activeFilterModels[filterModel].groups[filterCategory] ??= {filters: {}, label: ''};
this.state.activeFilterModels[filterModel].groups[filterCategory].filters[filterId] = this.filters_data[filterId].domain;
this.update(filterModel, filterCategory, filterId);
}
makeLabelForModelGroup(model, group){
let filters = this.state.activeFilterModels[model]?.groups?.[group]?.filters
let labels = ''
if(filters){
labels = Object.keys(filters).map(filterId => this.filters_data[filterId].name).join(' or ');
}
return labels ? labels : 'Applied Filter'
}
update(model, group , filter_id){
eraseCookie('PFilter' + this.props.options.ks_dashboard_id)
eraseCookie('PFilterDataObj' + this.props.options.ks_dashboard_id)
let domainToUpdate = { [model]: { [group]: {}}}
let domain = Domain.or([ ...Object.values(this.state.activeFilterModels[model].groups[group].filters) ]).toList();
let label = this.makeLabelForModelGroup(model, group)
domainToUpdate[model][group].domain = domain
this.state.activeFilterModels[model].groups[group].label = label === '' ? this.filters_data[filter_id].name : label
this.state.activeFilterModels[model].groups[group].domain = domain
setObjectInCookie('PFilter' + this.props.options.ks_dashboard_id, this.state.activeFilterModels, 1)
setObjectInCookie('PFilterDataObj' + this.props.options.ks_dashboard_id, this.filters_data, 1)
this.props.update(domainToUpdate)
}
pruneEmptyFilterGroups(filterData, modelName, groupName){
if(!Object.values(this.state.activeFilterModels[modelName].groups[groupName]?.filters ?? {}).length)
delete this.state.activeFilterModels[modelName].groups[groupName]
if(!Object.values(this.state.activeFilterModels[modelName].groups).length)
delete this.state.activeFilterModels[modelName]
if(!Object.values(this.state.activeFilterModels).length)
this.state.activeFilterModels = {"Select Filter": { model_name: 'Select Filter', groups: {}}}
}
removeGroup(model, group){
eraseCookie('PFilter' + this.props.options.ks_dashboard_id)
eraseCookie('PFilterDataObj' + this.props.options.ks_dashboard_id)
let model_group = this.state.activeFilterModels[model].groups[group]
Object.keys(model_group.filters).forEach( (filter_id) => this.filters_data[filter_id].active = false)
delete this.state.activeFilterModels[model].groups[group]
this.pruneEmptyFilterGroups(this.state.activeFilterModels, model, group)
setObjectInCookie('PFilter' + this.props.options.ks_dashboard_id, this.state.activeFilterModels, 1)
setObjectInCookie('PFilterDataObj' + this.props.options.ks_dashboard_id, this.filters_data, 1)
this.props.remove([model], [group])
}
}

View File

@@ -0,0 +1,171 @@
.ks_body_class .predefined-filters {
.ks_dn_filter_applied_container {
width: 100% !important;
margin-left: 0 !important;
padding: 0;
}
label {
font-size: $font-12;
font-weight: $f-w-500;
line-height: 16.8px;
text-align: left;
color: $color-paragraph;
margin-bottom: 10px;
}
button{
border: none !important;
padding: 0px !important;
width: 100%;
}
.dropdown-toggle {
border: 0.81px solid $color-D1D5DB;
border-radius: 6px !important;
padding: 10px;
min-height: 53px;
display: flex;
align-items: center;
padding-right: 21px;
.placeholder-custom {
font-size: $font-14;
font-weight: $f-w-400;
line-height: 18.52px;
text-align: left;
color: $color-placeholders;
display: inline-block;
}
.selcted-opt {
// display: none;
align-items: center;
gap: 4px;
background-color: $color-bg-main;
border-radius: 5px;
padding: 4px 8px;
width: fit-content;
.opt-title {
font-size: $font-14;
font-weight: $f-w-400;
line-height: 18.52px;
text-align: left;
color: $color-black;
text-wrap: wrap;
}
}
&::after {
position: absolute;
right: 18px;
top: 35%;
}
}
.dropdown-menu {
width: 30%;
li:not(:nth-child(1)) {
padding-left: 0 !important;
}
li {
&:hover {
background-color: transparent !important;
}
}
.line-seperator-ks {
border: 1px dashed $color-paragraph;
width: 100%;
display: block;
}
.search-input {
padding: 8px 12px;
border: 1px solid var(--Border-Color, #E5E7EB);
border-radius: 4px;
& input[type="search"] {
color: $color-black;
font-size: $font-16;
font-weight: $f-w-400;
line-height: 20px;
text-align: left;
border: none;
width: 100%;
&::-webkit-search-decoration,
&::-webkit-search-cancel-button {
appearance: none;
}
&:focus {
border: none;
outline: none;
}
&::placeholder {
color: $color-placeholders;
}
}
}
}
}
.ks-dropdown-menu {
li:not(:nth-child(1)) {
padding-left: 0 !important;
}
li {
&:hover {
background-color: transparent !important;
}
}
.line-seperator-ks {
border: 1px dashed $color-paragraph;
width: 100%;
display: block;
}
.search-input {
padding: 8px 12px;
border: 1px solid var(--Border-Color, #E5E7EB);
border-radius: 4px;
& input[type="search"] {
color: $color-black;
font-size: $font-16;
font-weight: $f-w-400;
line-height: 20px;
text-align: left;
border: none;
width: 100%;
&::-webkit-search-decoration,
&::-webkit-search-cancel-button {
appearance: none;
}
&:focus {
border: none;
outline: none;
}
&::placeholder {
color: $color-placeholders;
}
}
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-name="ks_dashboard_ninja.pre_defined_filter" owl="1">
<div class="predefined-filters">
<t t-foreach="Object.keys(state.activeFilterModels)" t-as="model" t-key="model_index">
<h4 class="">
<t t-esc="state.activeFilterModels[model].model_name"/>
</h4>
<Dropdown menuClass="'ks-dropdown-menu'">
<t t-set-slot="content">
<div class="w-500">
<PreDefinedFilterDropdown dropdown_items="filters_data_list" onDropDownSelect.bind="onPredefinedFilterSelect"/>
</div>
</t>
<t t-call="ks_dn.model_facets_renderer">
<t t-set="model_data" t-value="state.activeFilterModels[model]"/>
<t t-set="ks_model" t-value="model"/>
<t t-set="remove" t-value="removeGroup.bind(this)"/>
</t>
</Dropdown>
</t>
</div>
</t>
<t t-name="ks_dashboard_ninja.pre_defined_filter_dropdown">
<div class="search-input d-flex align-items-center">
<div class="me-1">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.66634 14.4999C3.89967 14.4999 0.833008 11.4333 0.833008 7.66659C0.833008 3.89992 3.89967 0.833252 7.66634 0.833252C11.433 0.833252 14.4997 3.89992 14.4997 7.66659C14.4997 11.4333 11.433 14.4999 7.66634 14.4999ZM7.66634 1.83325C4.44634 1.83325 1.83301 4.45325 1.83301 7.66659C1.83301 10.8799 4.44634 13.4999 7.66634 13.4999C10.8863 13.4999 13.4997 10.8799 13.4997 7.66659C13.4997 4.45325 10.8863 1.83325 7.66634 1.83325Z"
fill="#4b5563" stroke="none"/>
<path d="M14.6666 15.1666C14.54 15.1666 14.4133 15.12 14.3133 15.02L12.98 13.6866C12.7866 13.4933 12.7866 13.1733 12.98 12.98C13.1733 12.7866 13.4933 12.7866 13.6866 12.98L15.02 14.3133C15.2133 14.5066 15.2133 14.8266 15.02 15.02C14.92 15.12 14.7933 15.1666 14.6666 15.1666Z"
fill="#4b5563" stroke="none"/>
</svg>
</div>
<input class="predefinedFilterSearchInput" placeholder="Search" type="search" href="#" t-model="state.searchText"/>
</div>
<t t-if="dropdown_items.length">
<DropdownItem t-foreach="dropdown_items" t-as="dropdown_item" t-key="dropdown_item_index"
class="{ 'global-active': dropdown_item.active, 'ks-disabled' : dropdown_item['type'] !== 'filter', 'ellipsis-content' : true}"
onSelected="() => this.props.onDropDownSelect(dropdown_item.id, dropdown_item.categ, dropdown_item.model)">
<span t-if="dropdown_item['type'] === 'filter'"><t t-esc="dropdown_item.name"/> ( <t t-esc="dropdown_item.model_name"/> )</span>
<a t-else="" class="line-seperator-ks" href="#"/>
</DropdownItem>
</t>
<t t-else="" t-call="no-data-view-without-button">
<t t-set="header_text" t-value="'No Filter Available'"/>
<t t-set="body_text" t-value="'No search results :) '"/>
</t>
</t>
</templates>

View File

@@ -0,0 +1,52 @@
.ks_new_tag{
position: absolute;
font-size: 6px;
background: #71639e;
padding: 2px 6px;
margin: 0px;
border-radius: 4px;
color: white;
font-weight: 600;
text-transform: uppercase;
margin-right: 6px;
letter-spacing: 0.5px;
margin-top: -4px;
margin-left: 6px;
}
.ks_img_selected{
background:#f2f2f2;
}
.ks_dashboard_kpi_dashboard .ks_img_display{
display: block;
margin-left: auto;
margin-top: -7em
}
.ks_breadcrumb {
padding: 5px 5px;
}
.ks_breadcrumb ul {
padding: 0;
margin: 0;
}
.ks_breadcrumb ul li {
list-style: none;
display: inline;
text-transform: uppercase;
font-size: 15px;
}
.ks_breadcrumb ul li+li:before {
content: "\27E9";
padding: 0 8px;
color: #EA4335;
}
.ks_breadcrumb ul li:last-child:after {
content: "";
}
.ks_breadcrumb ul li span {
color: #4285F4;
text-decoration: none;
}
.ks_breadcrumb ul li span:hover {
color: #202f47;
}

View File

@@ -0,0 +1,36 @@
/*rtl:begin:ignore*/
/* Gridstack container custom css */
.ks_o_graph_svg_container{
height:calc(100% - 25px);
width:100%;
}
.ksChartTitle{
font-weight: bold;
color: black;
display: block;
height:calc(100% - 87%);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.ks_o_graph_svg_container > svg.ks_svg_chart{
height: calc(100% - 13%);
width:100%;
}
/* Mobile view item no scroll fix */
.grid-stack>.grid-stack-item>.ui-draggable-handle{
touch-action: unset;
}
.grid-stack > .grid-stack-item > .grid-stack-item-content {
}
/*rtl:end:ignore*/

View File

@@ -0,0 +1,75 @@
.ks_dashboard_theme_input::before {
content: '';
position: relative;
width: 25px;
height: 20px;
color: black;
display: flex;
justify-content: center;
}
.ks_color_white::before {
background: #DAEAF6;
color:black;
}
.ks_color_white {
border: 1px solid #bdbdbd
}
.ks_color_blue::before {
background: #FFF4DE;
}
.ks_color_red::before{
background: #DCFCE7;
}
.ks_color_yellow::before{
background: #F3E8FF;
}
.ks_color_green::before{
background: #FFE2E5;
}
.ks_dashboard_theme_view_render{
display: flex;
}
.ks_dashboard_theme_input:checked:before{
content: '✔';
}
.ks_dashboard_theme_input_container{
margin-right: 20px;
}
.ks_dashboard_top_settings .fa-cog{
font-size: 20px;
cursor: pointer;
}
.ks_dashboard_setting_container{
padding: 5px 10px 5px 10px;
cursor: pointer;
}
.ks_dashboard_setting_container:hover{
color: #333333;
background-color: #f5f5f5;
}
.ks_dashboard_ninja_toggle_menu::after {
display:none !important;
}
/*
Define here the CSS styles applied only to Safari browsers
(any version and any device)
*/
.ks_dashboard_theme_input{
-webkit-appearance: none;
}
/*Kpi CSS here*/
.ks_kpi_target_grey {
color: #777777;
}

View File

@@ -0,0 +1,747 @@
/* TO make whole page in white ks_dashboard_linkbackground(below grey screen start) : Hiding it for now */
.ks_dashboard_form {
min-height: 100%;
}
.ks_dashboard_ninja {
margin-bottom: 20px;
}
.ks_dashboard_main_content {
overflow: auto;
// height: calc(100vh - 175px);
max-height:100%;
}
.ks_dashboard_ninja_header{
text-align : right;
display: flex ;
justify-content: space-between;
flex-wrap: wrap;
padding: 15px 8px;
align-items: center;
margin-bottom: 25px;
}
.ks_overflow {
overflow: auto;
}
.ks_dashboard_header_name {
margin: 0;
max-width: 35%;
}
.ks_header_container_div{
float:none;
background: #f2f2f2;
margin-top: -20px;
box-shadow: 2px 3px 6px #bbb5b5;
}
.ks_white_background{
background-color : white;
}
.ks_dashboard_header_name {
margin: 0;
width: 80vmin;
overflow-wrap: break-word;
text-align: left;
}
.ks_layout_color{
background-color : #F9F9F9;
margin-left : 35px;
margin-top : 20px;
}
.ks_dashboard_item{
padding: 8px 15px 20px 15px;
/*border : 1px solid #CDD1D9;*/
color : black;
margin-top: 15px;
border-radius : 6px;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
overflow:visible !important;
}
.ks_dashboard_item:hover{
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
}
.ks_dashboard_item_header{
text-align : right;
min-height: 21px;
}
.ks_dashboard_item_main_body{
display: flex;
justify-content: space-between;
align-items: center;
}
.ks_dashboard_item_info{
margin-left : 10px;
width : 100%;
}
.ks_dashboard_item_name{
font-weight : bold;
font-size: 23px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-transform: none;
min-height : 34px;
}
.ks_dashboard_item_domain_count{
font-size: 28px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ks_dashboard_icon{
display: block;
margin: auto;
}
.ks_dashboard_icon>img {
width : 50px;
height : 50px;
}
.ks_dashboard_item_header>button{
visibility:hidden;
background: transparent;
border: none;
}
.ks_dashboard_item_hover:hover .ks_dashboard_item_header_hover > button {
visibility: visible;
}
.ks_dashboard_item_menu_show > button{
visibility: visible !important;
}
.ks_dashboard_item_header_hover>button:hover{
transition: 0.2s linear;
transform: scale(1.1);
cursor: pointer !important;
}
.ks_item_not_click {
cursor : move;
}
/* For Layout 2 Css */
.ks_dashboard_item_l2{
display : flex;
border-radius: 6px;
margin-top : 15px;
height: auto;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.ks_dashboard_item_l2:hover{
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
}
.ks_dashboard_icon_l2{
padding : 20px;
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
width: 100px;
/* width: 36%; */
padding-top: 35px;
position:relative;
display: flex;
justify-content: center;
}
/* font awesome resposive CSS for layou 2*/
.ks_dashboard_icon_l2>span{
font-size:5em;
}
.ks_dashboard_icon_l2>img {
width: 60px;
height : 60px;
}
.ks_dashboard_item_main_body_l2{
position : relative;
width: calc(100% - 100px);
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.ks_dashboard_item_header_l2{
position : absolute;
right: 5px;
top: 5px;
}
.ks_dashboard_item_header_l2>button{
visibility:hidden;
background: transparent;
border: none;
}
.ks_dashboard_item_domain_count_l2{
font-size: 35px;
margin-left: 20px;
margin-top: 25px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ks_dashboard_item_name_l2{
font-weight: bold;
font-size: medium;
width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 20px;
margin-bottom: 10px;
text-transform: none;
}
/* layout 3 */
.ks_dashboard_item_info_l3{
padding-left: 10px;
width: calc(100% - 60px);
margin-left: 0px;
}
.ks_dashboard_icon_l3 {
display: block;
max-width: 70px;
}
.ks_dashboard_icon_l3 > img{
width: 50px;
height : 50px;
}
.ks_dashboard_item_name_l3{
text-transform: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: right;
font-size: medium;
min-height : 24px;
}
.ks_dashboard_item_domain_count_l3{
text-align: right;
font-weight: bold;
font-size: 35px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Item Layout 4 */
.ks_dashboard_item_l4{
display: flex;
border-radius: 6px;
margin-top: 15px;
height: auto;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.ks_dashboard_item_l4:hover{
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
}
.ks_dashboard_icon_l4{
padding : 20px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
width : 100px;
padding-top: 35px;
display: flex;
justify-content: center;
}
.ks_dashboard_icon_l4>img {
width: 60px;
height : 60px;
}
.ks_dashboard_item_main_body_l5{
display: flex;
width: 100%;
}
.ks_dashboard_item_l5{
position : relative;
margin-top: 15px;
height: auto;
border-radius: 6px;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.ks_dashboard_item_l5:hover{
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
}
.ks_dashboard_icon_l5{
position : absolute;
z-index: 1;
}
.ks_dashboard_icon_l5 > img{
width: 20px !important;
height: 20px;
margin: 10px;
}
.ks_dashboard_item_main_body_l5{
text-align: center;
padding: 0 30px;
display: flex;
flex-direction: column;
}
.ks_dashboard_item_domain_count_l5{
font-size: 50px;
margin-left: 10px;
margin-top: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ks_dashboard_item_name_l5{
font-weight: bold;
font-size: medium;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 10px;
margin-bottom: 10px;
text-transform: none;
}
.ks_dashboard_item_header_l5{
position: absolute;
right: 5px;
top: 5px;
}
.ks_dashboard_icon_l5 span
{
font-size: 20px;
margin: 10px;
}
.ks_dashboard_item_header_l6{
position : absolute;
right: 25px;
top: 20px;
}
.ks_dashboard_item_header_l6>button{
visibility:hidden;
background: transparent;
border: none;
}
.ks_item_click{
cursor:pointer;
}
/* Dashboard filter related CSS */
.ks_action_btns{
display: flex;
align-items: center;
}
.welcome_note{
margin-top:10px;
}
.welcome_note h3{
font-size: 26px;
font-weight: bold;
}
.welcome_note h3 span{
color:#777777
}
.welcome_note img {
width: 30px;
}
@media (max-width: 1075px){
.ks_dashboard_header_name {
max-width: 100%;
}
.ks_dashboard_top_menu{
margin-top: 10px;
}
.ks_dashboard_top_menu.filter_design{
margin-top: 0 !important;
}
}
.ks_dashboard_header_sticky{
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0px;
z-index: 7;
}
.ks_dashboard_datepicker_z-index{
z-index: 10 !important;
}
/* Date Filter Selection CSS*/
#ks_date_filter_selection, #ks_dn_filter_selection{
font-family: inherit;
font-weight: bold;
padding: 0px 8px 0px 8px;
}
.ks_date_filter_selection_input{
display: flex;
flex-direction: row;
}
.ks_date_selection_box{
display: flex;
align-items: center;
}
.ks_date_input_fields{
display: flex;
// align-items: center;
}
.flex_column_datetimerow .ks_date_input_fields{
display: flex;
width:350px;
}
.flex_column_datetimerow .ks_date_apply_clear_print{margin-left:5px}
.flex_column_datetimerow #ks_btn_middle_child{margin:0 10px}
.ks_date_filter_selected > span, .ks_layout_selected > span{
font-weight: bold;
}
.ks_layout_selected > span::before{
font-family: FontAwesome;
position: absolute;
left: 6px;
content: "\f00c";
}
ul.nav li.dropdown:hover ul.dropdown-menu{ display: block; }
.df_selection_text{
display: block;
padding: 3px 12px;
font-weight: normal;
line-height: 1.42857143;
white-space: nowrap;
cursor: pointer;
}
.ks_date_filter_dropdown{
padding: 0.375rem 0.75rem;
border-color: #dee2e6;
border-radius: 3px !important;
}
.ks_btn_first_child_radius{
border-bottom-right-radius: 0 !important;
border-top-right-radius: 0 !important;
border-bottom-left-radius: 3px !important;
border-top-left-radius: 3px !important;
}
#ks_btn_middle_child{
border-radius: 0 !important;
height: 100%;
margin-right:10px;
}
#ks_btn_middle_child, #ks_btn_last_child {
padding: inherit !important;
}
.ks_btn_last_child{
border-bottom-left-radius: 0 !important;
border-top-left-radius: 0 !important;
height: 100%;
}
.ks_hide{
display:none !important;
}
.ks_dashboard_item_action{
background-color : inherit;
}
/* Dev Code here */
.ks_dashboard_header{
border-bottom: 1px solid #cccccc;
}
.ks_select_none{
-webkit-user-select: none; /* Chrome all / Safari all */
-moz-user-select: none; /* Firefox all */
-ms-user-select: none; /* IE 10+ */
user-select: none;
}
#ks_dashboard_title_input{
width: 300px;
}
// #ks_dashboard_title{
// padding-left: 1rem !important;
// }
.ks_preview_item {
width: calc(50% - 30px);
position: fixed;
margin-left:53%;
margin-top:3%;
}
.ks_dashboard_ninja_toggle_menu{
position:relative;
}
.ks_dashboard_ninja_toggle_menu:before {
font-family: FontAwesome;
top:0;
left:-5px;
padding-right:10px;
content: "\f013";
font-size: 2em;
cursor:pointer;
}
.ks_bg_white{
background : white;
}
/*
.dropdown-submenu {
position: relative;
}
*/
#ks_dashboard_title_label{
color: #777777;
font-size: 25px;
}
@media (max-width: 992px){
.ks_dashboard_ninja_toggle_menu:before {
content: "\f142";
}
#ks_dashboard_title_label{
font-size: 25px;
}
#ks_dashboard_title{
display: flex;
justify-content: space-between;
}
}
.config_dropdown .dropdown-item{
padding: 6px 20px;
}
.config_dropdown .dropdown-item .fa{
opacity: 0.8;
}
.config_dropdown .dropdown-item-lable {
display: block;
width: 100%;
padding: 5px 20px;
clear: both;
font-weight: bold;
color: #495057;
text-align: inherit;
white-space: nowrap;
margin: 3px 0px;
}
.button-50 {
appearance: button;
background-color: #000;
background-image: none;
border: 1px solid #000;
border-radius: 4px;
box-shadow: #fff 4px 4px 0 0,#000 4px 4px 0 1px;
box-sizing: border-box;
color: #fff;
cursor: pointer;
display: inline-block;
font-family: ITCAvantGardeStd-Bk,Arial,sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 20px;
margin: 0 5px 10px 0;
overflow: visible;
padding: 4px 15px;
text-align: center;
text-transform: none;
touch-action: manipulation;
user-select: none;
-webkit-user-select: none;
vertical-align: middle;
white-space: nowrap;
}
.button-50:focus {
text-decoration: none;
}
.button-50:hover {
text-decoration: none;
}
.button-50:active {
box-shadow: rgba(0, 0, 0, .125) 0 3px 5px inset;
outline: 0;
}
.button-50:not([disabled]):active {
box-shadow: #fff 2px 2px 0 0, #000 2px 2px 0 1px;
transform: translate(2px, 2px);
}
@media (min-width: 768px) {
.button-50 {
padding: 4px 15px;
}
}
@media (max-width: 350px) {
.ks_dashboard_item_button_container {
flex-wrap: wrap;
position: relative;
z-index: 11
}
}
// @media (max-width: 575px){
// #ks_date_filter_selection{
// display:none;
// }
// }
@media (max-width: 575px){
#play_button{
display:none;
}
}
@media (max-width: 575px){
#print_dashboard{
display:none;
}
}
.ks_dashboard_quick_edit_action .ks_qe_form_view .o_field_translate.btn.btn-link {
display: none;
}
.ks_import_dashboard_d_none .btn.btn-secondary.fa.fa-download {
display:none;
}
.ks_dashboard_ninja .o_view_nocontent {
background-image: NONE !important;
}
.ks_user{
position:relative !important;
}
.o_rtl .o_action_manager .ks_dashboard_main_content .ks_dashboard_item_content{
direction:rtl !important;
}
.o_rtl .o_action_manager .ks_dashboard_main_content .grid-stack-item{
direction:ltr !important;
}
.ks_list_item_table{
overflow: scroll !important;
}
.ks_save_filter_to_fav {
position: relative;
}
.ks_save_filter_to_fav .ks_fav_dropdown {
top: 0;
left: 100%;
margin-top: -1px;
}
.ks_date_filters_menu_drop_down.ks_favourite_filters_menu_drop_down {
overflow: visible !important;
}
.ks_o_add_favorite:hover .ks_dropdown_hover {
display: block !important;
}
.ks_dropdown_hover {
position: absolute;
right: auto !important;
left: 100% !important;
}
.ks_fav_checked{
position: absolute;
top: 6px;
left: auto;
bottom: auto;
right: auto;
transform: translate(-1.5em, 90%);
font: .7em / 1em FontAwesome;
color: #71639e;
}
.ks_fav_filters_checked > span{
font-weight: bold;
}
.ks_fav_filters_checked > span::before{
font-family: FontAwesome;
position: absolute;
left: 6px;
content: "\f00c";
}

View File

@@ -0,0 +1,190 @@
.ks_item_icon {
width : 110px !important;
}
.ks_item_icon>img {
width : 50px;
}
.ks_field_required{
color : red;
}
.ks_db_item_preview{
width: 280px;
}
.ks_not_click{
pointer-events: none;
}
.ks_color_picker{
margin-right: 10px;
}
.ks_color_opacity{
vertical-align:middle;
}
/* Icon Container in image widget render view*/
.ks_image_widget_icon_container{
background : transparent;
border: none;
cursor: pointer;
}
.ks_domain_content{
overflow : auto;
}
/* Preview Footer Note */
.ks_db_item_preview_footer_note{
margin-top : 10px;
}
/*Container CSS*/
.ks_md_heading{
justify-content: center;
font-size: 16px;
}
/* Dashboard Menu CSS*/
.ks_dashboard_menu_container{
padding: 10px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 14px 28px, rgba(0, 0, 0, 0.22) 0px 10px 10px;
border-radius: 5px;
max-width: 174px;
}
.ks_dashboard_menu_container > li{
margin: 2px;
display:flex;
}
.ks_dashboard_menu_container > li > button, .ks_dashboard_layout_dropdown_container > li > button{
margin-top : 5px;
margin-left: 2px;
border-radius: 5px;
}
#ks_add_item_selection,#ks_date_selector_container{
box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
}
#ks_dashboard_layout_edit{
border-radius: 3px;
color: #fff;
/*background-color: #357bb7;*/
/*border: 1px solid #357bb7;*/
}
.ks_add_item_type_button{
color:#fff;
/*background-color: #357bb7;
border: 1px solid #357bb7;*/
border-radius: 3px;
}
/* kpi css*/
.ks_dashboard_kpi_name_preview {
font-size: 21px;
white-space: nowrap;
text-overflow: ellipsis;
margin-top: 30px;
overflow: hidden;
}
.ks_kpi_main_body {
width: 100%;
/*
height: 67%;
*/
}
.ks_dashboard_kpi_count_preview {
font-weight: bold;
width:auto;
font-size: x-large;
text-align: center;
white-space: nowrap;
text-overflow: ellipsis;
text-transform: none;
overflow: hidden;
}
.ks_dashboard_kpi_arrow_preview {
font-size: medium;
font-weight: bold;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 10px;
margin-bottom: 10px;
text-transform: none;
}
.ks_db_kpi_preview {
width: 249px;
}
.ks_dashboard_kpi {
position : relative;
margin-top: 15px;
height: auto;
border-radius: 6px;
transition: all 0.3s cubic-bezier(.25,.8,.25,1);
box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
.ks_target_previous {
align-items: flex-end;
justify-content: space-between;
padding: 0 15px 0px 15px;
position: absolute;
width: 100%;
bottom: 0;
}
.ks_progress progress {
background-color: #000;
border: 0;
width: 80%;
height: 5px;
border-radius: 9px;
}
.ks_progress progress::-webkit-progress-bar {
background-color: #000;
border-radius: 9px;
}
.ks_progress progress::progress-value {
background: #fff;
border-radius: 9px;
}
.ks_progress progress::progress-bar {
background: #fff;
border-radius: 9px;
}
.ks_list_view_layout_3 .ks_tr:nth-child(even),
.ks_list_view_layout_1 .ks_tr:nth-child(even) {
background-color: #f2f2f2;
}
.ks_list_view_layout_3 .ks_tr:hover,
.ks_list_view_layout_1 .ks_tr:hover {
color: #666666 !important;
background-color: rgba(0, 0, 0, 0.26) !important;
}
.ks_map_card_body{
z-index:1;
}

View File

@@ -0,0 +1,321 @@
.ks_dashboard_item {
height: auto;
}
/* Form pie chart no data */
.ks_dn_graph_preview > .nv-noData > .ks_pie_chart_nocontent{
padding: 100px 0 0 137px;
min-height: 327px;
font-size: 125%;
}
.ks_dashboard_item_action {
color: black;
}
.ks_group_width {
width : 40% !important;
}
.ks_dashboard_quick_edit_action_popup {
color: black;
}
.ks_dashboard_item_button_container > .ks_chart_inner_buttons > button {
color:black;
}
/* Chart No Content Css */
.ks_chart_nocontent_form {
display: inherit !important;
}
.ks_pie_chart_nocontent > .nv-noData {
position: absolute;
top: 50%;
left: 50%;
-moz-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
font-size: 125%;
}
.ks_pie_chart_nocontent > .ksChartTitle {
position: absolute;
bottom: 0;
left: 50%;
-moz-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
.ks_dashboarditem_id, .ks_dashboarditem_chart_container{
overflow-x : visible !important;
overflow-y : visible !important;
}
.ks_previous_period .ks_dashboard_kpi{
overflow-y : auto !important;
}
.ks_dashboarditem_chart_container{
background : white;
}
/* Layout 6 exception case CSS */
.ks_dashboard_item_header_l6{
top: 5px !important;
right: 10px;
position:absolute;
}
.ks_chart_json_export {
padding-right: 10px !important;
}
.ks_chart_card_body{
position: absolute;
top: 65px;
left: 8px;
right: 8px;
bottom: 8px;
}
/*---------------------------------------------------*/
table#ksListViewTable {
margin: 0 auto;
border-collapse: collapse;
font-family: Agenda-Light, sans-serif;
/*font-weight: 100;*/
/*background: #333;*/ /*color: #fff;*/
text-rendering: optimizeLegibility;
border-radius: 5px;
width: -webkit-fill-available;
}
#ks_item_info {
cursor:pointer;
height: 18px;
width: 18px;
}
.ks_chart_heading{
/*width: 50%; */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ks_dashboard_item_drill_up {
min-width:110px;
margin-left:50px
}
.ks_list_view_heading{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 75%
}
.ks_pager {
min-width:98px;
}
.ks_dashboard_item_hover:hover .ks_dashboard_item_header_hover > .ks_chart_inner_buttons{
visibility: visible;
}
.ks_chart_inner_buttons{
visibility: hidden;
}
.ks_chart_color_options, .ks_item_size_options{
display: block;
padding: 3px 10px;
font-weight: normal;
line-height: 1.42857143;
white-space: nowrap;
cursor: pointer;
margin-left: 24px;
text-transform: capitalize;
}
.ks_chart_color_options:hover, .ks_item_size_options:hover{
color: #333333;
background-color: #f5f5f5;
}
.ks_pro_print_hide{
display:none !important;
}
.ks_dashboard_item_menu_show > .ks_chart_inner_buttons{
visibility: visible !important;
}
.ksMaxTableContent{
max-height: 300px;
}
.ks_list_view_container{
background : white;
}
.ks_chart_export_menu{
padding: 6px 0 6px 0;
}
.ks_chart_export_menu_header{
font-weight: 600;
padding: 0 14px 10px 14px;
}
.ks_chart_export_menu_item{
padding: 5px 14px 5px 14px;
cursor : pointer;
}
.ks_chart_image_export{
line-height: 29.5px;
padding-top: 7.5px;
padding-bottom: 7px;
}
.ks_chart_export_menu_item{
color: #4c4c4c;;
}
.ks_chart_export_menu_item:hover{
color: #4d4c4c;
background-color: #f5f5f5;
}
.ks_chart_export_menu_item > i{
padding-right: 10px;
}
.ks_date_filters_menu_drop_down {
max-height: 270px !important;
overflow: auto !important;
background-clip:unset;
}
/*.ks_ai_chart_body{
width:49% !important;
padding:0px !important
}
.ks_ai_explanation{
width: 49%;
position: absolute;
right: 8px;
top: 60px;
font-size: medium;
font-family: serif;
}
.ks_speaker{
width: 25px;
height: 25px;
bottom: 20px;
right: 30px;
position: absolute;
}
.ks_speaker span.fa.fa-volume-up, .ks_speaker span.fa.fa-pause {
border: 1px solid #71639e;
background: #71639e;
height: 35px;
width: 36px;
line-height: 35px;
text-align: center;
color: #fff;
font-size: 20px;
border-radius: 5px;
}
.ks_ai_dashboard_item {
height: auto !important;
}
.ks_ai_explain_tile{
background: #fff;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
margin: 0px 10px;
width: -webkit-fill-available !important;
border-radius: 5px;
height:calc(100vh - 290px)!important;
}
.ks_ai_explain_tile .ks_ai_dashboard_item, .ks_ai_explain_tile .ks_ai_dashboard_item:hover{ box-shadow: none !important;}*/
.ks_ai_explain_body{
display: flex;
position:relative;
flex-wrap: wrap;
}
.ks_ai_explain_body .ks_ai_dashboard_item{
flex: 0 0 50%;
max-width: 50%;
}
.ks_ai_explain_body{
height:100%;
}
.ks_ai_explain_body .ks_ai_explanation{
flex: 0 0 50%;
max-width: 50%;
padding:10px 20px;
height: 400px;
overflow-x: hidden;
overflow-y: auto;
}
.ks_speaker span.fa.fa-volume-up, .ks_speaker span.fa.fa-pause {
border: 1px solid #71639e;
background: #71639e;
height: 35px;
width: 36px;
line-height: 35px;
text-align: center;
color: #fff;
font-size: 20px;
border-radius: 5px;
}
.ks_speaker{
width: 25px;
height: 25px;
bottom: 20px;
right: 30px;
position: absolute;
}
.ks_ai_explain_body .ks_chart_card_body,.ks_ai_explain_body .ks_list_card_body{
position:relative !important;
top:inherit !important;
bottom:inherit !important;
right:inherit !important;
left: inherit !important;
flex: 0 0 50%;
max-width: 50%;
padding:inherit;
}
.ks_ai_explain_tile{
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
background-color:white;
width: 98% !important;
left: 50% !important;
transform: translate(-50%, 0) !important;
}
.ks_ai_dashboard_item {
height: auto !important;
}
.ks_ai_explanation{
font-size: medium;
font-family: serif;
}
.ks_ai_explain_body .ks_list_card_body{
overflow-y: auto;
height: calc(100% - 75px);
}
.ks_list_view_container .ks_speaker{
bottom: 65px !important
}

View File

@@ -0,0 +1,83 @@
.ks-options-btn{
border-radius: 25px;
margin-top: 2%;
visibility:collapse;
}
.ks_dashboard_link{
position: relative;
display: flex;
align-items: center;
}
.ks_date_apply_clear_print,.ks_dashboard_top_menu{
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
margin-right:1px;
}
i.ks-options-plus {
color: white;
display: inline-block;
border-radius: 60px;
background: #7c7bad;
box-shadow: 0px 0px 2px #888;
padding: 0.5em 0.6em;
}
.apply-dashboard-date-filter{
margin-left: 10px;
}
.clear-dashboard-date-filter{
margin-left:5px;
}
#ks_start_date_picker{
height: 30px;
width: 100px;
border: 1px solid #ccc;
padding: 2px 4px;
color: #1f1f1f;
}
#ks_end_date_picker{
height: 30px;
width: 100px;
border: 1px solid #ccc;
border-radius: 3px;
padding: 2px 4px;
color: #1f1f1f;
}
.ui-datepicker .ui-datepicker-title {
display: flex;
}
.o_view_manager_content{
font-family: arial;
}
.html2canvas-container {
height: 4000px !important;
}
.ks_dashboard_header_name {
text-align: start;
}
.ks-dashboard-date-labels{
font-weight: bold;
margin-top: inherit;
margin-left: 15px;
margin-right: 15px;
}
.ks_event_offer_list {
pointer-events: none;
color: #8080805e !important;
}
.ks_event_offer_list:focus {
box-shadow: none;
}

View File

@@ -0,0 +1,90 @@
.dropdown-menu.show.ks_dn_filter_dropdown_container {
display: flex !important;
}
.dn_dynamic_filter_selected > span{
font-weight: bold;
}
.dn_dynamic_filter_selected > span::before{
font-family: FontAwesome;
position: absolute;
left: 6px;
content: "\f00c";
}
.ks_dn_pre_filter_menu .df_selection_text {
padding: 3px 4px;
}
.ks_dn_pre_filter_menu {
list-style: none;
padding-left: 0px;
}
.ks_dn_pre_filter_menu > li {
padding-left: 20px;
}
.ks_dn_pre_filter_menu > li:hover {
color: #333333;
background-color: #f5f5f5;
}
.ks_dn_filter_dropdown_container > div {
/*
width: 225px;
*/
min-width :240px
}
.ks_dn_filter_dropdown_container > div > div.o_generator_menu > span {
margin-left: 20px;
font-weight: bold;
}
.ks_dn_filter_dropdown_container > div > div.o_generator_menu .ks_dn_custom_filter_input_container_section {
position: relative;
}
.ks_custom_filter_section_delete {
cursor: pointer;
position: absolute;
top: 5px;
left: auto;
bottom: auto;
right: -15px;
}
.o_or_filter {
position: absolute;
top: 0px;
left: -14px;
bottom: auto;
right: auto;
}
.ks_dn_or_container {
margin-top: 10px;
}
.ks_dn_filter_applied_container {
width: 500px !important;
padding: 0px 10px;
margin-left: 5px;
border-width: 1px;
border-color: #e5e5e5;
}
.ks_dn_pre_model_text {
}
@media screen and (max-width: 575px) {
.ks_o_add_favorite.o_add_favorite {
display: none;
}
.ks_dashboard_top_menu{
margin:0 auto;
}
}

View File

@@ -0,0 +1,21 @@
#ks_flower_chart {
width: 100%;
height: 300px;
}
#text {
margin-left:70px;
margin-top:25px;
}
.ks_flower_item_container{
margin-right:25px;
}
.flower_text{
font:3rem Lucida Grande;
align-items:center;
display:flex;
justify-content:center;
width:100%;
height:100%;
}

View File

@@ -0,0 +1,19 @@
#chartdiv {
width: 100%;
height: 300px;
}
#text {
margin-left:50px;
margin-top:25px;
}
.funnel_text , .graph_text{
font:2rem Lucida Grande;
align-items:center;
display:flex;
justify-content:center;
width:100%;
height:100%;
}

View File

@@ -0,0 +1,93 @@
.ks_icon_container_grid_view{
width:100%;
/* height:100%;*/
}
.ks_icon_container_list{
border: 1px solid transparent;
position: relative;
padding:2%;
float:left;
display:block;
transition: 0.3s ease-in-out;
width: 16.66%;
text-align: center;
}
.ks_font {
font-weight: bold;
min-width: 129px
}
.ks_icon_container_list:hover {
transform: scale(1.2);
cursor: pointer;
}
.ks_icon_selected{
background: #f2f2f2;
border-radius: 5px;
border: 1px solid black;
}
.ks_icon_selected img{
transform: scale(0.7) !important;
padding: 5px;
}
.ks_modal_icon_input_div{
width: 100%;
text-align: right;
margin-bottom: 12px;
}
.ks_modal_icon_input{
padding: 6px;
margin-top: 8px;
font-size: 17px;
}
.ks_fa_icon_search{
float: right;
padding: 6px 10px;
margin-top: 8px;
margin-right: 16px;
background: #ddd;
font-size: 17px;
border: none;
cursor: pointer;
}
.ks_search_modal_container{
display: flex;
justify-content: flex-end;
}
.x-tree-icon-leaf, .x-tree-icon-text::before {
font: normal normal normal 14px/1 "FontAwesome";
display: inline-block;
color: #5fa2dd;
}
@media (max-width: 475px){
.ks_icon_container_list{
width: 33.33%;
}
}
/* Responsive CSS */
@media (max-width: 1024px){
.ks_dashboard_item_header_hover > button, .ks_dashboard_item_header_hover > .ks_chart_inner_buttons{
visibility: visible !important;
}
}
@media (max-width: 1024px) {
.dropdown-max-height {
max-height: 40vh;
overflow: auto;
}
}

View File

@@ -0,0 +1,125 @@
.input_bar{
border-radius: 8px;
border: 0.5px solid #DADADA;
background: #FF;
color: #A8A8A8 !important;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
height: 40px;
padding-left: 24px !important;
}
input#ks_selection_field:focus {
border: 1px solid #797979;
}
.ks_input_custom{
position: relative;
}
.ks_input_custom svg{
position: absolute;
right: 0px;
top: 9px;
}
.search_style{
text-align:center;
font-size:14px;
color: #444;
border-radius: 5px;
}
.search_style{
overflow-y: scroll;
max-height: calc(100vh - 400px);
}
.search_style::-webkit-scrollbar {
width: 6px;
}
/* Track */
.search_style::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
.search_style::-webkit-scrollbar-thumb {
background: #888;
}
/* Handle on hover */
.search_style::-webkit-scrollbar-thumb:hover {
background: #555;
}
.ks_response_container_list{
border-bottom: 1px solid #d4d4d4;
border-left: 1px solid transparent;
padding: 17px 6px 18px 24px;
background-color: #fff;
color: #A8A8A8;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.ks_response_container_list:hover{
color: #9C5789;
border-left: 2px solid #9C5789;
}
.ks_response_container_list:hover svg path{
stroke: #9C5789;
}
.ks_response_container_list:hover svg line{
stroke: #9C5789;
}
.ks_dashboard_option_category span.ks_dashboard_option{
color: #9C5789;
height: 21px;
padding: 3px;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.ks_dashboard_option_category span{
display: flex;
align-items: center;
justify-content: start;
width: fit-content;
font-size: 8px;
background: #EEE;
padding: 0px 10px;
margin: 0px;
border-radius: 4px;
color: #888;
font-weight: 600;
text-transform: uppercase;
margin-right: 6px;
letter-spacing: 0.5px;
margin-top: 0px;
}
.ks_dashboard_ai_chart_icons{
display: flex;
align-items: center;
justify-content: start;
width: fit-content;
padding: 0px 10px;
margin: 0px;
font-weight: 600;
margin-top: 0px;
}
.ks_border_class{
border-radius: 8px !important;
border: 1px solid #DADADA !important;
background: #FFF !important;
margin-top: 8px !important;
height: 40px !important;
padding: 11px 11px 11px 24px !important;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.ks_border_class .ks_response{
width: 100%;
}

View File

@@ -0,0 +1,21 @@
#map_chart {
width: 100%;
height: 300px;
}
#text {
margin-left:70px;
margin-top:25px;
}
.ks_map_item_container{
margin-right:25px;
}
.map_text{
font:2rem Lucida Grande;
align-items:center;
display:flex;
justify-content:center;
width:100%;
height:100%;
}

View File

@@ -0,0 +1,22 @@
#radial_chart {
width: 100%;
height: 300px;
}
#text {
margin-left:70px;
margin-top:25px;
}
.ks_radial_item_container{
margin-right:25px;
}
.radial_text{
font:3rem Lucida Grande;
align-items:center;
display:flex;
justify-content:center;
width:100%;
height:100%;
}

View File

@@ -0,0 +1,160 @@
.ks_list_view_container .nav-tabs-wrapper .nav-tabs-title{
float: left;
line-height: 33px;
color: #fff;
font-size: 14px;
}
.ks_list_view_container .nav-tabs-wrapper .nav-tabs{
border: none;
flex-grow: 1;
}
.ks_list_view_container .nav-tabs-wrapper .nav-tabs .nav-item{
margin-right: 0.75rem;
margin-bottom: 0.75rem;
}
.ks_list_view_container .nav-tabs-wrapper .nav-tabs .nav-link {
border: none;
background: none;
color: #fff;
border-radius: 3px;
cursor: pointer;
}
.ks_list_view_container .nav-tabs-wrapper .nav-tabs .nav-link.active{
background-color: hsla(0,0%,100%,.2);
transition: background-color .3s .2s;
}
.ks_list_view_container .card{
border: none;
/*box-shadow: 0 1px 4px 0 rgb(0 0 0 / 14%);*/
}
.ks_list_view_container .card-header{
border: none;
border-radius: 0px;
background: none;
padding: 0 12px 0px;
}
.ks_to_do_card_body{
padding: 0px;
}
.ks_to_do_card_body .form-check {
padding: 0;
margin: -22px 0 0;
}
.form-check .form-check-label {
cursor: pointer;
padding: 0;
position: relative;
}
.ks_custom_check .form-check .form-check-input {
opacity: 0;
height: 0;
width: 0;
overflow: hidden;
position: absolute;
margin: 0;
z-index: -1;
left: 0;
pointer-events: none;
}
.ks_custom_check.form-check .form-check-label span {
display: block;
position: absolute;
left: -1px;
top: -1px;
transition-duration: .2s;
}
.ks_custom_check.form-check .form-check-sign {
vertical-align: middle;
position: relative;
top: -2px;
float: left;
padding-right: 0px;
display: inline-block;
}
.form-check .form-check-sign:before {
display: block;
position: absolute;
left: 0;
content: "";
background-color: rgba(0,0,0,.84);
height: 20px;
width: 20px;
border-radius: 100%;
z-index: 1;
opacity: 0;
margin: 0;
top: 0;
transform: scale3d(2.3,2.3,1);
}
.ks_custom_check .form-check .form-check-input:checked~.form-check-sign .check {
background: #9c27b0;
}
.form-check .form-check-sign .check {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
border: 1px solid rgba(0,0,0,.54);
overflow: hidden;
z-index: 1;
border-radius: 3px;
}
.ks_custom_check .form-check .form-check-input:checked~.form-check-sign .check:before {
color: #fff;
box-shadow: 0 0 0 10px, 10px -10px 0 10px, 32px 0 0 20px, 0 32px 0 20px, -5px 5px 0 10px, 20px -12px 0 11px;
}
.form-check .form-check-sign .check:before {
position: absolute;
content: "";
transform: rotate(45deg);
display: block;
margin-top: -3px;
margin-left: 7px;
width: 0;
color: #fff;
height: 0;
box-shadow: 0 0 0 0, 0 0 0 0, 0 0 0 0, 0 0 0 0, 0 0 0 0, 0 0 0 0, inset 0 0 0 0;
}
.ks_to_do_card_body .ks_description{
text-align: left;
}
.ks_to_do_card_body .td-actions{
width: 70px;
}
.ks_to_do_card_body .ks_custom_check{
width: 30px;
vertical-align: middle;
}
.ks_to_do_card_body .ks_edit_content{
background: none;
border:none;
}
.ks_card_header{
}
.ks_card_header .nav-tabs-wrapper{
width: 100%;
display: flex;
align-items: flex-start;
}
.ks_card_header .nav-tabs-wrapper .header_add_btn{
background: none;
margin-left: auto;
border: none;
background: none;
outline: none;
color: #fff;
}
.ks_do_item_line_through {
text-decoration: line-through;
}

View File

@@ -0,0 +1,150 @@
.ks_toggle_icon_input{
vertical-align: middle;
}
.ks_select_dashboard_item_toggle{
display: flex;
}
.ks_select_icon_div{
margin-right : 45px;
}
/*New Quick Edit View*/
.ks_dashboard_item_button_container{
display: flex;
justify-content: flex-end;
}
.ks_dashboard_quick_edit_action > button{
position: absolute;
right: -30px;
visibility: hidden;
}
.ks_dashboard_quick_edit_action{
z-index: 99;
}
/*UI CHANGES*/
.ks_qe_footer_span{
display: block;
width: 18px;
height: 18px;
-webkit-transform: rotate(-135deg) translateZ(0);
transform: rotate(-135deg) translateZ(0);
outline: 1px solid transparent;
background-color: white;
border-top: 1px solid #c7c7c7;
border-right: 1px solid #c7c7c7;
position: absolute;
left: -9px;
top: 22px;
}
.ks_qe_dropdown_menu{
max-width: 280px;
box-shadow: 3px 7px 12px 1px rgba(0, 0, 0, 0.32);
}
.ks_item_field_info{
/*width: 230px;*/
max-height: 225px;
overflow-y: scroll;
}
.ks_quick_edit_footer{
padding-top: 1rem !important;
padding-bottom: 0.7rem !important;
border-top: 1px solid #dcdada;
display: flex;
justify-content: center;
}
.ks_qe_form_view{
padding-bottom: 0px !important;
padding-top: 0px !important;
}
.ks_qe_form_view > .ks_qe_form_view_group{
margin-bottom: 0 !important;
}
/* width */
.ks_item_field_info::-webkit-scrollbar,
.ks_dashboard_custom_srollbar::-webkit-scrollbar {
width: 7px;
}
/* Track */
.ks_item_field_info::-webkit-scrollbar-track,
.ks_dashboard_custom_srollbar::-webkit-scrollbar-track,
{
background: #f1f1f1;
}
/* Handle */
.ks_item_field_info::-webkit-scrollbar-thumb,
.ks_dashboard_custom_srollbar::-webkit-scrollbar-thumb {
background: #888;
}
/* Handle on hover */
.ks_item_field_info::-webkit-scrollbar-thumb:hover,
.ks_dashboard_custom_srollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
.ks_quick_edit_footer > .ks_discard {
border-color: #dee2e6;
}
@media(min-width: 768px){
.ks_dashboard_item_hover .ks_dashboard_item_header_hover > .dn-setting-panel{
visibility: hidden;
}
.ks_dashboard_item_hover:hover .ks_dashboard_item_header_hover > .dn-setting-panel{
visibility: visible;
}
}
@media(max-width: 768px){
.ks_dashboard_header {
height: 145px !important;
}
.ks_dashboard_top_menu{
margin-top: -6px;
}
}
@media(max-width: 767.8px){
.ks_dashboard_item_name{
font-size: 16px;
}
.ks_dashboard_item_hover .ks_dashboard_item_header_hover > .dn-setting-panel button {
background: transparent;
}
a.dropdown-item.ks_dashboard_item_chart_info.d-none {
display: block !important;
}
.ks_dashboard_item_fa_con {
display: none !important;
}
}
.ks_chart_inner_min_width {
min-width: 10rem;
}
.ks_info_display {
display: block !important;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
/** @odoo-module **/
import { KsItemButton } from '@ks_dashboard_ninja/components/chart_buttons/chart_buttons';
import { CustomDialog } from '@ks_dashboard_ninja/components/charts_insights/chart_insights';
import { patch } from "@web/core/utils/patch";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
patch(KsItemButton.prototype,{
_onButtonClick() {
let dashboard_data = this.env.getDashboardDataAsObj([])
let item = dashboard_data.ks_item_data[this.id]
let openDialog = () => {
this.env.services.dialog.add(CustomDialog,{
ks_dashboard_manager: item.ks_dashboard_manager,
ks_dashboard_items: dashboard_data.ks_dashboard_items_ids,
ks_dashboard_data: dashboard_data,
item: item,
ks_dashboard_item_type: item.ks_dashboard_item_type,
dashboard_data: dashboard_data,
ksdatefilter: 'none',
ks_speak: () => {},
ksDateFilterSelection: '',
pre_defined_filter: {},
custom_filter: {},
title:"Hello",
hideButtons: 0,
current_graph: this.__owl__.parent.component,
getDomainParams: this.env.ksGetParamsForItemFetch,
getDashboardContext: this.env.getContext,
});
}
let self = this
if(!item.ks_ai_analysis){
rpc("/web/dataset/call_kw/ks_dashboard_ninja.arti_int/ks_generate_analysis",{
model: 'ks_dashboard_ninja.arti_int',
method: 'ks_generate_analysis',
args: [[item],[],item.ks_dashboard_id],
kwargs:{context: {explain_items_with_ai: true}},
}).then((result) => {
if (result){
rpc("/web/dataset/call_kw/ks_dashboard_ninja.arti_int/get_ai_explain",{
model: 'ks_dashboard_ninja.arti_int',
method: 'get_ai_explain',
args: [item.id, item.id],
kwargs:{ },
}).then(function(res) {
item.ks_ai_analysis = res
openDialog();
});
}
});
}
else {
openDialog();
}
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/** @odoo-module **/
import { isMobileOS } from "@web/core/browser/feature_detection";
const setCookie = (name, value, days) => {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
export const setObjectInCookie = (name, object, days) => {
var jsonString = JSON.stringify(object);
!isMobileOS() ? setCookie(name, jsonString, days) : '';
}
const getCookie = (name) => {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
export const getObjectFromCookie = (name) => {
var jsonString = getCookie(name);
return !isMobileOS() && jsonString ? JSON.parse(jsonString) : null;
}
export const eraseCookie = (name) => {
document.cookie = name + '=; Max-Age=-99999999; path=/';
}

View File

@@ -0,0 +1,96 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import {RecordAutocomplete} from "@web/core/record_selectors/record_autocomplete";
import { _t } from "@web/core/l10n/translation";
import { Domain } from "@web/core/domain";
import {getFormat,DomainSelectorSingleAutocomplete,DomainSelectorAutocomplete} from "@web/core/tree_editor/tree_editor_autocomplete"
const SEARCH_LIMIT = 7;
const SEARCH_MORE_LIMIT = 320;
//Patching to add uid and mycompany in dropdown
patch(RecordAutocomplete.prototype,{
async loadOptionsSource(name) {
if (this.env.services.action.currentController?.action?.tag === "ks_dashboard_ninja"){
if (this.lastProm) {
this.lastProm.abort(false);
}
this.lastProm = this.search(name, SEARCH_LIMIT + 1);
const nameGets = (await this.lastProm).map(([id, label]) => ([id, label ? label.split("\n")[0] : _t("Unnamed")]));
this.addNames(nameGets);
const options = nameGets.map(([value, label]) => ({value, label}));
if (this.props.resModel !== 'res.company' && !this.props.ks_res_ids?.includes("%UID")){
options.push({value:"%UID",label:"%UID"})
}
if (this.props.resModel === 'res.company' && !this.props.ks_res_ids?.includes("%MYCOMPANY")){
options.push({value:"%MYCOMPANY",label:"%MYCOMPANY"})
}
if (SEARCH_LIMIT < nameGets.length) {
options.push({
label: _t("Search More..."),
action: this.onSearchMore.bind(this, name),
classList: "o_m2o_dropdown_option",
});
}
if (options.length === 0) {
options.push({ label: _t("(no result)"), unselectable: true });
}
return options;
}else{
return await super.loadOptionsSource(...arguments);
}
},
});
RecordAutocomplete.props = {...RecordAutocomplete.props,ks_res_ids:{ type: Array,optional:true}};
//Patching to remove invalid domain uid and mycompany from domain-modal
patch(DomainSelectorAutocomplete.prototype,{
getTags(props, displayNames) {
if (this.env.services.action.currentController?.action?.tag === "ks_dashboard_ninja"){
return props.resIds.map((val, index) => {
const { text, colorIndex } = ksgetFormat(val, displayNames);
return {
text,
colorIndex,
onDelete: () => {
this.props.update([
...this.props.resIds.slice(0, index),
...this.props.resIds.slice(index + 1),
]);
},
};
});
}else{
return super.getTags(...arguments)
}
},
});
patch(DomainSelectorSingleAutocomplete.prototype,{
getDisplayName(props = this.props, displayNames) {
if (this.env.services.action.currentController?.action?.tag === "ks_dashboard_ninja"){
const { resId } = props;
if (resId === false) {
return "";
}
const { text } = ksgetFormat(resId, displayNames);
return text;
}else{
return super.getDisplayName(...arguments)
}
},
});
export const ksgetFormat = (val, displayNames) => {
let text;
let colorIndex;
if (val === '%UID'){
return{text:'%UID',colorIndex:0}
}else if (val === "%MYCOMPANY"){
return{text:'%MYCOMPANY',colorIndex:0}
}else{
return getFormat(val,displayNames)
}
};

View File

@@ -0,0 +1,492 @@
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
import { Component, onWillStart, useState ,onMounted, onWillRender, useRef, onWillPatch } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { renderToString,renderToElement } from "@web/core/utils/render";
import { useService } from "@web/core/utils/hooks";
//import { useSetupAction } from "@web/webclient/actions/action_hook";
import { localization } from "@web/core/l10n/localization";
import { browser } from '@web/core/browser/browser';
import { session } from "@web/session";
import { BlockUI } from "@web/core/ui/block_ui";
import { WebClient } from "@web/webclient/webclient";
import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
//import { patch } from "@web/core/utils/patch";
import { isBrowserChrome, isMobileOS } from "@web/core/browser/feature_detection";
import { loadBundle } from '@web/core/assets';
import { globalfunction } from '@ks_dashboard_ninja/js/ks_global_functions'
import { Ksdashboardtile } from '@ks_dashboard_ninja/components/ks_dashboard_tile_view/ks_dashboard_tile';
//import { Ksdashboardlistview } from '@ks_dashboard_ninja/components/ks_dashboard_list_view/ks_dashboard_list';
import { Ksdashboardtodo } from '@ks_dashboard_ninja/components/ks_dashboard_to_do_item/ks_dashboard_to_do';
import { Ksdashboardkpiview } from '@ks_dashboard_ninja/components/ks_dashboard_kpi_view/ks_dashboard_kpi';
import { Ksdashboardgraph } from '@ks_dashboard_ninja/components/ks_dashboard_graphs/ks_dashboard_graphs';
import { Dialog } from "@web/core/dialog/dialog";
import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user";
export class KsAIDashboardNinja extends Component {
setup() {
this.actionService = useService("action");
this.dialogService = useService("dialog");
this.notification = useService("notification");
this._rpc = rpc
this.dialogService = useService("dialog");
this.header = useRef("ks_dashboard_header");
this.footer = useRef("ks_dashboard_footer");
this.main_body = useRef("ks_main_body");
this.grid_stack = useRef("ks_grid_stack")
this.reload_menu_option = {
reload:this.props.action.context.ks_reload_menu,
menu_id: this.props.action.context.ks_menu_id
};
this.generate_dialog = false;
this.generate_dialog = this.props.action.context.generate_dialog ? true : false;
this.ks_ai_dash_id = this.props.action.context['ks_dash_id'];
this.ks_ai_dash_name = this.props.action.context['ks_dash_name'];
this.ks_ai_del_id =this.props.action.context['ks_delete_dash_id'];
this.ks_mode = 'active';
this.action_manager = parent;
// this.controllerID = params.controllerID;
this.name = "ks_dashboard";
this.ksIsDashboardManager = false;
this.ksDashboardEditMode = false;
this.ksNewDashboardName = false;
this.file_type_magic_word = {
'/': 'jpg',
'R': 'gif',
'i': 'png',
'P': 'svg+xml',
};
this.ksAllowItemClick = true;
//Dn Filters Iitialization
this.date_format = localization.dateFormat
// this.date_format = this.date_format.replace(/\bYY\b/g, "YYYY");
this.datetime_format = localization.dateTimeFormat
// this.is_dateFilter_rendered = false;
this.ks_date_filter_data;
// Adding date filter selection options in dictionary format : {'id':{'days':1,'text':"Text to show"}}
this.ks_date_filter_selections = {
'l_none': _t('Date Filter'),
'l_day': _t('Today'),
't_week': _t('This Week'),
'td_week': _t('Week To Date'),
't_month': _t('This Month'),
'td_month': _t('Month to Date'),
't_quarter': _t('This Quarter'),
'td_quarter': _t('Quarter to Date'),
't_year': _t('This Year'),
'td_year': _t('Year to Date'),
'n_day': _t('Next Day'),
'n_week': _t('Next Week'),
'n_month': _t('Next Month'),
'n_quarter': _t('Next Quarter'),
'n_year': _t('Next Year'),
'ls_day': _t('Last Day'),
'ls_week': _t('Last Week'),
'ls_month': _t('Last Month'),
'ls_quarter': _t('Last Quarter'),
'ls_year': _t('Last Year'),
'l_week': _t('Last 7 days'),
'l_month': _t('Last 30 days'),
'l_quarter': _t('Last 90 days'),
'l_year': _t('Last 365 days'),
'ls_past_until_now': _t('Past Till Now'),
'ls_pastwithout_now': _t('Past Excluding Today'),
'n_future_starting_now': _t('Future Starting Now'),
'n_futurestarting_tomorrow': _t('Future Starting Tomorrow'),
'l_custom': _t('Custom Filter'),
};
// To make sure date filter show date in specific order.
this.ks_date_filter_selection_order = ['l_day', 't_week', 't_month', 't_quarter','t_year',
'td_week','td_month','td_quarter', 'td_year','n_day','n_week', 'n_month', 'n_quarter', 'n_year',
'ls_day','ls_week', 'ls_month', 'ls_quarter', 'ls_year', 'l_week', 'l_month', 'l_quarter', 'l_year',
'ls_past_until_now', 'ls_pastwithout_now','n_future_starting_now', 'n_futurestarting_tomorrow',
'l_custom'
];
this.ks_dashboard_id = this.props.action.params.ks_dashboard_id;
this.gridstack_options = {
staticGrid:true,
float: false,
cellHeight: 80,
styleInHead : true,
// disableOneColumnMode: true,
};
this.ksSelectedgraphid = [];
if (isMobileOS()) {
this.gridstack_options.disableOneColumnMode = false
}
this.gridstackConfig = {};
this.grid = true;
this.chartMeasure = {};
this.chart_container = {};
this.list_container = {};
this.state = useState({
ks_dashboard_name: '',
ks_multi_layout: false,
ks_dash_name: '',
ks_dashboard_manager :false,
date_selection_data: {},
date_selection_order :[],
ks_show_create_layout_option : true,
ks_show_layout :false,
ks_selected_board_id:false,
ks_child_boards:false,
ks_dashboard_data:{},
ks_dn_pre_defined_filters:[],
ks_dashboard_item_length:0,
ks_dashboard_items:[],
ksDateFilterSelection:'none',
pre_defined_filter:{},
custom_filter:{}
})
this.ksChartColorOptions = ['default', 'cool', 'warm', 'neon'];
// this.ksUpdateDashboardItem = this.ksUpdateDashboardItem.bind(this);
this.ksDateFilterSelection = false;
this.ksDateFilterStartDate = false;
this.ksDateFilterEndDate = false;
this.ksUpdateDashboard = {};
$("head").append('<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">');
if(this.props.action.context.ks_reload_menu){
this.trigger_up('reload_menu_data', { keep_open: true, scroll_to_bottom: true});
}
var context = {
ksDateFilterSelection: self.ksDateFilterSelection,
ksDateFilterStartDate: self.ksDateFilterStartDate,
ksDateFilterEndDate: self.ksDateFilterEndDate,
}
this.dn_state = {}
this.dn_state['user_context']=context
onWillStart(this.willStart);
onWillRender(this.dashboard_mount);
onMounted(() => {
this.grid_initiate();
});
}
willStart(){
var self = this;
var def;
if (this.reload_menu_option.reload && this.reload_menu_option.menu_id) {
def = this.getParent().actionService.ksDnReloadMenu(this.reload_menu_option.menu_id);
}
return $.when(def, loadBundle("ks_dashboard_ninja.ks_dashboard_lib")).then(function() {
return self.ks_fetch_data().then(function(){
return self.ks_fetch_items_data()
});
});
}
grid_initiate(){
$(".o_dialog .o_inactive_modal").remove()
if (!this.generate_dialog) {
var self=this;
var $gridstackContainer = $(this.grid_stack.el);
if($gridstackContainer.length){
this.grid = GridStack.init(this.gridstack_options,$gridstackContainer[0]);
if(this.ks_dashboard_data.ks_gridstack_config){
this.gridstackConfig = JSON.parse(this.ks_dashboard_data.ks_gridstack_config);
}
for (var i = 0; i < this.state.ks_dashboard_items.length; i++) {
var graphs = ['ks_scatter_chart','ks_bar_chart', 'ks_horizontalBar_chart', 'ks_line_chart', 'ks_area_chart', 'ks_doughnut_chart','ks_polarArea_chart','ks_pie_chart','ks_flower_view', 'ks_radar_view','ks_radialBar_chart','ks_map_view','ks_funnel_chart','ks_bullet_chart', 'ks_to_do', 'ks_list_view']
var $ks_preview = $('#' + self.state.ks_dashboard_items[i].id)
if ($ks_preview.length) {
if (self.state.ks_dashboard_items[i].id in self.gridstackConfig) {
self.grid.addWidget($ks_preview[0], {x:self.gridstackConfig[self.state.ks_dashboard_items[i].id].x, y:self.gridstackConfig[self.state.ks_dashboard_items[i].id].y, w:self.gridstackConfig[self.state.ks_dashboard_items[i].id].w, h: self.gridstackConfig[self.state.ks_dashboard_items[i].id].h, autoPosition:true, minW:2, maxW:null, minH:2, maxH:null, id:self.state.ks_dashboard_items[i].id});
} else if ( graphs.includes (self.state.ks_dashboard_items[i].ks_dashboard_item_type)) {
self.grid.addWidget($ks_preview[0], {x:0, y:0, w:5, h:5,autoPosition:true,minW:4,maxW:null,minH:3,maxH:null, id :self.state.ks_dashboard_items[i].id});
}else{
self.grid.addWidget($ks_preview[0], {x:0, y:0, w:3, h:2,autoPosition:true,minW:2,maxW:null,minH:2,maxH:2,id:self.state.ks_dashboard_items[i].id});
}
}
}
this.grid.setStatic(true);
}
}
// Events //
const ks_element = this.main_body.el;
Object.values(ks_element.querySelectorAll(".ks_dashboarditem_chart_container")).map((item) => { item.addEventListener('click', this.onkschartcontainerclick.bind(this))})
// Object.values(ks_element.querySelectorAll(".ks_list_view_container")).map((item) => { item.addEventListener('click', this.onkschartcontainerclick.bind(this))})
Object.values(ks_element.querySelectorAll(".ks_dashboard_kpi_dashboard")).map((item) => { item.addEventListener('click', this.onkschartcontainerclick.bind(this))})
$(document.querySelectorAll(".modal-body .ks_dashboard_item_button_container")).remove();
$('#ks_ai_add_item').addClass("d-none");
$(document.querySelectorAll(".modal-header .btn-close")).remove();
$(document.querySelectorAll(".modal-footer .o-default-button")).remove();
let ks_footer = $(renderToElement("ks_ai_dashboard_footer",{
self:this
}))
$(document.querySelectorAll(".modal-footer")).append(ks_footer)
}
getContext() {
var self = this;
var context = {
ksDateFilterSelection: self.ksDateFilterSelection,
ksDateFilterStartDate: self.ksDateFilterStartDate,
ksDateFilterEndDate: self.ksDateFilterEndDate,
}
if(self.dn_state['user_context']['ksDateFilterSelection'] !== undefined && self.ksDateFilterSelection !== 'l_none'){
context = self.dn_state['user_context']
}
var ks_new_obj = {...session.user_context,...{allowed_company_ids:this.env.services.company.activeCompanyIds}}
return Object.assign(context, ks_new_obj);
}
ks_fetch_data(){
var self = this;
return this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_fetch_dashboard_data",{
model: 'ks_dashboard_ninja.board',
method: 'ks_fetch_dashboard_data',
args: [self.ks_dashboard_id],
kwargs : {context: self.getContext()},
}).then(function(result) {
self.ks_dashboard_data = result;
self.ks_dashboard_data['ks_ai_dashboard'] = true
if(self.dn_state['domain_data'] != undefined){
self.ks_dashboard_data.ks_dashboard_domain_data=self.dn_state['domain_data']
Object.values(self.ks_dashboard_data.ks_dashboard_pre_domain_filter).map((x)=>{
if(self.dn_state['domain_data'][x['model']] != undefined){
if(self.dn_state['domain_data'][x['model']]['ks_domain_index_data'][0]['label'].indexOf(x['name']) ==-1){
self.ks_dashboard_data.ks_dashboard_pre_domain_filter[x['id']].active = false;
}
}
else{
self.ks_dashboard_data.ks_dashboard_pre_domain_filter[x['id']].active = false;
}
})
}
});
}
async dashboard_mount(){
var self = this;
var items = self.ksSortItems(self.ks_dashboard_data.ks_item_data)
self.state.ks_dashboard_items = items
self.ksRenderDashboard();
}
ks_fetch_items_data(){
var self = this;
var items_promises = []
self.ks_dashboard_data.ks_dashboard_items_ids.forEach(function(item_id){
items_promises.push(self._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/ks_fetch_item",{
model: "ks_dashboard_ninja.board",
method: "ks_fetch_item",
args : [[item_id], self.ks_dashboard_id, self.ksGetParamsForItemFetch(item_id)],
kwargs:{context: self.getContext()}
}).then(function(result){
self.ks_dashboard_data.ks_item_data[item_id] = result[item_id];
}));
});
self.state.ks_dash_name = self.ks_dashboard_data.name,
self.state.ks_ai_dashboard = true,
self.state.ks_dashboard_manager = self.ks_dashboard_data.ks_dashboard_manager,
self.state.ks_dashboard_data = self.ks_dashboard_data,
self.state.ks_dashboard_item_length = self.ks_dashboard_data.ks_dashboard_items_ids.length
return Promise.all(items_promises)
}
ksGetParamsForItemFetch(){
return {};
}
ksRenderDashboard(){
var self = this;
if (self.ks_dashboard_data.ks_child_boards) self.ks_dashboard_data.name = this.ks_dashboard_data.ks_child_boards[self.ks_dashboard_data.ks_selected_board_id][0];
if (!isMobileOS()) {
$(self.header.el).addClass("ks_dashboard_header_sticky")
}
if (Object.keys(self.ks_dashboard_data.ks_item_data).length===0){
$(self.header.el).find('.ks_dashboard_link').addClass("d-none");
$(self.header.el).find('.ks_dashboard_edit_layout').addClass("d-none");
}
}
ksSortItems(ks_item_data) {
var items = []
var self = this;
var item_data = Object.assign({}, ks_item_data);
if (self.ks_dashboard_data.ks_gridstack_config) {
self.gridstackConfig = JSON.parse(self.ks_dashboard_data.ks_gridstack_config);
var a = Object.values(self.gridstackConfig);
var b = Object.keys(self.gridstackConfig);
for (var i = 0; i < a.length; i++) {
a[i]['id'] = b[i];
}
a.sort(function(a, b) {
return (35 * a.y + a.x) - (35 * b.y + b.x);
});
for (var i = 0; i < a.length; i++) {
if (item_data[a[i]['id']]) {
items.push(item_data[a[i]['id']]);
delete item_data[a[i]['id']];
}
}
}
return items.concat(Object.values(item_data));
}
onKsDuplicateItemClick(e) {
var self = this;
var ks_item_id = $($(e.target).parentsUntil(".ks_dashboarditem_id").slice(-1)[0]).parent().attr('id');
var dashboard_id = $($(e.target).parentsUntil(".ks_dashboarditem_id").slice(-1)[0]).find('.ks_dashboard_select').val();
var dashboard_name = $($(e.target).parentsUntil(".ks_dashboarditem_id").slice(-1)[0]).find('.ks_dashboard_select option:selected').text();
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/copy",{
model: 'ks_dashboard_ninja.item',
method: 'copy',
args: [parseInt(ks_item_id), {
'ks_dashboard_ninja_board_id': parseInt(dashboard_id)
}],
kwargs:{},
}).then(function(result) {
self.notification.add(_t('Selected item is duplicated to ' + dashboard_name + ' .'),{
title:_t("Item Duplicated"),
type: 'success',
});
self.actionService.doAction({
type: "ir.actions.client",
tag: "reload",
});
})
}
onkschartcontainerclick(ev){
if($(ev.currentTarget).hasClass('ks_dashboard_kpi_dashboard')){
if(!($(ev.currentTarget).find('.select-btn').hasClass('active'))){
$('#ks_ai_add_item').removeClass("d-none");
$(ev.currentTarget).find('.select-btn').addClass('active');
this.ksSelectedgraphid.push(parseInt($(ev.currentTarget).parent()[0].id));
}else{
$(ev.currentTarget).find('.select-btn').removeClass('active');
const index = this.ksSelectedgraphid.indexOf(parseInt($(ev.currentTarget).parent()[0].id));
this.ksSelectedgraphid.splice(index, 1);
}
}else{
if(!($(ev.currentTarget).find('.select-btn').hasClass('active'))){
$('#ks_ai_add_item').removeClass("d-none");
$(ev.currentTarget).find('.select-btn').addClass('active');
this.ksSelectedgraphid.push(parseInt($(ev.currentTarget).parent()[0].id));
}else{
$(ev.currentTarget).find('.select-btn').removeClass('active');
const index = this.ksSelectedgraphid.indexOf(parseInt($(ev.currentTarget).parent()[0].id));
this.ksSelectedgraphid.splice(index, 1);
}
}
const selectedCount = this.ksSelectedgraphid.length;
$('#selected_chart_count').text(selectedCount);
if (this.ksSelectedgraphid.length == 0){
$('#ks_ai_add_item').addClass("d-none")
}
}
onselectallitems(){
this.ksSelectedgraphid = []
document.querySelectorAll(".modal-body .ks_list_view_container").forEach((item) =>{
// $(item).addClass('.active')
$(item).find('.select-btn').addClass("active");
this.ksSelectedgraphid.push(parseInt($(item).parent()[0].id))
});
document.querySelectorAll(".modal-body .ks_dashboard_kpi_dashboard").forEach((item) =>{
// $(item).parent().addClass('.active')
$(item).find('.select-btn').addClass("active");
this.ksSelectedgraphid.push(parseInt($(item).parent()[0].id))
});
document.querySelectorAll(".modal-body .ks_dashboarditem_chart_container").forEach((item) =>{
// $(item).addClass('.active')
$(item).find('.select-btn').addClass("active");
this.ksSelectedgraphid.push(parseInt($(item).parent()[0].id))
});
$('#ks_ai_add_item').removeClass("d-none")
$('#ks_ai_remove_all_item').removeClass("d-none")
$('#ks_ai_add_all_item').addClass("d-none")
const selectedCount = this.ksSelectedgraphid.length;
$('#selected_chart_count').text(selectedCount);
}
onremoveallitems(){
document.querySelectorAll(".modal-body .ks_list_view_container").forEach((item) =>{
// $(item).removeClass('ks_img_selected')
// $(item).find('.ks_img_display').addClass("d-none");
$(item).find('.select-btn').removeClass("active");
})
document.querySelectorAll(".modal-body .ks_dashboard_kpi_dashboard").forEach((item) =>{
// $(item).parent().removeClass('ks_img_selected')
// $(item).find('.ks_img_display').addClass("d-none");
$(item).find('.select-btn').removeClass("active");
});
document.querySelectorAll(".modal-body .ks_dashboarditem_chart_container").forEach((item) =>{
$(item).find('.select-btn').removeClass("active");
// $(item).removeClass('ks_img_selected')
// $(item).find('.ks_img_display').addClass("d-none");
});
this.ksSelectedgraphid = [];
$('#ks_ai_add_item').addClass("d-none")
$('#ks_ai_remove_all_item').addClass("d-none")
$('#ks_ai_add_all_item').removeClass("d-none")
$('#selected_chart_count').text(0);
}
onKsaddItemClick(e) {
var self = this;
var dashboard_id = this.ks_ai_dash_id;
var dashboard_name = this.ks_ai_dash_name;
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.item/write",{
model: 'ks_dashboard_ninja.item',
method: 'write',
args: [this.ksSelectedgraphid, {
'ks_dashboard_ninja_board_id': parseInt(dashboard_id)
}],
kwargs:{}
}).then(function(result) {
self.notification.add(_t('Items are added to ' + dashboard_name + ' .'),{
title:_t("Items added"),
type: 'success',
});
$.when(self.ks_fetch_data()).then(function() {
self.onksaideletedash();
});
});
}
onksaideletedash(){
var self= this;
this._rpc("/web/dataset/call_kw/ks_dashboard_ninja.board/unlink",{
model: 'ks_dashboard_ninja.board',
method: 'unlink',
args: [this.ks_ai_del_id],
kwargs:{}
}).then(function(result){
window.location.reload();
});
}
speak_once(ev,item){
}
}
KsAIDashboardNinja.components = { Ksdashboardtile,Ksdashboardgraph,Ksdashboardkpiview, Ksdashboardtodo, Dialog, FormViewDialog};
KsAIDashboardNinja.template = "ksaiDashboardNinjaHeader"
registry.category("actions").add("ks_ai_dashboard_ninja",KsAIDashboardNinja);

View File

@@ -0,0 +1,261 @@
/** @odoo-module **/
import { getCurrency } from "@web/core/currency";
import { formatFloat, formatInteger } from "@web/views/fields/formatters";
import { localization } from "@web/core/l10n/localization";
import { eraseCookie } from "@ks_dashboard_ninja/js/cookies";
export const globalfunction = {
ksNumIndianFormatter(num, digits){
var negative;
var si = [{
value: 1,
symbol: ""
},
{
value: 1E3,
symbol: "Th"
},
{
value: 1E5,
symbol: "Lakh"
},
{
value: 1E7,
symbol: "Cr"
},
{
value: 1E9,
symbol: "Arab"
}
];
if (num < 0) {
num = Math.abs(num)
negative = true
}
var rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
var i;
for (i = si.length - 1; i > 0; i--) {
if (num >= si[i].value) {
break;
}
}
if (negative) {
return "-" + (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol;
} else {
return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol;
}
},
ksNumFormatter(num, digits) {
var negative;
var si = [{
value: 1,
symbol: ""
},
{
value: 1E3,
symbol: "k"
},
{
value: 1E6,
symbol: "M"
},
{
value: 1E9,
symbol: "G"
},
{
value: 1E12,
symbol: "T"
},
{
value: 1E15,
symbol: "P"
},
{
value: 1E18,
symbol: "E"
}
];
if (num < 0) {
num = Math.abs(num)
negative = true
}
var rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
var i;
for (i = si.length - 1; i > 0; i--) {
if (num >= si[i].value) {
break;
}
}
if (negative) {
return "-" + (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol;
} else {
return (num / si[i].value).toFixed(digits).replace(rx, "$1") + si[i].symbol;
}
},
ks_monetary(value, currency_id) {
///get currency changed ////
var currency = getCurrency(currency_id);
if (!currency) {
return value;
}
if (currency.position === "after") {
return value += ' ' + currency.symbol;
} else {
return currency.symbol + ' ' + value;
}
},
_onKsGlobalFormatter(ks_record_count, ks_data_format, ks_precision_digits){
var self = this;
if (ks_data_format == 'exact'){
return formatFloat(ks_record_count, {digits: [0, ks_precision_digits]})
// return field_utils.format.float(ks_record_count, Float64Array,{digits:[0,ks_precision_digits]});
}else{
if (ks_data_format == 'indian'){
return self.ksNumIndianFormatter( ks_record_count, 1);
}else if (ks_data_format == 'colombian'){
return self.ksNumColombianFormatter( ks_record_count, 1, ks_precision_digits);
}else{
return self.ksNumFormatter(ks_record_count, 1);
}
}
},
ksNumColombianFormatter(num, digits, ks_precision_digits) {
var negative;
var si = [{
value: 1,
symbol: ""
},
{
value: 1E3,
symbol: ""
},
{
value: 1E6,
symbol: "M"
},
{
value: 1E9,
symbol: "M"
},
{
value: 1E12,
symbol: "M"
},
{
value: 1E15,
symbol: "M"
},
{
value: 1E18,
symbol: "M"
}
];
if (num < 0) {
num = Math.abs(num)
negative = true
}
var rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
var i;
for (i = si.length-1; i > 0; i--) {
if (num >= si[i].value) {
break;
}
}
if (si[i].symbol === 'M'){
// si[i].value = 1000000;
num = parseInt(num) / 1000000
// changes
num = formatInteger(num)
if (negative) {
return "-" + num + si[i].symbol;
} else {
return num + si[i].symbol;
}
}else{
if (num % 1===0){
// changes
num = formatInteger(num)
}else{
// num = field_utils.format.float(num, Float64Array, {digits:[0,ks_precision_digits]});
num = formatFloat(num, {digits: [0, ks_precision_digits]})
}
if (negative) {
return "-" + num;
} else {
return num;
}
}
}
}
function onAudioEnded (ev){
ev.currentTarget?.parentElement?.querySelector('.voice-cricle')?.classList.toggle("d-none");
ev.currentTarget?.parentElement?.querySelector('.comp-gif')?.classList.toggle("d-none");
}
function ks_get_current_gridstack_config(gridstackRootElement){
if (gridstackRootElement && gridstackRootElement.gridstack){
var items = gridstackRootElement.gridstack.el.gridstack.engine.nodes;
}
let grid_config = {}
if (items){
for (var i = 0; i < items.length; i++) {
grid_config[items[i].id] = {
'x': items[i].x, 'y': items[i].y,
'w': items[i].w, 'h': items[i].h,
}
}
}
return grid_config;
}
function convert_data_to_utc(list_view_data) { // TODO Can be moved to python side for faster computation
list_view_data = JSON.parse(list_view_data);
let datetime_format = localization.dateTimeFormat;
let date_format = localization.dateFormat;
if (list_view_data && list_view_data.type === "ungrouped") {
if (list_view_data.date_index) {
let index_data = list_view_data.date_index;
for (let i = 0; i < index_data.length; i++) {
for (let j = 0; j < list_view_data.data_rows.length; j++) {
var index = index_data[i]
var date = list_view_data.data_rows[j]["data"][index]
if (date) {
if (list_view_data.fields_type[index] === 'date'){
list_view_data.data_rows[j]["data"][index] = luxon.DateTime.fromJSDate(new Date(date + " UTC")).toFormat?.(date_format);
}else if(list_view_data.fields_type[index] === 'datetime'){
list_view_data.data_rows[j]["data"][index] = luxon.DateTime.fromJSDate(new Date(date + " UTC")).toFormat?.(datetime_format);
}
}
// else{
//// list_view_data.data_rows[j]["data"][index] = "";
// }
}
}
}
}
return list_view_data;
}
function eraseAllCookies(dashboard_id, cookie_name_list_to_be_deleted = []){
cookie_name_list_to_be_deleted.forEach( (name) => {
eraseCookie(name + dashboard_id)
});
}
return {
globalfunction: globalfunction,
onAudioEnded,
ks_get_current_gridstack_config,
convert_data_to_utc,
eraseAllCookies
}

View File

@@ -0,0 +1,23 @@
/** @odoo-module **/
import { ActionContainer } from "@web/webclient/actions/action_container";
import { patch } from "@web/core/utils/patch";
import { onPatched } from "@odoo/owl";
patch(ActionContainer.prototype,{
setup(){
super.setup();
onPatched( () => {
if(this?.env.services.menu.getCurrentApp?.()?.xmlid === "ks_dashboard_ninja.board_menu_root" || this.info?.componentProps?.action?.tag === 'ks_dashboard_ninja'){
if(!$('body').hasClass('ks_body_class'))
$('body').addClass('ks_body_class');
}
else if(this?.env.services.menu.getCurrentApp?.()?.xmlid !== "ks_dashboard_ninja.board_menu_root" || this.info?.componentProps?.action?.tag !== 'ks_dashboard_ninja'){
if($('body').hasClass('ks_body_class'))
$('body').removeClass('ks_body_class');
}
});
},
});

View File

@@ -0,0 +1,24 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { onMounted } from "@odoo/owl";
import { renderToString } from "@web/core/utils/render";
import { WarningDialog } from "@web/core/errors/error_dialogs";
patch(ConfirmationDialog.prototype,{
setup(){
super.setup();
onMounted( () => {
let modalBody = this.modalRef?.el?.querySelector('.modal-body');
if(modalBody && (this.env.services.menu?.getCurrentApp()?.xmlid === "ks_dashboard_ninja.board_menu_root" ||
this.env.services.action?.currentController?.action?.tag === 'ks_dashboard_ninja')){
modalBody.innerHTML = renderToString('ks_dashboard_ninja.ksConfirmationDialogBody', {
body: this.props.body,
title: this.props.title,
});
}
});
}
});

View File

@@ -0,0 +1,38 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import { FileUploader } from "@web/views/fields/file_handler";
import { onMounted, useEffect } from "@odoo/owl";
import { renderToString } from "@web/core/utils/render";
patch(FileUploader.prototype,{
setup() {
super.setup();
useEffect(
() => this.changeIcons()
);
},
changeIcons(){
let ks_upload_parent_field_el = this.fileInputRef?.el?.closest('.upload-file-btn');
if(ks_upload_parent_field_el){
let pencil_icon_btn = ks_upload_parent_field_el.querySelector('.fa-pencil');
let fileUploadIcon = ks_upload_parent_field_el.querySelector('.o_select_file_button.btn-primary');
let download_icon_btn = ks_upload_parent_field_el.querySelector('.fa-download');
let trash_icon_btn = ks_upload_parent_field_el.querySelector('.fa-trash');
if(pencil_icon_btn){
pencil_icon_btn.classList.remove('fa', 'fa-pencil');
pencil_icon_btn.innerHTML += renderToString("ks_dashboard_ninja.edit_svg", {})
}
if(download_icon_btn){
download_icon_btn.classList.remove('fa', 'fa-download')
download_icon_btn.innerHTML += renderToString("ks_dashboard_ninja.download_svg", {})
}
if(trash_icon_btn){
trash_icon_btn.classList.remove('fa', 'fa-trash')
trash_icon_btn.innerHTML += renderToString("ks_dashboard_ninja.trash_svg", {})
}
}
},
});

View File

@@ -0,0 +1,25 @@
import { patch } from "@web/core/utils/patch";
import { FormViewDialog } from '@web/views/view_dialogs/form_view_dialog';
import { onMounted } from "@odoo/owl";
patch(FormViewDialog.prototype,{
setup(){
super.setup();
onMounted(()=>{
if(this.props.is_expand_icon_visible){
let expand_icon = this.modalRef.el?.querySelector('.o_expand_button')
expand_icon?.remove?.()
}
});
},
async onExpand(){
if(this.props.is_expand_icon_visible) return;
super.onExpand();
}
});
FormViewDialog.props = {
...FormViewDialog.props,
is_expand_icon_visible : { type: Boolean, optional: true }
}

View File

@@ -0,0 +1,24 @@
import { patch } from "@web/core/utils/patch";
import { FormController } from "@web/views/form/form_controller";
import { eraseAllCookies } from '@ks_dashboard_ninja/js/ks_global_functions';
patch(FormController.prototype,{
async onRecordSaved(record, changes){
if(this.model?.config?.resModel === 'ks_dashboard_ninja.board' && this.model.config.resId){
let field_names = ['ks_dashboard_custom_filters_ids', 'ks_dashboard_defined_filters_ids', 'ks_date_filter_selection',
'ks_default_end_time', 'ks_dashboard_start_date', 'ks_dashboard_end_date']
let is_dn_cookie_related_field_changes = field_names.some(field_name => changes.hasOwnProperty(field_name));
if(is_dn_cookie_related_field_changes)
eraseAllCookies(this.model.config.resId,
['PFilter', 'PFilterDataObj', 'Filter', 'CFilter', 'FilterDateData', 'ChartFilter', 'FFilter']);
// TODO : Apply such functionlity that we donot have to give name of the name of the filter as string to erase cookies Also dont need to remove all cookies"
}
super.onRecordSaved(record, changes);
}
});

View File

@@ -0,0 +1,16 @@
import { patch } from "@web/core/utils/patch";
import { onMounted, useRef } from "@odoo/owl";
import { FormLabel } from "@web/views/form/form_label";
patch(FormLabel.prototype,{
setup(){
this.ksRootRef = useRef("ksRootRef");
onMounted(()=>{
let tooltip = this.ksRootRef.el?.querySelector('.text-info')
if(tooltip && (this.env.model?.config?.resModel.startsWith('ks_dashboard_ninja.' ||
this.env.services.action?.currentController?.action?.tag === 'ks_dashboard_ninja')))
tooltip.innerHTML = '<i class="fa fa-exclamation-circle" aria-hidden="true"></i>'
});
}
});

View File

@@ -0,0 +1,36 @@
.ks_body_class .o_spinner {
padding: 50px;
position: relative;
text-align: center;
img{
display : none;
}
}
.ks_body_class .o_spinner:before {
content: "";
height: 105px;
width: 105px;
margin: -38px auto auto -23px;
position: absolute;
top: 50%;
left: 50%;
border-radius: 100%;
animation: rotation .9s infinite linear;
background: conic-gradient(from 180deg at 50% 50%, #E84A5F 0deg, rgba(232, 74, 95, 0) 360deg);
mask: radial-gradient(circle, transparent 50%, black 51%);
-webkit-mask: radial-gradient(circle, transparent 50%, black 51%);
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
.ks_body_class .o_blockUI {
z-index: 1055 !important;
}

View File

@@ -0,0 +1,80 @@
/** @odoo-module **/
import { LoadingIndicator } from "@web/webclient/loading_indicator/loading_indicator";
import { patch } from "@web/core/utils/patch";
import { browser } from "@web/core/browser/browser";
import { useService } from "@web/core/utils/hooks";
import { BlockUI } from "@web/core/ui/block_ui";
import { useEffect, useRef, xml } from "@odoo/owl";
patch(LoadingIndicator.prototype, {
setup() {
super.setup();
this.shouldBlock = false;
},
requestCall({ detail }) {
if (detail.settings.silent) {
return;
}
if (this.state.count === 0) {
browser.clearTimeout(this.startShowTimer);
this.startShowTimer = browser.setTimeout(() => {
if (this.state.count) {
this.state.show = true;
let ks_active_el = this.env.services.ui.activeElement.querySelector('.chat-ai-box')?.length
if((!ks_active_el) &&( this.env.services.menu?.getCurrentApp()?.xmlid === "ks_dashboard_ninja.board_menu_root" ||
this.env.services.action.currentController?.action?.tag === 'ks_dashboard_ninja' )){
this.blockUITimer = browser.setTimeout(() => {
this.env.services.ui.block();
this.shouldBlock = true;
}, 3000);
}
}
}, 250);
}
this.rpcIds.add(detail.data.id);
this.state.count++;
},
responseCall({ detail }) {
if(this.blockUITimer){
clearTimeout(this.blockUITimer)
if(this.shouldBlock){
this.env.services.ui.unblock();
this.shouldBlock = false;
}
}
if (detail.settings.silent) {
return;
}
this.rpcIds.delete(detail.data.id);
this.state.count = this.rpcIds.size;
if (this.state.count === 0) {
browser.clearTimeout(this.startShowTimer);
this.state.show = false;
}
}
});
//patch(BlockUI.prototype, {
// setup(){
// super.setup();
// this.menuService = useService('menu');
//
// useEffect( () => {
// let spinnerImg = document.querySelector('.o_blockUI .o_spinner img');
// if(spinnerImg && spinnerImg.src){
// spinnerImg.src = "/web/static/img/spin.svg"
// }
// let currentApp = this.menuService?.getCurrentApp();
// if (currentApp && currentApp.xmlid === "ks_dashboard_ninja.board_menu_root"){
// if(spinnerImg && spinnerImg.src){
// spinnerImg.src = "/ks_dashboard_ninja/static/images/loader.gif"
// }
// }
// });
// }
//});

View File

@@ -0,0 +1,25 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { NavBar } from "@web/webclient/navbar/navbar";
patch(NavBar.prototype,{
async adapt(){
if(this.currentApp?.xmlid === "ks_dashboard_ninja.board_menu_root" || this.actionService?.currentController?.action.tag === 'ks_dashboard_ninja'){
if(!$('body').hasClass('ks_body_class'))
$('body').addClass('ks_body_class');
}
else{
if($('body').hasClass('ks_body_class'))
$('body').removeClass('ks_body_class');
}
return super.adapt();
},
});

View File

@@ -0,0 +1,94 @@
.ks_body_class .o_notification_manager {
position: fixed !important;
left: 0 !important;
right: 0 !important;
margin: 0 auto !important;
width: 50% !important;
top: 30px !important;
.o_notification_title {
font-size: $font-18 !important;
font-weight: $f-w-600 !important;
line-height: 25.2px;
text-align: left;
padding: 0 8px !important;
align-self: end;
}
.o_notification_body {
padding: 0 8px !important;
margin-top: 4px !important;
grid-column: 2 / 3;
}
.o_notification_close {
top: 50% !important;
left: auto;
bottom: auto;
right: 20px !important;
transform: translateY(-50%);
border: 1px solid $color-black !important;
border-radius: 50% !important;
height: 29px !important;
width: 29px !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
margin: 0 !important;
}
.border {
border: none !important;
}
.o_notification_content {
font-size: $font-16 !important;
font-weight: $f-w-400 !important;
line-height: 22.4px;
text-align: left;
color: $color-4B5563 !important;
padding-right: 20px;
ul {
padding-left: 0px !important;
display: flex;
align-items: center;
margin-bottom: 0;
flex-wrap: wrap;
li {
list-style: none !important;
&::after {
content: ",\00a0";
}
&:last-child::after {
content: "";
}
}
}
}
.o_notification {
border-radius: 10px !important;
padding: 20px !important;
background-color: $color-white !important;
border: none !important;
border: 3px solid $color-E5E7EB !important;
min-height: 107px;
// display: flex;
// flex-direction: column;
justify-content: center;
display: grid;
width: 100%;
grid-template-columns: 1.4fr 8fr;
grid-template-rows: 1fr;
align-items: center;
svg {
grid-row: 1 / 3;
}
}
}

View File

@@ -0,0 +1,42 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { onMounted, useRef, onWillUnmount } from "@odoo/owl";
import { Notification } from "@web/core/notifications/notification";
import { renderToElement } from "@web/core/utils/render";
patch(Notification.prototype,{
setup(){
super.setup();
this.notificationRef = useRef('notificationRef');
onMounted( () => {
let notificationContainer = this.notificationRef.el ? [this.notificationRef.el] : document.querySelectorAll('.o-main-components-container .o_notification');
if( (this.props.ks_dn_flag && notificationContainer) || (notificationContainer && (this.env.services.menu?.getCurrentApp()?.xmlid === "ks_dashboard_ninja.board_menu_root" ||
this.env.services.action?.currentController?.action?.tag === 'ks_dashboard_ninja'))){
let image = renderToElement('ks_dashboard_ninja.ksNotificationImage', {
type: this.props.type ,
});
notificationContainer.forEach((notification) => {
notification.parentElement.classList.add('ks-dn-website-notification');
notification.prepend(image);
})
}
});
onWillUnmount(() => {
let notificationContainer = this.notificationRef.el ? [this.notificationRef.el] : document.querySelectorAll('.o-main-components-container .o_notification');
if(notificationContainer){
notificationContainer.forEach((notification) => {
notification.parentElement.classList.remove('ks-dn-website-notification');
})
}
});
}
});
Notification.props = {
...Notification.props,
ks_dn_flag: { type: Boolean, optional: true },
};

View File

@@ -0,0 +1,24 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { onMounted } from "@odoo/owl";
import { renderToString } from "@web/core/utils/render";
import { WarningDialog } from "@web/core/errors/error_dialogs";
patch(WarningDialog.prototype,{
setup(){
super.setup();
onMounted( () => {
let modalBody = this.env.services.ui.activeElement?.querySelector('.modal-body');
this.env.services.ui.activeElement?.querySelector('.modal-content')?.classList?.add('error-modal-ks');
if(modalBody && (this.env.services.menu?.getCurrentApp()?.xmlid === "ks_dashboard_ninja.board_menu_root" ||
this.env.services.action?.currentController?.action?.tag === 'ks_dashboard_ninja')){
modalBody.innerHTML = renderToString('ks_dashboard_ninja.ksAccessErrorDialog', {
message: this.message ,
});
}
});
}
});

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,245 @@
.chat-ai-box {
padding-right: 11px;
height: auto;
padding: 0 10px !important;
@include max-992 {
max-height: calc(100vh - 387px);
}
.chat-sec {
max-height: calc(100vh - 350px);
overflow: auto;
@include custom-scrollbar;
.left,
.right {
.title {
font-size: $font-14;
font-weight: $f-w-500;
line-height: 19.6px;
text-align: left;
color: $color-black;
@include max-992 {
font-size: $font-12;
}
}
.answers,
.questions {
font-size: $font-12;
font-weight: $f-w-400;
line-height: 19.2px;
text-align: left;
color: $color-black;
@include max-992 {
font-size: $font-10;
}
}
.user-icon {
height: 30px;
width: 30px;
border-radius: 5px;
font-size: $font-14;
font-weight: $f-w-500;
line-height: 32px;
text-align: left;
color: $color-black;
background-color: $color-white;
}
}
}
.typer-box {
position: absolute;
bottom: 0px;
left: 0;
padding: 12px;
width: 100%;
height: auto !important;
.typer {
border-radius: 16px;
background-color: $color-white;
padding: 10px 46px 10px 10px;
position: relative;
textarea {
min-height: 79px;
border: none;
flex: 1;
resize: none;
position: relative;
padding: 6px;
@include custom-scrollbar;
&:focus {
outline: none;
}
&::placeholder {
font-size: $font-14;
font-weight: $f-w-400;
line-height: 18.52px;
text-align: left;
color: $color-placeholders;
position: absolute;
bottom: 0;
}
}
.chat-logo {
border-radius: 5px;
background-color: $color-E7495E;
height: 30px;
width: 30px;
cursor: pointer;
border: none;
position: absolute;
right: 10px;
&:focus {
outline: none;
border: none;
}
}
}
}
}
.chat_explain_ai {
.ks_chart_container {
height: 100%;
.ks_dashboarditem_chart_container {
height: 100%;
}
}
}
// message-with-ai-ui
.ks_body_class .message_with_ai {
.modal-dialog {
width: 50% !important;
height: calc(100% - 90%) !important;
min-height: calc(100% - 26%) !important;
}
.modal-content {
left: auto !important;
border: 1px solid $color-E5E7EB !important;
background-color: $color-F5F8FB;
}
.modal-header {
border-bottom: 1px solid $color-E5E7EB !important;
}
.chat-ai-box {
height: auto !important;
padding: 0 !important;
.chat-sec {
padding: 0 8px !important;
}
}
}
// not draggable-modal-css
.ks_body_class #chatid {
.modal-dialog {
height: calc(100vh - 75px) !important;
right: 0;
border-radius: 10px !important;
margin: 0 !important;
padding: 0;
position: absolute;
}
height: 100%;
}
// new chat-ai-css
.ks_body_class .answers {
.chart-table-wrapper {
overflow: auto;
max-height: calc(100vh - 615px);
border: 0.66px solid $color-EDEDED;
box-shadow: 1.32px 1.32px 2.64px 0px #0000000F;
border-radius: 10px;
@include custom-scrollbar;
table {
// overflow: hidden;
width: 100%;
border-collapse: collapse;
thead {
tr {
th {
background-color: $color-secondary-bg;
padding: 10px;
font-size: $font-10;
font-weight: $f-w-600;
line-height: 13.88px;
text-align: left;
color: $color-2C2D35;
border-right: 0.66px solid $color-FFFCFC;
position: sticky;
top: 0;
z-index: 20;
}
}
}
tbody {
tr {
td {
font-size: $font-10;
font-weight: $f-w-400;
line-height: 15.86px;
text-align: left;
color: $color-black;
padding: 10px;
border-right: 0.66px solid $color-FFFCFC;
&:nth-child(2) {
color: $color-727378;
}
}
&:nth-of-type(odd)>* {
--bs-table-bg-type: $color-white !important;
}
&:nth-of-type(even)>* {
--bs-table-bg-type: $color-secondary-bg !important;
}
}
}
}
}
}
// chart-insight

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,208 @@
.explain-ai {
// max-height: calc(100vh - 200px);
// overflow: auto;
//
// &.ks_ai_explain_body{
// height: auto !important;
// }
.ks_ai_explain_tile {
box-shadow: none;
}
@include custom-scrollbar;
.row {
margin-bottom: 20px;
row-gap: 16px;
.charts-sec {
border: 0.82px solid $color-E5E7EB;
border-radius: 16px;
min-height: 290px;
height: 100%;
padding: 9px 16px;
display: flex;
justify-content: center;
flex-direction: column;
align-items: start;
.encapsulated-tile-container {
position: relative !important;
min-height: 185px !important;
border-radius:16px !important;
&.layout-6-box {
padding: 0px !important;
}
}
.encap-layout-2-box {
display: flex !important;
flex-direction: column !important;
.layout-2-count {
font-size: $font-24;
font-weight: $f-w-600;
line-height: 32px;
text-align: left;
margin-top: 20px;
margin-left: 10px;
}
.layout-2-icon {
border-bottom-left-radius: 16px;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 16px;
}
.layout-2-name {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 24px;
text-align: left;
margin-left: 10px;
margin-top: 4px;
margin-bottom: 14px;
}
.layout-2-icon {
border-bottom-left-radius: 16px;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 16px;
padding: 17px;
height: 100%;
width: 100%;
span {
font-size: 4rem !important;
}
}
}
& .ks_list_explain {
min-height: 290px;
height: 100%;
max-height: 290px;
}
&.ks_explain_ai {
display: flex;
align-items: center;
justify-content: center;
& .ks_explain_ai_view {
width: 100% !important;
flex: unset !important;
border-radius:16px;
}
}
&.ks_list_card_body {
max-width: 100%;
}
.ks_ai_dashboard_item {
flex: 0 0 100%;
max-width: 100%;
}
.card-body {
width: 100%;
max-width: 100%;
flex: 1;
border: none;
&>div {
width: 100%;
}
}
h3 {
font-size: $font-14;
font-weight: $f-w-500;
line-height: 26.35px;
text-align: left;
color: $color-05004E;
margin-bottom: 12px;
padding: 12px;
}
.chart-preview {
padding: 12px;
width: 100%;
height: 226px;
img {
object-fit: contain;
width: 100%;
height: 100%;
}
}
}
.charts-data {
border: 1px solid $color-E5E7EB;
border-radius: 20px;
min-height: 290px;
font-size: $font-12;
font-weight: $f-w-400;
line-height: 19.2px;
text-align: left;
color: $color-black;
position: relative;
background-color: $color-bg-main;
padding: 18px 14px;
.ks_ai_explanation {
max-width: 100% !important;
}
.charts-content-box {
max-height: 220px;
overflow: auto;
max-width: 100% !important;
.ks_ai_explanation {
max-width: 100% !important;
height: auto !important;
padding: 9px 33px !important;
}
@include custom-scrollbar;
}
.voice-button {
height: 40px;
width: 40px;
border-radius: 50%;
background-color: $color-white;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 16px;
bottom: 16px;
}
}
}
}
.ks_ai_explain_tile {
position: relative;
}
.explain-ai {
&+.ks_explain_todo {
display: none !important;
}
}
.modal-body {
.explain-ai-header {
display: none !important;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
.generated-chart {
row-gap: 12px;
column-gap: 8px;
@media screen and (min-width:992px) {
.col-lg-4 {
padding:0 6px !important;
}
}
.row {
row-gap:12px;
padding:0 24px;
}
.generated-cart-box {
box-shadow: 0px 3.29px 16.47px 0px #EEEEEE80;
border: 0.82px solid $color-E5E7EB;
border-radius: 16px;
min-height: 292px;
background-color: $color-white;
position: relative;
// min-width: 421px;
height:100%;
.select-btn {
position: absolute;
right: 12px;
top: 9px;
z-index: 10;
display: block !important;
#count_selected_btn {
border: initial;
background-color: transparent;
}
svg {
stroke: $color-737791;
}
&.active {
svg {
fill: #6789C6 !important;
stroke: white !important;
}
}
}
.title {
padding: 8px 16px;
position: relative;
h5 {
font-size: $font-14;
font-weight: $f-w-500;
line-height: 26.35px;
text-align: left;
color: $color-05004E;
}
}
.chart-preview {
padding: 8px 12px;
min-height: 228px;
position:relative;
height:100%;
.chart {
height: 100%;
width: 100%;
object-fit: contain;
}
}
}
}
.btn-box {
.select-deselect#ks_ai_add_all_item,.select-deselect#ks_ai_remove_all_item,.select-deselect#ks_close_dialog {
font-size: $font-14;
font-weight: $f-w-500;
line-height: 22.4px;
text-align: left;
// color: $color-151D48;
min-height: 40px;
padding: 0 20px;
background-color: $color-white;
cursor: pointer;
border: none;
color: inherit !important;
&:focus {
outline: none;
border: none;
}
svg {
stroke: #737791;
}
&.active {
svg {
fill: #6789C6;
stroke: white;
}
}
}
}

View File

@@ -0,0 +1,19 @@
/*rtl:begin:ignore*/
.grid-stack > .grid-stack-item {
$gridstack-columns: 36;
min-width: (100% / $gridstack-columns);
@for $i from 1 through $gridstack-columns {
&[data-gs-width='#{$i}'] { width: (100% / $gridstack-columns) * $i; }
&[data-gs-x='#{$i}'] { left: (100% / $gridstack-columns) * $i; }
&[data-gs-min-width='#{$i}'] { min-width: (100% / $gridstack-columns) * $i; }
&[data-gs-max-width='#{$i}'] { max-width: (100% / $gridstack-columns) * $i; }
}
}
/*rtl:end:ignore*/
#dn_fav_filters .active{
background-color: #c9dbf0;
}

View File

@@ -0,0 +1,428 @@
// chat-inbox
.ks_body_class .chart-sec-modal {
.modal {
background-color: transparent;
.modal-dialog {
height: calc(100% - 2%) !important;
right: 0;
border-radius: 10px !important;
margin: 0 !important;
position: absolute;
@include max-575 {
justify-content: end;
}
.modal-content {
top: 71px !important;
height: 100%;
box-shadow: 0px 0px 10px 5px #9C8B8A26;
right: 0px;
border-radius: 10px !important;
border: none !important;
@include max-575 {
height: 92% !important;
width: 80%;
}
.modal-header {
border-radius: 10px 10px 0 0 !important;
}
.modal-body {
>div {
height: 100%;
>div {
height: 100%;
display: flex;
flex-direction: column;
>* {
height: auto;
}
}
}
}
}
}
}
}
.ks_body_class .o-mail-ChatWindow:not(.o-folded) {
height: 100%;
box-shadow: 0px 0px 10px 5px #9C8B8A26;
right: 0px !important;
border-radius: 10px !important;
border: none !important;
width: 600px !important;
max-height: 92vh !important;
.o-mail-Composer{
max-height: 50vh;
}
}
.ks_body_class .modal-backdrop.show {
opacity: 0.2;
}
.ks_body_class {
.modal-dialog {
.modal-content {
border-radius: 16px;
.filter {
display: none !important;
}
@include max-575 {
height: auto !important;
}
.modal-header {
padding: 12px 16px;
background-color: $color-bg-main;
border-radius: 16px 16px 0 0;
border-bottom: 0;
justify-content: start !important;
@include max-575 {
background-color: $color-bg-main !important;
}
.modal-title {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 22.4px;
text-align: left;
color: $color-black;
@include max-575 {
color: $color-black !important;
}
}
.btn-close {
background: url("../../images/icons/close-circle.svg") center/1.5em auto no-repeat;
opacity: 1;
&:focus {
box-shadow: none;
}
}
}
.modal-body {
.encapsulated-tile-container {
min-height: 180px;
position: relative;
}
.minimum-180 {
min-height: 140px;
height: 100%;
}
.ks_list_view_container {
padding: unset !important;
border-radius: unset;
border: unset !important;
}
.main-box {
overflow: unset !important;
height: auto;
}
// for double scroll of modal body on chart screen view
.encapsulated-form-view .o_form_renderer {
max-height: calc(100vh - 316px);
}
// for chart screen double arrow fix
.encapsulated-form-arrow {
.o_dropdown_button::after {
display: none !important;
}
}
.screen-info,
.chart_button_container, .ks_explain_todo {
display: none !important;
}
// max-height: calc(100vh - 200px);
// overflow: auto;
@include custom-scrollbar;
.ks_dashboard_main_content {
max-height: unset !important;
overflow: unset;
}
// delete-modal
.modal-para {
font-size: $font-24;
font-weight: $f-w-500;
line-height: 33.6px;
text-align: center;
color: $color-black;
}
// duplicate dashboard
.duplicate-db {
.o_form_renderer {
display: flex !important;
justify-content: center;
align-items: center;
padding-bottom: 0;
.o_inner_group {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 378.5px;
width: 100%;
label {
margin-bottom: 0 !important;
}
}
}
}
.o_form_label {
font-size: $font-12;
font-weight: $f-w-400;
line-height: 9.66px;
letter-spacing: 0.0015em;
text-align: left;
color: $color-black;
margin-bottom: 8px;
opacity: 1 !important;
&:focus {
border: 1px solid $color-black;
}
}
.ks_dash_row {
.o_wrap_field {
@include max-768 {
margin-bottom: 0 !important;
}
}
.o_cell {
width: 100% !important;
@include max-992 {
margin-bottom: 24px;
}
}
.o_inner_group {
gap: 32px 16px !important;
@include max-992 {
gap: 0 !important;
}
}
}
@include max-768 {
.o_cell {
width: 100% !important;
}
.o_form_view .o_group {
.o_inner_group {
flex-direction: column !important;
}
}
}
.ui-widget.ui-autocomplete {
// position: absolute !important;
// width: 100%;
// max-width: 100% !important;
box-shadow: 0px 4px 12px 0px #00000040;
border-radius: 8px;
border: none;
padding: 8px;
& .ui-menu-item>a {
padding: 8px;
font-size: $font-14;
font-weight: $f-w-400;
line-height: 16px;
text-align: left;
color: $color-paragraph ;
&:hover,
&.ui-state-active {
color: $color-secondary-main;
background-color: $color-E6FCF5;
}
}
}
// dashboard-settings-start
&.o_act_window {
.o_notebook {
margin-top: 20px;
.o_notebook_headers {
padding: 0 16px;
.nav-tabs {
border-bottom: 1px solid $color-E5E7EB;
padding-left: 0;
padding-right: 0;
background-color: transparent;
.nav-link {
font-size: $font-16;
font-weight: $f-w-500;
line-height: 24px;
letter-spacing: 0.01em;
text-align: left;
color: $color-black;
padding: 16px;
border: none;
background-color: transparent;
&.active {
color: $color-E7495E;
border-bottom: 2px solid $color-E7495E;
}
}
}
}
.o_notebook_content {
margin-top: 24px;
.tab-pane {
.o_renderer.table-responsive {
margin: -16px 0;
table {
thead {
tr {
th {
&:last-child {
width: 84px !important;
}
// max-width: none !important;
// width: auto !important;
background-color: $color-secondary-bg;
font-size: $font-16;
font-weight: $f-w-500;
line-height: 18px;
padding: 12px 24px;
text-align: left;
color: $color-paragraph;
@include max-768 {
width: fit-content !important;
max-width: fit-content !important;
}
}
}
}
tbody {
tr {
td {
padding: 16px 24px;
border-bottom: 1px solid $color-EAECF0;
background-color: $color-white !important;
--table-accent-bg: $color-white;
font-size: 14px;
font-weight: 400;
line-height: 20px;
text-align: left;
color: $color-black;
vertical-align: middle;
button {
&.fa-trash-o {
display: flex;
justify-content: center;
align-items: center;
background-color: $color-bg-main !important;
border: 0.38px solid $color-CDAAAA;
border-radius: 50% !important;
height: 30px;
width: 30px;
}
}
a {
font-size: $font-16;
font-weight: $f-w-400;
line-height: 20px;
text-align: left;
color: $color-secondary-main !important;
}
}
}
}
}
}
}
}
}
}
}
.modal-footer {
border: 0;
justify-content: flex-end !important;
.o_form_buttons_edit {
justify-content: end !important;
}
footer {
justify-content: flex-end !important;
button {
@include max-768 {
width: auto !important;
}
}
}
.duplicate-btn-box {
justify-content: center !important;
}
}
}
.favFilterDialog {
.modal-footer {
justify-content: end !important;
}
}
}
}

View File

@@ -0,0 +1,161 @@
// Base colors
$color-black: #241C1D;
$color-white: #FFFFFF;
$color-dark-black: #000000;
$color-nav-bg: #E2E9F0;
// Primary colors
$color-primary-main: #E84A5F;
$color-bg-main: #F5F8FB;
// Secondary colors
$color-secondary-main: #04A9CC;
$color-secondary-bg: #F4FAFF;
// State colors
$color-danger: #EC2D30;
$color-warning: #FFC62B;
$color-success: #0C9D61;
$color-info: #3A70E2;
// Element colors
$color-paragraph: #4b5563;
$color-placeholders: #9ca3af;
$color-E6FCF5: #E6FCF5;
$color-D9F1FD: #D9F1FD;
$color-737791: #737791;
$color-E5E7EB: #E5E7EB;
$color-btn-hover: #ff2341;
$color-FFE2E5: #FFE2E5;
$color-FFF4DE: #FFF4DE;
$color-DCFCE7: #DCFCE7;
$color-F3E8FF: #F3E8FF;
$color-E7495E: #E7495E;
$color-292D32: #292D32;
$color-05004E: #05004E;
$color-D1D5DB: #D1D5DB;
$btn-hover: #DE1D37;
$color-1E1E1E: #1E1E1E;
$color-EAECF0: #EAECF0;
$color-CDAAAA: #CDAAAA;
$color-6789C6: #6789C6;
$color-FFF5F5: #FFF5F5;
$color-7B91B0: #7B91B0;
$color-F5F8FB: #F5F8FB;
$color-F5F8FB :#F5F8FB;
$color-2C2D35: #2C2D35;
$color-EDEDED: #EDEDED;
$color-FFFCFC: #FFFCFC;
$color-727378: #727378;
$color-4B5563: #4B5563;
$color-fff4f4: #fff4f4;
$color-ABC8E7: #ABC8E7;
$color-E95266: #E95266;
$color-597ebe: #597ebe;
// font-weights
$f-w-400: 400;
$f-w-500: 500;
$f-w-600: 600;
$f-w-700: 700;
// font-size
$font-8: 8px;
$font-10: 10px;
$font-12: 12px;
$font-14: 14px;
$font-15: 15px;
$font-16: 16px;
$font-18: 18px;
$font-20: 20px;
$font-22: 22px;
$font-24: 24px;
$font-26: 26px;
$font-28: 28px;
$font-30: 30px;
$font-32: 32px;
$font-34: 34px;
$font-36: 36px;
$font-40: 40px;
// media queris:
@mixin minmax1260 {
@media screen and (min-width:1200px) and (max-width:1300px) {
@content;
}
}
@mixin min768 {
@media screen and (min-width:768px) {
@content;
}
}
@mixin max-1500 {
@media screen and (max-width:1500px) {
@content;
}
}
@mixin max-1260 {
@media screen and (max-width:1260px) {
@content;
}
}
@mixin max-992 {
@media screen and (max-width:992px) {
@content;
}
}
@mixin max-768 {
@media screen and (max-width:768px) {
@content;
}
}
@mixin max-575 {
@media screen and (max-width:575.8px) {
@content;
}
}
@mixin min-1600 {
@media screen and (min-width:1600px) {
@content;
}
}
@mixin custom-scrollbar {
&::-webkit-scrollbar {
width: 5px;
height: 5px;
}
&::-webkit-scrollbar-track {
background-color: var(--color-transparent) !important;
border-radius: 12px;
}
&::-webkit-scrollbar-thumb {
background-color: $color-D1D5DB !important;
border-radius: 12px;
}
}
@mixin max-1500 {
@media screen and (max-width: 1500px) {
@content;
}
}
@mixin max-1600 {
@media screen and (max-width: 1600px) {
@content;
}
}

View File

@@ -0,0 +1,72 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
const { Component,useState,onWillUpdateProps} = owl;
export class KsColorPicker extends Component{
setup(){
var self=this.props;
}
get value(){
return{
'ks_color':this.props.record.data[this.props.name].split(",")[0] || "#376CAE",
'ks_opacity':this.props.record.data[this.props.name].split(",")[1] ||'0.99'
}
}
_ksOnColorChange(ev) {
var new_value=(ev.currentTarget.value.concat("," + this.props.record.data[this.props.name].split(',')[1]));
this.props.record.update({ [this.props.name]: new_value });
}
_ksOnOpacityChange(ev) {
var new_value=(this.props.record.data[this.props.name].split(',')[0].concat("," + event.currentTarget.value));
this.props.record.update({ [this.props.name]: new_value });
}
_ksOnOpacityInputNew(ev){
const newOpacity = ev.currentTarget.value;
const percentage = (newOpacity - ev.currentTarget.min) / (ev.currentTarget.max - ev.currentTarget.min) * 100;
ev.currentTarget.style.background = `linear-gradient(to right, rgba(231, 198, 201, 1) ${percentage}%, #d3d3d3 ${percentage}%)`;
}
_ksOnOpacityInput(ev) {
var self = this;
var color;
if (this.props.name == "ks_background_color") {
color = $('.ks_db_item_preview_color_picker').css("background-color")
$('.ks_db_item_preview_color_picker').css("background-color", self.get_color_opacity_value(color, event.currentTarget.value))
color = $('.ks_db_item_preview_l2').css("background-color")
$('.ks_db_item_preview_l2').css("background-color", self.get_color_opacity_value(color, event.currentTarget.value))
} else if (this.props.name == "ks_default_icon_color") {
color = $('.ks_dashboard_icon_color_picker > span').css('color')
$('.ks_dashboard_icon_color_picker > span').css('color', self.get_color_opacity_value(color, event.currentTarget.value))
} else if (this.props.name == "ks_font_color") {
color = $('.ks_db_item_preview').css("color")
color = $('.ks_db_item_preview').css("color", self.get_color_opacity_value(color, event.currentTarget.value))
}
}
get_color_opacity_value(color, val) {
if (color) {
return color.replace(color.split(',')[3], val + ")");
} else {
return false;
}
}
}
KsColorPicker.template="Ks_color_picker_opacity_view";
KsColorPicker.props = {
...standardFieldProps,
};
export const ksColorPickerField = {
component: KsColorPicker,
supportedTypes: ["char"],
};
registry.category("fields").add('Ks_dashboard_color_picker_owl', ksColorPickerField);

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="Ks_color_picker_opacity_view" owl="1">
<input class="ks_color_picker" type="color" t-att-value="value['ks_color']"
t-on-change="(ev) => this._ksOnColorChange(ev)"/>
<input type="range" t-att-value="value['ks_opacity']" class="ks_color_opacity" name="ks_db_item_opacity"
min="0" max="0.99" step="0.01" t-on-input="(ev) => this._ksOnOpacityInputNew(ev)" t-on-change="(ev) => this._ksOnOpacityChange(ev)"/>
<!-- t-on-input="(ev) => this._ksOnOpacityInput(ev)"/>-->
</t>
</templates>

Some files were not shown because too many files have changed in this diff Show More