leave balance in dashboard
This commit is contained in:
parent
d2f1caf810
commit
90f50950fd
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
from . import controllers
|
||||
from . import models
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
'version': '1.0',
|
||||
'category': 'Human Resources',
|
||||
'summary': 'Display employee profile using Owl.js',
|
||||
'depends': ['base', 'hr', 'web','muk_web_theme'], # Depends on base, hr, and web for Owl.js and hr.employee model
|
||||
'depends': ['base', 'hr', 'web'], # Depends on base, hr, and web for Owl.js and hr.employee model
|
||||
'data': [
|
||||
'views/employee_dashboard_views.xml', # Your template
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from . import employee_dashboard
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
from odoo import http
|
||||
from odoo.http import request
|
||||
|
||||
class EmployeeDashboard(http.Controller):
|
||||
|
||||
@http.route('/employee/leave/balance', auth='user', type='json')
|
||||
def get_leave_balance(self):
|
||||
employee = request.env.user.employee_id
|
||||
if not employee:
|
||||
return {'error': 'No employee linked to this user'}
|
||||
|
||||
leave_data = {}
|
||||
leave_types = request.env['hr.leave.type'].search([
|
||||
])
|
||||
if not leave_types:
|
||||
return []
|
||||
|
||||
for leave_type in leave_types:
|
||||
allocations = request.env['hr.leave.allocation'].search([
|
||||
('employee_id', '=', employee.id),
|
||||
('holiday_status_id', '=', leave_type.id),
|
||||
('state', '=', 'validate'),
|
||||
])
|
||||
taken_leaves = request.env['hr.leave'].search([
|
||||
('employee_id', '=', employee.id),
|
||||
('holiday_status_id', '=', leave_type.id),
|
||||
('state', '=', 'validate'),
|
||||
])
|
||||
|
||||
total_allocated = sum(a.number_of_days for a in allocations)
|
||||
total_taken = sum(l.number_of_days for l in taken_leaves)
|
||||
remaining = total_allocated - total_taken
|
||||
|
||||
leave_data[leave_type.name] = {
|
||||
'allocated': total_allocated,
|
||||
'taken': total_taken,
|
||||
'remaining': remaining,
|
||||
}
|
||||
|
||||
return leave_data
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { Component, useEffect, useState, onMounted, useRef, onWillStart } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
const actionRegistry = registry.category("actions");
|
||||
|
|
@ -34,13 +34,31 @@ export class NetflixProfileContainer extends Component {
|
|||
department_id: ''
|
||||
},
|
||||
attendance_lines: [],
|
||||
leaves: []
|
||||
leaves: [],
|
||||
leave_allocations:[]
|
||||
});
|
||||
|
||||
onWillStart(() => loadJS(["/web/static/lib/Chart/Chart.js"]));
|
||||
onWillStart(async () => {
|
||||
await loadJS("/web/static/lib/Chart/Chart.js");
|
||||
const result = await rpc("/employee/leave/balance");
|
||||
const LEAVE_NAME_MAP = {
|
||||
"Sick Time Off": "SL",
|
||||
"Casual Leave": "CL",
|
||||
"Privilege Leave": "PL"
|
||||
};
|
||||
const filteredLeaves = {};
|
||||
for (const [name, data] of Object.entries(result)) {
|
||||
const code = LEAVE_NAME_MAP[name];
|
||||
if (code) {
|
||||
filteredLeaves[code] = data;
|
||||
}
|
||||
}
|
||||
this.state.leave_allocations = filteredLeaves;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await this.fetchEmployeeData();
|
||||
await this.drawChartWhenReady();
|
||||
|
||||
// Make sure Chart is loaded and canvas is available
|
||||
await loadJS(["/web/static/lib/Chart/Chart.js"]);
|
||||
|
|
@ -58,7 +76,48 @@ export class NetflixProfileContainer extends Component {
|
|||
|
||||
});
|
||||
}
|
||||
drawChartWhenReady() {
|
||||
// Wait for data to be loaded before drawing chart
|
||||
const interval = setInterval(() => {
|
||||
if (Object.keys(this.state.leave_allocations).length > 0 && window.Chart) {
|
||||
this.drawChart();
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
drawChart() {
|
||||
const ctx = document.getElementById("leaveChart")?.getContext("2d");
|
||||
if (!ctx) {
|
||||
console.warn("leaveChart canvas not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const leaves = this.state.leave_allocations;
|
||||
|
||||
const data = {
|
||||
labels: ['Sick Leave (SL)', 'Casual Leave (CL)', 'Privilege Leave (PL)'],
|
||||
datasets: [{
|
||||
data: [
|
||||
leaves.SL?.remaining || 0,
|
||||
leaves.CL?.remaining || 0,
|
||||
leaves.PL?.remaining || 0,
|
||||
],
|
||||
backgroundColor: ['#ed8e34', '#4787bf', '#ff0080'],
|
||||
}]
|
||||
};
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'bottom' },
|
||||
title: { display: true, text: 'Available Leave Balance' }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
hr_timesheets() {
|
||||
this.action.doAction({
|
||||
name: "Timesheets",
|
||||
|
|
@ -144,7 +203,8 @@ export class NetflixProfileContainer extends Component {
|
|||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: true },
|
||||
tooltip: { mode: 'index', intersect: false }
|
||||
tooltip: { mode: 'index', intersect: false },
|
||||
title: { display: true, text: 'Attendance' }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
|
|
|
|||
|
|
@ -130,11 +130,55 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" >
|
||||
<div style="width: 100%; max-width: 600px; display: block; margin-left: 0;">
|
||||
<canvas id="attendanceChart" width="600" height="300"></canvas>
|
||||
<div class="container m-3" style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
||||
|
||||
<!-- Attendance Chart Block -->
|
||||
<div class="dashboard-card" style="
|
||||
flex: 1 1 500px;
|
||||
min-width: 300px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
">
|
||||
<h3>📊 Attendance Summary</h3>
|
||||
<canvas id="attendanceChart" style="width: 100%; height: 350px;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Leave Balance Card -->
|
||||
<div class="dashboard-card" style="
|
||||
flex: 1 1 400px;
|
||||
min-width: 300px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
">
|
||||
<h3>🗓️ Leave Balance</h3>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 15px;">
|
||||
<!-- Chart Column -->
|
||||
<div style="flex: 1 1 200px; min-width: 150px;">
|
||||
<canvas id="leaveChart" style="width: 100%; height: auto;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Leave Summary Cards -->
|
||||
<div style="flex: 1 1 200px; min-width: 150px;">
|
||||
<ul style="display: flex; flex-direction: column; gap: 10px; padding-top:30px ; list-style: none; margin:0;">
|
||||
<li style="padding: 12px 16px; background: #f5f5f5; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.1);">
|
||||
<strong>Sick Leave (SL):</strong> <t t-esc="state.leave_allocations.SL?.remaining || 0"/> days
|
||||
</li>
|
||||
<li style="padding: 12px 16px; background: #f5f5f5; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.1);">
|
||||
<strong>Casual Leave (CL):</strong> <t t-esc="state.leave_allocations.CL?.remaining || 0"/> days
|
||||
</li>
|
||||
<li style="padding: 12px 16px; background: #f5f5f5; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.1);">
|
||||
<strong>Privilege Leave (PL):</strong> <t t-esc="state.leave_allocations.PL?.remaining || 0"/> days
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="tables-section">
|
||||
<!-- Attendance Table -->
|
||||
<div class="table-card">
|
||||
|
|
|
|||
Loading…
Reference in New Issue