[ADD] ATTENDANCE: Attendance dashboard

This commit is contained in:
Horilla
2024-01-12 21:24:01 +05:30
parent 627c17f13a
commit b4fcd7e5ee
13 changed files with 1069 additions and 206 deletions

View File

@@ -1,4 +1,33 @@
$(document).ready(function () {
// initializing the department overtime chart.
var departmentChartData = {
labels: [],
datasets: [],
};
window["departmentOvertimeChart"] = {};
const departmentOvertimeChart = document.getElementById("departmentOvertimeChart");
var departmentAttendanceChart = new Chart(departmentOvertimeChart, {
type: "pie",
data: departmentChartData,
options: {
responsive: true,
maintainAspectRatio: false,
},
plugins: [
{
afterRender: (departmentAttendanceChart) => emptyOvertimeChart(departmentAttendanceChart),
},
],
});
var today = new Date();
month = ("0" + (today.getMonth() + 1)).slice(-2);
year = today.getFullYear();
var day = ("0" + today.getDate()).slice(-2);
var formattedDate = year + "-" + month + "-" + day;
var currentWeek = getWeekNumber(today);
$("#attendance_month").val(formattedDate);
$.ajax({
@@ -11,6 +40,148 @@ $(document).ready(function () {
createAttendanceChart(response.dataSet, response.labels);
},
});
// Function to update the department overtime chart according to the response fetched from backend.
function departmentDataUpdate(response) {
departmentChartData.labels = response.labels;
departmentChartData.datasets=response.dataset;
departmentChartData.message=response.message;
departmentChartData.emptyImageSrc=response.emptyImageSrc;
departmentAttendanceChart.update();
}
// Function to update the department overtime chart according to the dates provided.
function changeDepartmentMonth() {
let type = $("#department_date_type").val();
let date = $("#department_month").val();
let end_date = $("#department_month2").val();
$.ajax({
type: "GET",
url: "/attendance/department-overtime-chart",
dataType: "json",
data: {
date: date,
type: type,
end_date: end_date,
},
success: function (response) {
departmentDataUpdate(response);
},
error: (error) => {},
});
}
// Function to update the input fields according to type select field.
function changeDepartmentView(element) {
var dataType = $(element).val();
if (dataType === "date_range") {
$("#department_month").prop("type", "date");
$("#department_day_input").after(
'<input type="date" class="mb-2 float-end pointer oh-select ml-2" id="department_month2" style="width: 100px;color:#5e5c5c;"/>'
);
$("#department_month").val(formattedDate);
$("#department_month2").val(formattedDate);
changeDepartmentMonth();
} else {
$("#department_month2").remove();
if (dataType === "weekly") {
$("#department_month").prop("type", "week");
if (currentWeek <10){
$("#department_month").val(`${year}-W0${currentWeek}`);
}
else {
$("#department_month").val(`${year}-W${currentWeek}`);
}
changeDepartmentMonth();
} else if (dataType === "day") {
$("#department_month").prop("type", "date");
$("#department_month").val(formattedDate);
changeDepartmentMonth();
} else {
$("#department_month").prop("type", "month");
$("#department_month").val(`${year}-${month}`);
changeDepartmentMonth();
}
}
}
// Function for empty message for department overtime chart.
function emptyOvertimeChart(departmentAttendanceChart, args, options) {
flag = false;
for (let i = 0; i < departmentAttendanceChart.data.datasets.length; i++) {
flag = flag + departmentAttendanceChart.data.datasets[i].data.some(Boolean);
}
if (!flag) {
const { ctx, canvas } = departmentAttendanceChart;
departmentAttendanceChart.clear();
const parent = canvas.parentElement;
// Set canvas width/height to match
canvas.width = parent.clientWidth;
canvas.height = parent.clientHeight;
// Calculate center position
const x = canvas.width / 2;
const y = (canvas.height - 70) / 2;
var noDataImage = new Image();
noDataImage.src = departmentAttendanceChart.data.emptyImageSrc
? departmentAttendanceChart.data.emptyImageSrc
: "/static/images/ui/joiningchart.png";
message = departmentAttendanceChart.data.message
? departmentAttendanceChart.data.message
: emptyMessages[languageCode];
noDataImage.onload = () => {
// Draw image first at center
ctx.drawImage(noDataImage, x - 35, y, 70, 70);
// Draw text below image
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "hsl(0,0%,45%)";
ctx.font = "16px Poppins";
ctx.fillText(message, x, y + 70 + 30);
};
}
}
// Ajax request to create department overtime chart initially.
$.ajax({
url: "/attendance/department-overtime-chart",
type: "GET",
dataType: "json",
headers: {
"X-Requested-With": "XMLHttpRequest",
},
success: (response) => {
departmentDataUpdate(response);
},
error: (error) => {
console.log("Error", error);
},
});
// Functions to update department overtime chart while changing the date input field and select input field.
$("#departmentChartCard").on("change","#department_date_type", function (e) {
changeDepartmentView($(this))
})
$("#departmentChartCard").on("change","#department_month", function (e) {
changeDepartmentMonth()
})
$("#departmentChartCard").on("change","#department_month2", function (e) {
changeDepartmentMonth()
})
});
var data;
@@ -149,7 +320,12 @@ function changeView(element) {
$("#attendance_month2").remove();
if (dataType === "weekly") {
$("#attendance_month").prop("type", "week");
$("#attendance_month").val(`${year}-W${currentWeek}`);
if (currentWeek <10){
$("#attendance_month").val(`${year}-W0${currentWeek}`);
}
else {
$("#attendance_month").val(`${year}-W${currentWeek}`);
}
changeMonth();
} else if (dataType === "day") {
$("#attendance_month").prop("type", "date");
@@ -163,7 +339,7 @@ function changeView(element) {
}
}
var chart = new Chart(
document.getElementById("pendingHoursCanvas").getContext("2d"),
document.getElementById("pendingHoursCanvas"),
{}
);
window["pendingHoursCanvas"] = chart;
@@ -174,12 +350,13 @@ function pendingHourChart(year, month) {
data: { month: month, year: year },
success: function (response) {
pendingHoursCanvas.destroy();
var ctx = document.getElementById("pendingHoursCanvas").getContext("2d");
var ctx = document.getElementById("pendingHoursCanvas");
pendingHoursCanvas = new Chart(ctx, {
type: "bar", // Bar chart type
data: response.data,
options: {
responsive: true,
aspectRatio: false,
indexAxis: "x",
scales: {
x: {
@@ -187,6 +364,7 @@ function pendingHourChart(year, month) {
},
y: {
beginAtZero: true,
stacked: true,
},
},
onClick: (e, activeEls) => {

View File

@@ -1,181 +1,333 @@
{% extends 'index.html' %}
{% block content %}
{% load static %}
{% extends 'index.html' %} {% load i18n %} {% block content %} {% load static %}
<style>
.oh-sticky-table__right {
position: sticky;
right: 0;
background-color: #fff;
}
</style>
<div class="oh-wrapper">
<div class="oh-dashboard row">
<div class="oh-dashboard__left col-12 col-sm-12 col-md-12 col-lg-9">
<div class="oh-dashboard__cards row">
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--success">
<div class="oh-card-dashboard__header">
<span class="oh-card-dashboard__title">On Time</span>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__counts">
<span class="oh-card-dashboard__sign">
{% if on_time < late_come %}
<ion-icon name="caret-down-outline"></ion-icon>
{% else %}
<ion-icon name="caret-up-outline"></ion-icon>
{% endif %}
</span>
<span class="oh-card-dashboard__count">{{on_time}}</span>
<div class="oh-dashboard__left col-12 col-sm-12 col-md-12 col-lg-12 pb-5">
<div class="oh-dashboard row">
<div class="oh-dashboard__cards row">
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<div class="oh-card-dashboard oh-card-dashboard--neutral">
<div class="oh-card-dashboard__header">
<span class="oh-card-dashboard__title"
>{% trans "Today's Attendances" %}</span
>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__counts">
<span class="oh-card-dashboard__count"
>{{marked_attendances_ratio}}%</span
>
</div>
<span class="oh-badge oh-card-dashboard__badge"
>{{marked_attendances}}/{{expected_attendances}}</span
>
</div>
</div>
</div>
<span class="oh-badge oh-card-dashboard__badge"
>{{on_time_ratio}}%</span
>
</div>
</div>
</div>
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<div class="oh-card-dashboard oh-card-dashboard--neutral">
<div class="oh-card-dashboard__header">
<span class="oh-card-dashboard__title"
>Today's Attendances</span
>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__counts">
{% comment %} <span class="oh-card-dashboard__count">100%</span> {% endcomment %}
<span class="oh-card-dashboard__count">{{marked_attendances_ratio}}%</span>
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<div
class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--success"
>
<div class="oh-card-dashboard__header">
<span class="oh-card-dashboard__title"
>{% trans "On Time" %}</span
>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__counts">
<span class="oh-card-dashboard__sign">
{% if on_time < late_come %}
<ion-icon
name="caret-down-outline"
></ion-icon>
{% else %}
<ion-icon
name="caret-up-outline"
></ion-icon>
{% endif %}
</span>
<span class="oh-card-dashboard__count"
>{{on_time}}</span
>
</div>
<span class="oh-badge oh-card-dashboard__badge"
>{{on_time_ratio}}%</span
>
</div>
</div>
</div>
<span class="oh-badge oh-card-dashboard__badge"
>{{marked_attendances}}/{{expected_attendances}}</span
>
</div>
</div>
</div>
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<div class="oh-card-dashboard oh-card-dashboard--danger">
<div class="oh-card-dashboard__header">
<span class="oh-card-dashboard__title"
>Late Come</span
>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__counts">
<span class="oh-card-dashboard__sign">
{% if late_come < on_time %}
<ion-icon name="caret-down-outline"></ion-icon>
{% else %}
<ion-icon name="caret-up-outline"></ion-icon>
{% endif %}
</span>
<span class="oh-card-dashboard__count">{{late_come}}</span>
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<div class="oh-card-dashboard oh-card-dashboard--danger">
<div class="oh-card-dashboard__header">
<span class="oh-card-dashboard__title"
>{% trans "Late Come" %}</span
>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__counts">
<span class="oh-card-dashboard__sign">
{% if late_come < on_time %}
<ion-icon
name="caret-down-outline"
></ion-icon>
{% else %}
<ion-icon
name="caret-up-outline"
></ion-icon>
{% endif %}
</span>
<span class="oh-card-dashboard__count"
>{{late_come}}</span
>
</div>
<span class="oh-badge oh-card-dashboard__badge"
>{{late_come_ratio}}%</span
>
</div>
</div>
</div>
<span class="oh-badge oh-card-dashboard__badge"
>{{late_come_ratio}}%</span
</div>
</div>
<div class="oh-dashboard row pt-3">
<div class="oh-dashboard__movable-cards row mt-4">
<div
class="col-12 col-sm-12 col-md-12 col-lg-4 "
>
</div>
</div>
</div>
</div>
<div class="oh-dashboard__movable-cards row mt-4">
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
<span class="oh-card-dashboard__title">Day Analytic</span>
</div>
<div class="oh-card-dashboard__body">
<canvas id="dailyAnalytic"></canvas>
</div>
</div>
</div>
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
<span class="oh-card-dashboard__title">Overall Conversions</span>
</div>
<div class="oh-card-dashboard__body">
<canvas id="myChart2"></canvas>
</div>
</div>
</div>
</div>
</div>
<div class="oh-dashboard__right col-12 col-sm-12 col-md-12 col-lg-3">
<div class="oh-dashboard__events">
<div class="oh-dashbaord__events-reel">
<div class="oh-dashboard__event oh-dashboard__event--purple">
<div class="oh-dasboard__event-photo">
<img src="/static/images/upload/userphoto.png" class="oh-dashboard__event-userphoto"/>
</div>
<div class="oh-dasboard__event-details">
<span class="oh-dashboard__event-title">Birthday</span>
<span class="oh-dashboard__event-main">Katie Melua</span>
<span class="oh-dashboard__event-date">29/03/2023</span>
</div>
</div>
<div class="oh-dashboard__event oh-dashboard__event--crimson">
<div class="oh-dasboard__event-photo">
<img src="/static/images/upload/userphoto.png" class="oh-dashboard__event-userphoto"/>
</div>
<div class="oh-dasboard__event-details">
<span class="oh-dashboard__event-title">Birthday</span>
<span class="oh-dashboard__event-main">Katie Melua</span>
<span class="oh-dashboard__event-date">29/03/2023</span>
</div>
</div>
<div class="oh-dashboard__event oh-dashboard__event--purple">
<div class="oh-dasboard__event-photo">
<img src="/static/images/upload/userphoto.png" class="oh-dashboard__event-userphoto"/>
</div>
<div class="oh-dasboard__event-details">
<span class="oh-dashboard__event-title">Birthday</span>
<span class="oh-dashboard__event-main">Katie Melua</span>
<span class="oh-dashboard__event-date">29/03/2023</span>
</div>
</div>
</div>
<ul class="oh-dashboard__events-nav">
<li class="oh-dashboard__events-nav-item oh-dashboard__events-nav-item--active" data-target="0"></li>
<li class="oh-dashboard__events-nav-item" data-target="1"></li>
<li class="oh-dashboard__events-nav-item" data-target="2"></li>
</ul>
</div>
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent mt-3">
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
<span class="oh-card-dashboard__title">On Break</span>
</div>
<div class="oh-card-dashboard__body">
<ul class="oh-card-dashboard__user-list">
{% for emp in on_break %}
<li class="oh-card-dashboard__user-item">
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="https://ui-avatars.com/api/?name={{emp.employee_id}}&background=random"
class="oh-profile__image"
alt="Amy Winehouse"
/>
</div>
<span class="oh-profile__name oh-text--dark"
>{{emp.employee_id}}</span
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider" style="margin-bottom:2.25rem;"
id="attendance_header"
>
<div class="oh-card-dashboard__title mb-2">
{% trans "Attendance Analytic" %}
</div>
<div class="d-flex justify-content-between mb-2">
<select
id="type"
class="oh-select"
name="type"
onchange="changeView(this)"
style="
width: 150px;
padding: 3px;
color: #5e5c5c;
"
>
<option value="day">
{% trans "Day" %}
</option>
<option value="weekly">
{% trans "Weekly" %}
</option>
<option value="monthly">
{% trans "Monthly" %}
</option>
<option value="date_range">
{% trans "Date range" %}
</option>
</select>
<span id="day_input">
<input
type="date"
class="mb-2 float-end pointer oh-select"
id="attendance_month"
onchange="changeMonth()"
style="width: 100px; color: #5e5c5c"
/>
</span>
</div>
<div class="oh-card-dashboard__body">
<canvas
id="dailyAnalytic"
style="cursor: pointer"
></canvas>
</div>
</div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="col-12 col-sm-12 col-md-12 col-lg-4"
id="notInYetId"
hx-get="{% url "not-in-yet" %}" hx-trigger="load" >
{% include "dashboard/not_in_yet.html" %}
</div>
<div
class="col-12 col-sm-12 col-md-12 col-lg-4 "
id="pendingHours"
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
id="pendingHoursHeader"
>
<div class="d-flex justify-content-between">
<div class="oh-card-dashboard__title mb-2">
{% trans "Hours Chart" %}
</div>
<div class="d-flex justify-content-between mb-2">
<input
type="month"
class="mb-2 float-end pointer oh-select"
id="hourAccountMonth"
onchange="dynamicMonth($(this))"
style="width: 100px; color: #5e5c5c"
/>
</div>
</div>
<div class="oh-card-dashboard__body" style="height:275px">
<canvas
id="pendingHoursCanvas"
style="cursor: pointer"
></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="oh-dashboard row pt-3">
<div
class="col-12 col-sm-12 col-md-12 col-lg-3 "
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
>
<span class="oh-card-dashboard__title"
>{% trans "On Break" %}</span
>
</div>
<div class="oh-card-dashboard__body" style="height:400px">
{% if on_break %}
<ul class="oh-card-dashboard__user-list">
{% for emp in on_break %}
<li class="oh-card-dashboard__user-item">
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="https://ui-avatars.com/api/?name={{emp.employee_id}}&background=random"
class="oh-profile__image"
alt="Amy Winehouse"
/>
</div>
<span class="oh-profile__name oh-text--dark"
>{{emp.employee_id}}</span
>
</div>
</li>
{% endfor %}
</ul>
{% else %}
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
<div style="" class="">
<img style=" display: block;width: 70px;margin: 20px auto ;" src="/static/images/ui/coffee-break.png" class="" alt=""/>
<h3 style="font-size:16px" class="oh-404__subtitle">{% trans "No employees on Break...." %}</h3>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div
class="col-12 col-sm-12 col-md-12 col-lg-9"
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
>
<span class="oh-card-dashboard__title"
>{% trans "Overtime to validate" %}</span
>
</div>
<div class="oh-card-dashboard__body" style="height:400px">
{% include "attendance/dashboard/overtime_table.html" %}
</div>
</div>
</div>
</div>
<div class="oh-dashboard row pt-3">
<div
class="col-12 col-sm-12 col-md-12 col-lg-8" id="validateTableCard"
>
{% include "attendance/dashboard/to_validate_table.html" %}
</div>
<div
class="col-12 col-sm-12 col-md-12 col-lg-4 "
id="departmentChartCard"
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
id="departmentChartHeader"
>
<div class="oh-card-dashboard__title mb-2">
{% trans "Department overtime Chart" %}
</div>
<div class="d-flex justify-content-between mb-2">
<select
id="department_date_type"
class="oh-select"
name="type"
style="
width: 150px;
padding: 3px;
color: #5e5c5c;
"
>
<option value="day">
{% trans "Day" %}
</option>
<option value="weekly">
{% trans "Weekly" %}
</option>
<option value="monthly">
{% trans "Monthly" %}
</option>
<option value="date_range">
{% trans "Date range" %}
</option>
</select>
<span id="department_day_input">
<input
type="date"
class="mb-2 float-end pointer oh-select"
id="department_month"
style="width: 100px; color: #5e5c5c"
/>
</span>
</div>
<div class="oh-card-dashboard__body" style="height:400px">
<canvas
id="departmentOverChart"
style="cursor: pointer"
></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="{% static 'dashboard/attendanceChart.js' %}"></script>
<script src="{% static 'dashboard/attendanceChart.js' %}"></script>
{% endblock content %}
{% endblock content %}

View File

@@ -0,0 +1,92 @@
{% load i18n %}
{% if overtime_attendances %}
<div class="oh-sticky-table h-100">
<div class="oh-sticky-table__table oh-table--sortable">
<div class="oh-sticky-table__thead">
<div class="oh-sticky-table__tr">
<div
class="oh-sticky-table__th"
>
{% trans "Employee" %}
</div>
<div class="oh-sticky-table__th">{% trans "Check-In" %}</div>
<div
class="oh-sticky-table__th"
>
{% trans "In Date" %}
</div>
<div class="oh-sticky-table__th">{% trans "Check-Out" %}</div>
<div
class="oh-sticky-table__th"
>
{% trans "Out Date" %}
</div>
<div
class="oh-sticky-table__th"
>
{% trans "Overtime" %}
</div>
<div class="oh-sticky-table__th"></div>
</div>
</div>
{% for attendance in overtime_attendances %}
<div class="oh-sticky-table__tbody">
<div
class="oh-sticky-table__tr"
draggable="false"
data-toggle="oh-modal-toggle"
data-target="#detailAttendance"
hx-target="#detailAttendanceModalBody"
hx-get="{% url 'user-request-one-view' attendance.id %}?ot=true&instances_ids={{ot_attendances_ids}}"
>
<div class="oh-sticky-table__sd">
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="{{attendance.employee_id.get_avatar}}"
class="oh-profile__image"
alt=""
/>
</div>
<span class="oh-profile__name oh-text--dark"
>{{attendance.employee_id}}</span
>
</div>
</div>
<div class="oh-sticky-table__td timeformat_changer">
{{attendance.attendance_clock_in}}
</div>
<div class="oh-sticky-table__td dateformat_changer">
{{attendance.attendance_clock_in_date}}
</div>
<div class="oh-sticky-table__td timeformat_changer">
{{attendance.attendance_clock_out}}
</div>
<div class="oh-sticky-table__td dateformat_changer">
{{attendance.attendance_clock_out_date}}
</div>
<div class="oh-sticky-table__td">
{{attendance.attendance_overtime}}
</div>
<div class="oh-sticky-table__td">
<a
href="{% url 'approve-overtime' attendance.id %}"
onclick="event.stopPropagation()"
class="oh-btn oh-btn--info"
>
{% trans "Approve" %}
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
<div style="" class="">
<img style="display: block;width: 70px;margin: 20px auto ;" src="/static/images/ui/overtime-icon.png" class="" alt=""/>
<h3 style="font-size:16px" class="oh-404__subtitle">{% trans "No Overtime to Validate...." %}</h3>
</div>
</div>
{% endif %}

View File

@@ -0,0 +1,142 @@
{% load i18n %}
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
>
{% if validate_attendances.has_previous %}
<span class="oh-card-dashboard__title" id="department-previous" style="cursor: pointer;" hx-target="#validateTableCard"
hx-get="{% url 'attendance-validate-table' %}?{{pd}}&page={{ validate_attendances.previous_page_number }}">
<ion-icon name="caret-back-outline" role="img" class="md hydrated" aria-label="caret back outline"></ion-icon>
</span>
{% endif %}
<span class="oh-card-dashboard__title">{% trans 'Attendance to validate' %} </span>
{% if validate_attendances.has_next %}
<span class="oh-card-dashboard__title float-end" id="department-previous" style="cursor: pointer;" hx-target="#validateTableCard"
hx-get="{% url 'attendance-validate-table' %}?{{pd}}&page={{ validate_attendances.next_page_number }}">
<ion-icon name="caret-forward-outline" role="img" class="md hydrated" aria-label="caret back outline"></ion-icon>
</span>
{% endif %}
</div>
<div class="oh-card-dashboard__body" style="height:450px">
{% if validate_attendances %}
<div class="oh-sticky-table h-100">
<div class="oh-sticky-table__table oh-table--sortable">
<div class="oh-sticky-table__thead">
<div class="oh-sticky-table__tr">
<div
class="oh-sticky-table__th"
>
{% trans "Employee" %}
</div>
<div
class="oh-sticky-table__th"
>
{% trans "Date" %}
</div>
<div class="oh-sticky-table__th">{% trans "Check-In" %}</div>
<div
class="oh-sticky-table__th"
>
{% trans "In Date" %}
</div>
<div class="oh-sticky-table__th">{% trans "Check-Out" %}</div>
<div
class="oh-sticky-table__th"
>
{% trans "Out Date" %}
</div>
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
<div class="oh-sticky-table__th">{% trans "Work Type" %}</div>
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
<div
class="oh-sticky-table__th"
>
{% trans "At Work" %}
</div>
<div
class="oh-sticky-table__th"
>
{% trans "Pending Hour" %}
</div>
<div class="oh-sticky-table__th oh-sticky-table__right"></div>
</div>
</div>
{% for attendance in validate_attendances %}
<div class="oh-sticky-table__tbody">
<div
class="oh-sticky-table__tr"
draggable="false"
data-toggle="oh-modal-toggle"
data-target="#detailAttendance"
hx-target="#detailAttendanceModalBody"
hx-get="{% url 'user-request-one-view' attendance.id %}?validate=true&instances_ids={{validate_attendances_ids}}"
>
<div class="oh-sticky-table__sd">
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="{{attendance.employee_id.get_avatar}}"
class="oh-profile__image"
alt="Mary Magdalene"
/>
</div>
<span class="oh-profile__name oh-text--dark"
>{{attendance.employee_id}}</span
>
</div>
</div>
<div class="oh-sticky-table__td dateformat_changer">{{attendance.attendance_date}}</div>
<div class="oh-sticky-table__td timeformat_changer">
{{attendance.attendance_clock_in}}
</div>
<div class="oh-sticky-table__td dateformat_changer">
{{attendance.attendance_clock_in_date}}
</div>
<div class="oh-sticky-table__td timeformat_changer">
{{attendance.attendance_clock_out}}
</div>
<div class="oh-sticky-table__td dateformat_changer">
{{attendance.attendance_clock_out_date}}
</div>
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
<div class="oh-sticky-table__td">{{attendance.work_type_id}}</div>
<div class="oh-sticky-table__td">{{attendance.minimum_hour}}</div>
<div class="oh-sticky-table__td">
{{attendance.attendance_worked_hour}}
</div>
<div class="oh-sticky-table__td">{{attendance.hours_pending}}</div>
<div class="oh-sticky-table__td oh-sticky-table__right">
{% if perms.attendance.change_attendance %}
<a
href='{% url "validate-this-attendance" attendance.id %}'
hx-target="#updateAttendanceBody"
data-req="/attendance/request-attendance-view/?id={{attendance.id}}"
onclick="event.stopPropagation(); {% if attendance.is_validate_request %} event.preventDefault(); showSweetAlert($(this).data('req')); {% endif %}"
class="oh-btn oh-btn--info"
>
{% trans "Validate" %}
</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div style="height: 380px; display:flex;align-items: center;justify-content: center;" class="">
<div style="" class="">
<img style="display: block;width: 70px;margin: 20px auto ;" src="/static/images/ui/attendance-validate.png" class="" alt=""/>
<h3 style="font-size:16px" class="oh-404__subtitle">{% trans "All Attendance Validated." %}</h3>
</div>
</div>
{% endif %}
</div>
</div>

View File

@@ -15,11 +15,14 @@ from attendance.filters import (
LateComeEarlyOutFilter,
AttendanceOverTimeFilter,
)
from attendance.models import Attendance, AttendanceLateComeEarlyOut, AttendanceOverTime
from attendance.models import Attendance, AttendanceLateComeEarlyOut, AttendanceOverTime, AttendanceValidationCondition
from attendance.views.views import strtime_seconds
from base.methods import filtersubordinates
from base.models import Department, EmployeeShiftSchedule
from employee.models import Employee
from employee.not_in_out_dashboard import paginator_qry
from horilla.decorators import login_required
from leave.models import LeaveRequest
def find_on_time(request, today, week_day, department=None):
@@ -29,22 +32,12 @@ def find_on_time(request, today, week_day, department=None):
on_time = 0
attendances = Attendance.objects.filter(attendance_date=today)
attendances = filtersubordinates(request, attendances, "attendance.view_attendance")
if department is not None:
attendances = attendances.filter(
employee_id__employee_work_info__department_id=department
)
excepted_attendances = 0
for attendance in attendances:
shift = attendance.shift_id
schedules_today = shift.employeeshiftschedule_set.filter(day__day=week_day)
if schedules_today.first() is not None:
excepted_attendances = excepted_attendances + 1
late_come_obj = attendance.late_come_early_out.filter(
type="late_come"
).first()
if late_come_obj is None:
on_time = on_time + 1
late_come = AttendanceLateComeEarlyOut.objects.filter(attendance_id__attendance_date=today,type="late_come")
on_time = len(attendances) - len(late_come)
return on_time
@@ -52,13 +45,9 @@ def find_expected_attendances(week_day):
"""
This method is used to find count of expected attendances for the week day
"""
schedules_today = EmployeeShiftSchedule.objects.filter(day__day=week_day)
expected_attendances = 0
for schedule in schedules_today:
shift = schedule.shift_id
expected_attendances = expected_attendances + len(
shift.employeeworkinformation_set.all()
)
employees = Employee.objects.filter(is_active=True)
on_leave = LeaveRequest.objects.filter(status = "Approved")
expected_attendances = len(employees) - len(on_leave)
return expected_attendances
@@ -67,6 +56,8 @@ def dashboard(request):
"""
This method is used to render individual dashboard for attendance module
"""
page_number = request.GET.get("page")
previous_data = request.GET.urlencode()
employees = Employee.objects.filter(
is_active=True,
).filter(~Q(employee_work_info__shift_id=None))
@@ -76,7 +67,8 @@ def dashboard(request):
week_day = today.strftime("%A").lower()
on_time = find_on_time(request, today=today, week_day=week_day)
late_come_obj = find_late_come(start_date=today)
late_come = find_late_come(start_date=today)
late_come_obj = len(late_come)
marked_attendances = late_come_obj + on_time
@@ -85,15 +77,30 @@ def dashboard(request):
late_come_ratio = 0
marked_attendances_ratio = 0
if expected_attendances != 0:
on_time_ratio = f"{(on_time / expected_attendances) * 100:.1f}"
late_come_ratio = f"{(late_come_obj / expected_attendances) * 100:.1f}"
on_time_ratio = f"{(on_time / expected_attendances) * 100:.2f}"
late_come_ratio = f"{(late_come_obj / expected_attendances) * 100:.2f}"
marked_attendances_ratio = (
f"{(marked_attendances / expected_attendances) * 100:.1f}"
f"{(marked_attendances / expected_attendances) * 100:.2f}"
)
early_outs = AttendanceLateComeEarlyOut.objects.filter(
type="early_out", attendance_id__attendance_date=today
)
condition = AttendanceValidationCondition.objects.first()
min_ot = strtime_seconds("00:00")
if condition is not None and condition.minimum_overtime_to_approve is not None:
min_ot = strtime_seconds(condition.minimum_overtime_to_approve)
ot_attendances = Attendance.objects.filter(
overtime_second__gte=min_ot,
attendance_validated=True,
employee_id__is_active=True,
attendance_overtime_approve =False,
)
validate_attendances = Attendance.objects.filter(
attendance_validated=False, employee_id__is_active=True
)
return render(
request,
"attendance/dashboard/dashboard.html",
@@ -107,9 +114,30 @@ def dashboard(request):
"marked_attendances": marked_attendances,
"marked_attendances_ratio": marked_attendances_ratio,
"on_break": early_outs,
"overtime_attendances": ot_attendances,
"validate_attendances": paginator_qry(validate_attendances, page_number),
"pd": previous_data,
},
)
@login_required
def validated_attendances_table(request):
page_number = request.GET.get("page")
previous_data = request.GET.urlencode()
validate_attendances = Attendance.objects.filter(
attendance_validated=False, employee_id__is_active=True
)
context = {
"validate_attendances": paginator_qry(validate_attendances, page_number),
"pd": previous_data,
}
return render(
request,
"attendance/dashboard/to_validate_table.html",
context
)
def total_attendance(start_date, department, end_date=None):
"""
@@ -138,6 +166,14 @@ def find_late_come(start_date, department=None, end_date=None):
"attendance_date__lte": end_date,
}
).qs
else:
late_come_obj = LateComeEarlyOutFilter(
{
"type": "late_come",
"attendance_date__gte": start_date,
"attendance_date__lte": end_date,
}
).qs
return late_come_obj
@@ -322,3 +358,79 @@ def pending_hours(request):
}
return JsonResponse({"data": data})
@login_required
def department_overtime_chart(request):
start_date = request.GET.get("date") if request.GET.get("date") else date.today()
chart_type = request.GET.get("type") if request.GET.get("type") else "day"
end_date = request.GET.get("end_date") if request.GET.get("end_date") else start_date
if chart_type == "day":
start_date = start_date
end_date = start_date
if chart_type == "weekly":
start_date, end_date = get_week_start_end_dates(start_date)
if chart_type == "monthly":
start_date, end_date = get_month_start_end_dates(start_date)
if chart_type == "date_range":
start_date = start_date
end_date = end_date
attendance = total_attendance(
start_date=start_date, department=None, end_date=end_date
)
condition = AttendanceValidationCondition.objects.first()
min_ot = strtime_seconds("00:00")
if condition is not None and condition.minimum_overtime_to_approve is not None:
min_ot = strtime_seconds(condition.minimum_overtime_to_approve)
attendances = attendance.filter(
overtime_second__gte=min_ot,
attendance_validated=True,
employee_id__is_active=True,
attendance_overtime_approve =True,
)
departments = []
department_total= []
for attendance in attendances:
departments.append(
attendance.employee_id.employee_work_info.department_id.department
)
departments = list(set(departments))
for depart in departments:
department_total.append({"department": depart, "ot_hours": 0})
for attendance in attendances:
if attendance.employee_id.employee_work_info.department_id:
department = attendance.employee_id.employee_work_info.department_id.department
ot = attendance.approved_overtime_second
ot_hrs = ot / 3600
for depart in department_total:
if depart["department"] == department:
depart["ot_hours"] += ot_hrs
dataset = [
{
"label": "",
"data": [],
}
]
for depart_total, depart in zip(department_total, departments):
if depart == depart_total["department"]:
dataset[0]["data"].append(depart_total["ot_hours"])
response = {
"dataset": dataset,
"labels": departments,
"department_total": department_total,
"message": _("No validated Overtimes were found"),
"emptyImageSrc":"/static/images/ui/overtime-icon.png",
}
return JsonResponse(response)

View File

@@ -15,6 +15,17 @@ from horilla.decorators import manager_can_enter, login_required
from horilla import settings
from employee.filters import EmployeeFilter
from recruitment.models import RecruitmentMailTemplate
from django.core.paginator import Paginator
def paginator_qry(qryset, page_number):
"""
This method is used to paginate query set
"""
paginator = Paginator(qryset, 20)
qryset = paginator.get_page(page_number)
return qryset
@login_required
@@ -24,12 +35,15 @@ def not_in_yet(request):
This context processor wil return the employees, if they not marked the attendance
for the day
"""
page_number = request.GET.get("page")
previous_data = request.GET.urlencode()
emps = (
EmployeeFilter({"not_in_yet": date.today()})
.qs.exclude(employee_work_info__isnull=True)
.filter(is_active=True)
)
return render(request, "dashboard/not_in_yet.html", {"employees": emps})
return render(request, "dashboard/not_in_yet.html", {"employees": paginator_qry(emps, page_number),"pd": previous_data,})
@login_required

View File

@@ -1,10 +1,24 @@
{% load i18n %}
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent">
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider mb-0">
<span class="oh-card-dashboard__title">{% trans 'Not In Yet' %}</span>
{% if employees.has_previous %}
<span class="oh-card-dashboard__title" id="employee-previous" style="cursor: pointer;" hx-target="#notInYetId"
hx-get="{% url 'not-in-yet' %}?{{pd}}&page={{ employees.previous_page_number }}">
<ion-icon name="caret-back-outline" role="img" class="md hydrated" aria-label="caret back outline"></ion-icon>
</span>
{% endif %}
<span class="oh-card-dashboard__title">{% trans 'Not In Yet' %} </span>
{% if employees.has_next %}
<span class="oh-card-dashboard__title float-end" id="employee-previous" style="cursor: pointer;" hx-target="#notInYetId"
hx-get="{% url 'not-in-yet' %}?{{pd}}&page={{ employees.next_page_number }}">
<ion-icon name="caret-forward-outline" role="img" class="md hydrated" aria-label="caret back outline"></ion-icon>
</span>
{% endif %}
</div>
<div class="oh-card-dashboard__body">
<div class="oh-sticky-table" style="max-height:320px;">
<div class="oh-sticky-table" style="height:320px;">
<div class="oh-sticky-table__table oh-table--sortable">
<div class="oh-sticky-table__tbody">
{% for emp in employees %}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -51,7 +51,7 @@
>
</div>
<!-- end of back button -->
<div class="oh-dashboard row" id="dashboard">
<div class="oh-dashboard row" id="dashboard" style="padding-bottom: 3.5rem;">
<div class="oh-dashboard__left col-12 col-sm-12 col-md-12 col-lg-9">
<div class="oh-dashboard__cards row">
{% if perms.employee.view_employee %}
@@ -144,7 +144,7 @@
{% if perms.candidate.view_employee or request.user|is_reportingmanager %}
<div
class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable"
id="notInYetdd"
id="notInYetId"
hx-get="{% url "not-in-yet" %}"
hx-trigger="load"
>
@@ -326,7 +326,7 @@
style="width: 100px; color: #5e5c5c"
/>
</div>
<div class="oh-card-dashboard__body">
<div class="oh-card-dashboard__body" style="height:300px">
<canvas
id="pendingHoursCanvas"
style="cursor: pointer"
@@ -456,6 +456,57 @@
</div>
</div>
{% endif %}
{% if perms.base.change_shiftrequest or request.user|is_reportingmanager %}
<div
class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable" id="shiftRequestApprove"
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent" style="height:425px"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
>
<span class="oh-card-dashboard__title"
>{% trans "Shift Request Approve" %}</span
>
</div>
<div class="oh-card-dashboard__body"
id="shiftRequestApproveBody"
hx-get="{% url "dashboard-shift-request" %}"
hx-trigger="load"
style="height:80%"
>
{% include "request_and_approve/shift_request.html" %}
</div>
</div>
</div>
{% endif %}
{% if perms.base.change_shiftrequest or request.user|is_reportingmanager %}
<div
class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable" id="WorkTypeRequestApprove"
>
<div
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent" style="height:425px"
>
<div
class="oh-card-dashboard__header oh-card-dashboard__header--divider"
>
<span class="oh-card-dashboard__title"
>{% trans "Work Type Request Approve" %}</span
>
</div>
<div class="oh-card-dashboard__body"
id="WorkTypeRequestApproveBody"
hx-get="{% url "dashboard-work-type-request" %}"
hx-trigger="load"
style="height:80%"
>
{% include "request_and_approve/work_type_request.html" %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
@@ -539,6 +590,32 @@
<div class="oh-modal__dialog-body" id="mail-content"></div>
</div>
</div>
<div
class="oh-modal"
id="shiftRequestDetailModal"
role="dialog"
aria-labelledby="shiftRequestDetailModal"
aria-hidden="true"
>
<div class="oh-modal__dialog oh-modal__dialog--timeoff oh-timeoff-modal">
<div class="oh-modal__dialog-header">
<h2
class="oh-modal__dialog-title"
id=""
>
{% trans "Details" %}
</h2>
<button class="oh-modal__close" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
</div>
<div
class="oh-modal__dialog-body oh-modal__dialog-relative"
id="shiftRequestDetailTarget"
></div>
</div>
</div>
</main>

View File

@@ -399,6 +399,11 @@
>
</li>
{% endif %}
<li class="oh-sidebar__submenu-item">
<a href="{%url 'view-reimbursement' %}" class="oh-sidebar__submenu-link"
>{% trans "Encashments & Reimbursements" %}</a
>
</li>
{% if perms.payroll.view_filingstatus %}
<li class="oh-sidebar__submenu-item">
<a
@@ -440,6 +445,15 @@
style="display: none"
>
<ul class="oh-sidebar__submenu-items">
{% if perms.attendance.view_attendance or request.user|is_reportingmanager %}
<li class="oh-sidebar__submenu-item">
<a
class="oh-sidebar__submenu-link"
href="{% url 'attendance-dashboard' %}"
>{% trans "Dashboard" %}</a
>
</li>
{% endif %}
{% if perms.attendance.view_attendance or request.user|is_reportingmanager %}
<li class="oh-sidebar__submenu-item">
<a
@@ -561,7 +575,7 @@
>
</li>
{% endif %}
{% if perms.leave.view_leaverequest or request.user|is_reportingmanager %}
{% if perms.leave.view_leaverequest or request.user|is_reportingmanager or request.user|is_leave_approval_manager %}
<li class="oh-sidebar__submenu-item">
<a
href="{% url 'request-view' %}"
@@ -721,7 +735,7 @@
<li
class="oh-sidebar__menu-item"
x-data="{ isOpen: getOpenState('helpDeskNav') }"
>
>
<a
class="oh-sidebar__menu-link"
data-id="helpDeskNav"
@@ -765,6 +779,74 @@
</ul>
</div>
</li>
<li
class="oh-sidebar__menu-item"
x-data="{ isOpen: getOpenState('configNav') }"
>
<a
class="oh-sidebar__menu-link"
data-id="configNav"
x-on:click.prevent="isOpen = !isOpen; saveOpenState('configNav', isOpen)"
x-bind:class="isOpen ? 'oh-sidebar__menu-link--active' : ''"
style="cursor: pointer"
>
<div class="oh-sidebar__menu-icon">
<svg
fill="#ffffff"
height="20"
width="24"
version="1.1"
id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 482.568 482.568"
xml:space="preserve"
stroke="#ffffff"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<g>
<g>
<path
d="M116.993,203.218c13.4-1.8,26.8,2.8,36.3,12.3l24,24l22.7-22.6l-32.8-32.7c-5.1-5.1-5.1-13.4,0-18.5s13.4-5.1,18.5,0 l32.8,32.8l22.7-22.6l-24.1-24.1c-9.5-9.5-14.1-23-12.3-36.3c4-30.4-5.7-62.2-29-85.6c-23.8-23.8-56.4-33.4-87.3-28.8 c-4.9,0.7-6.9,6.8-3.4,10.3l30.9,30.9c14.7,14.7,14.7,38.5,0,53.1l-19,19c-14.7,14.7-38.5,14.7-53.1,0l-31-30.9 c-3.5-3.5-9.5-1.5-10.3,3.4c-4.6,30.9,5,63.5,28.8,87.3C54.793,197.518,86.593,207.218,116.993,203.218z"
></path>
<path
d="M309.193,243.918l-22.7,22.6l134.8,134.8c5.1,5.1,5.1,13.4,0,18.5s-13.4,5.1-18.5,0l-134.8-134.8l-22.7,22.6l138.9,138.9 c17.6,17.6,46.1,17.5,63.7-0.1s17.6-46.1,0.1-63.7L309.193,243.918z"
></path>
<path
d="M361.293,153.918h59.9l59.9-119.7l-29.9-29.9l-119.8,59.8v59.9l-162.8,162.3l-29.3-29.2l-118,118 c-24.6,24.6-24.6,64.4,0,89s64.4,24.6,89,0l118-118l-29.9-29.9L361.293,153.918z"
></path>
</g>
</g>
</g>
</svg>
</div>
<span>{% trans "Configuration" %}</span>
</a>
<div
class="oh-sidebar__submenu"
id="configNav"
style="display: none"
x-show="isOpen"
x-transition
style="display: none"
>
<ul class="oh-sidebar__submenu-items">
<li class="oh-sidebar__submenu-item">
<a
href="{% url 'multiple-approval-condition' %}"
class="oh-sidebar__submenu-link"
>{% trans "Multiple Approvals " %}</a
>
</li>
</ul>
</div>
</li>
</ul>
</div>
</div>