29-08-2025

Merge branch 'develop'
This commit is contained in:
administrator 2025-08-29 10:54:39 +05:30
commit f2e8c40c7f
22 changed files with 1159 additions and 141 deletions

View File

@ -61,111 +61,116 @@ class AttendanceReport(models.Model):
# Define the query # Define the query
query = """ query = """
WITH date_range AS ( WITH date_range AS (
SELECT generate_series( SELECT generate_series(
%s::date, %s::date,
%s::date, %s::date,
interval '1 day' interval '1 day'
)::date AS date )::date AS date
), ),
employee_dates AS ( employee_dates AS (
SELECT SELECT
emp.id AS employee_id, emp.id AS employee_id,
emp.name AS employee_name, emp.name AS employee_name,
dr.date, dr.date,
TO_CHAR(dr.date, 'Day') AS day_name, TO_CHAR(dr.date, 'Day') AS day_name,
EXTRACT(WEEK FROM dr.date) AS week_number, EXTRACT(WEEK FROM dr.date) AS week_number,
TO_CHAR(date_trunc('week', dr.date), 'MON DD') || ' - ' || TO_CHAR(date_trunc('week', dr.date), 'MON DD') || ' - ' ||
TO_CHAR(date_trunc('week', dr.date) + interval '6 days', 'MON DD') AS week_range, TO_CHAR(date_trunc('week', dr.date) + interval '6 days', 'MON DD') AS week_range,
dep.name->>'en_US' AS department dep.name->>'en_US' AS department
FROM FROM
hr_employee emp hr_employee emp
CROSS JOIN CROSS JOIN
date_range dr date_range dr
LEFT JOIN LEFT JOIN
hr_department dep ON emp.department_id = dep.id hr_department dep ON emp.department_id = dep.id
WHERE WHERE
emp.active = true emp.active = true
""" + (" AND " + " AND ".join(emp_date_conditions) if emp_date_conditions else "") + """ """ + (" AND " + " AND ".join(emp_date_conditions) if emp_date_conditions else "") + """
), ),
daily_checkins AS ( daily_checkins AS (
SELECT SELECT
emp.id, emp.id,
emp.name, emp.name,
DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') AS date, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') AS date,
at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_in, at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_in,
at.check_out AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_out, at.check_out AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_out,
at.worked_hours, at.worked_hours,
ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in) AS first_checkin_row, ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in) AS first_checkin_row,
ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in DESC) AS last_checkout_row, ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in DESC) AS last_checkout_row,
dep.name->>'en_US' AS department dep.name->>'en_US' AS department,
FROM LEAD(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in) AS next_check_in
hr_attendance at FROM
LEFT JOIN hr_attendance at
hr_employee emp ON at.employee_id = emp.id LEFT JOIN
LEFT JOIN hr_employee emp ON at.employee_id = emp.id
hr_department dep ON emp.department_id = dep.id LEFT JOIN
WHERE hr_department dep ON emp.department_id = dep.id
""" + " AND ".join(checkin_conditions) + """ WHERE
), """ + " AND ".join(checkin_conditions) + """
attendance_summary AS ( ),
SELECT attendance_summary AS (
id, SELECT
name, id,
date, name,
MAX(CASE WHEN first_checkin_row = 1 THEN check_in END) AS first_check_in, date,
MAX(CASE WHEN last_checkout_row = 1 THEN check_out END) AS last_check_out, MAX(CASE WHEN first_checkin_row = 1 THEN check_in END) AS first_check_in,
SUM(worked_hours) AS total_worked_hours, MAX(CASE WHEN last_checkout_row = 1 THEN check_out END) AS last_check_out,
department SUM(worked_hours) AS total_worked_hours,
FROM -- 👇 Calculate total break time (sum of gaps between check_out and next check_in)
daily_checkins SUM(
GROUP BY EXTRACT(EPOCH FROM (next_check_in - check_out)) / 3600
id, name, date, department ) FILTER (WHERE next_check_in IS NOT NULL AND check_out IS NOT NULL) AS break_hours,
), department
leave_data AS ( FROM
SELECT daily_checkins
hl.employee_id, GROUP BY
hl.date_from AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_start, id, name, date, department
hl.date_to AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_end, ),
hlt.name->>'en_US' AS leave_type, leave_data AS (
hl.request_unit_half AS is_half_day, SELECT
hl.request_date_from, hl.employee_id,
hl.request_date_to hl.date_from AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_start,
FROM hl.date_to AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_end,
hr_leave hl hlt.name->>'en_US' AS leave_type,
JOIN hl.request_unit_half AS is_half_day,
hr_leave_type hlt ON hl.holiday_status_id = hlt.id hl.request_date_from,
WHERE hl.request_date_to
hl.state IN ('validate', 'confirm', 'validate1') FROM
AND (hl.date_from, hl.date_to) OVERLAPS (%s::timestamp, %s::timestamp) hr_leave hl
) JOIN
SELECT hr_leave_type hlt ON hl.holiday_status_id = hlt.id
ed.employee_id AS id, WHERE
ed.employee_name AS name, hl.state IN ('validate', 'confirm', 'validate1')
ed.date, AND (hl.date_from, hl.date_to) OVERLAPS (%s::timestamp, %s::timestamp)
'Week ' || ed.week_number || ' (' || ed.week_range || ')' AS week_info, )
TRIM(ed.day_name) AS day_name, SELECT
COALESCE(ats.first_check_in, NULL) AS first_check_in, ed.employee_id AS id,
COALESCE(ats.last_check_out, NULL) AS last_check_out, ed.employee_name AS name,
COALESCE(ats.total_worked_hours, 0) AS total_worked_hours, ed.date,
ed.department, 'Week ' || ed.week_number || ' (' || ed.week_range || ')' AS week_info,
CASE TRIM(ed.day_name) AS day_name,
WHEN ld.leave_type IS NOT NULL AND ld.is_half_day THEN 'on Half day ' || ld.leave_type COALESCE(ats.first_check_in, NULL) AS first_check_in,
WHEN ld.leave_type IS NOT NULL THEN 'on ' || ld.leave_type COALESCE(ats.last_check_out, NULL) AS last_check_out,
WHEN ats.first_check_in IS NOT NULL THEN 'Present' COALESCE(ats.total_worked_hours, 0) AS total_worked_hours,
ELSE 'NA' COALESCE(ats.break_hours, 0) AS total_break_hours,
END AS status ed.department,
FROM CASE
employee_dates ed WHEN ld.leave_type IS NOT NULL AND ld.is_half_day THEN 'on Half day ' || ld.leave_type
LEFT JOIN WHEN ld.leave_type IS NOT NULL THEN 'on ' || ld.leave_type
attendance_summary ats ON ed.employee_id = ats.id AND ed.date = ats.date WHEN ats.first_check_in IS NOT NULL THEN 'Present'
LEFT JOIN ELSE 'NA'
leave_data ld ON ed.employee_id = ld.employee_id END AS status
AND ed.date >= DATE(ld.leave_start) FROM
AND ed.date <= DATE(ld.leave_end) employee_dates ed
ORDER BY LEFT JOIN
ed.employee_id, ed.date; attendance_summary ats ON ed.employee_id = ats.id AND ed.date = ats.date
LEFT JOIN
leave_data ld ON ed.employee_id = ld.employee_id
AND ed.date >= DATE(ld.leave_start)
AND ed.date <= DATE(ld.leave_end)
ORDER BY
ed.employee_id, ed.date;
""" """
# Combine all parameters in the correct order: # Combine all parameters in the correct order:
# 1. date_range params (start_date_str, end_date_str) # 1. date_range params (start_date_str, end_date_str)
# 2. employee_dates params (emp_date_params) # 2. employee_dates params (emp_date_params)
@ -196,6 +201,7 @@ class AttendanceReport(models.Model):
'check_in': r['first_check_in'], 'check_in': r['first_check_in'],
'check_out': r['last_check_out'], 'check_out': r['last_check_out'],
'worked_hours': float(r['total_worked_hours']) if r['total_worked_hours'] is not None else 0.0, 'worked_hours': float(r['total_worked_hours']) if r['total_worked_hours'] is not None else 0.0,
'break_hours': float(r['total_break_hours']) if r['total_break_hours'] is not None else 0.0,
'status': r['status'] 'status': r['status']
}) })
@ -213,7 +219,6 @@ class AttendanceReport(models.Model):
attendance_data = self.get_attendance_report(department_id, employee_id, start_date, end_date) attendance_data = self.get_attendance_report(department_id, employee_id, start_date, end_date)
if not attendance_data: if not attendance_data:
raise UserError("No data to export!") raise UserError("No data to export!")
# Create workbook and sheet # Create workbook and sheet
workbook = xlwt.Workbook(encoding='utf-8') workbook = xlwt.Workbook(encoding='utf-8')
sheet = workbook.add_sheet('Attendance Report') sheet = workbook.add_sheet('Attendance Report')
@ -281,28 +286,26 @@ class AttendanceReport(models.Model):
) )
# Set column widths (in units of 1/256 of a character width) # Set column widths (in units of 1/256 of a character width)
col_widths = [6000, 8000, 7000, 3000, 4000, 5000, 5000, 4000, 5000] col_widths = [6000, 8000, 7000, 3000, 4000, 5000, 5000, 4000, 4000, 5000]
for i, width in enumerate(col_widths): for i, width in enumerate(col_widths):
sheet.col(i).width = width sheet.col(i).width = width
# Write title # Write title
sheet.write_merge(0, 0, 0, 8, 'ATTENDANCE REPORT', title_style) sheet.write_merge(0, 0, 0, 9, 'ATTENDANCE REPORT', title_style)
# Write date range # Write date range
date_range = f"From: {start_date} To: {end_date}" date_range = f"From: {start_date} To: {end_date}"
sheet.write_merge(1, 1, 0, 8, date_range, xlwt.easyxf( sheet.write_merge(1, 1, 0, 9, date_range, xlwt.easyxf(
'font: italic on; align: horiz center' 'font: italic on; align: horiz center'
)) ))
# Write headers # Write headers
headers = [ headers = [
'Department', 'Employee Name','Week', 'Date', 'Day', 'Department', 'Employee Name','Week', 'Date', 'Day',
'Check-in', 'Check-out', 'Worked Hours', 'Status' 'Check-in', 'Check-out', 'Worked Hours', 'Break Hours', 'Status'
] ]
for col_num, header in enumerate(headers): for col_num, header in enumerate(headers):
sheet.write(2, col_num, header, header_style) sheet.write(2, col_num, header, header_style)
# Write data rows # Write data rows
current_employee = None current_employee = None
for row_num, record in enumerate(attendance_data, start=3): for row_num, record in enumerate(attendance_data, start=3):
@ -345,11 +348,16 @@ class AttendanceReport(models.Model):
else: else:
sheet.write(row_num, 7, str(record['worked_hours']), data_style) sheet.write(row_num, 7, str(record['worked_hours']), data_style)
sheet.write(row_num, 8, record['status'], status_style) # Break hours formatting
if isinstance(record['break_hours'], (float, int)):
sheet.write(row_num, 8, float(record['break_hours']), hours_style)
else:
sheet.write(row_num, 8, str(record['break_hours']), data_style)
sheet.write(row_num, 9, record['status'], status_style)
# Add freeze panes (headers will stay visible when scrolling) # Add freeze panes (headers will stay visible when scrolling)
sheet.set_panes_frozen(True) sheet.set_panes_frozen(True)
sheet.set_horz_split_pos(4) # After row 3 (headers) sheet.set_horz_split_pos(3) # After row 3 (headers)
sheet.set_vert_split_pos(0) # No vertical split sheet.set_vert_split_pos(0) # No vertical split
# Save to buffer # Save to buffer

