leave balance in dashboard

This commit is contained in:
raman 2025-05-22 15:58:50 +05:30
parent d2f1caf810
commit 90f50950fd
6 changed files with 156 additions and 11 deletions

View File

@ -1,2 +1,2 @@
from . import controllers
from . import models from . import models

View File

@ -5,7 +5,7 @@
'version': '1.0', 'version': '1.0',
'category': 'Human Resources', 'category': 'Human Resources',
'summary': 'Display employee profile using Owl.js', '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': [ 'data': [
'views/employee_dashboard_views.xml', # Your template 'views/employee_dashboard_views.xml', # Your template
], ],

View File

@ -0,0 +1 @@
from . import employee_dashboard

View File

@ -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

View File

@ -1,5 +1,5 @@
/** @odoo-module **/ /** @odoo-module **/
import { rpc } from "@web/core/network/rpc";
import { Component, useEffect, useState, onMounted, useRef, onWillStart } from "@odoo/owl"; import { Component, useEffect, useState, onMounted, useRef, onWillStart } from "@odoo/owl";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
const actionRegistry = registry.category("actions"); const actionRegistry = registry.category("actions");
@ -34,13 +34,31 @@ export class NetflixProfileContainer extends Component {
department_id: '' department_id: ''
}, },
attendance_lines: [], 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 () => { onMounted(async () => {
await this.fetchEmployeeData(); await this.fetchEmployeeData();
await this.drawChartWhenReady();
// Make sure Chart is loaded and canvas is available // Make sure Chart is loaded and canvas is available
await loadJS(["/web/static/lib/Chart/Chart.js"]); 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() { hr_timesheets() {
this.action.doAction({ this.action.doAction({
name: "Timesheets", name: "Timesheets",
@ -144,7 +203,8 @@ export class NetflixProfileContainer extends Component {
responsive: true, responsive: true,
plugins: { plugins: {
legend: { display: true }, legend: { display: true },
tooltip: { mode: 'index', intersect: false } tooltip: { mode: 'index', intersect: false },
title: { display: true, text: 'Attendance' }
}, },
scales: { scales: {
y: { y: {

View File

@ -130,11 +130,55 @@
</div> </div>
</div> </div>
</div> </div>
<div class="container" > <div class="container m-3" style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<div style="width: 100%; max-width: 600px; display: block; margin-left: 0;">
<canvas id="attendanceChart" width="600" height="300"></canvas> <!-- Attendance Chart Block -->
</div> <div class="dashboard-card" style="
</div> 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"> <div class="tables-section">
<!-- Attendance Table --> <!-- Attendance Table -->
<div class="table-card"> <div class="table-card">