pengyuthon wechat

This commit is contained in:
李鹏宇
2024-11-14 19:22:51 +08:00
parent 98a45cfb49
commit 2b94763a96
53 changed files with 1967 additions and 1237 deletions
+6 -3
View File
@@ -53,16 +53,19 @@
'assets': {
'web.assets_backend':[
'ks_dashboard_ninja/static/lib/css/Chart.css',
# 'ks_dashboard_ninja/static/lib/css/Chart.css',
'ks_dashboard_ninja/static/lib/css/gridstack.min.css',
'ks_dashboard_ninja/static/lib/js/gridstack-h5.js',
'ks_dashboard_ninja/static/lib/js/Chart.bundle.min.js',
# 'ks_dashboard_ninja/static/lib/js/Chart.bundle.min.js',
'ks_dashboard_ninja/static/lib/js/chart.umd.min.js',
'ks_dashboard_ninja/static/lib/js/exporting.js',
'ks_dashboard_ninja/static/lib/js/pdfmake.min.js',
'ks_dashboard_ninja/static/src/css/*',
'ks_dashboard_ninja/static/src/scss/*',
'ks_dashboard_ninja/static/src/dashboard/*',
'ks_dashboard_ninja/static/src/items/*',
'ks_dashboard_ninja/static/src/items/**/*',
'ks_dashboard_ninja/static/src/preview/**/*',
'ks_dashboard_ninja/static/src/preview/*',
'ks_dashboard_ninja/static/src/tools/*',
# 'ks_dashboard_ninja/static/src/**/**/*',
],
@@ -11,6 +11,18 @@ from odoo.tools.safe_eval import safe_eval
import locale
from dateutil.parser import parse
def calculate_time(func):
import time
"""
计算函数执行时间的装饰器
"""
def wrapper(*args, **kwargs):
start_time = time.time() # 记录开始时间
result = func(*args, **kwargs) # 执行函数
end_time = time.time() # 记录结束时间
print(f"{func.__name__} 函数执行时间: {end_time - start_time} 秒")
return result
return wrapper
class KsDashboardNinjaBoard(models.Model):
_name = 'ks_dashboard_ninja.board'
@@ -217,6 +229,7 @@ class KsDashboardNinjaBoard(models.Model):
return default_grid_id
@api.model
@calculate_time
def ks_fetch_dashboard_data(self, ks_dashboard_id, ks_item_domain=False):
"""
Return Dictionary of Dashboard Data.
@@ -297,6 +310,7 @@ class KsDashboardNinjaBoard(models.Model):
return dashboard_data
@api.model
@calculate_time
def ks_fetch_item(self, item_list, ks_dashboard_id, params={}):
"""
:rtype: object
@@ -394,8 +408,6 @@ class KsDashboardNinjaBoard(models.Model):
'ks_background_color': rec.ks_background_color,
'ks_font_color': rec.ks_font_color,
'ks_header_bg_color': rec.ks_header_bg_color,
# 'ks_domain': rec.ks_domain.replace('"%UID"', str(
# self.env.user.id)) if rec.ks_domain and "%UID" in rec.ks_domain else rec.ks_domain,
'ks_domain': rec.ks_convert_into_proper_domain(rec.ks_domain, rec, item_domain1),
'ks_dashboard_id': rec.ks_dashboard_ninja_board_id.id,
'ks_icon': rec.ks_icon,
File diff suppressed because it is too large Load Diff
+43 -81
View File
@@ -19,93 +19,55 @@ class KsDashboardNinjaItems(models.Model):
ks_to_do_data = rec._ksGetToDOData()
rec.ks_to_do_data = ks_to_do_data
def getToDoData(self):
return self._ksGetToDOData()
def _ksGetToDOData(self):
""" @return
ks_to_do_data = {
'label': [],
'ks_link': [],
'ks_href_id': [],
'ks_section_id': [],
'ks_content': {},
'ks_content_record_id': {},
'ks_content_active': {}
"items":[
{"item_id": item_id1, "label": label1, "lines": [{"id": id1, "content": content1, "active": active},{"id": id2, "content": content1, "active": active}] ,
{"item_id": item_id2,"label": label2,"lines": [{"id": id3, "content": content2, "active": active},...],
]
}
if self.ks_dn_header_lines:
for ks_dn_header_line in self.ks_dn_header_lines:
ks_to_do_header_label = ks_dn_header_line.ks_to_do_header[:]
ks_to_do_data['label'].append(ks_to_do_header_label)
ks_dn_header_line_id = str(ks_dn_header_line.id)
if type(ks_dn_header_line.id).__name__ != 'int' and ks_dn_header_line.id.ref != None:
ks_dn_header_line_id = ks_dn_header_line.id.ref
if ' ' in ks_dn_header_line.ks_to_do_header:
ks_temp = ks_dn_header_line.ks_to_do_header.replace(" ", "")
ks_to_do_data['ks_link'].append('#' + ks_temp + ks_dn_header_line_id)
ks_to_do_data['ks_href_id'].append(ks_temp + str(ks_dn_header_line.id))
"""
elif ks_dn_header_line.ks_to_do_header[0].isdigit():
ks_temp = ks_dn_header_line.ks_to_do_header.replace(
ks_dn_header_line.ks_to_do_header[0], 'z')
ks_to_do_data['ks_link'].append('#' + ks_temp + ks_dn_header_line_id)
ks_to_do_data['ks_href_id'].append(ks_temp + str(ks_dn_header_line.id))
if not self.ks_dn_header_lines:
return False
ks_to_do_data = {
'items': [],
}
items = []
for record in self.ks_dn_header_lines:
lines = []
for line in record.ks_to_do_description_lines:
if isinstance(line.id, int):
line_id = line.id
else:
ks_to_do_data['ks_link'].append('#' + ks_dn_header_line.ks_to_do_header + ks_dn_header_line_id)
ks_to_do_data['ks_href_id'].append(ks_dn_header_line.ks_to_do_header + str(ks_dn_header_line.id))
ks_to_do_data['ks_section_id'].append(str(ks_dn_header_line.id))
if len(ks_dn_header_line.ks_to_do_description_lines):
for ks_to_do_description_line in ks_dn_header_line.ks_to_do_description_lines:
if ' ' in ks_dn_header_line.ks_to_do_header or ks_dn_header_line.ks_to_do_header[0].isdigit():
if ks_to_do_data['ks_content'].get(ks_temp +
str(ks_dn_header_line.id), False):
ks_to_do_data['ks_content'][ks_temp +
str(ks_dn_header_line.id)].append(
ks_to_do_description_line.ks_description)
ks_to_do_data['ks_content_record_id'][ks_temp +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.id))
ks_to_do_data['ks_content_active'][ks_temp +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.ks_active))
else:
ks_to_do_data['ks_content'][ks_temp +
str(ks_dn_header_line.id)] = [
ks_to_do_description_line.ks_description]
ks_to_do_data['ks_content_record_id'][ks_temp +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.id)]
ks_to_do_data['ks_content_active'][ks_temp +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.ks_active)]
else:
if ks_to_do_data['ks_content'].get(ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id), False):
ks_to_do_data['ks_content'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)].append(
ks_to_do_description_line.ks_description)
ks_to_do_data['ks_content_record_id'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.id))
ks_to_do_data['ks_content_active'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)].append(
str(ks_to_do_description_line.ks_active))
else:
ks_to_do_data['ks_content'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)] = [
ks_to_do_description_line.ks_description]
ks_to_do_data['ks_content_record_id'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.id)]
ks_to_do_data['ks_content_active'][ks_dn_header_line.ks_to_do_header +
str(ks_dn_header_line.id)] = [
str(ks_to_do_description_line.ks_active)]
ks_to_do_data = json.dumps(ks_to_do_data)
else:
ks_to_do_data = False
return ks_to_do_data
try:
line_id = line.ids[0]
except IndexError:
line_id = 0
lines.append({
"id": line_id,
"ks_description": line.ks_description,
"ks_active": line.ks_active
})
if isinstance(record.id, int):
record_id = record.id
else:
try:
record_id = record.ids[0]
except IndexError:
record_id = 0
items.append({
"item_id": record_id,
"label": record.ks_to_do_header,
"lines": lines
})
ks_to_do_data['items'] = items
return json.dumps(ks_to_do_data)
class KsToDoheaders(models.Model):
File diff suppressed because one or more lines are too long
@@ -86,6 +86,7 @@
left: 8px;
right: 8px;
bottom: 8px;
/*max-height: 24rem;*/
}
@@ -3,6 +3,14 @@
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.DashboardAction" >
<style>
.modal-lg, .modal-xl {
max-width: 96%;
padding: 2% 0;
border-radius: 10px;
}
</style>
<div class="o_action ks_dashboard_ninja d-flex flex-column">
<t t-if="ref.dashboardData">
<KsDashboard dashboardName="session.name"
+45 -18
View File
@@ -1,6 +1,5 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { itemProps } from "../item_props";
import { get_rgba_format, mainBodyStyle } from "@ks_dashboard_ninja/dashboard/utils";
@@ -28,6 +27,36 @@ export class KsKpi extends Component {
this.count_2 = this.kpi_data[1] ? this.kpi_data[1]?.record_data : undefined;
this.target_1 = this.kpi_data[0]?.target;
this.deviation = Math.round((this.difference / this.target_1) * 100)
if (this.deviation !== Infinity) {
this.deviation = this.deviation ? this.deviation + '%' : 0 + '%';
}
this.previous_period_data = this.kpi_data[0].previous_period;
this.pre_diffrence = (this.count_1 - this.previous_period_data);
const { ks_multiplier_active, ks_multiplier } = this.props.record
if (ks_multiplier_active){
this.previous_period_data = this.kpi_data[0].previous_period * ks_multiplier;
this.pre_diffrence = (this.count_1 * ks_multiplier - this.previous_period_data);
}
this.pre_achieve = this.pre_diffrence > 0;
this.pre_diffrence = Math.abs(this.pre_diffrence);
this.pre_deviation = this.previous_period_data ? parseInt((this.pre_diffrence / this.previous_period_data) * 100) + '%' : "100%"
}
onClickAction() {
const { name, ks_model_name, ks_domain } = this.props.record;
const action = {
name: name,
type: 'ir.actions.act_window',
res_model:ks_model_name,
domain: ks_domain,
views: [[false, 'tree']],
view_mode: 'tree',
target: 'current',
};
this.action.doAction(action);
}
get difference() {
@@ -44,14 +73,24 @@ export class KsKpi extends Component {
return this.difference >= 0;
}
get target_arrow() {
get targetArrow() {
if (this.deviation === Infinity) {
return ;
}
return this.achieve ? 'up' : 'down';
}
get pre_deviation() {
get preArrow() {
return this.pre_achieve ? 'up' : 'down';
}
get preDeviationColor() {
return this.pre_achieve ? 'color: green' : 'color: red'
}
get targetDeviationColor() {
return this.achieve ? 'color: green' : 'color: red'
}
get container_class() {
return "grid-stack-item ui-draggable-disabled ui-resizable-disabled"
@@ -82,19 +121,7 @@ export class KsKpi extends Component {
return mainBodyStyle(rgba_background_color, rgba_font_color, this.props.record).background_style;
}
onClickAction() {
const action = {
name: this.props.record.ks_model_display_name,
type: 'ir.actions.act_window',
res_model: this.props.record.ks_model_name,
// res_id: this.props.dashboard_id,
// context: {'create':false},
domain: this.props.record.ks_domain,
views: [[false, 'tree']],
view_mode: 'tree',
target: 'current',
};
this.action.doAction(action);
get ks_icon_url() {
return `data:image/png;base64,${this.props.record.ks_icon}`;
}
}
@@ -35,10 +35,10 @@
<span>vs Target</span>
</div>
<div>
<span style="font-size : medium;">
<t t-esc="item.deviation"/>
<span style="font-size : medium;" t-att-style="targetDeviationColor">
<t t-esc="deviation"/>
<t t-if="achieve">
<i t-att-class="'fa fa-arrow-'+ target_arrow"/>
<i t-att-class="'fa fa-arrow-'+ targetArrow"/>
</t>
</span>
</div>
@@ -48,9 +48,10 @@
<span>vs Prev</span>
</div>
<div>
<span style="font-size : medium;">
<span style="font-size : medium;"
t-att-style="preDeviationColor">
<t t-esc="pre_deviation"/>
<i t-att-class="'fa fa-arrow-'+ pre_arrow"/>
<i t-att-class="'fa fa-arrow-'+ preArrow"/>
</span>
</div>
</t>
@@ -1,12 +1,10 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { itemProps } from "../item_props";
import { get_rgba_format, mainBodyStyle } from "@ks_dashboard_ninja/dashboard/utils";
import { KsToolBar } from "../tool_bar";
const { Component, onWillStart, onWillUpdateProps, useRef, onMounted, useState, onWillUnmount } = owl;
const { Component } = owl;
export class KsTile extends Component {
@@ -25,6 +23,18 @@ export class KsTile extends Component {
this.action = useService('action');
}
onClickAction() {
const { name, ks_model_name, ks_domain } = this.props.record;
this.action.doAction({
name: name,
type: 'ir.actions.act_window',
res_model: ks_model_name,
domain: ks_domain,
views: [[false, 'tree']],
view_mode: 'tree',
target: 'current',
});
}
get container_class() {
return "grid-stack-item ui-draggable-disabled ui-resizable-disabled"
@@ -55,18 +65,5 @@ export class KsTile extends Component {
return mainBodyStyle(rgba_background_color, rgba_font_color, this.props.record).background_style;
}
onClickAction() {
this.action.doAction({
name: this.props.record.ks_model_display_name,
type: 'ir.actions.act_window',
res_model: this.props.record.ks_model_name,
// res_id: this.props.dashboard_id,
// context: {'create':false},
domain: this.props.record.ks_domain,
views: [[false, 'tree']],
view_mode: 'tree',
target: 'current',
});
}
}
+123 -27
View File
@@ -1,10 +1,12 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { itemProps } from "../item_props";
import { get_rgba_format, mainBodyStyle } from "@ks_dashboard_ninja/dashboard/utils";
import { KsToolBar } from "../tool_bar";
import {useService} from "@web/core/utils/hooks"
import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
import {_t} from "@web/core/l10n/translation";
import {itemProps} from "../item_props";
import {get_rgba_format, mainBodyStyle} from "@ks_dashboard_ninja/dashboard/utils";
import {KsToolBar} from "../tool_bar";
import {TodoTaskDialog} from "./todo_task_dialog";
const { Component, onWillStart, onWillUpdateProps, useRef, onMounted, useState, onWillUnmount } = owl;
@@ -18,34 +20,142 @@ export class KsTODO extends Component {
};
static components = {
KsToolBar
KsToolBar,
TodoTaskDialog
}
setup() {
this.action = useService('action');
this.orm = useService("orm");
this.action = useService("action");
this.notification = useService("notification");
this.dialog = useService("dialog");
this.state = useState({
tab: 0,
labels: null,
tableData: null,
});
onMounted(() => {
this.prepareTableData();
});
}
get todoData() {
return JSON.parse(this.props.record.ks_to_do_data);
}
set todoData(data) {
this.props.record.ks_to_do_data = data;
}
prepareTableData() {
const { items } = this.todoData;
this.state.labels = items.map(item => [item.item_id, item.label]);
this.state.tableData = items[this.state.tab];
}
onTabClick(index) {
this.state.tab = index;
this.prepareTableData();
}
createContent() {
this.dialog.add(TodoTaskDialog, {
title: _t('Create Task'),
body: _t('Create Task'),
mode: 'create',
cancel: () => {},
confirm: (content) => this.doCreate(content),
});
}
async doCreate(content) {
const result = await this.orm.create('ks_to.do.description', [{ks_to_do_header_id: this.state.tableData.item_id, ks_description: content}])
const newItem = {
id: result[0],
ks_description: content,
ks_active: true,
}
this.state.tableData.lines.push(newItem);
this.todoData = await this.loadTodoData();
}
editContent(id) {
this.dialog.add(TodoTaskDialog, {
title: _t('Edit Task'),
body: _t('Edit Task'),
mode: 'edit',
record: this.state.tableData.lines.filter(line => line.id === id),
cancel: () => {},
confirm: (content) => this.doEdit(id, content),
});
}
async doEdit(id, content) {
await this.orm.write('ks_to.do.description', [id], { ks_description: content });
// document.getElementById(`todoTask_${id}`).innerText = content;
this.todoData = await this.loadTodoData();
this.prepareTableData();
}
deleteContent(id) {
this.dialog.add(ConfirmationDialog, {
body: _t('Do you really want to delete this task?'),
cancel: () => {},
confirm: () => this.doDelete(id),
});
}
async doDelete(id) {
const res = await this.orm.unlink('ks_to.do.description', [id])
this.state.tableData.lines = this.state.tableData.lines.filter(line => line.id !== id)
this.todoData = await this.loadTodoData()
if (res) {
this.notification.add(
_t('Selected task is deleted.'),
{
title: _t("Task Deleted"),
type: "success",
}
);
}
}
async loadTodoData() {
return await this.orm.call('ks_dashboard_ninja.item', 'getToDoData', [this.props.record.id]);
}
async doCheckBox(id, active) {
await this.orm.write('ks_to.do.description', [id], { ks_active: !active });
this.todoData = await this.loadTodoData();
this.prepareTableData();
}
get container_class() {
return "grid-stack-item ui-draggable-disabled ui-resizable-disabled"
return "grid-stack-item ui-draggable-disabled ui-resizable-disabled";
}
get inner_container_class() {
return "grid-stack-item-content"
return "grid-stack-item-content";
}
get rgba_button_color() {
return get_rgba_format(this.props.record.ks_button_color)
return get_rgba_format(this.props.record.ks_button_color);
}
get rgba_background_color() {
return get_rgba_format(this.props.record.ks_background_color)
return get_rgba_format(this.props.record.ks_background_color);
}
get header_bg_color() {
return get_rgba_format(this.props.record.ks_header_bg_color);
}
get rgba_font_color() {
return get_rgba_format(this.props.record.ks_font_color)
return get_rgba_format(this.props.record.ks_font_color);
}
get rgba_default_icon_color() {
return get_rgba_format(this.props.record.ks_default_icon_color)
return get_rgba_format(this.props.record.ks_default_icon_color);
}
get style_main_body() {
@@ -54,18 +164,4 @@ export class KsTODO extends Component {
return mainBodyStyle(rgba_background_color, rgba_font_color, this.props.record).background_style;
}
onClickAction() {
this.action.doAction({
name: this.props.record.ks_model_display_name,
type: 'ir.actions.act_window',
res_model: this.props.record.ks_model_name,
// res_id: this.props.dashboard_id,
// context: {'create':false},
domain: this.props.record.ks_domain,
views: [[false, 'tree']],
view_mode: 'tree',
target: 'current',
});
}
}
+118 -31
View File
@@ -4,43 +4,130 @@
<t t-name="ks_dashboard_ninja.todo" >
<t t-set="item" t-value="props?.record"/>
<!-- <div >-->
<div t-att-id="item.id" t-on-click="onClickAction"
t-att-class="'ks_dashboarditem_id ks_dashboard_item ks_dashboard_item_hover ks_item_click ' + inner_container_class"
t-att-style="style_main_body"
t-att-title="item.ks_info">
<div class="ks_dashboard_item_button_container ks_dropdown_container ks_dashboard_item_header ks_dashboard_item_header_hover"
t-att-data-item_id="item.id">
<KsToolBar item="props?.record"
dashboardList="props.dashboardList"
doRefresh.bind="props.doRefresh"/>
</div>
<!-- <div class="grid-stack-item" t-att-id="item.id">-->
<div class="grid-stack-item-content ks_list_view_container ks_dashboard_item_hover card shadow"
t-att-title="item.ks_info" t-att-id="item.id">
<div class="ks_card_header" t-att-style=" 'background-color:' + header_bg_color ">
<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" t-att-title="item.name">
<t t-esc="item.name"/>
</h6>
</div>
<div class="ks_dashboard_item_main_body">
<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_name" t-att-title="item.name">
<t t-esc="item.name"/>
</div>
<div class="ks_dashboard_item_domain_count" t-att-title="count">
<t t-esc="item.ks_record_count"/>
<div class="ks_dashboard_item_button_container ks_dropdown_container ks_dashboard_item_header ks_dashboard_item_header_hover"
t-att-data-item_id="item.id">
<KsToolBar item="props?.record"
dashboardList="props.dashboardList"
doRefresh.bind="props.doRefresh"/>
</div>
</div>
<t t-call="todo_card_header" />
</div>
<div class="ks_to_do_card_body card-body table-responsive">
<t t-call="ks_to_do_dashboard_inner_container" />
</div>
</div>
<!-- </div>-->
</t>
<t t-name="todo_card_header">
<div class="card-header">
<div class="nav-tabs-navigation">
<div class="nav-tabs-wrapper">
<t t-if="state.labels">
<ul class="nav nav-tabs" data-tabs="tabs" style="overflow:overlay">
<t t-foreach="state.labels" t-as="label" t-key="label_index">
<li class="nav-item">
<a class="nav-link"
t-att-style="'color:' + rgba_font_color"
t-attf-class="{{ label_index == state.tab ? 'active': '' }}"
t-att-data-item-id="item.id"
t-att-data-section-id="label[label_index]"
data-toggle="pill"
t-att-href="label[1]"
t-on-click.prevent.stop="() => this.onTabClick(label_index)"
>
<t t-esc="label[1]"/>
</a>
</li>
</t>
</ul>
<button class="header_add_btn"
t-att-data-item-id="item.id"
t-att-style="'color:'+ rgba_button_color + ' !important' + ';'"
t-on-click="createContent"
>
<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>
</t>
<t t-name="ks_to_do_dashboard_inner_container">
<div class="container-fluid p-0">
<div class="card">
<t t-if="state.tableData?.lines.length > 0">
<div class="card-body">
<div class="tab-content">
<table class="table">
<t t-foreach="state.tableData?.lines" t-as="data" t-key="data_index">
<tr>
<td class="ks_custom_check">
<div class="form-check" t-on-click="() => this.doCheckBox(data.id, data.ks_active)">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" t-att-checked="!data.ks_active"/>
<span class="form-check-sign ks_do_item_active_handler"
t-att-data-item-id="data.id">
<span class="check"/>
</span>
</label>
</div>
</td>
<td class="ks_description"
t-att-id="'todoTask_' + data.id"
t-attf-class="{{ data.ks_active ? '': 'ks_do_item_line_through' }}"
t-esc="data.ks_description" />
<td class="td-actions" style="text-align:right; padding-right:0">
<button type="button" rel="tooltip"
t-att-data-item-id="data.id"
class="btn-link btn-sm ks_edit_content"
t-on-click="() => this.editContent(data.id)"
data-original-title="Edit Task">
<span class="fa fa-lg fa-pencil"/>
</button>
</td>
<td class="td-actions" style="text-align:left; padding-left:0">
<button type="button"
t-att-data-item-id="data.id"
class="btn btn-danger btn-link btn-sm ks_delete_content"
t-on-click="() => this.deleteContent(data.id)"
data-original-title="Remove">
<i class="fa fa-trash-o"/>
</button>
</td>
</tr>
</t>
</table>
</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>
</t>
</templates>
@@ -0,0 +1,40 @@
/** @odoo-module */
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
const { useState, useRef } = owl;
export class TodoTaskDialog extends ConfirmationDialog {
static template = "ks_dashboard_ninja.todoTaskDialog";
static props = {
...ConfirmationDialog.props,
record: { type: Object, optional: true },
mode: { type: String }
}
setup() {
super.setup();
this.todoInput = useRef('todoInput');
this.todoTask = useState({
id: '',
content: ''
});
}
confirm() {
this.props.confirm();
this.props.close();
}
editConfirm() {
const content = this.todoInput.el.value;
this.props.confirm(content);
this.props.close();
}
createConfirm() {
const content = this.todoInput.el.value;
this.props.confirm(content);
this.props.close();
}
}
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.todoTaskDialog">
<Dialog size="'md'" title="props.title" modalRef="modalRef">
<div class="mb-3">
<input type="text" class="form-control"
t-if="props.mode === 'create'"
t-ref="todoInput"
/>
<input type="text" class="form-control"
t-att-value="props.record[0].ks_description"
t-if="props.mode === 'edit'"
t-ref="todoInput"/>
</div>
<t t-set-slot="footer">
<div class="btn btn-primary"
t-if="props.mode === 'edit'"
t-on-click="() => this.editConfirm()">Confirm</div>
<div class="btn btn-primary"
t-if="props.mode === 'create'"
t-on-click="() => this.createConfirm()">Confirm</div>
<div class="btn btn-secondary" t-on-click="_cancel">Cancel</div>
</t>
</Dialog>
</t>
</templates>
@@ -6,13 +6,14 @@ const { Component, onWillStart, onMounted } = owl;
export class KsAreaChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'line';
onMounted(() => {
this.renderChart();
})
}
}
@@ -6,58 +6,16 @@ const { Component, onWillStart, onMounted } = owl;
export class KsBarChart extends KsChartBase {
static template = 'ks_dashboard_ninja.barChart';
setup() {
super.setup();
this.type = 'bar';
onWillStart(() => {
this.options = {
scales: {
y: {
beginAtZero: true
}
}
}
this.data = {
labels: ['1', '2', '3', '4', '5', '6', '7'],
datasets: [{
label: 'My First Dataset',
data: [65, 59, 80, 81, 56, 55, 40],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 205, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(201, 203, 207, 0.2)'
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)'
],
borderWidth: 1
}]
};
})
onMounted(() => {
const ctx = document.getElementById(`barChart_${this.props.record.id}`);
new Chart(ctx, {
type: this.type,
data: this.data,
options: this.options
})
this.renderChart();
})
}
}
}
@@ -6,13 +6,13 @@ const { Component, onWillStart, onMounted } = owl;
export class KsBulletChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
onMounted(() => {
this.renderChart();
})
}
}
@@ -8,61 +8,20 @@
<div class="d-flex align-items-center w-50">
<h6 t-esc="props?.record.name"/>
</div>
<div class="ks_dashboard_item_button_container ks_dropdown_container ks_dashboard_item_header ks_dashboard_item_header_hover chart_button_container d-md-flex d-none">
<div class="ks_dashboard_item_button_container ks_dropdown_container ks_dashboard_item_header
ks_dashboard_item_header_hover chart_button_container d-md-flex d-none">
<KsToolBar item="props?.record"
dashboardList="props.dashboardList"
doRefresh.bind="props.doRefresh"/>
</div>
</div>
<div class="card-body ks_chart_card_body">
<div class="card-body ks_chart_card_body d-flex flex-row align-items-center justify-content-center">
<canvas t-att-id=" props.record.ks_dashboard_item_type + '_' + props.record.id" >
</canvas>
</div>
</div>
</t>
<t t-name="ks_dashboard_ninja.barChart" t-inherit="ks_dashboard_ninja.chartBase" t-inherit-mode="primary">
<xpath expr="//div[hasclass('card-body')]" position="inside">
<canvas t-att-id="'barChart_' + props.record.id">
</canvas>
</xpath>
</t>
<t t-name="ks_dashboard_ninja.lineChart" t-inherit="ks_dashboard_ninja.chartBase" t-inherit-mode="primary">
<xpath expr="//div[hasclass('card-body')]" position="inside">
<canvas t-att-id="'lineChart_' + props.record.id">
</canvas>
</xpath>
</t>
<t t-name="ks_dashboard_ninja.pieChart" t-inherit="ks_dashboard_ninja.chartBase" t-inherit-mode="primary">
<xpath expr="//div[hasclass('card-body')]" position="inside">
<canvas t-att-id="'pieChart_' + props.record.id">
</canvas>
</xpath>
</t>
<t t-name="ks_dashboard_ninja.scatterChart" t-inherit="ks_dashboard_ninja.chartBase" t-inherit-mode="primary">
<xpath expr="//div[hasclass('card-body')]" position="inside">
<canvas t-att-id="'scatterChart_' + props.record.id">
</canvas>
</xpath>
</t>
<t t-name="ks_dashboard_ninja.doughnutChart" t-inherit="ks_dashboard_ninja.chartBase" t-inherit-mode="primary">
<xpath expr="//div[hasclass('card-body')]" position="inside">
<!-- <div class="chart-canvas">-->
<canvas t-att-id="'doughnutChart_' + props.record.id" class="canvas-box">
</canvas>
<!-- </div>-->
</xpath>
</t>
</templates>
@@ -2,11 +2,12 @@
import { itemProps } from "../item_props";
import { KsToolBar } from "../tool_bar";
const { Component } = owl;
const { Component, onWillUnmount } = owl;
export class KsChartBase extends Component {
static template = 'ks_dashboard_ninja.chartBase';
static props = {
...itemProps,
doRefresh: { type: Function, optional: true },
@@ -16,90 +17,36 @@ export class KsChartBase extends Component {
setup() {
this.chartData = JSON.parse(this.props.record.ks_chart_data);
this.chartIns = null;
this.type = '';
this.data = {};
this.options = {};
this.data = {
labels: this.chartData.labels ?? [],
datasets: this.chartData.datasets ?? '',
}
this.options = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
}
}
this.plugins = [];
}
renderGraph(item) {
let self = this;
let chart_data = JSON.parse(item.ks_chart_data);
let isDrill = item.isDrill ? item.isDrill : false;
let chart_id = item.id;
let chart_title = item.name;
let chart_type = item.ks_dashboard_item_type.split('_')[1];
let chart_family;
switch (chart_type) {
case "pie":
case "doughnut":
case "polarArea":
chart_family = "circle";
break;
case "radar":
chart_family = "circle";
break;
case "bar":
case "horizontalBar":
case "line":
case "area":
chart_family = "square"
break;
case "scatter":
chart_family = "square"
break;
default:
chart_family = "none";
break;
}
let ks_description
if (item.ks_info){
ks_description = item.ks_info.split('\n');
ks_description = ks_description.filter(element => element !== '')
}else {
ks_description = false;
}
let container_data = this.ks_container_option(chart_title, self.ks_dashboard_data.ks_dashboard_manager, true, self.ks_dashboard_data.ks_dashboard_list, chart_id, chart_family, chart_type, this.ksChartColorOptions, ks_description, item.ks_company, item.ks_dashboard_item_type);
let $ks_gridstack_container = $(QWeb.render('ks_gridstack_container', container_data)).addClass('ks_dashboarditem_id');
self.ks_set_selected_color_pallet($ks_gridstack_container, item);
let ksLayoutGridId = $(self.$el[0]).find('.ks_layout_selected').attr('data-ks_layout_id')
if(ksLayoutGridId && ksLayoutGridId != 'ks_default'){
self.gridstackConfig = JSON.parse(self.ks_dashboard_data.ks_child_boards[parseInt(ksLayoutGridId)][1])
}
parseInt($(self.$el[0]).find('.ks_layout_selected').attr('data-ks_layout_id'))
if (chart_id in self.gridstackConfig) {
if (config.device.isMobile){
self.grid.addWidget($ks_gridstack_container[0], {x:self.gridstackConfig[chart_id].x, y:self.gridstackConfig[chart_id].y, w:self.gridstackConfig[chart_id].w, h:self.gridstackConfig[chart_id].h, autoPosition:true,minW:4,maxW:null,minH:3,maxH:null,id :chart_id});
onWillUnmount(() => {
if (this.chartIns) {
this.chartIns.destroy();
}
else{
self.grid.addWidget($ks_gridstack_container[0], {x:self.gridstackConfig[chart_id].x, y:self.gridstackConfig[chart_id].y, w:self.gridstackConfig[chart_id].w, h:self.gridstackConfig[chart_id].h, autoPosition:false,minW:4,maxW:null,minH:3,maxH:null,id :chart_id});
}
} else {
self.grid.addWidget($ks_gridstack_container[0], {x:0, y:0, w:5, h:4,autoPosition:true,minW:4,maxW:null,minH:3,maxH:null, id :chart_id});
}
self._renderChart($ks_gridstack_container, item);
})
}
ks_set_selected_color_pallet($ks_gridstack_container, item){
$ks_gridstack_container.find('.ks_li_' + item.ks_chart_item_color).addClass('ks_date_filter_selected');
}
ks_container_option(chart_title, ksIsDashboardManager, ksIsUser, ks_dashboard_list, chart_id, chart_family, chart_type, ksChartColorOptions, ks_info, ks_company,ks_dashboard_item_type){
return {
ks_chart_title: chart_title,
ksIsDashboardManager: ksIsDashboardManager,
ksIsUser: ksIsUser,
ks_dashboard_list: ks_dashboard_list,
chart_id: chart_id,
chart_family: chart_family,
chart_type: chart_type,
ksChartColorOptions: ksChartColorOptions,
ks_info: ks_info,
ks_company: ks_company,
ks_dashboard_item_type: ks_dashboard_item_type
};
renderChart() {
const ctx = document.getElementById(`${this.props.record.ks_dashboard_item_type}_${this.props.record.id}`);
this.chartIns = new Chart(ctx, {
type: this.type,
data: this.data,
options: this.options
})
}
}
@@ -6,50 +6,16 @@ const { onWillStart, onMounted } = owl;
export class KsDoughnutChart extends KsChartBase {
static template = 'ks_dashboard_ninja.doughnutChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
this.type = 'doughnut';
onWillStart(() => {
this.options = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: 'Chart.js Doughnut Chart'
}
}
};
this.data = {
labels: [
'Red',
'Blue',
'Yellow'
],
datasets: [{
label: 'My First Dataset',
data: [300, 50, 100],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)'
],
hoverOffset: 4
}]
};
})
onMounted(() => {
const ctx = document.getElementById(`doughnutChart_${this.props.record.id}`);
new Chart(ctx, {
type: this.type,
data: this.data,
options: this.options
})
this.renderChart();
})
}
}
@@ -6,13 +6,13 @@ const { Component, onWillStart, onMounted } = owl;
export class KsFlower extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
onMounted(() => {
this.renderChart();
})
}
}
@@ -6,13 +6,12 @@ const { Component, onWillStart, onMounted } = owl;
export class KsFunnelChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
// this.type = 'doughnut';
onMounted(() => {
this.renderChart();
})
}
}
@@ -2,17 +2,26 @@
import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
const { Component, onWillStart, onMounted, onWillUnmount } = owl;
export class KsHorizontalBarChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'bar';
this.options = {
...this.options,
...{indexAxis: 'y'},
// ...{scales: {
// x: {
// beginAtZero: true
// }
// }}
}
onMounted(() => {
this.renderChart();
})
}
}
@@ -5,14 +5,13 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsLineChart extends KsChartBase {
static template = 'ks_dashboard_ninja.lineChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'line';
onMounted(() => {
this.renderChart();
})
}
}
@@ -5,10 +5,7 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsMap extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
@@ -6,18 +6,18 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsPieChart extends KsChartBase {
static template = 'ks_dashboard_ninja.pieChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
this.type = 'pie';
onWillStart(() => {
// this.chartData = JSON.parse(this.props.record.ks_chart_data);
})
onMounted(() => {
const ctx = document.getElementById(`pieChart_${this.props.record.id}`);
})
onMounted(() => {
this.renderChart();
})
}
}
@@ -5,14 +5,13 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsPolarAreaChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'polarArea';
onMounted(() => {
this.renderChart();
})
}
}
@@ -5,14 +5,13 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsRadar extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'radar';
onMounted(() => {
this.renderChart();
})
}
}
@@ -5,14 +5,13 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsRadialBarChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'radar';
onMounted(() => {
this.renderChart();
})
}
}
@@ -5,14 +5,13 @@ import { KsChartBase } from "./chart_base"
const { Component, onWillStart, onMounted } = owl;
export class KsScatterChart extends KsChartBase {
static template = 'ks_dashboard_ninja.scatterChart';
static template = 'ks_dashboard_ninja.chartBase';
setup() {
super.setup();
onMounted(() => {
this.type = 'scatter';
onMounted(() => {
this.renderChart();
})
}
}
@@ -3,10 +3,8 @@
import { useService } from "@web/core/utils/hooks";
import { itemProps } from "../item_props";
import { KsToolBar } from "../tool_bar";
const { Component, onWillStart, onWillUpdateProps, useRef, onMounted, useState, onWillUnmount } = owl;
const { Component, onWillStart, useState } = owl;
export class KsList extends Component {
@@ -19,12 +17,17 @@ export class KsList extends Component {
setup() {
this.action = useService('action');
this.orm = useService('orm');
this.ref = useState({
listData: null
listData: null,
prevOffset: false,
nextOffset: false
})
onWillStart(() => {
this.ref.listData = JSON.parse(this.props.record.ks_list_view_data)
this.ref.listData = JSON.parse(this.props.record.ks_list_view_data);
this.ref.prevOffset = 1;
this.ref.nextOffset = this.ref.listData?.data_rows?.length ?? false;
})
}
@@ -43,4 +46,42 @@ export class KsList extends Component {
});
}
loadPreviousRecords() {
const {id, ks_pagination_limit, ks_dashboard_id} = this.props.record
const ksOffset = parseInt(this.ref.prevOffset) - (ks_pagination_limit + 1)
this.orm.call('ks_dashboard_ninja.board', 'ks_get_list_view_data_offset', [id, {
ks_intial_count: this.ref.nextOffset,
offset: ksOffset,
}, parseInt(ks_dashboard_id), {} ]).then(result => {
this.ref.listData = JSON.parse(result.ks_list_view_data);
this.ref.prevOffset = result.offset;
this.ref.nextOffset = result.next_offset;
})
}
loadMoreRecords() {
const {id, ks_dashboard_id} = this.props.record
this.orm.call('ks_dashboard_ninja.board', 'ks_get_list_view_data_offset', [id, {
ks_intial_count: this.ref.prevOffset,
offset: this.ref.nextOffset,
}, parseInt(ks_dashboard_id), {} ]).then(result => {
this.ref.listData = JSON.parse(result.ks_list_view_data);
this.ref.prevOffset = result.offset;
this.ref.nextOffset = result.next_offset;
})
}
get prevBtnClass() {
if (this.ref.prevOffset === 1) {
return 'ks_event_offer_list'
}
}
get nextBtnClass() {
const { ks_pagination_limit, ks_record_count } = this.props.record
if (ks_pagination_limit < this.ref.nextOffset || ks_pagination_limit >= ks_record_count) {
return 'ks_event_offer_list'
}
}
}
@@ -8,23 +8,27 @@
<div class="d-flex align-items-center w-50">
<h6 t-esc="props?.record.name"/>
</div>
<!-- <div class="ks_pager_name">-->
<!-- <div class="ks_pager">-->
<!-- <span class="ks_counter">-->
<!-- <span class="ks_value">-->
<!-- <t t-esc="props.record.ks_record_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="props.record.id" title="Previous"/>-->
<!-- <button type="button" class="fa fa-chevron-right btn ks_load_next"-->
<!-- t-att-data-item-id="props.record.id" title="Next"/>-->
<!-- </span>-->
<!-- </div>-->
<!-- </div>-->
<div class="ks_pager_name">
<div class="ks_pager">
<span class="ks_counter">
<span class="ks_value">
<t t-esc="ref.prevOffset"/>-<t t-esc="ref.nextOffset"/>
</span>
</span>
<span class="btn-group" aria-atomic="true"
t-att-data-next-offset="ref.prevOffset"
t-att-data-prev-offset="ref.nextOffset">
<button type="button" class="fa fa-chevron-left btn"
t-on-click="loadPreviousRecords"
t-att-class="prevBtnClass"
t-att-data-item-id="props.record.id" title="Previous"/>
<button type="button" class="fa fa-chevron-right btn"
t-att-class="nextBtnClass"
t-on-click="loadMoreRecords"
t-att-data-item-id="props.record.id" title="Next"/>
</span>
</div>
</div>
<div class="ks_dashboard_item_button_container ks_dropdown_container ks_dashboard_item_header ks_dashboard_item_header_hover chart_button_container d-md-flex d-none">
<KsToolBar item="props?.record"
dashboardList="props.dashboardList"
@@ -33,58 +37,56 @@
</div>
<div class="card-body table-responsive">
<t t-if="ref.listData">
<table id="ListViewTable" class="table table-hover list_view_layout_1"
t-att-data-model="ref.listData.model">
<thead>
<tr>
<t t-foreach="ref.listData['label']" t-as="header" t-key="header_index">
<th>
<t t-esc="header"/>
</th>
</t>
<th/>
</tr>
</thead>
<tbody class="table_body">
<t t-foreach="ref.listData.data_rows" t-as="row" t-key="row_index">
<tr class="tr" t-att-data-record-id="row['id']" t-att-data-domain="row['domain']"
t-att-data-item-Id="props.record.id"
t-att-data-sequence="row['sequence']" t-att-data-last_seq="row['last_seq']">
<t t-set="rec_count" t-value="0" />
<t t-foreach="row['data']" t-as="data" t-key="data_index">
<t t-if="row['ks_column_type'][rec_count]=='html'">
<td class="list_canvas_click">
<t t-out="data"/>
</td>
<t t-set="data" t-value="rec_count + 1"/>
</t>
<t t-else="">
<td class="list_canvas_click">
<t t-esc="data"/>
</td>
<t t-set="rec_count" t-value="rec_count + 1"/>
</t>
</t>
<td class="info">
<i id="item_info" t-att-data-model="ref.listData.model"
t-att-data-list-type="ref.listData.list_view_type"
t-att-data-groupby="ref.listData.groupby"
t-att-data-record-id="row['id']" t-att-data-item-id="props.record.id"
t-att-data-list-view-type="list_type"
t-att-data-domain="row['domain']"
t-on-click="() => this.onClickAction(row)"
t-att-class="'ks_item_click '"
class="fa fa-pencil"/>
</td>
</tr>
<t t-if="ref.listData">
<table id="ListViewTable" class="table table-hover list_view_layout_1" t-att-data-model="ref.listData.model">
<thead>
<tr>
<t t-foreach="ref.listData['label']" t-as="header" t-key="header_index">
<th>
<t t-esc="header"/>
</th>
</t>
</tbody>
</table>
</t>
<t t-else="">No Data Present</t>
</div>
<th/>
</tr>
</thead>
<tbody class="table_body">
<t t-foreach="ref.listData.data_rows" t-as="row" t-key="row_index">
<tr class="tr" t-att-data-record-id="row['id']" t-att-data-domain="row['domain']"
t-att-data-item-Id="props.record.id"
t-att-data-sequence="row['sequence']" t-att-data-last_seq="row['last_seq']">
<t t-set="rec_count" t-value="0" />
<t t-foreach="row['data']" t-as="data" t-key="data_index">
<t t-if="row['ks_column_type'][rec_count]=='html'">
<td class="list_canvas_click">
<t t-out="data"/>
</td>
<t t-set="data" t-value="rec_count + 1"/>
</t>
<t t-else="">
<td class="list_canvas_click">
<t t-esc="data"/>
</td>
<t t-set="rec_count" t-value="rec_count + 1"/>
</t>
</t>
<td class="info">
<i id="item_info" t-att-data-model="ref.listData.model"
t-att-data-list-type="ref.listData.list_view_type"
t-att-data-groupby="ref.listData.groupby"
t-att-data-record-id="row['id']" t-att-data-item-id="props.record.id"
t-att-data-list-view-type="list_type"
t-att-data-domain="row['domain']"
t-on-click="() => this.onClickAction(row)"
t-att-class="'ks_item_click '"
class="fa fa-pencil"/>
</td>
</tr>
</t>
</tbody>
</table>
</t>
<t t-else="">No Data Present</t>
</div>
</div>
</t>
+166 -12
View File
@@ -5,6 +5,7 @@ import { useService } from "@web/core/utils/hooks";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { download } from "@web/core/network/download";
import { _t } from "@web/core/l10n/translation";
import { loadCSS } from "@web/core/assets";
const { Component, onWillStart, onWillUpdateProps, useRef, onMounted, useState } = owl;
export class KsToolBar extends Component {
@@ -26,11 +27,36 @@ export class KsToolBar extends Component {
targetId: null,
targetName: null,
});
this.chartTypeMap = new Map([
['ks_pie_chart', 'pie'],
['ks_bar_chart', 'bar'],
['ks_line_chart', 'line'],
['ks_horizontalBar_chart', 'bar'],
['ks_area_chart', 'line'],
['ks_doughnut_chart', 'doughnut'],
['ks_polarArea_chart', 'polarArea'],
['ks_radialBar_chart', 'radar'],
['ks_scatter_chart', 'scatter'],
['ks_funnel_chart', 'bar'],
['ks_bullet_chart', 'bar'],
['ks_radar_view', 'radar'],
['ks_flower_view', 'flower'],
])
this.chartColorOptions = {
default: 'default',
cool: 'cool',
warm: 'warm',
neon: 'neon',
}
onWillStart(async () => {
// await loadCSS('ks_dashboard_ninja/static/src/items/css/dialog.css');
})
}
changeDashboard(ev) {
this.state.targetId = ev.target.value
// console.log(ev.target)
}
async duplicateItem() {
@@ -46,8 +72,6 @@ export class KsToolBar extends Component {
type: "success",
}
);
// this.refreshAction()
}
async moveItem() {
@@ -62,8 +86,6 @@ export class KsToolBar extends Component {
type: "success",
}
);
// this.refreshAction()
}
customizeItem() {
@@ -81,7 +103,7 @@ export class KsToolBar extends Component {
removeItem() {
this.dialog.add(ConfirmationDialog, {
body: _t('确定删除这个图表吗?'),
body: _t('确定删除这个图表吗'),
cancel: () => {},
confirm: () => this.doDelete(),
});
@@ -100,12 +122,144 @@ export class KsToolBar extends Component {
}
exportItem() {
console.log(`id`)
onClickMoreInfo() {
const {
name,
ks_model_name,
ks_chart_data,
ks_chart_groupby_type,
ks_chart_relation_groupby_name,
ks_chart_date_groupby,
ks_show_records,
action,
ks_is_client_action,
ks_domain
} = this.props.item;
debugger
const domain = JSON.parse(ks_chart_data).previous_domain
// const groupBy = ks_chart_groupby_type === 'relational_type' ? ks_chart_relation_groupby_name : ks_chart_relation_groupby_name + ':' + ks_chart_date_groupby;
const groupBy = ks_chart_relation_groupby_name;
if (ks_show_records) {
let newAction;
if (action) {
if (!ks_is_client_action) {
newAction = Object.assign({}, action);
if (newAction.view_mode.includes('tree')) {
newAction.view_mode = newAction.view_mode.replace('tree', 'list');
}
for (let i = 0; i < newAction.views.length; i++) {
newAction.views[i][1].includes('tree') ? newAction.views[i][1] = newAction.views[i][1].replace('tree', 'list') : newAction.views[i][1]
}
newAction['domain'] = ks_domain || [];
newAction['search_view_id'] = [newAction.search_view_id, 'search']
} else {
newAction = Object.assign({}, action[0]);
if (newAction.params) {
newAction.params.default_active_id || 'mailbox_inbox';
} else {
newAction.params = {
'default_active_id': 'mailbox_inbox'
}
newAction.context = {}
newAction.context.params = {
'active_model': false
};
}
}
} else {
newAction = {
name: name,
type: 'ir.actions.act_window',
res_model: ks_model_name,
domain: domain || [],
context: {
'group_by': groupBy ? groupBy : false ,
},
views: [[false, 'list'], [false, 'form']],
view_mode: 'list',
target: 'current',
}
}
this.action.doAction(newAction);
}
}
// refreshAction() {
// const { action, context, params} = this.action.currentController
// // this.action.doAction(action)
// }
onClickColorOption(color) {
if (color === this.props.item.ks_chart_item_color) {
return;
}
this.orm.write('ks_dashboard_ninja.item', [this.props.item.id], {ks_chart_item_color: color});
}
async exportExcelOrCsv(type) {
const { id, name, ks_dashboard_item_type, ks_chart_data, ks_list_view_data } = this.props.item;
const fmt = ks_dashboard_item_type === 'ks_list_view'? `list_${type}` : `chart_${type}`;
const context = this.__owl__.parent.parent.parent.parent.props.getContext();
let data = {
header: name,
chart_data: ks_dashboard_item_type === 'ks_list_view' ? ks_list_view_data : ks_chart_data,
};
if (ks_dashboard_item_type === 'ks_list_view') {
data = {
...data,
ks_export_boolean: true,
context: context,
params: {},
ks_item_id: id,
};
}
download({
data: {
data: JSON.stringify(data),
},
url: `/ks_dashboard_ninja/export/${fmt}`,
});
}
async exportPdf() {
const { id, name, ks_dashboard_item_type } = this.props.item;
const { chartIns } = this.__owl__.parent.component
const base64Image = chartIns.toBase64Image();
const canvas = document.getElementById(`${ks_dashboard_item_type}_${id}`);
const ImgDef = {
content: [{
image: canvas.toDataURL("image/png"),
width: canvas.width,
height: canvas.height,
}],
images: {
bee: base64Image,
}
}
pdfMake.createPdf(ImgDef).download(name + '.pdf');
}
async exportImg() {
const name = this.props.item.name;
const { chartIns } = this.__owl__.parent.component;
const base64Image = chartIns.toBase64Image();
const link = document.createElement('a');
link.href = base64Image;
link.download = name;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
async exportItem() {
const { id, name} = this.props.item;
const data = {
header: name,
item_id: id,
}
download({
data: {
data: JSON.stringify(data),
},
url: "/ks_dashboard_ninja/export/item_json",
});
}
}
@@ -4,51 +4,49 @@
<t t-name="ks_dashboard_ninja.toolBar">
<button class="ks_dashboard_item_chart_info" title="More Information" type="button" t-att-data-item-id="props.item.id">
<button class="ks_dashboard_item_chart_info" title="More Info" type="button"
t-att-data-item-id="props.item.id"
t-on-click.stop="onClickMoreInfo"
t-if="!['ks_tile', 'ks_kpi', 'ks_to_do', 'ks_list_view'].includes(props.item.ks_dashboard_item_type)" >
<i class="fa fa-info-circle"/>
</button>
<div class="ks_chart_inner_buttons">
<button title="调色板" data-bs-toggle="dropdown" class="ks_dashboard_item_action ks_dashboard_color_option btn dropdown-toggle btn-xs o-no-caret show" type="button" aria-expanded="true">
<div class="ks_chart_inner_buttons"
t-if="!['ks_tile', 'ks_kpi', 'ks_to_do', 'ks_list_view'].includes(props.item.ks_dashboard_item_type)">
<button title="Color Palette"
data-bs-toggle="dropdown"
class="ks_dashboard_item_action ks_dashboard_color_option btn dropdown-toggle btn-xs o-no-caret"
type="button"
aria-expanded="true">
<i class="fa fa-paint-brush"/>
</button>
<ul role="menu" class="dropdown-menu dropdown-menu-right ks_color_pallate " t-att-data-item-id="props.item.id" data-chart-type="doughnut" data-chart-family="circle" style="position: absolute; inset: 0px auto auto 0px; margin: 0px; transform: translate(403px, 45px);" data-popper-placement="bottom-start">
<li class="ks_li_default ks_date_filter_selected">
<span class="default ks_chart_color_options" data-chart-color="default">
default
</span>
</li>
<li class="ks_li_cool">
<span class="cool ks_chart_color_options" data-chart-color="cool">
cool
</span>
</li>
<li class="ks_li_warm">
<span class="warm ks_chart_color_options" data-chart-color="warm">
warm
</span>
</li>
<li class="ks_li_neon">
<span class="neon ks_chart_color_options" data-chart-color="neon">
neon
</span>
</li>
<ul role="menu" class="dropdown-menu dropdown-menu-right ks_color_pallate"
t-on-click.stop=""
t-att-data-item-id="props.item.id"
t-att-data-chart-type="this.chartTypeMap.get(props.item.ks_dashboard_item_type)"
data-chart-family="circle">
<t t-foreach="chartColorOptions" t-as="color" t-key="color_index">
<li t-attf-class="{{ color === props.item.ks_chart_item_color ? 'ks_date_filter_selected': ''}}"
t-on-click="() => this.onClickColorOption(color)">
<span class="ks_chart_color_options" t-att-data-chart-color="color">
<t t-esc="color"/>
</span>
</li>
</t>
</ul>
</div>
<!-- Dashboard Item Move/Copy Feature -->
<button title="Move/Duplicate" data-bs-toggle="dropdown"
class="dashboard_item_action btn dropdown-toggle btn-xs d-md-block d-none"
type="button"
t-on-click.stop=""
t-att-style="'color:'+ rgba_button_color + ';'"
aria-expanded="true">
<i class="fa fa-files-o"/>
</button>
<ul role="menu" class="dashboard_menu_container dropdown-menu dropdown-menu-right">
<ul role="menu" class="dashboard_menu_container dropdown-menu dropdown-menu-right" t-on-click.stop="">
<li class="md_heading">
<span>Select Dashboard</span>
</li>
@@ -63,20 +61,20 @@
</select>
</li>
<li class="mt-3">
<button class="btn btn-primary o_apply_group o_add_group duplicate_item"
t-on-click="(ev) => this.duplicateItem(ev)"
<button class="btn btn-primary o_apply_group o_add_group"
t-on-click.stop="(ev) => this.duplicateItem(ev)"
tabindex="-1" type="button">Duplicate
</button>
<button class="btn btn-primary o_apply_group o_add_group move_item" tabindex="-1"
t-on-click="(ev) => this.moveItem(ev)"
<button class="btn btn-primary o_apply_group o_add_group mx-1" tabindex="-1"
t-on-click.stop="(ev) => this.moveItem(ev)"
type="button">Move
</button>
</li>
</ul>
<button title="Quick Customize"
<button title="Edit"
class="dashboard_quick_edit_action_popup d-md-block d-none "
t-on-click="customizeItem"
t-on-click.stop="customizeItem"
type="button" t-att-data-item-id="props.item.id">
<i class="fa fa-pencil"/>
</button>
@@ -89,57 +87,55 @@
<button type="button" title="Remove Item"
class="dashboard_item_delete d-md-block d-none"
t-on-click="removeItem">
t-on-click.stop="removeItem">
<i class="fa fa-times"/>
</button>
<!-- <div class="chart_inner_buttons dropdown d-md-none dn-setting-panel">-->
<!-- <button data-bs-toggle="dropdown"-->
<!-- class="dashboard_item_action btn dropdown-toggle btn-xs o-no-caret btn"-->
<!-- type="button"-->
<!-- t-att-style="'color:'+ rgba_button_color + ';'"-->
<!-- aria-expanded="true">-->
<!-- <i class="fa fa-cog"/>-->
<!-- </button>-->
<!-- <div role="menu" class="dropdown-menu dropdown-menu-right chart_inner_min_width">-->
<!-- &lt;!&ndash;Dynamic Rendering&ndash;&gt;-->
<!-- <div class="chart_export_menu">-->
<!-- <div class="chart_export_menu_item"-->
<!-- t-on-click="customizeItem"-->
<!-- t-att-data-item-id="props.item.id"-->
<!-- data-format="chart_xls">-->
<!-- <i class="fa fa-pencil"/>-->
<!-- <span>Customize Item</span>-->
<!-- </div>-->
<!-- <div class="chart_export_menu_item"-->
<!-- t-on-click="removeItem"-->
<!-- t-att-data-item-id="props.item.id"-->
<!-- data-format="chart_xls">-->
<!-- <i class="fa fa-times"/>-->
<!-- <span>Remove Item</span>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class="chart_inner_buttons d-md-block d-none">
<div class="ks_chart_inner_buttons d-md-block d-none">
<button title="Export Item" data-bs-toggle="dropdown"
class="dashboard_item_action btn dropdown-toggle btn-xs o-no-caret btn"
type="button"
t-on-click.stop=""
t-att-style="'color:'+ rgba_button_color + ';'"
aria-expanded="true">
<i class="fa fa-ellipsis-v"/>
</button>
<div role="menu" class="dropdown-menu dropdown-menu-right">
<!--Dynamic Rendering-->
<div class="chart_export_menu">
<div class="chart_export_menu_header">
<div class="ks_chart_export_menu">
<div class="ks_chart_export_menu_header">
<span>Export</span>
</div>
<div class="chart_json_export chart_export_menu_item"
<div class=" ks_chart_export_menu_item"
t-on-click.stop="() => this.exportExcelOrCsv('xls')"
t-if="!['ks_tile', 'ks_kpi', 'ks_to_do'].includes(props.item.ks_dashboard_item_type)"
t-att-data-chart-id="props.item.id">
<i class="fa fa-file-excel-o"/>
<span>Export to Excel</span>
</div>
<div class="ks_chart_export_menu_item"
t-on-click.stop="() => this.exportExcelOrCsv('csv')"
t-if="!['ks_tile', 'ks_kpi', 'ks_to_do'].includes(props.item.ks_dashboard_item_type)"
t-att-data-chart-id="props.item.id">
<i class="fa fa-file-code-o"/>
<span>Export to CSV</span>
</div>
<div t-att-data-chart-id="props.item.id"
t-on-click.stop="exportPdf"
t-if="!['ks_tile', 'ks_kpi', 'ks_to_do', 'ks_list_view'].includes(props.item.ks_dashboard_item_type)"
class="ks_chart_export_menu_item">
<i class="fa fa-file-pdf-o"/>
<span>Save as PDF</span>
</div>
<div class="ks_chart_export_menu_item"
t-att-data-chart-id="props.item.id"
t-if="!['ks_tile', 'ks_kpi', 'ks_to_do', 'ks_list_view'].includes(props.item.ks_dashboard_item_type)"
t-on-click.stop="exportImg">
<i class="fa fa-file-image-o"/>
<span>Save as Image</span>
</div>
<div class="ks_chart_export_menu_item"
t-att-data-item-id="props.item.id"
t-on-click="exportItem"
data-format="chart_xls">
t-on-click.stop="exportItem">
<i class="fa fa-file-code-o"/>
<span>Export Item</span>
</div>
@@ -147,33 +143,32 @@
</div>
</div>
<div class="chart_inner_buttons dropdown ">
<div class="ks_chart_inner_buttons dropdown ">
<button title="Info" data-bs-toggle="dropdown"
class="item_description btn dropdown-toggle btn-xs o-no-caret btn"
type="button"
t-on-click.stop=""
t-att-style="'color:'+ rgba_button_color + ';'"
aria-expanded="true">
<i class="fa fa-book"/>
</button>
<div role="menu" class="dropdown-menu dropdown-menu-right" style="width:20rem">
<div role="menu" class="dropdown-menu dropdown-menu-right"
style="width: 20rem; position: absolute; inset: auto auto 0px 0px; margin: 0px; transform: translate(0px, -33px);">
<!--Dynamic Rendering-->
<div class="chart_export_menu">
<div class="chart_export_menu_header" style="margin-left:-10px">
<div class="ks_chart_export_menu">
<div class="chart_export_menu_header" style="margin-left:10px">
<span>Info</span>
</div>
<div class="info" style="margin-left:10px">
<div class="ks_info" style="margin-left:10px">
<span>Company:
<t t-esc="props.item?.ks_company"/>
</span>
</div>
<div class="info" style="margin-left:10px">
<div class="ks_info" style="margin-left:10px">
<t t-if="props.item.ks_info">
<t t-foreach="props.item.ks_info" t-as="ks" t-key="ks_index">
<span>
<t t-esc="ks"/>
</span>
<br/>
</t>
<span>Description:
<t t-esc="props.item.ks_info"/>
</span>
</t>
</div>
</div>
@@ -26,6 +26,10 @@ export class BasePreview extends Component {
return get_rgba_format(this.props.record.data.ks_button_color)
}
get header_bg_color() {
return get_rgba_format(this.props.record.data.ks_header_bg_color);
}
get rgba_background_color() {
return get_rgba_format(this.props.record.data.ks_background_color)
}
@@ -43,4 +47,8 @@ export class BasePreview extends Component {
return mainBodyStyle(rgba_background_color, rgba_font_color, this.props.record.data).background_style;
}
get ks_icon_url() {
return `/web/image?model=ks_dashboard_ninja.item&id=${this.props.record.resId}&field=ks_icon`
}
}
@@ -0,0 +1,89 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { BasePreview } from "./base_preview";
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
const { Component, onMounted, onWillUnmount, onPatched, useRef} = owl;
export class ChartPreview extends Component {
static template = 'ks_dashboard_ninja.chartPreview';
static props = {...standardWidgetProps};
setup() {
this.chartIns = null;
this.chartCtx = useRef('chartCtx');
this.chartTypeMap = new Map([
['ks_pie_chart', 'pie'],
['ks_bar_chart', 'bar'],
['ks_line_chart', 'line'],
['ks_horizontalBar_chart', 'bar'],
['ks_area_chart', 'line'],
['ks_doughnut_chart', 'doughnut'],
['ks_polarArea_chart', 'polarArea'],
['ks_radialBar_chart', 'radar'],
['ks_scatter_chart', 'scatter'],
['ks_funnel_chart', 'bar'],
['ks_bullet_chart', 'bar'],
// ['ks_radar_view', 'radar'],
// ['ks_flower_view', 'flower'],
])
onMounted(() => {
this.renderChart(this.chartCtx.el)
})
onPatched(() => {
if (this.chartIns){
this.chartIns.destroy();
}
this.renderChart(this.chartCtx.el);
})
onWillUnmount(() => {
if (this.chartIns) {
this.chartIns.destroy();
}
})
}
setChartData() {
this.chartData = JSON.parse(this.props.record.data.ks_chart_data);
this.data = {
labels: this.chartData.labels ?? [],
datasets: this.chartData.datasets ?? '',
}
this.options = {
responsive: true,
plugins: {
legend: {
position: 'top',
},
},
indexAxis: 'x',
}
if (this.ks_type === 'ks_horizontalBar_chart') {
this.options.indexAxis ='y';
}
if (this.props.record.data.ks_semi_circle_chart && ["pie", "doughnut"].includes(this.ks_type)) {
this.options.rotation = Math.PI;
this.options.circumference = Math.PI;
}
}
renderChart(ctx) {
this.ks_type = this.props.record.data.ks_dashboard_item_type;
this.setChartData();
this.chartIns = new Chart(ctx, {
type: this.chartTypeMap.get(this.ks_type),
data: this.data,
options: this.options
})
}
}
registry.category("view_widgets").add("chart_preview", {
component: ChartPreview,
});
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.chartPreview" >
<div class="grid-stack-item-content ks_dashboarditem_chart_container ks_dashboard_item_hover card shadow" style="min-height: 20rem">
<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 t-esc="props?.record.data.name"/>
</div>
</div>
<div class="card-body ks_chart_card_body d-flex flex-row align-items-center justify-content-center">
<canvas t-ref="chartCtx">
</canvas>
</div>
</div>
</t>
</templates>
+27 -3
View File
@@ -11,10 +11,27 @@ export class KpiPreview extends BasePreview {
static template = 'ks_dashboard_ninja.kpi_preview';
setup() {
this.kpi_data = this.props.record.data.ks_kpi_data;
this.kpi_data = JSON.parse(this.props.record.data.ks_kpi_data);
this.count_1 = this.kpi_data[0] ? this.kpi_data[0]?.record_data : undefined;
this.count_2 = this.kpi_data[1] ? this.kpi_data[1]?.record_data : undefined;
this.target_1 = this.kpi_data[0]?.target;
this.deviation = Math.round((this.difference / this.target_1) * 100)
if (this.deviation !== Infinity) {
this.deviation = this.deviation ? this.deviation + '%' : 0 + '%';
}
this.previous_period_data = this.kpi_data[0].previous_period;
this.pre_diffrence = (this.count_1 - this.previous_period_data);
const { ks_multiplier_active, ks_multiplier } = this.props.record.data
if (ks_multiplier_active){
this.previous_period_data = this.kpi_data[0].previous_period * ks_multiplier;
this.pre_diffrence = (this.count_1 * ks_multiplier - this.previous_period_data);
}
this.pre_achieve = this.pre_diffrence > 0;
this.pre_diffrence = Math.abs(this.pre_diffrence);
this.pre_deviation = this.previous_period_data ? parseInt((this.pre_diffrence / this.previous_period_data) * 100) + '%' : "100%"
}
get difference() {
@@ -31,12 +48,19 @@ export class KpiPreview extends BasePreview {
return this.difference >= 0;
}
get target_arrow() {
get targetArrow() {
if (this.deviation === Infinity) {
return ;
}
return this.achieve ? 'up' : 'down';
}
get pre_deviation() {
get preDeviationColor() {
return this.pre_achieve ? 'color: green' : 'color: red'
}
get targetDeviationColor() {
return this.achieve ? 'color: green' : 'color: red'
}
}
@@ -6,7 +6,7 @@
<t t-set="item" t-value="props?.record?.data" />
<div class="ks_dashboard_kpi ks_dashboard_custom_srollbar ks_db_kpi_preview"
t-att-class="'ks_dashboarditem_id ks_dashboard_item_hover ' + inner_container_class"
t-att-style="style_main_body">
t-att-style="'min-height: 9rem!important;' + style_main_body">
<div class="ks_dashboard_icon_l5 ">
<t t-if="item.ks_icon_select=='Custom'">
<t t-if="ks_icon_url">
@@ -34,10 +34,10 @@
<span>vs Target</span>
</div>
<div>
<span style="font-size : medium;">
<t t-esc="item.deviation"/>
<span style="font-size : medium;" t-att-style="targetDeviationColor">
<t t-esc="deviation"/>
<t t-if="achieve">
<i t-att-class="'fa fa-arrow-'+ target_arrow"/>
<i t-att-class="'fa fa-arrow-'+ targetArrow"/>
</t>
</span>
</div>
@@ -47,9 +47,9 @@
<span>vs Prev</span>
</div>
<div>
<span style="font-size : medium;">
<span style="font-size : medium;" t-att-style="preDeviationColor">
<t t-esc="pre_deviation"/>
<i t-att-class="'fa fa-arrow-'+ pre_arrow"/>
<i t-att-class="'fa fa-arrow-'+ preArrow"/>
</span>
</div>
</t>
@@ -0,0 +1,40 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { BasePreview } from "./base_preview";
const { useState, onMounted, onPatched } = owl;
export class ListPreview extends BasePreview {
static template = 'ks_dashboard_ninja.listPreview';
setup() {
this.state = useState({
listData: null,
});
onMounted(() => {
this.loadListData();
});
onPatched(() => {
this.loadListData();
})
}
get listData() {
const toParse = this.props.record.data.ks_list_view_data;
return toParse === "" ? "" : JSON.parse(toParse);
}
loadListData() {
this.state.listData = this.listData !== "" ? this.listData : null;
}
}
registry.category("view_widgets").add("list_preview", {
component: ListPreview,
});
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.listPreview" >
<div class="grid-stack-item-content ks_list_view_container ks_dashboard_item_hover card shadow">
<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 t-esc="props?.record.data.name"/>
</div>
</div>
<div class="card-body table-responsive">
<t t-if="state.listData">
<table id="ListViewTable" class="table table-hover list_view_layout_1"
t-att-data-model="state.listData.model">
<thead>
<tr>
<t t-foreach="state.listData['label']" t-as="header" t-key="header_index">
<th>
<t t-esc="header"/>
</th>
</t>
<th/>
</tr>
</thead>
<tbody class="table_body">
<t t-foreach="state.listData.data_rows" t-as="row" t-key="row_index">
<tr t-if="row_index &lt; 5" class="tr" t-att-data-record-id="row['id']" t-att-data-domain="row['domain']"
t-att-data-item-Id="props.record.id"
t-att-data-sequence="row['sequence']" t-att-data-last_seq="row['last_seq']">
<t t-set="rec_count" t-value="0" />
<t t-foreach="row['data']" t-as="data" t-key="data_index">
<t t-if="row['ks_column_type'][rec_count]=='html'">
<td class="list_canvas_click">
<t t-out="data"/>
</td>
<t t-set="data" t-value="rec_count + 1"/>
</t>
<t t-else="">
<td class="list_canvas_click">
<t t-esc="data"/>
</td>
<t t-set="rec_count" t-value="rec_count + 1"/>
</t>
</t>
<td class="ks_info">
<i id="item_info" t-att-data-model="state.listData.model"
t-att-data-list-type="state.listData.list_view_type"
t-att-data-groupby="state.listData.groupby"
t-att-data-record-id="row['id']" t-att-data-item-id="props.record.id"
t-att-data-list-view-type="list_type"
t-att-data-domain="row['domain']"
t-on-click="() => this.onClickAction(row)"
t-att-class="'ks_item_click '"
class="fa fa-pencil"/>
</td>
</tr>
</t>
</tbody>
</table>
</t>
<t t-else="">No Data Present</t>
</div>
</div>
</t>
</templates>
@@ -0,0 +1,50 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { BasePreview } from "./base_preview";
const { useState, onMounted, onPatched } = owl;
export class TodoPreview extends BasePreview {
static template = 'ks_dashboard_ninja.todoPreview';
setup() {
this.state = useState({
tab: 0,
labels: null,
tableData: null,
});
onMounted(() => {
this.prepareTableData();
});
onPatched(() => {
this.prepareTableData();
})
}
get todoData() {
const toParse = this.props.record.data.ks_to_do_data;
return toParse === "" ? "" : JSON.parse(toParse);
}
prepareTableData() {
const items = this.todoData?.items ?? "";
if (items === "") {
return;
}
this.state.labels = items.map(item => [item.item_id, item.label]);
this.state.tableData = items[this.state.tab];
}
onTabClick(index) {
this.state.tab = index;
this.prepareTableData();
}
}
registry.category("view_widgets").add("todo_preview", {
component: TodoPreview,
});
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_dashboard_ninja.todoPreview" >
<t t-set="item" t-value="props?.record.data"/>
<div class="grid-stack-item" t-att-id="item.id">
<div class="grid-stack-item-content ks_list_view_container ks_dashboard_item_hover card shadow"
t-att-title="item.ks_info">
<div class="ks_card_header" t-att-style=" 'background-color:' + header_bg_color ">
<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" t-att-title="item.name">
<t t-esc="item.name"/>
</h6>
</div>
</div>
<t t-call="ks_dashboard_ninja.todoPreviewHeader" />
</div>
<div class="ks_to_do_card_body card-body table-responsive">
<t t-call="ks_dashboard_ninja.todoPreviewInnerContainer" />
</div>
</div>
</div>
</t>
<t t-name="ks_dashboard_ninja.todoPreviewHeader">
<div class="card-header">
<div class="nav-tabs-navigation">
<div class="nav-tabs-wrapper">
<t t-if="state.labels">
<ul class="nav nav-tabs" data-tabs="tabs" style="overflow:overlay">
<t t-foreach="state.labels" t-as="label" t-key="label_index">
<li class="nav-item">
<a class="nav-link"
t-att-style="'color:' + rgba_font_color"
t-attf-class="{{ label_index == state.tab ? 'active': '' }}"
t-att-data-item-id="item.id"
t-att-data-section-id="label[label_index]"
data-toggle="pill"
t-att-href="label[1]"
t-on-click.prevent.stop="() => this.onTabClick(label_index)"
>
<t t-esc="label[1]"/>
</a>
</li>
</t>
</ul>
</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>
</t>
<t t-name="ks_dashboard_ninja.todoPreviewInnerContainer">
<div class="container-fluid p-0">
<div class="card">
<t t-if="state.tableData?.lines.length > 0">
<div class="card-body">
<div class="tab-content">
<table class="table">
<t t-foreach="state.tableData?.lines" t-as="data" t-key="data_index">
<tr>
<td class="ks_custom_check">
<div class="form-check" >
<label class="form-check-label">
<input class="form-check-input" type="checkbox" t-att-checked="!data.ks_active"/>
<span class="form-check-sign ks_do_item_active_handler"
t-att-data-item-id="data.id">
<span class="check"/>
</span>
</label>
</div>
</td>
<td class="ks_description"
t-att-id="'todoTask_' + data.id"
t-attf-class="{{ data.ks_active ? '': 'ks_do_item_line_through' }}"
t-esc="data.ks_description" />
</tr>
</t>
</table>
</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>
</t>
</templates>
@@ -163,6 +163,7 @@
<field context="{'current_id': id}" name="ks_dashboard_item_type" string="Type" />
<field name="name" required="1"/>
<field invisible="ks_data_calculation_type" name="data_source"/>
<field name="ks_model_id" invisible="model_bool"/>
<field name="ks_company_id" options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
<field invisible="1" name="name_seq"/>
<field invisible="1" name="ks_partners_map"/>
@@ -205,6 +206,9 @@
<div>
<widget name="tile_preview" invisible="ks_dashboard_item_type != 'ks_tile'" />
<widget name="kpi_preview" invisible="ks_dashboard_item_type != 'ks_kpi'" />
<widget name="todo_preview" invisible="ks_dashboard_item_type != 'ks_to_do'" />
<widget name="list_preview" invisible="ks_dashboard_item_type != 'ks_list_view'" />
<widget name="chart_preview" invisible="not ks_model_id or ks_dashboard_item_type not in ['ks_bar_chart', 'ks_line_chart', 'ks_scatter_chart', 'ks_area_chart', 'ks_horizontalBar_chart', 'ks_pie_chart', 'ks_doughnut_chart', 'ks_polarArea_chart', 'ks_funnel_chart', 'ks_bullet_chart', 'ks_radialBar_chart']" />
</div>
<notebook>
<page name="data_sets" string="Data">
@@ -16,21 +16,22 @@
<field name="ks_sequence"/>
</group>
</group>
<h3 class="oe_grey" invisible="not ks_template and not ks_template and not ks_template and not ks_template and not ks_template and not ks_template and not ks_template">
<h3 class="oe_grey" invisible="ks_template not in [2, 3, 4, 5, 6, 7 ,8]">
Preview
</h3>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_template1.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_template2.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_template3.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_account_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_crm_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_inventory_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="not ks_template" src="/ks_dashboard_ninja/static/description/templates/ks_sale_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 2" src="/ks_dashboard_ninja/static/description/templates/ks_template1.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 3" src="/ks_dashboard_ninja/static/description/templates/ks_template2.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 4" src="/ks_dashboard_ninja/static/description/templates/ks_template3.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 5" src="/ks_dashboard_ninja/static/description/templates/ks_account_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 6" src="/ks_dashboard_ninja/static/description/templates/ks_crm_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 7" src="/ks_dashboard_ninja/static/description/templates/ks_inventory_template.png" style="padding: 10px; border: 2px solid;"/>
<img alt="#" class="img img-fluid" invisible="ks_template != 8" src="/ks_dashboard_ninja/static/description/templates/ks_sale_template.png" style="padding: 10px; border: 2px solid;"/>
<footer>
<button class="btn-primary" name="ks_create_record" string="Save" type="object"/>
<button class="oe_link" special="cancel" string="Cancel"/>
</footer>
</form></field>
</form>
</field>
</record>
<record id="ks_create_dashboard_wizard" model="ir.actions.act_window">
<field name="name">Create Dashboard</field>