View File

@ -206,7 +206,7 @@ export default class AttendanceReport extends Component {
} }
async generateReport() { async generateReport() {
debugger;
let { startDate, endDate, selectedEmployeeIds } = this.state; let { startDate, endDate, selectedEmployeeIds } = this.state;
startDate = $('#from_date').val() startDate = $('#from_date').val()
endDate = $('#to_date').val() endDate = $('#to_date').val()
@ -231,7 +231,7 @@ export default class AttendanceReport extends Component {
// Fetch the attendance data based on the date range and selected employees // Fetch the attendance data based on the date range and selected employees
// const attendanceData = await this.orm.searchRead('hr.attendance', domain, ['employee_id', 'check_in', 'check_out', 'worked_hours']); // const attendanceData = await this.orm.searchRead('hr.attendance', domain, ['employee_id', 'check_in', 'check_out', 'worked_hours']);
const attendanceData = await this.orm.call('attendance.report','get_attendance_report',[$('#dept').val(),$('#emp').val(),startDate,endDate]); const attendanceData = await this.orm.call('attendance.report','get_attendance_report',[$('#dept').val(),$('#emp').val(),startDate,endDate]);
debugger;
// Group data by employee_id // Group data by employee_id
const rawGroups = this.groupDataByEmployee(attendanceData); const rawGroups = this.groupDataByEmployee(attendanceData);

View File

@ -65,6 +65,7 @@
<th>Check In</th> <th>Check In</th>
<th>Check Out</th> <th>Check Out</th>
<th>Worked Hours</th> <th>Worked Hours</th>
<th>Break Hours</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
</thead> </thead>
@ -78,7 +79,16 @@
<td><t t-esc="data.day_name"/></td> <td><t t-esc="data.day_name"/></td>
<td><t t-esc="data.check_in"/></td> <td><t t-esc="data.check_in"/></td>
<td><t t-esc="data.check_out"/></td> <td><t t-esc="data.check_out"/></td>
<td><t t-esc="data.worked_hours"/></td> <td>
<t t-set="hours" t-value="Math.floor(data.worked_hours)"/>
<t t-set="minutes" t-value="Math.round((data.worked_hours - hours) * 60)"/>
<t t-esc="hours"/>:<t t-esc="minutes >= 10 ? minutes : '0' + minutes"/>
</td>
<td>
<t t-set="hours" t-value="Math.floor(data.break_hours)"/>
<t t-set="minutes" t-value="Math.round((data.break_hours - hours) * 60)"/>
<t t-esc="hours"/>:<t t-esc="minutes >= 10 ? minutes : '0' + minutes"/>
</td>
<td><t t-esc="data.status"/></td> <td><t t-esc="data.status"/></td>
</tr> </tr>
</tbody> </tbody>

View File

@ -116,6 +116,7 @@ Dashboard Ninja v16.0,
'ks_dashboard_ninja/static/src/css/ks_toggle_icon.css', 'ks_dashboard_ninja/static/src/css/ks_toggle_icon.css',
'ks_dashboard_ninja/static/src/css/ks_flower_view.css', 'ks_dashboard_ninja/static/src/css/ks_flower_view.css',
'ks_dashboard_ninja/static/src/css/ks_map_view.css', 'ks_dashboard_ninja/static/src/css/ks_map_view.css',
'ks_dashboard_ninja/static/src/css/ks_profile.css',
'ks_dashboard_ninja/static/src/css/ks_funnel_view.css', 'ks_dashboard_ninja/static/src/css/ks_funnel_view.css',
'ks_dashboard_ninja/static/src/css/ks_dashboard_options.css', 'ks_dashboard_ninja/static/src/css/ks_dashboard_options.css',
'/ks_dashboard_ninja/static/lib/js/gridstack-h5.js', '/ks_dashboard_ninja/static/lib/js/gridstack-h5.js',

View File

@ -387,7 +387,8 @@ class KsDashboardNinjaItems(models.Model):
('ks_to_do', 'To Do'), ('ks_to_do', 'To Do'),
('ks_map_view', 'Map View'), ('ks_map_view', 'Map View'),
('ks_funnel_chart', 'Funnel Chart'), ('ks_funnel_chart', 'Funnel Chart'),
('ks_bullet_chart', 'Bullet Chart') ('ks_bullet_chart', 'Bullet Chart'),
('ks_profile', 'Profile Chart'),
], default=lambda self: self._context.get('ks_dashboard_item_type', ], default=lambda self: self._context.get('ks_dashboard_item_type',
'ks_tile'), required=True, 'ks_tile'), required=True,
string="Dashboard Item Type", string="Dashboard Item Type",
@ -639,6 +640,7 @@ class KsDashboardNinjaItems(models.Model):
help="Display the total sum of each legends as it grows with times") help="Display the total sum of each legends as it grows with times")
ks_radial_preview = fields.Char(string="Radial Preview", default="Radial Preview") ks_radial_preview = fields.Char(string="Radial Preview", default="Radial Preview")
ks_map_preview = fields.Char(string="Map Preview", default="Map Preview") ks_map_preview = fields.Char(string="Map Preview", default="Map Preview")
ks_profile_preview = fields.Char(string="Profile Preview", default="Profile Preview")
ks_partners_map = fields.Char(compute="_compute_map_partners") ks_partners_map = fields.Char(compute="_compute_map_partners")
ks_country_id = fields.Many2one('res.country', string="Country") ks_country_id = fields.Many2one('res.country', string="Country")
ks_country_code = fields.Char(related="ks_country_id.code", store=True) ks_country_code = fields.Char(related="ks_country_id.code", store=True)
@ -694,6 +696,11 @@ class KsDashboardNinjaItems(models.Model):
ks_three_d = fields.Boolean(string='3D View', default=False) ks_three_d = fields.Boolean(string='3D View', default=False)
@api.onchange('ks_dashboard_item_type')
def onchange_ks_dashboard_item_type(self):
for rec in self:
if rec.ks_dashboard_item_type == 'ks_profile':
rec.ks_model_id = self.env['ir.model'].sudo().search([('model','=','hr.employee')],limit=1)
@api.model @api.model
def create_ai_dash(self, data, ks_dash_id, model): def create_ai_dash(self, data, ks_dash_id, model):
@ -4533,7 +4540,9 @@ class KsDashboardItemsActions(models.Model):
('ks_radar_view', 'Radar View'), ('ks_radar_view', 'Radar View'),
('ks_flower_view', 'Flower View'), ('ks_flower_view', 'Flower View'),
('ks_funnel_chart', 'Funnel Chart'), ('ks_funnel_chart', 'Funnel Chart'),
('ks_bullet_chart', 'Bullet Chart')], ('ks_bullet_chart', 'Bullet Chart'),
('ks_profile', 'Profile Chart')
],
string="Item Type") string="Item Type")
ks_dashboard_item_id = fields.Many2one('ks_dashboard_ninja.item', string="Dashboard Item") ks_dashboard_item_id = fields.Many2one('ks_dashboard_ninja.item', string="Dashboard Item")

View File

@ -0,0 +1,6 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 24C29.5228 24 34 19.5228 34 14C34 8.47715 29.5228 4 24 4C18.4772 4 14 8.47715 14 14C14 19.5228 18.4772 24 24 24Z"
stroke="" stroke-width="1.49595" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/>
<path d="M10 40C10 33.3726 16.3726 28 24 28C31.6274 28 38 33.3726 38 40"
stroke="" stroke-width="1.49595" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/>
</svg>

After

Width:  |  Height:  |  Size: 521 B

View File

@ -401,7 +401,6 @@ export class Ksdashboardgraph extends Component{
// 3D Bar Chart Implementation // 3D Bar Chart Implementation
create3DBarChart(chart, data, ks_data, item) { create3DBarChart(chart, data, ks_data, item) {
// Create axes // Create axes
debugger;
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis()); var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "category"; categoryAxis.dataFields.category = "category";
categoryAxis.renderer.labels.template.rotation = -45; categoryAxis.renderer.labels.template.rotation = -45;
@ -736,7 +735,6 @@ export class Ksdashboardgraph extends Component{
['ks_bar_chart','ks_horizontalBar_chart','ks_pie_chart','ks_doughnut_chart'].includes(item.ks_dashboard_item_type); ['ks_bar_chart','ks_horizontalBar_chart','ks_pie_chart','ks_doughnut_chart'].includes(item.ks_dashboard_item_type);
if (use3D) { if (use3D) {
debugger;
switch (theme) { switch (theme) {
case "dark": case "dark":
am4core.unuseAllThemes(); am4core.unuseAllThemes();

View File

@ -0,0 +1,218 @@
/** @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 { _t } from "@web/core/l10n/translation";
import { onAudioEnded } from '@ks_dashboard_ninja/js/ks_global_functions';
import { rpc } from "@web/core/network/rpc";
export class Ksdashboardprofile extends Component {
setup() {
debugger;
var self = this;
this.orm = useService("orm");
this.actionService = useService("action");
this.ks_profile = useRef('ks_profile');
this.ks_container_class = 'grid-stack-item';
this.aiAudioRef = useRef("aiAudioRef");
this.ks_inner_container_class = 'grid-stack-item-content';
this.state = useState({
employee: {
name: '',
image_1920: '',
job_id: null,
current_company_exp: null,
doj: '',
employee_id: null,
birthday: '',
attendance_state: null,
mobile_phone: '',
work_email: '',
private_street: '',
department_id: ''
}
});
this.storeService = useService("mail.store");
this.item = this.props.item;
this.ks_dashboard_data = this.props.dashboard_data;
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-profile-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_profile();
var update_interval = this.props.dashboard_data.ks_set_interval;
onWillUpdateProps(async (nextprops) => {
if (nextprops.ksdatefilter != 'none') {
await this.ksFetchUpdateProfile(this.item.id, this.props.dashboard_data.context);
}
if (Object.keys(nextprops.pre_defined_filter).length) {
if (nextprops.pre_defined_filter?.item_ids?.includes(this.item.id)) {
await this.ksFetchUpdateProfile(this.item.id, this.props.dashboard_data.context);
}
}
if (Object.keys(nextprops.custom_filter).length) {
if (nextprops.custom_filter?.item_ids?.includes(this.item.id)) {
await this.ksFetchUpdateProfile(this.item.id, this.props.dashboard_data.context);
}
}
});
useEffect(() => {
if (update_interval && !this.env.inDialog) {
const interval = setInterval(() => {
this.ksFetchUpdateProfile(this.item.id, this.props.dashboard_data.context);
}, update_interval);
return () => clearInterval(interval);
}
});
onMounted(() => {
if (this.ks_ai_analysis) {
const dashboardItems = this.ks_profile.el.querySelectorAll('.ks_dashboarditem_id');
dashboardItems.forEach(item => {
item.classList.add('ks_ai_chart_body');
});
}
this.aiAudioRef.el?.addEventListener('ended', onAudioEnded);
});
onWillUnmount(() => {
this.aiAudioRef.el?.removeEventListener('ended', onAudioEnded);
});
}
ksFetchUpdateProfile(item_id, domain) {
var self = this;
if (!domain) {
if (this.__owl__.parent.component.hasOwnProperty('ksGetParamsForItemFetch') &&
this.__owl__.parent.component?.ksGetParamsForItemFetch(item_id) instanceof Function) {
domain = this.__owl__.parent.component?.ksGetParamsForItemFetch(item_id);
} else {
domain = {};
}
}
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, domain
],
kwargs: { context: this.props.dashboard_data.context },
}).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_profile();
}.bind(this));
}
ksStopClickPropagation(e) {
this.ksAllowItemClick = false;
}
prepare_profile() {
var self = this;
var ks_rgba_background_color, ks_rgba_font_color, ks_rgba_button_color;
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_button_color = self._ks_get_rgba_format(this.item.ks_button_color);
if (this.item.ks_info) {
var ks_description = this.item.ks_info.split('\n');
var ks_description = ks_description.filter(element => element !== '');
} else {
var ks_description = false;
}
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;
// Fetch employee data
this.fetchEmployeeData();
}
async fetchEmployeeData() {
try {
debugger;
const employeeData = await this.orm.call("hr.employee", 'get_user_employee_details');
debugger;
if (employeeData && employeeData.length > 0) {
const employee = employeeData[0];
this.state.employee = {
name: employee.name,
image_1920: employee.image_1920,
doj: employee.doj,
job_id: employee.job_id,
employee_id: employee.employee_id,
current_company_exp: employee.current_company_exp,
attendance_state: employee.attendance_state,
birthday: employee.birthday,
mobile_phone: employee.mobile_phone,
work_email: employee.work_email,
private_street: employee.private_street,
department_id: employee.department_id ? employee.department_id[1] : ''
};
}
} catch (error) {
console.error("Error fetching employee data:", error);
}
}
async attendance_sign_in_out() {
try {
await this.orm.call('hr.employee', 'attendance_manual', [[this.state.employee.id]]);
this.state.employee.attendance_state =
this.state.employee.attendance_state === 'checked_in' ? 'checked_out' : 'checked_in';
} catch (error) {
console.error("Error updating attendance:", error);
}
}
_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 + ";";
return {
'background_style': background_style,
};
}
}
Ksdashboardprofile.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 },
ks_speak: { type: Function, Optional: true },
hideButtons: { type: Number, optional: true },
on_dialog: { type: Boolean, optional: true },
generate_dialog: { type: Boolean, optional: true },
};
Ksdashboardprofile.template = "Ksdashboardprofile";

View File

@ -0,0 +1,330 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="Ksdashboardprofile">
<t t-if="props.on_dialog">
<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_profile">
<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_profile_layout"/>
</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_profile_layout"/>
</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_profile">
<t t-if="ks_ai_analysis">
<div class="ks_ai_explain_body">
<t t-call="ks_profile_layout"/>
<!-- <t t-call="ks_item_explanation"/>-->
</div>
</t>
<t t-else="">
<t t-call="ks_profile_layout"/>
</t>
</div>
</t>
</t>
<t t-name="ks_profile_layout">
<div t-att-class="'ks_dashboarditem_id ks_profile_carousel encapsulated-profile-container ks_explain_ai_view ks_dashboard_profile 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">
<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">
<t t-if="item.ksIsDashboardManager and props.hideButtons and !props.on_dialog">
<button title="Move/Duplicate" data-bs-toggle="dropdown"
class="ks_dashboard_item_action dropdown-toggle img-bg hover-item me-lg-2 kpi-tile-img bg-white d-md-block d-none"
type="button"
t-att-style="'color:'+ ks_rgba_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.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 dropdown-menu-right">
<li class="ks_md_heading m-2">
<span>Select Dashboard</span>
</li>
<li class="m-2">
<select class="o_input o_group_selector o_add_group ks_dashboard_select">
<t t-foreach="ks_dashboard_list" t-as="ks_dashboard" t-key="ks_dashboard.id">
<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"
tabindex="-1" type="button">Duplicate
</button>
<button class="dash-btn-red o_apply_group o_add_group ks_move_item" tabindex="-1"
type="button">Move
</button>
</li>
</ul>
<button title="Quick Customize"
class="ks_dashboard_quick_edit_action_popup d-md-block d-none img-bg hover-item me-lg-2 kpi-tile-img bg-white"
t-att-style="'color:'+ ks_rgba_button_color + ';'"
type="button" t-att-data-item-id="item.id">
<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>
<div class="ks_chart_inner_buttons dropdown d-md-none dn-setting-panel">
<button data-bs-toggle="dropdown"
class="ks_dashboard_item_action bg-transparent img-bg hover-item me-lg-2 kpi-tile-img dropdown-toggle"
type="button"
t-att-style="'color:'+ ks_rgba_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 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z" fill="#4B5563" stroke="currentColor" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33301 8.58667V7.41333C1.33301 6.72 1.89967 6.14667 2.59967 6.14667C3.80634 6.14667 4.29967 5.29333 3.69301 4.24667C3.34634 3.64667 3.55301 2.86667 4.15967 2.52L5.31301 1.86C5.83967 1.54666 6.51967 1.73333 6.83301 2.26L6.90634 2.38666C7.50634 3.43333 8.49301 3.43333 9.09967 2.38666L9.17301 2.26C9.48634 1.73333 10.1663 1.54666 10.693 1.86L11.8463 2.52C12.453 2.86667 12.6597 3.64667 12.313 4.24667C11.7063 5.29333 12.1997 6.14667 13.4063 6.14667C14.0997 6.14667 14.673 6.71333 14.673 7.41333V8.58667C14.673 9.28 14.1063 9.85333 13.4063 9.85333C12.1997 9.85333 11.7063 10.7067 12.313 11.7533C12.6597 12.36 12.453 13.1333 11.8463 13.48L10.693 14.14C10.1663 14.4533 9.48634 14.2667 9.17301 13.74L9.09967 13.6133C8.49967 12.5667 7.51301 12.5667 6.90634 13.6133L6.83301 13.74C6.51967 14.2667 5.83967 14.4533 5.31301 14.14L4.15967 13.48C3.55301 13.1333 3.34634 12.3533 3.69301 11.7533C4.29967 10.7067 3.80634 9.85333 2.59967 9.85333C1.89967 9.85333 1.33301 9.28 1.33301 8.58667Z" stroke="currentColor" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div role="menu" class="dropdown-menu dropdown-menu-right ks_chart_inner_min_width">
<div class="ks_chart_export_menu">
<div class="ks_dashboard_item_customize ks_chart_export_menu_item"
t-att-data-item_id="item.id"
data-format="chart_xls">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
<span>Customize Item</span>
</div>
<div class="ks_dashboard_item_delete ks_chart_export_menu_item"
t-att-data-item_id="item.id"
data-format="chart_xls">
<svg width="16" height="16" viewBox="0 0 16 16" fill="" class="me-1" 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> <span>Remove Item</span>
</div>
</div>
</div>
</div>
</t>
<t t-if="item.ksIsUser and props.hideButtons and !props.on_dialog">
<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-lg-2 kpi-tile-img bg-white d-md-block d-none"
type="button"
t-att-style="'color:'+ ks_rgba_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 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="item.ks_company"/></span>
</div>
<div class="ks_info" style="margin-left:10px">
<t t-if="ks_info">
<t t-foreach="ks_info" t-as="ks_description" t-key="ks_description_index">
<span><t t-esc="ks_description"/></span>
<br></br>
</t>
</t>
</div>
</div>
</div>
</div>
</t>
<t t-if="item.ksIsUser and props.hideButtons and !props.on_dialog">
<div class="ks_chart_inner_buttons d-md-block d-none">
<button title="More" data-bs-toggle="dropdown"
class="ks_dashboard_item_action img-bg hover-item me-lg-2 kpi-tile-img bg-white dropdown-toggle o-no-caret"
type="button"
t-att-style="'color:'+ ks_rgba_button_color + ';'"
aria-expanded="true">
<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="dropdown-menu dropdown-menu-right">
<div class="ks_chart_export_menu">
<div class="ks_chart_json_export ks_chart_export_menu_item d-flex"
t-att-data-item_id="item_id"
data-format="chart_xls">
<img src="/ks_dashboard_ninja/static/images/dashboardOverview/document-upload.svg" alt="document-upload" class="img-fluid" loading="lazy"/>
<span>Export Item</span>
</div>
<t t-if="item.ksIsDashboardManager and props.hideButtons and !props.on_dialog">
<div class="d-flex align-items-center">
<button type="button" title="Remove Item"
class="ks_dashboard_item_delete d-md-flex d-none img-bg hover-item me-lg-2 kpi-tile-img bg-white"
t-att-style="'color:'+ ks_rgba_button_color + ';'">
<svg width="16" height="16" viewBox="0 0 16 16" class="me-1" 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>
Remove
</button>
</div>
<div class="d-flex align-items-center">
<button id='ks_ai_item_exp_dash' class="img-bg hover-item me-lg-2 kpi-tile-img bg-white"
t-on-click="_onButtonClick4" 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 align-items-center">
<button class="ks_dashboard_item_chatter_wizard img-bg hover-item me-lg-2 kpi-tile-img bg-white" title="Internal Chat" 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>
</t>
</div>
</div>
</div>
</t>
</div>
<!-- Profile Card Content -->
<div class="ks-profile-header">
<div class="ks-profile-image">
<img t-att-src="'data:image/png;base64,' + state.employee.image_1920"
alt="Employee Image"
t-att-class="state.employee.image_1920 ? '' : 'ks-default-image'" />
</div>
<div class="ks-profile-details">
<div class="ks-profile-info">
<h1>
<t t-esc="state.employee.name"/>
<t t-if="state.employee.employee_id">
- <t t-esc="state.employee.employee_id"/>
</t>
</h1>
<p class="ks-job-title">
<t t-if="state.employee.job_id">
<t t-esc="state.employee.job_id[1]"/>
</t>
<t t-if="!state.employee.job_id">
Add Job Title
</t>
</p>
<p class="ks-joined">
<t t-if="state.employee.doj">
Joined <t t-esc="state.employee.current_company_exp"/> ago
</t>
<t t-if="!state.employee.doj">
Joined Date: <t t-esc="state.employee.doj"/>
</t>
</p>
<p>
Birthday :
<t t-if="state.employee.birthday">
<t t-esc="state.employee.birthday"/>
</t>
<t t-else="">
--/--/----
</t>
</p>
</div>
<div class="ks-profile-contact">
<p>
<i class="fa fa-phone"></i>
<t t-if="state.employee.mobile_phone">
<t t-esc="state.employee.mobile_phone"/>
</t>
<t t-if="!state.employee.mobile_phone">
---
</t>
</p>
<p>
<i class="fa fa-envelope"></i>
<t t-if="state.employee.work_email">
<t t-esc="state.employee.work_email"/>
</t>
<t t-if="!state.employee.work_email">
---
</t>
</p>
<p>
<i class="fa fa-home"></i> Address:
<t t-if="state.employee.private_street">
<t t-esc="state.employee.private_street"/>
</t>
<t t-if="!state.employee.private_street">
---
</t>
</p>
</div>
</div>
<div class="ks-stat-att" t-on-click="attendance_sign_in_out">
<t t-if="state.employee.attendance_state == 'checked_out'">
<div class="ks-stat-atticon">
<i class="fa fa-sign-in"></i>
</div>
<div class="ks-stat-attcontent">
<p>Check In</p>
</div>
</t>
<t t-if="state.employee.attendance_state == 'checked_in'">
<div class="ks-stat-atticon">
<i class="fa fa-sign-out"></i>
</div>
<div class="ks-stat-attcontent">
<p>Check Out</p>
</div>
</t>
</div>
</div>
</div>
</t>
</templates>

View File

@ -0,0 +1,239 @@
.ks-profile-container {
font-family: 'Arial', sans-serif;
color: #333;
background: linear-gradient(135deg, #6a11cb, #2575fc);
border-radius: 15px;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.ks-profile-header {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 20px;
color: #fff;
}
.ks-profile-image {
flex: 0 0 150px;
height: 150px;
border-radius: 50%;
overflow: hidden;
margin-right: 20px;
border: 4px solid rgba(255, 255, 255, 0.3);
}
.ks-profile-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.ks-default-image {
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.ks-profile-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.ks-profile-info h1 {
margin: 0;
font-size: 1.8em;
font-weight: bold;
color: #fff;
}
.ks-job-title {
font-style: italic;
font-size: 1.1em;
color: rgba(255, 255, 255, 0.9);
margin: 0;
}
.ks-joined {
font-size: 0.9em;
opacity: 0.9;
margin: 0;
}
.ks-profile-contact {
margin-top: 10px;
font-size: 0.9em;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.ks-profile-contact p {
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.ks-profile-contact i {
color: rgba(255, 255, 255, 0.8);
font-size: 1.2em;
}
.ks-stat-att {
width: 100px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
text-align: center;
padding: 15px;
cursor: pointer;
transition: all 0.3s ease;
margin-left: auto;
}
.ks-stat-att:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-3px);
}
.ks-stat-atticon {
font-size: 2em;
color: #fff;
margin-bottom: 5px;
}
.ks-stat-attcontent p {
font-size: 0.9em;
color: #fff;
margin: 0;
font-weight: bold;
}
.ks-profile-container {
font-family: 'Arial', sans-serif;
color: #333;
background: linear-gradient(135deg, #6a11cb, #2575fc);
border-radius: 15px;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.ks-profile-header {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 20px;
color: #fff;
}
.ks-profile-image {
flex: 0 0 150px;
height: 150px;
border-radius: 50%;
overflow: hidden;
margin-right: 20px;
border: 4px solid rgba(255, 255, 255, 0.3);
}
.ks-profile-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.ks-default-image {
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.ks-profile-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.ks-profile-info h1 {
margin: 0;
font-size: 1.8em;
font-weight: bold;
color: #fff;
}
.ks-job-title {
font-style: italic;
font-size: 1.1em;
color: rgba(255, 255, 255, 0.9);
margin: 0;
}
.ks-joined {
font-size: 0.9em;
opacity: 0.9;
margin: 0;
}
.ks-profile-contact {
margin-top: 10px;
font-size: 0.9em;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.ks-profile-contact p {
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.ks-profile-contact i {
color: rgba(255, 255, 255, 0.8);
font-size: 1.2em;
}
.ks-stat-att {
width: 100px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
text-align: center;
padding: 15px;
cursor: pointer;
transition: all 0.3s ease;
margin-left: auto;
}
.ks-stat-att:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-3px);
}
.ks-stat-atticon {
font-size: 2em;
color: #fff;
margin-bottom: 5px;
}
.ks-stat-attcontent p {
font-size: 0.9em;
color: #fff;
margin: 0;
font-weight: bold;
}
/* Additional styles for the dashboard container */
.encapsulated-profile-container {
background: linear-gradient(135deg, #6a11cb, #2575fc);
border-radius: 15px;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
overflow: hidden;
}

View File

@ -7,6 +7,8 @@ import { Ksdashboardtile } from '@ks_dashboard_ninja/components/ks_dashboard_til
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { rpc } from "@web/core/network/rpc"; import { rpc } from "@web/core/network/rpc";
import { Ksdashboardkpiview } from '@ks_dashboard_ninja/components/ks_dashboard_kpi_view/ks_dashboard_kpi'; import { Ksdashboardkpiview } from '@ks_dashboard_ninja/components/ks_dashboard_kpi_view/ks_dashboard_kpi';
import { Ksdashboardprofile } from '@ks_dashboard_ninja/components/ks_dashboard_profile_view/ks_dashboard_profile';
patch(Ksdashboardgraph.prototype,{ patch(Ksdashboardgraph.prototype,{
@ -35,6 +37,7 @@ patch(Ksdashboardgraph.prototype,{
} }
}); });
patch(Ksdashboardkpiview.prototype,{ patch(Ksdashboardkpiview.prototype,{
async _openChatWizard(ev){ async _openChatWizard(ev){

View File

@ -23,6 +23,7 @@ import { Ksdashboardtile } from '@ks_dashboard_ninja/components/ks_dashboard_til
import { Ksdashboardtodo } from '@ks_dashboard_ninja/components/ks_dashboard_to_do_item/ks_dashboard_to_do'; 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 { 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 { Ksdashboardgraph } from '@ks_dashboard_ninja/components/ks_dashboard_graphs/ks_dashboard_graphs';
import { Ksdashboardprofile } from '@ks_dashboard_ninja/components/ks_dashboard_profile_view/ks_dashboard_profile';
import { Dialog } from "@web/core/dialog/dialog"; import { Dialog } from "@web/core/dialog/dialog";
import { rpc } from "@web/core/network/rpc"; import { rpc } from "@web/core/network/rpc";
@ -208,7 +209,7 @@ export class KsAIDashboardNinja extends Component {
// Add widgets to the grid // Add widgets to the grid
this.state.ks_dashboard_items.forEach(function (item) { this.state.ks_dashboard_items.forEach(function (item) {
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 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', 'ks_profile'];
var ksPreview = document.getElementById(item.id); var ksPreview = document.getElementById(item.id);
if (ksPreview) { if (ksPreview) {
@ -533,6 +534,6 @@ export class KsAIDashboardNinja extends Component {
speak_once(ev,item){ speak_once(ev,item){
} }
} }
KsAIDashboardNinja.components = { Ksdashboardtile,Ksdashboardgraph,Ksdashboardkpiview, Ksdashboardtodo, Dialog, FormViewDialog}; KsAIDashboardNinja.components = { Ksdashboardtile,Ksdashboardgraph,Ksdashboardkpiview, Ksdashboardprofile, Ksdashboardtodo, Dialog, FormViewDialog};
KsAIDashboardNinja.template = "ksaiDashboardNinjaHeader" KsAIDashboardNinja.template = "ksaiDashboardNinjaHeader"
registry.category("actions").add("ks_ai_dashboard_ninja",KsAIDashboardNinja); registry.category("actions").add("ks_ai_dashboard_ninja",KsAIDashboardNinja);

View File

@ -10,6 +10,9 @@ import { Ksdashboardgraph } from './../components/ks_dashboard_graphs/ks_dashboa
import { Ksdashboardtile } from './../components/ks_dashboard_tile_view/ks_dashboard_tile'; import { Ksdashboardtile } from './../components/ks_dashboard_tile_view/ks_dashboard_tile';
import { Ksdashboardtodo } from './../components/ks_dashboard_to_do_item/ks_dashboard_to_do'; import { Ksdashboardtodo } from './../components/ks_dashboard_to_do_item/ks_dashboard_to_do';
import { Ksdashboardkpiview } from './../components/ks_dashboard_kpi_view/ks_dashboard_kpi'; import { Ksdashboardkpiview } from './../components/ks_dashboard_kpi_view/ks_dashboard_kpi';
import { Ksdashboardprofile } from './../components/ks_dashboard_profile_view/ks_dashboard_profile';
import { rpc } from "@web/core/network/rpc"; import { rpc } from "@web/core/network/rpc";
import { user } from "@web/core/user"; import { user } from "@web/core/user";
@ -129,7 +132,7 @@ CustomDialog.props = {
} }
CustomDialog.components = { Dialog, Ksdashboardgraph, Ksdashboardtile, Ksdashboardkpiview, Ksdashboardtodo }; CustomDialog.components = { Dialog, Ksdashboardgraph, Ksdashboardtile, Ksdashboardkpiview, Ksdashboardtodo, Ksdashboardprofile };
CustomDialog.template = "ks_dashboard_ninja.CustomDialog"; CustomDialog.template = "ks_dashboard_ninja.CustomDialog";

View File

@ -22,6 +22,7 @@ import { Ksdashboardlistview } from '@ks_dashboard_ninja/components/ks_dashboard
import { Ksdashboardtodo } from '@ks_dashboard_ninja/components/ks_dashboard_to_do_item/ks_dashboard_to_do'; 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 { 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 { Ksdashboardgraph } from '@ks_dashboard_ninja/components/ks_dashboard_graphs/ks_dashboard_graphs';
import { Ksdashboardprofile } from '@ks_dashboard_ninja/components/ks_dashboard_profile_view/ks_dashboard_profile';
import { KschatwithAI } from '@ks_dashboard_ninja/components/chatwithAI/ks_chat'; import { KschatwithAI } from '@ks_dashboard_ninja/components/chatwithAI/ks_chat';
import { CustomFilter } from '@ks_dashboard_ninja/js/custom_filter'; import { CustomFilter } from '@ks_dashboard_ninja/js/custom_filter';
import { DateTimePicker } from "@web/core/datetime/datetime_picker"; import { DateTimePicker } from "@web/core/datetime/datetime_picker";
@ -379,7 +380,7 @@ export class KsDashboardNinja extends Component {
this.gridstackConfig = JSON.parse(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++) { 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 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', 'ks_profile']
var ks_preview = document.getElementById(item[i].id) var ks_preview = document.getElementById(item[i].id)
if (ks_preview && !this.ks_dashboard_data.ks_ai_explain_dash) { if (ks_preview && !this.ks_dashboard_data.ks_ai_explain_dash) {
if (item[i].id in self.gridstackConfig) { if (item[i].id in self.gridstackConfig) {
@ -2022,7 +2023,7 @@ export class KsDashboardNinja extends Component {
} }
KsDashboardNinja.components = { Ksdashboardtile, Ksdashboardlistview, Ksdashboardgraph, Ksdashboardkpiview, Ksdashboardtodo, DateTimePicker, DateTimeInput,KschatwithAI, CustomFilter}; KsDashboardNinja.components = { Ksdashboardtile, Ksdashboardlistview, Ksdashboardgraph, Ksdashboardkpiview, Ksdashboardtodo, Ksdashboardprofile, DateTimePicker, DateTimeInput,KschatwithAI, CustomFilter};
KsDashboardNinja.template = "ks_dashboard_ninja.KsDashboardNinjaHeader" KsDashboardNinja.template = "ks_dashboard_ninja.KsDashboardNinjaHeader"
registry.category("actions").add("ks_dashboard_ninja", KsDashboardNinja); registry.category("actions").add("ks_dashboard_ninja", KsDashboardNinja);

View File

@ -35,7 +35,7 @@ export class KsGraphPreview extends Component {
el.remove(); el.remove();
}); });
if (rec.ks_dashboard_item_type !== 'ks_tile' && rec.ks_dashboard_item_type !== 'ks_kpi' && if (rec.ks_dashboard_item_type !== 'ks_tile' && rec.ks_dashboard_item_type !== 'ks_kpi' && rec.ks_dashboard_item_type !== 'ks_profile' &&
rec.ks_dashboard_item_type !== 'ks_list_view' && rec.ks_dashboard_item_type !== 'ks_to_do') { rec.ks_dashboard_item_type !== 'ks_list_view' && rec.ks_dashboard_item_type !== 'ks_to_do') {
if (rec.ks_data_calculation_type !== "query") { if (rec.ks_data_calculation_type !== "query") {

View File

@ -24,7 +24,8 @@ export class KsDashboarditemtype extends Component {
{'id':14,'name':'List','Tech_name':'ks_list_view'}, {'id':14,'name':'List','Tech_name':'ks_list_view'},
{'id':15,'name':'Map','Tech_name':'ks_map_view'}, {'id':15,'name':'Map','Tech_name':'ks_map_view'},
{'id':16,'name':'To-do','Tech_name':'ks_to_do'}, {'id':16,'name':'To-do','Tech_name':'ks_to_do'},
{'id':17,'name':'KPI','Tech_name':'ks_kpi'} {'id':17,'name':'KPI','Tech_name':'ks_kpi'},
{'id':18,'name':'Profile','Tech_name':'ks_profile'}
]}); ]});
} }

View File

@ -195,6 +195,15 @@
<path d="M14.7006 21.5609L20.8749 16.4059C21.3649 15.9943 22.0117 16.1119 22.3449 16.6607L24.6971 20.5809C25.0303 21.1297 25.6771 21.2277 26.1671 20.8357L32.3414 15.6807" stroke="" stroke-width="1.49595" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> <path d="M14.7006 21.5609L20.8749 16.4059C21.3649 15.9943 22.0117 16.1119 22.3449 16.6607L24.6971 20.5809C25.0303 21.1297 25.6771 21.2277 26.1671 20.8357L32.3414 15.6807" stroke="" stroke-width="1.49595" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
</t> </t>
<t t-elif="item_name === 'Profile'">
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24 24C29.5228 24 34 19.5228 34 14C34 8.47715 29.5228 4 24 4C18.4772 4 14 8.47715 14 14C14 19.5228 18.4772 24 24 24Z"
stroke="" stroke-width="1.49595" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/>
<path d="M10 40C10 33.3726 16.3726 28 24 28C31.6274 28 38 33.3726 38 40"
stroke="" stroke-width="1.49595" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10"/>
</svg>
</t>
</t> </t>

View File

@ -0,0 +1,73 @@
/** @odoo-module */
import { registry } from "@web/core/registry";
import { useState, onMounted } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { Component } from "@odoo/owl";
class KsProfile extends Component {
setup() {
this.orm = useService("orm");
this.state = useState({
employee: {
name: '',
image_1920: '',
job_id: null,
current_company_exp: null,
doj: '',
employee_id: null,
birthday: '',
attendance_state: null,
mobile_phone: '',
work_email: '',
private_street: '',
department_id: ''
}
});
onMounted(async () => {
await this.fetchEmployeeData();
});
}
async fetchEmployeeData() {
try {
debugger;
const employeeData = await this.orm.call("hr.employee", 'get_user_employee_details');
if (employeeData && employeeData.length > 0) {
const employee = employeeData[0];
this.state.employee = {
name: employee.name,
image_1920: employee.image_1920,
doj: employee.doj,
job_id: employee.job_id,
employee_id: employee.employee_id,
current_company_exp: employee.current_company_exp,
attendance_state: employee.attendance_state,
birthday: employee.birthday,
mobile_phone: employee.mobile_phone,
work_email: employee.work_email,
private_street: employee.private_street,
department_id: employee.department_id ? employee.department_id[1] : ''
};
}
} catch (error) {
console.error("Error fetching employee data:", error);
}
}
async attendance_sign_in_out() {
try {
await this.orm.call('hr.employee', 'attendance_manual', [[this.state.employee.id]]);
this.state.employee.attendance_state =
this.state.employee.attendance_state === 'checked_in' ? 'checked_out' : 'checked_in';
} catch (error) {
console.error("Error updating attendance:", error);
}
}
}
KsProfile.template = "ks_profile";
export const KsProfileField = {
component: KsProfile,
};
registry.category("fields").add('ks_profile_preview', KsProfileField);

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="ks_profile" owl="1">
<div class="ks-profile-container">
<div class="ks-profile-header">
<div class="ks-profile-image">
<img t-att-src="'data:image/png;base64,' + state.employee.image_1920"
alt="Employee Image"
t-att-class="state.employee.image_1920 ? '' : 'ks-default-image'" />
</div>
<div class="ks-profile-details">
<div class="ks-profile-info">
<h1>
<t t-esc="state.employee.name"/>
<t t-if="state.employee.employee_id">
- <t t-esc="state.employee.employee_id"/>
</t>
</h1>
<p class="ks-job-title">
<t t-if="state.employee.job_id">
<t t-esc="state.employee.job_id[1]"/>
</t>
<t t-if="!state.employee.job_id">
Add Job Title
</t>
</p>
<p class="ks-joined">
<t t-if="state.employee.doj">
Joined <t t-esc="state.employee.current_company_exp"/> ago
</t>
<t t-if="!state.employee.doj">
Joined Date: <t t-esc="state.employee.doj"/>
</t>
</p>
<p>
Birthday :
<t t-if="state.employee.birthday">
<t t-esc="state.employee.birthday"/>
</t>
<t t-else="">
--/--/----
</t>
</p>
</div>
<div class="ks-profile-contact">
<p>
<i class="fa fa-phone"></i>
<t t-if="state.employee.mobile_phone">
<t t-esc="state.employee.mobile_phone"/>
</t>
<t t-if="!state.employee.mobile_phone">
---
</t>
</p>
<p>
<i class="fa fa-envelope"></i>
<t t-if="state.employee.work_email">
<t t-esc="state.employee.work_email"/>
</t>
<t t-if="!state.employee.work_email">
---
</t>
</p>
<p>
<i class="fa fa-home"></i> Address:
<t t-if="state.employee.private_street">
<t t-esc="state.employee.private_street"/>
</t>
<t t-if="!state.employee.private_street">
---
</t>
</p>
</div>
</div>
<div class="ks-stat-att" t-on-click="attendance_sign_in_out">
<t t-if="state.employee.attendance_state == 'checked_out'">
<div class="ks-stat-atticon">
<i class="fa fa-sign-in"></i>
</div>
<div class="ks-stat-attcontent">
<p>Check In</p>
</div>
</t>
<t t-if="state.employee.attendance_state == 'checked_in'">
<div class="ks-stat-atticon">
<i class="fa fa-sign-out"></i>
</div>
<div class="ks-stat-attcontent">
<p>Check Out</p>
</div>
</t>
</div>
</div>
</div>
</t>
</templates>

View File

@ -6,17 +6,17 @@
<div class="row" style="min-height: 60vh;"> <div class="row" style="min-height: 60vh;">
<div class="col-6 d-flex flex-column align-items-stretch chat_explain_ai"> <div class="col-6 d-flex flex-column align-items-stretch chat_explain_ai">
<t t-if="props.ks_dashboard_item_type === 'ks_tile'"> <!-- <t t-if="props.ks_dashboard_item_type === 'ks_tile'">-->
<Ksdashboardtile item="props.item" <!-- <Ksdashboardtile item="props.item"-->
dashboard_data="props.dashboard_data" <!-- dashboard_data="props.dashboard_data"-->
ksdatefilter="props.ksdatefilter" <!-- ksdatefilter="props.ksdatefilter"-->
pre_defined_filter="props.pre_defined_filter" <!-- pre_defined_filter="props.pre_defined_filter"-->
custom_filter="props.custom_filter" <!-- custom_filter="props.custom_filter"-->
hideButtons="0" <!-- hideButtons="0"-->
on_dialog="true" <!-- on_dialog="true"-->
ks_speak="props.ks_speak"/> <!-- ks_speak="props.ks_speak"/>-->
</t> <!-- </t>-->
<t t-elif="props.ks_dashboard_item_type === 'ks_kpi'"> <t t-if="props.ks_dashboard_item_type === 'ks_kpi'">
<Ksdashboardkpiview item="props.item" <Ksdashboardkpiview item="props.item"
dashboard_data="props.dashboard_data" dashboard_data="props.dashboard_data"
ksdatefilter="props.ksdatefilter" ksdatefilter="props.ksdatefilter"

View File

@ -938,6 +938,9 @@
<t t-elif="items.ks_dashboard_item_type === 'ks_to_do'"> <t t-elif="items.ks_dashboard_item_type === 'ks_to_do'">
<Ksdashboardtodo item="items" hideButtons="1" dashboard_data="ks_dashboard_data" explain_ai_whole="explain_ai_whole"/> <Ksdashboardtodo item="items" hideButtons="1" dashboard_data="ks_dashboard_data" explain_ai_whole="explain_ai_whole"/>
</t> </t>
<t t-elif="items.ks_dashboard_item_type === 'ks_profile'">
<Ksdashboardprofile item="items" hideButtons="1" on_dialog="on_dialog" dashboard_data="ks_dashboard_data" />
</t>
<t t-else=""> <t t-else="">
<Ksdashboardgraph item="items" hideButtons="1" explain_ai_whole="explain_ai_whole" dashboard_data="ks_dashboard_data" ksdatefilter="state.ksDateFilterSelection" pre_defined_filter = "state.pre_defined_filter" custom_filter="state.custom_filter" ks_speak="(ev)=>this.speak_once(ev,items)"/> <Ksdashboardgraph item="items" hideButtons="1" explain_ai_whole="explain_ai_whole" dashboard_data="ks_dashboard_data" ksdatefilter="state.ksDateFilterSelection" pre_defined_filter = "state.pre_defined_filter" custom_filter="state.custom_filter" ks_speak="(ev)=>this.speak_once(ev,items)"/>
</t> </t>

View File

@ -219,7 +219,7 @@
class="w-100" class="w-100"
widget="ks_dashboard_graph_preview" widget="ks_dashboard_graph_preview"
invisible="ks_dashboard_item_type in invisible="ks_dashboard_item_type in
['ks_to_do', 'ks_tile', 'ks_list_view', 'ks_kpi', 'ks_map_view', 'ks_funnel_chart', 'ks_bullet_chart', 'ks_kpi']"/> ['ks_to_do', 'ks_tile', 'ks_list_view', 'ks_kpi', 'ks_map_view', 'ks_funnel_chart', 'ks_bullet_chart', 'ks_kpi', 'ks_profile']"/>
<field name="ks_list_view_preview" class="w-100 encapsulated-list-preview" <field name="ks_list_view_preview" class="w-100 encapsulated-list-preview"
widget="ks_dashboard_list_view_preview" widget="ks_dashboard_list_view_preview"
invisible="ks_dashboard_item_type != 'ks_list_view'"/> invisible="ks_dashboard_item_type != 'ks_list_view'"/>
@ -240,10 +240,19 @@
<field name="ks_map_preview" string="Map Preview" <field name="ks_map_preview" string="Map Preview"
widget="ks_dashboard_map_preview" class="flex-grow-1 w-100" widget="ks_dashboard_map_preview" class="flex-grow-1 w-100"
invisible="ks_dashboard_item_type != 'ks_map_view'"/> invisible="ks_dashboard_item_type != 'ks_map_view'"/>
<field name="ks_profile_preview" string="Profile Preview"
widget="ks_profile_preview" class="flex-grow-1 w-100"
invisible="ks_dashboard_item_type != 'ks_profile'"/>
</div> </div>
</div> </div>
</div> </div>
<div class="encapsulated-input-group col-xl-7 col-lg-6"> <div class="col-6 gap-2 d-flex flex-column" invisible="ks_dashboard_item_type != 'ks_profile'">
<label for="name" string="Name" class="o_form_label"/>
<field name="name" required="1" placeholder="Enter Name" class="form-control form-input-box w-100"/>
</div>
<div class="encapsulated-input-group col-xl-7 col-lg-6" invisible="ks_dashboard_item_type == 'ks_profile'">
<div class="chart-form-detail row"> <div class="chart-form-detail row">
<div class="col-6 gap-2 d-flex flex-column"> <div class="col-6 gap-2 d-flex flex-column">
<label for="name" string="Name" class="o_form_label"/> <label for="name" string="Name" class="o_form_label"/>
@ -267,7 +276,7 @@
<field name="ks_model_id" placeholder="Enter Model Name" <field name="ks_model_id" placeholder="Enter Model Name"
class="form-control form-input-box encapsulated-form-arrow bg-white" class="form-control form-input-box encapsulated-form-arrow bg-white"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}" nolabel="1" options="{'no_create': True, 'no_create_edit':True, 'no_open': True}" nolabel="1"
context="{'current_id': id}" required="ks_dashboard_item_type != 'ks_to_do' and data_source == 'odoo'" context="{'current_id': id}" required="ks_dashboard_item_type not in ['ks_to_do','ks_profile'] and data_source == 'odoo'"
invisible="ks_data_calculation_type == 'query' or data_source != 'odoo' or ks_dashboard_item_type not in invisible="ks_data_calculation_type == 'query' or data_source != 'odoo' or ks_dashboard_item_type not in
['ks_kpi','ks_radar_view','ks_flower_view','ks_tile','ks_bar_chart','ks_horizontalBar_chart','ks_line_chart','ks_area_chart','ks_pie_chart', ['ks_kpi','ks_radar_view','ks_flower_view','ks_tile','ks_bar_chart','ks_horizontalBar_chart','ks_line_chart','ks_area_chart','ks_pie_chart',
'ks_doughnut_chart','ks_polarArea_chart','ks_list_view','ks_scatter_chart','ks_radialBar_chart','ks_funnel_chart','ks_bullet_chart','ks_map_view']" 'ks_doughnut_chart','ks_polarArea_chart','ks_list_view','ks_scatter_chart','ks_radialBar_chart','ks_funnel_chart','ks_bullet_chart','ks_map_view']"