Add UserRights branch from github changeset: 10a346901a9018735d9fd70a1a79a737595177ab

This commit is contained in:
Vikas Nale 2025-03-25 17:22:23 +05:30
parent 11241696b3
commit ba302898b8
83 changed files with 11480 additions and 2110 deletions

14
docker-compose.yml Normal file
View File

@ -0,0 +1,14 @@
services:
react:
image: node:18
container_name: react_app
working_dir: /app
volumes:
- ./react:/app
ports:
- "3000:3000"
environment:
- REACT_APP_API_URL=http://dotnet_api:5000
command: sh -c "git clone https://github.com/marcoioitsoft/marco.pms.web.git /app && npm install && npm start"
networks:
- dev_network

View File

@ -40,6 +40,9 @@
<link rel="stylesheet" href="/assets/vendor/libs/bootstrap-select/bootstrap-select.css" /> <link rel="stylesheet" href="/assets/vendor/libs/bootstrap-select/bootstrap-select.css" />
<link rel="stylesheet" href="/assets/vendor/libs/select2/select2.css" /> <link rel="stylesheet" href="/assets/vendor/libs/select2/select2.css" />
<link rel="stylesheet" href="/assets/vendor/libs/animate-css/animate.css" />
<link rel="stylesheet" href="/assets/vendor/libs/sweetalert2/sweetalert2.css" />
<!-- Helpers --> <!-- Helpers -->
<script src="/assets/vendor/js/helpers.js"></script> <script src="/assets/vendor/js/helpers.js"></script>
<script src="/assets/js/config.js"></script> <script src="/assets/js/config.js"></script>
@ -64,6 +67,7 @@
<script src="/assets/vendor/libs/hammer/hammer.js"></script> <script src="/assets/vendor/libs/hammer/hammer.js"></script>
<script src="/assets/vendor/libs/i18n/i18n.js"></script> <script src="/assets/vendor/libs/i18n/i18n.js"></script>
<script src="/assets/vendor/libs/typeahead-js/typeahead.js"></script> <script src="/assets/vendor/libs/typeahead-js/typeahead.js"></script>
<script src="/assets/vendor/libs/select2/select2.js"></script>
<script src="/assets/vendor/js/menu.js"></script> <script src="/assets/vendor/js/menu.js"></script>
@ -91,7 +95,7 @@
<!-- component --> <!-- component -->
<script src="./public/js/timppick.js"></script> <script src="./public/js/timppick.js"></script>
<script src="/assets/vendor/libs/sweetalert2/sweetalert2.js" ></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script> <!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>

View File

@ -0,0 +1,825 @@
/**
* DataTables Basic
*/
'use strict';
let fv, offCanvasEl;
document.addEventListener('DOMContentLoaded', function (e) {
(function () {
const formAddNewRecord = document.getElementById('form-add-new-record');
setTimeout(() => {
const newRecord = document.querySelector('.create-new'),
offCanvasElement = document.querySelector('#add-new-record');
// To open offCanvas, to add new record
if (newRecord) {
newRecord.addEventListener('click', function () {
offCanvasEl = new bootstrap.Offcanvas(offCanvasElement);
// Empty fields on offCanvas open
(offCanvasElement.querySelector('.dt-full-name').value = ''),
(offCanvasElement.querySelector('.dt-post').value = ''),
(offCanvasElement.querySelector('.dt-email').value = ''),
(offCanvasElement.querySelector('.dt-date').value = ''),
(offCanvasElement.querySelector('.dt-salary').value = '');
// Open offCanvas with form
offCanvasEl.show();
});
}
}, 200);
// Form validation for Add new record
fv = FormValidation.formValidation(formAddNewRecord, {
fields: {
basicFullname: {
validators: {
notEmpty: {
message: 'The name is required'
}
}
},
basicPost: {
validators: {
notEmpty: {
message: 'Post field is required'
}
}
},
basicEmail: {
validators: {
notEmpty: {
message: 'The Email is required'
},
emailAddress: {
message: 'The value is not a valid email address'
}
}
},
basicDate: {
validators: {
notEmpty: {
message: 'Joining Date is required'
},
date: {
format: 'MM/DD/YYYY',
message: 'The value is not a valid date'
}
}
},
basicSalary: {
validators: {
notEmpty: {
message: 'Basic Salary is required'
}
}
}
},
plugins: {
trigger: new FormValidation.plugins.Trigger(),
bootstrap5: new FormValidation.plugins.Bootstrap5({
// Use this for enabling/changing valid/invalid class
// eleInvalidClass: '',
eleValidClass: '',
rowSelector: '.col-sm-12'
}),
submitButton: new FormValidation.plugins.SubmitButton(),
// defaultSubmit: new FormValidation.plugins.DefaultSubmit(),
autoFocus: new FormValidation.plugins.AutoFocus()
},
init: instance => {
instance.on('plugins.message.placed', function (e) {
if (e.element.parentElement.classList.contains('input-group')) {
e.element.parentElement.insertAdjacentElement('afterend', e.messageElement);
}
});
}
});
// FlatPickr Initialization & Validation
const flatpickrDate = document.querySelector('[name="basicDate"]');
if (flatpickrDate) {
flatpickrDate.flatpickr({
enableTime: false,
// See https://flatpickr.js.org/formatting/
dateFormat: 'm/d/Y',
// After selecting a date, we need to revalidate the field
onChange: function () {
fv.revalidateField('basicDate');
}
});
}
})();
});
// datatable (jquery)
$(function () {
var dt_basic_table = $('.datatables-basic'),
dt_complex_header_table = $('.dt-complex-header'),
dt_row_grouping_table = $('.dt-row-grouping'),
dt_multilingual_table = $('.dt-multilingual'),
dt_basic;
// DataTable with buttons
// --------------------------------------------------------------------
if (dt_basic_table.length) {
dt_basic = dt_basic_table.DataTable({
ajax: assetsPath + 'json/table-datatable.json',
columns: [
{ data: '' },
{ data: 'id' },
{ data: 'id' },
{ data: 'full_name' },
{ data: 'email' },
{ data: 'start_date' },
{ data: 'salary' },
{ data: 'status' },
{ data: '' }
],
columnDefs: [
{
// For Responsive
className: 'control',
orderable: false,
searchable: false,
responsivePriority: 2,
targets: 0,
render: function (data, type, full, meta) {
return '';
}
},
{
// For Checkboxes
targets: 1,
orderable: false,
searchable: false,
responsivePriority: 3,
checkboxes: true,
render: function () {
return '<input type="checkbox" class="dt-checkboxes form-check-input">';
},
checkboxes: {
selectAllRender: '<input type="checkbox" class="form-check-input">'
}
},
{
targets: 2,
searchable: false,
visible: false
},
{
// Avatar image/badge, Name and post
targets: 3,
responsivePriority: 4,
render: function (data, type, full, meta) {
var $user_img = full['avatar'],
$name = full['full_name'],
$post = full['post'];
if ($user_img) {
// For Avatar image
var $output =
'<img src="' + assetsPath + 'img/avatars/' + $user_img + '" alt="Avatar" class="rounded-circle">';
} else {
// For Avatar badge
var stateNum = Math.floor(Math.random() * 6);
var states = ['success', 'danger', 'warning', 'info', 'dark', 'primary', 'secondary'];
var $state = states[stateNum],
$name = full['full_name'],
$initials = $name.match(/\b\w/g) || [];
$initials = (($initials.shift() || '') + ($initials.pop() || '')).toUpperCase();
$output = '<span class="avatar-initial rounded-circle bg-label-' + $state + '">' + $initials + '</span>';
}
// Creates full output for row
var $row_output =
'<div class="d-flex justify-content-start align-items-center user-name">' +
'<div class="avatar-wrapper">' +
'<div class="avatar me-2">' +
$output +
'</div>' +
'</div>' +
'<div class="d-flex flex-column">' +
'<span class="emp_name text-truncate">' +
$name +
'</span>' +
'<small class="emp_post text-truncate text-muted">' +
$post +
'</small>' +
'</div>' +
'</div>';
return $row_output;
}
},
{
responsivePriority: 1,
targets: 4
},
{
// Label
targets: -2,
render: function (data, type, full, meta) {
var $status_number = full['status'];
var $status = {
1: { title: 'Current', class: 'bg-label-primary' },
2: { title: 'Professional', class: ' bg-label-success' },
3: { title: 'Rejected', class: ' bg-label-danger' },
4: { title: 'Resigned', class: ' bg-label-warning' },
5: { title: 'Applied', class: ' bg-label-info' }
};
if (typeof $status[$status_number] === 'undefined') {
return data;
}
return (
'<span class="badge ' + $status[$status_number].class + '">' + $status[$status_number].title + '</span>'
);
}
},
{
// Actions
targets: -1,
title: 'Actions',
orderable: false,
searchable: false,
render: function (data, type, full, meta) {
return (
'<div class="d-inline-block">' +
'<a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow me-1" data-bs-toggle="dropdown"><i class="bx bx-dots-vertical-rounded bx-md"></i></a>' +
'<ul class="dropdown-menu dropdown-menu-end m-0">' +
'<li><a href="javascript:;" class="dropdown-item">Details</a></li>' +
'<li><a href="javascript:;" class="dropdown-item">Archive</a></li>' +
'<div class="dropdown-divider"></div>' +
'<li><a href="javascript:;" class="dropdown-item text-danger delete-record">Delete</a></li>' +
'</ul>' +
'</div>' +
'<a href="javascript:;" class="btn btn-icon item-edit"><i class="bx bx-edit bx-md"></i></a>'
);
}
}
],
order: [[2, 'desc']],
dom: '<"card-header flex-column flex-md-row pb-0"<"head-label text-center"><"dt-action-buttons text-end pt-6 pt-md-0"B>><"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6 d-flex justify-content-center justify-content-md-end mt-n6 mt-md-0"f>>t<"row"<"col-sm-12 col-md-6"i><"col-sm-12 col-md-6"p>>',
displayLength: 7,
lengthMenu: [7, 10, 25, 50, 75, 100],
language: {
paginate: {
next: '<i class="bx bx-chevron-right bx-18px"></i>',
previous: '<i class="bx bx-chevron-left bx-18px"></i>'
}
},
buttons: [
{
extend: 'collection',
className: 'btn btn-label-primary dropdown-toggle me-4',
text: '<i class="bx bx-export bx-sm me-sm-2"></i> <span class="d-none d-sm-inline-block">Export</span>',
buttons: [
{
extend: 'print',
text: '<i class="bx bx-printer me-1" ></i>Print',
className: 'dropdown-item',
exportOptions: {
columns: [3, 4, 5, 6, 7],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
},
customize: function (win) {
//customize print view for dark
$(win.document.body)
.css('color', config.colors.headingColor)
.css('border-color', config.colors.borderColor)
.css('background-color', config.colors.bodyBg);
$(win.document.body)
.find('table')
.addClass('compact')
.css('color', 'inherit')
.css('border-color', 'inherit')
.css('background-color', 'inherit');
}
},
{
extend: 'csv',
text: '<i class="bx bx-file me-1" ></i>Csv',
className: 'dropdown-item',
exportOptions: {
columns: [3, 4, 5, 6, 7],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
},
{
extend: 'excel',
text: '<i class="bx bxs-file-export me-1"></i>Excel',
className: 'dropdown-item',
exportOptions: {
columns: [3, 4, 5, 6, 7],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
},
{
extend: 'pdf',
text: '<i class="bx bxs-file-pdf me-1"></i>Pdf',
className: 'dropdown-item',
exportOptions: {
columns: [3, 4, 5, 6, 7],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
},
{
extend: 'copy',
text: '<i class="bx bx-copy me-1" ></i>Copy',
className: 'dropdown-item',
exportOptions: {
columns: [3, 4, 5, 6, 7],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
}
]
},
{
text: '<i class="bx bx-plus bx-sm me-sm-2"></i> <span class="d-none d-sm-inline-block">Add New Record</span>',
className: 'create-new btn btn-primary'
}
],
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function (row) {
var data = row.data();
return 'Details of ' + data['full_name'];
}
}),
type: 'column',
renderer: function (api, rowIdx, columns) {
var data = $.map(columns, function (col, i) {
return col.title !== '' // ? Do not show row in modal popup if title is blank (for check box)
? '<tr data-dt-row="' +
col.rowIndex +
'" data-dt-column="' +
col.columnIndex +
'">' +
'<td>' +
col.title +
':' +
'</td> ' +
'<td>' +
col.data +
'</td>' +
'</tr>'
: '';
}).join('');
return data ? $('<table class="table"/><tbody />').append(data) : false;
}
}
}
});
$('div.head-label').html('<h5 class="card-title mb-0">DataTable with Buttons</h5>');
// To remove default btn-secondary in export buttons
$('.dt-buttons > .btn-group > button').removeClass('btn-secondary');
}
// Add New record
// ? Remove/Update this code as per your requirements
var count = 101;
// On form submit, if form is valid
fv.on('core.form.valid', function () {
var $new_name = $('.add-new-record .dt-full-name').val(),
$new_post = $('.add-new-record .dt-post').val(),
$new_email = $('.add-new-record .dt-email').val(),
$new_date = $('.add-new-record .dt-date').val(),
$new_salary = $('.add-new-record .dt-salary').val();
if ($new_name != '') {
dt_basic.row
.add({
id: count,
full_name: $new_name,
post: $new_post,
email: $new_email,
start_date: $new_date,
salary: '$' + $new_salary,
status: 5
})
.draw();
count++;
// Hide offcanvas using javascript method
offCanvasEl.hide();
}
});
// Delete Record
$('.datatables-basic tbody').on('click', '.delete-record', function () {
dt_basic.row($(this).parents('tr')).remove().draw();
});
// Complex Header DataTable
// --------------------------------------------------------------------
if (dt_complex_header_table.length) {
var dt_complex = dt_complex_header_table.DataTable({
ajax: assetsPath + 'json/table-datatable.json',
columns: [
{ data: 'full_name' },
{ data: 'email' },
{ data: 'city' },
{ data: 'post' },
{ data: 'salary' },
{ data: 'status' },
{ data: '' }
],
columnDefs: [
{
// Label
targets: -2,
render: function (data, type, full, meta) {
var $status_number = full['status'];
var $status = {
1: { title: 'Current', class: 'bg-label-primary' },
2: { title: 'Professional', class: ' bg-label-success' },
3: { title: 'Rejected', class: ' bg-label-danger' },
4: { title: 'Resigned', class: ' bg-label-warning' },
5: { title: 'Applied', class: ' bg-label-info' }
};
if (typeof $status[$status_number] === 'undefined') {
return data;
}
return (
'<span class="badge ' + $status[$status_number].class + '">' + $status[$status_number].title + '</span>'
);
}
},
{
// Actions
targets: -1,
title: 'Actions',
orderable: false,
render: function (data, type, full, meta) {
return (
'<div class="d-inline-block">' +
'<a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow me-1" data-bs-toggle="dropdown"><i class="bx bx-dots-vertical-rounded bx-md"></i></a>' +
'<div class="dropdown-menu dropdown-menu-end m-0">' +
'<a href="javascript:;" class="dropdown-item">Details</a>' +
'<a href="javascript:;" class="dropdown-item">Archive</a>' +
'<div class="dropdown-divider"></div>' +
'<a href="javascript:;" class="dropdown-item text-danger delete-record">Delete</a>' +
'</div>' +
'</div>' +
'<a href="javascript:;" class="btn btn-icon item-edit"><i class="bx bx-edit bx-md"></i></a>'
);
}
}
],
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6 d-flex justify-content-center justify-content-md-end mt-n6 mt-md-0"f>><"table-responsive"t><"row"<"col-sm-12 col-md-6"i><"col-sm-12 col-md-6"p>>',
displayLength: 7,
lengthMenu: [7, 10, 25, 50, 75, 100],
language: {
paginate: {
next: '<i class="bx bx-chevron-right bx-18px"></i>',
previous: '<i class="bx bx-chevron-left bx-18px"></i>'
}
}
});
}
// Row Grouping
// --------------------------------------------------------------------
var groupColumn = 2;
if (dt_row_grouping_table.length) {
var groupingTable = dt_row_grouping_table.DataTable({
ajax: assetsPath + 'json/table-datatable.json',
columns: [
{ data: '' },
{ data: 'full_name' },
{ data: 'post' },
{ data: 'email' },
{ data: 'city' },
{ data: 'start_date' },
{ data: 'salary' },
{ data: 'status' },
{ data: '' }
],
columnDefs: [
{
// For Responsive
className: 'control',
orderable: false,
targets: 0,
searchable: false,
render: function (data, type, full, meta) {
return '';
}
},
{ visible: false, targets: groupColumn },
{
// Label
targets: -2,
render: function (data, type, full, meta) {
var $status_number = full['status'];
var $status = {
1: { title: 'Current', class: 'bg-label-primary' },
2: { title: 'Professional', class: ' bg-label-success' },
3: { title: 'Rejected', class: ' bg-label-danger' },
4: { title: 'Resigned', class: ' bg-label-warning' },
5: { title: 'Applied', class: ' bg-label-info' }
};
if (typeof $status[$status_number] === 'undefined') {
return data;
}
return (
'<span class="badge ' + $status[$status_number].class + '">' + $status[$status_number].title + '</span>'
);
}
},
{
// Actions
targets: -1,
title: 'Actions',
orderable: false,
searchable: false,
render: function (data, type, full, meta) {
return (
'<div class="d-inline-block">' +
'<a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow me-1" data-bs-toggle="dropdown"><i class="bx bx-dots-vertical-rounded bx-sm"></i></a>' +
'<div class="dropdown-menu dropdown-menu-end m-0">' +
'<a href="javascript:;" class="dropdown-item">Details</a>' +
'<a href="javascript:;" class="dropdown-item">Archive</a>' +
'<div class="dropdown-divider"></div>' +
'<a href="javascript:;" class="dropdown-item text-danger delete-record">Delete</a>' +
'</div>' +
'</div>' +
'<a href="javascript:;" class="btn btn-icon item-edit"><i class="bx bx-edit bx-sm"></i></a>'
);
}
}
],
order: [[groupColumn, 'asc']],
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6 d-flex justify-content-center justify-content-md-end mt-n6 mt-md-0"f>>t<"row"<"col-sm-12 col-md-6"i><"col-sm-12 col-md-6"p>>',
displayLength: 7,
lengthMenu: [7, 10, 25, 50, 75, 100],
language: {
paginate: {
next: '<i class="bx bx-chevron-right bx-18px"></i>',
previous: '<i class="bx bx-chevron-left bx-18px"></i>'
}
},
drawCallback: function (settings) {
var api = this.api();
var rows = api.rows({ page: 'current' }).nodes();
var last = null;
api
.column(groupColumn, { page: 'current' })
.data()
.each(function (group, i) {
if (last !== group) {
$(rows)
.eq(i)
.before('<tr class="group"><td colspan="8">' + group + '</td></tr>');
last = group;
}
});
},
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function (row) {
var data = row.data();
return 'Details of ' + data['full_name'];
}
}),
type: 'column',
renderer: function (api, rowIdx, columns) {
var data = $.map(columns, function (col, i) {
return col.title !== '' // ? Do not show row in modal popup if title is blank (for check box)
? '<tr data-dt-row="' +
col.rowIndex +
'" data-dt-column="' +
col.columnIndex +
'">' +
'<td>' +
col.title +
':' +
'</td> ' +
'<td>' +
col.data +
'</td>' +
'</tr>'
: '';
}).join('');
return data ? $('<table class="table"/><tbody />').append(data) : false;
}
}
}
});
// Order by the grouping
$('.dt-row-grouping tbody').on('click', 'tr.group', function () {
var currentOrder = groupingTable.order()[0];
if (currentOrder[0] === groupColumn && currentOrder[1] === 'asc') {
groupingTable.order([groupColumn, 'desc']).draw();
} else {
groupingTable.order([groupColumn, 'asc']).draw();
}
});
}
// Multilingual DataTable
// --------------------------------------------------------------------
var lang = 'German';
if (dt_multilingual_table.length) {
var table_language = dt_multilingual_table.DataTable({
ajax: assetsPath + 'json/table-datatable.json',
columns: [
{ data: '' },
{ data: 'full_name' },
{ data: 'post' },
{ data: 'email' },
{ data: 'start_date' },
{ data: 'salary' },
{ data: 'status' },
{ data: '' }
],
columnDefs: [
{
// For Responsive
className: 'control',
orderable: false,
targets: 0,
searchable: false,
render: function (data, type, full, meta) {
return '';
}
},
{
// Label
targets: -2,
render: function (data, type, full, meta) {
var $status_number = full['status'];
var $status = {
1: { title: 'Current', class: 'bg-label-primary' },
2: { title: 'Professional', class: ' bg-label-success' },
3: { title: 'Rejected', class: ' bg-label-danger' },
4: { title: 'Resigned', class: ' bg-label-warning' },
5: { title: 'Applied', class: ' bg-label-info' }
};
if (typeof $status[$status_number] === 'undefined') {
return data;
}
return (
'<span class="badge ' + $status[$status_number].class + '">' + $status[$status_number].title + '</span>'
);
}
},
{
// Actions
targets: -1,
title: 'Actions',
orderable: false,
searchable: false,
render: function (data, type, full, meta) {
return (
'<div class="d-inline-block">' +
'<a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow me-1" data-bs-toggle="dropdown"><i class="bx bx-dots-vertical-rounded bx-sm"></i></a>' +
'<div class="dropdown-menu dropdown-menu-end m-0">' +
'<a href="javascript:;" class="dropdown-item">Details</a>' +
'<a href="javascript:;" class="dropdown-item">Archive</a>' +
'<div class="dropdown-divider"></div>' +
'<a href="javascript:;" class="dropdown-item text-danger delete-record">Delete</a>' +
'</div>' +
'</div>' +
'<a href="javascript:;" class="btn btn-icon item-edit"><i class="bx bx-edit bx-sm"></i></a>'
);
}
}
],
language: {
url: '//cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/' + lang + '.json',
paginate: {
next: '<i class="bx bx-chevron-right bx-18px"></i>',
previous: '<i class="bx bx-chevron-left bx-18px"></i>'
}
},
order: [[2, 'desc']],
displayLength: 7,
dom: '<"row"<"col-sm-12 col-md-6 ps-md-4"l><"col-sm-12 col-md-6 d-flex justify-content-center justify-content-md-end mt-n6 mt-md-0"f>>t<"row"<"col-sm-12 col-md-6"i><"col-sm-12 col-md-6"p>>',
lengthMenu: [7, 10, 25, 50, 75, 100],
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function (row) {
var data = row.data();
return 'Details of ' + data['full_name'];
}
}),
type: 'column',
renderer: function (api, rowIdx, columns) {
var data = $.map(columns, function (col, i) {
return col.title !== '' // ? Do not show row in modal popup if title is blank (for check box)
? '<tr data-dt-row="' +
col.rowIndex +
'" data-dt-column="' +
col.columnIndex +
'">' +
'<td>' +
col.title +
':' +
'</td> ' +
'<td>' +
col.data +
'</td>' +
'</tr>'
: '';
}).join('');
return data ? $('<table class="table"/><tbody />').append(data) : false;
}
}
}
});
}
// Filter form control to default size
// ? setTimeout used for multilingual table initialization
setTimeout(() => {
$('.dataTables_filter .form-control').removeClass('form-control-sm');
$('.dataTables_length .form-select').removeClass('form-select-sm');
}, 300);
});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,817 @@
@charset "UTF-8";
:root {
--dt-row-selected: 13, 110, 253;
--dt-row-selected-text: 255, 255, 255;
--dt-row-selected-link: 9, 10, 11;
--dt-row-stripe: 0, 0, 0;
--dt-row-hover: 0, 0, 0;
--dt-column-ordering: 0, 0, 0;
--dt-html-background: white;
}
:root.dark {
--dt-html-background: rgb(33, 37, 41);
}
table.dataTable td.dt-control {
text-align: center;
cursor: pointer;
}
table.dataTable td.dt-control:before {
display: inline-block;
color: rgba(0, 0, 0, 0.5);
content: "▶";
}
table.dataTable tr.dt-hasChild td.dt-control:before {
content: "▼";
}
html.dark table.dataTable td.dt-control:before,
:root[data-bs-theme=dark] table.dataTable td.dt-control:before {
color: rgba(255, 255, 255, 0.5);
}
html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
color: rgba(255, 255, 255, 0.5);
}
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
table.dataTable thead > tr > td.sorting,
table.dataTable thead > tr > td.sorting_asc,
table.dataTable thead > tr > td.sorting_desc,
table.dataTable thead > tr > td.sorting_asc_disabled,
table.dataTable thead > tr > td.sorting_desc_disabled {
cursor: pointer;
position: relative;
padding-right: 26px;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
position: absolute;
display: block;
opacity: 0.125;
right: 10px;
line-height: 9px;
font-size: 0.8em;
}
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
table.dataTable thead > tr > td.sorting:before,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:before,
table.dataTable thead > tr > td.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:before {
bottom: 50%;
content: "▲";
content: "▲"/"";
}
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting:after,
table.dataTable thead > tr > td.sorting_asc:after,
table.dataTable thead > tr > td.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc_disabled:after,
table.dataTable thead > tr > td.sorting_desc_disabled:after {
top: 50%;
content: "▼";
content: "▼"/"";
}
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
table.dataTable thead > tr > td.sorting_asc:before,
table.dataTable thead > tr > td.sorting_desc:after {
opacity: 0.6;
}
table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before,
table.dataTable thead > tr > td.sorting_desc_disabled:after,
table.dataTable thead > tr > td.sorting_asc_disabled:before {
display: none;
}
table.dataTable thead > tr > th:active,
table.dataTable thead > tr > td:active {
outline: none;
}
div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after,
div.dataTables_scrollBody > table.dataTable > thead > tr > td:before,
div.dataTables_scrollBody > table.dataTable > thead > tr > td:after {
display: none;
}
div.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
margin-left: -100px;
margin-top: -26px;
text-align: center;
padding: 2px;
z-index: 10;
}
div.dataTables_processing > div:last-child {
position: relative;
width: 80px;
height: 15px;
margin: 1em auto;
}
div.dataTables_processing > div:last-child > div {
position: absolute;
top: 0;
width: 13px;
height: 13px;
border-radius: 50%;
background: rgb(13, 110, 253);
background: rgb(var(--dt-row-selected));
animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
div.dataTables_processing > div:last-child > div:nth-child(1) {
left: 8px;
animation: datatables-loader-1 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(2) {
left: 8px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(3) {
left: 32px;
animation: datatables-loader-2 0.6s infinite;
}
div.dataTables_processing > div:last-child > div:nth-child(4) {
left: 56px;
animation: datatables-loader-3 0.6s infinite;
}
@keyframes datatables-loader-1 {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes datatables-loader-3 {
0% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}
@keyframes datatables-loader-2 {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(24px, 0);
}
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th,
table.dataTable thead td,
table.dataTable tfoot th,
table.dataTable tfoot td {
text-align: left;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
/*! Bootstrap 5 integration for DataTables
*
* ©2020 SpryMedia Ltd, all rights reserved.
* License: MIT datatables.net/license/mit
*/
table.dataTable {
clear: both;
margin-top: 6px !important;
margin-bottom: 6px !important;
max-width: none !important;
border-collapse: separate !important;
border-spacing: 0;
}
table.dataTable td,
table.dataTable th {
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
table.dataTable td.dataTables_empty,
table.dataTable th.dataTables_empty {
text-align: center;
}
table.dataTable.nowrap th,
table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none;
}
table.dataTable > tbody > tr {
background-color: transparent;
}
table.dataTable > tbody > tr.selected > * {
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
color: rgb(255, 255, 255);
color: rgb(var(--dt-row-selected-text));
}
table.dataTable > tbody > tr.selected a {
color: rgb(9, 10, 11);
color: rgb(var(--dt-row-selected-link));
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
}
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
}
table.dataTable.table-hover > tbody > tr:hover > * {
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
}
table.dataTable.table-hover > tbody > tr.selected:hover > * {
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
}
div.dataTables_wrapper div.dataTables_length label {
font-weight: normal;
text-align: left;
white-space: nowrap;
}
div.dataTables_wrapper div.dataTables_length select {
width: auto;
display: inline-block;
}
div.dataTables_wrapper div.dataTables_filter {
text-align: right;
}
div.dataTables_wrapper div.dataTables_filter label {
font-weight: normal;
white-space: nowrap;
text-align: left;
}
div.dataTables_wrapper div.dataTables_filter input {
margin-left: 0.5em;
display: inline-block;
width: auto;
}
div.dataTables_wrapper div.dataTables_info {
padding-top: 0.85em;
}
div.dataTables_wrapper div.dataTables_paginate {
margin: 0;
white-space: nowrap;
text-align: right;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
margin: 2px 0;
white-space: nowrap;
justify-content: flex-end;
}
div.dataTables_wrapper div.dt-row {
position: relative;
}
div.dataTables_scrollHead table.dataTable {
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table {
border-top: none;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
div.dataTables_scrollBody > table > thead .sorting:before,
div.dataTables_scrollBody > table > thead .sorting_asc:before,
div.dataTables_scrollBody > table > thead .sorting_desc:before,
div.dataTables_scrollBody > table > thead .sorting:after,
div.dataTables_scrollBody > table > thead .sorting_asc:after,
div.dataTables_scrollBody > table > thead .sorting_desc:after {
display: none;
}
div.dataTables_scrollBody > table > tbody tr:first-child th,
div.dataTables_scrollBody > table > tbody tr:first-child td {
border-top: none;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner {
box-sizing: content-box;
}
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
margin-top: 0 !important;
border-top: none;
}
@media screen and (max-width: 767px) {
div.dataTables_wrapper div.dataTables_length,
div.dataTables_wrapper div.dataTables_filter,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
text-align: center;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
justify-content: center !important;
}
}
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
padding-right: 20px;
}
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):before, table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):after {
right: 5px;
}
table.table-bordered.dataTable {
border-right-width: 0;
}
table.table-bordered.dataTable thead tr:first-child th,
table.table-bordered.dataTable thead tr:first-child td {
border-top-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-left-width: 0;
}
table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
table.table-bordered.dataTable td:first-child,
table.table-bordered.dataTable td:first-child {
border-left-width: 1px;
}
table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
table.table-bordered.dataTable td:last-child,
table.table-bordered.dataTable td:last-child {
border-right-width: 1px;
}
table.table-bordered.dataTable th,
table.table-bordered.dataTable td {
border-bottom-width: 1px;
}
div.dataTables_scrollHead table.table-bordered {
border-bottom-width: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row {
margin: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child {
padding-left: 0;
}
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child {
padding-right: 0;
}
:root[data-bs-theme=dark] {
--dt-row-hover: 255, 255, 255;
--dt-row-stripe: 255, 255, 255;
--dt-column-ordering: 255, 255, 255;
}
div.dataTables_wrapper div.dataTables_length select {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
div.dataTables_wrapper div.dataTables_filter input {
margin-left: 1rem;
}
.dataTable .emp_name {
font-weight: 500;
}
div.dataTables_wrapper .card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
div.dataTables_wrapper div.dataTables_info {
padding-top: 0.5rem;
}
table.dataTable thead th.sorting_disabled::before, table.dataTable thead th.sorting_disabled::after {
display: none !important;
}
table.dataTable thead th.sorting:before, table.dataTable thead th.sorting:after {
visibility: hidden;
}
table.dataTable thead th.sorting:hover:before, table.dataTable thead th.sorting:hover:after {
visibility: visible;
}
table.table-bordered.dataTable.dt-complex-header thead tr th, table.table-bordered.dataTable.dt-column-search thead tr th {
border-width: 1px;
}
table.table-bordered.dataTable.dt-complex-header tfoot tr th, table.table-bordered.dataTable.dt-column-search tfoot tr th {
border-width: 1px;
}
table.table-bordered.dataTable tfoot tr th {
border-bottom-width: 1px;
}
table.table-bordered.dataTable > :not(caption) > * > * {
border-width: 0;
}
html:not([dir=rtl]) table.table-bordered.dataTable tr:first-child th:first-child,
html:not([dir=rtl]) table.table-bordered.dataTable td:first-child {
border-left-width: 0;
}
[dir=rtl] table.table-bordered.dataTable tr:first-child th:first-child,
[dir=rtl] table.table-bordered.dataTable td:first-child {
border-right-width: 0;
}
html:not([dir=rtl]) table.table-bordered.dataTable tr:first-child th:last-child,
html:not([dir=rtl]) table.table-bordered.dataTable td:last-child {
border-right-width: 0;
}
[dir=rtl] table.table-bordered.dataTable tr:first-child th:last-child,
[dir=rtl] table.table-bordered.dataTable td:last-child {
border-left-width: 0;
}
table.table-bordered.dataTable > tbody:not(caption) tr:first-child {
border-top-width: 0;
}
@media screen and (min-width: 1399.98px) {
table.table-responsive {
display: table;
}
}
[dir=rtl] div.dataTables_wrapper .dataTables_filter {
display: flex;
justify-content: flex-end;
}
[dir=rtl] div.dataTables_wrapper .dataTables_filter input {
margin-left: 0;
margin-right: 1rem;
}
[dir=rtl] table.table-bordered.dataTable th,
[dir=rtl] table.table-bordered.dataTable td {
border-right-width: 0;
border-left-width: 1px;
}
[dir=rtl] table.table-bordered.dataTable th:last-child,
[dir=rtl] table.table-bordered.dataTable td:last-child {
border-left-width: 0;
}
table.dataTable {
width: 100% !important;
border-collapse: collapse !important;
margin-bottom: 1rem !important;
margin-top: 0 !important;
}
[dir=rtl] table.dataTable.table-sm > thead > tr > th {
padding-left: 1.25rem;
}
[dir=rtl] table.dataTable.table-sm .sorting:before, [dir=rtl] table.dataTable.table-sm .sorting_asc:before, [dir=rtl] table.dataTable.table-sm .sorting_desc:before {
right: auto !important;
left: 0.85em !important;
}
[dir=rtl] table.dataTable thead th,
[dir=rtl] table.dataTable thead td,
[dir=rtl] table.dataTable tfoot th,
[dir=rtl] table.dataTable tfoot td {
text-align: right;
}
table.dataTable .form-check-input {
width: 18px;
height: 18px;
}
.dataTables_scroll {
margin-bottom: 0.75rem;
}
table.dataTable thead th {
vertical-align: middle;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
padding-right: inherit;
}
table.dataTable thead .sorting::before, table.dataTable thead .sorting::after,
table.dataTable thead .sorting_asc::before,
table.dataTable thead .sorting_asc::after,
table.dataTable thead .sorting_desc::before,
table.dataTable thead .sorting_desc::after,
table.dataTable thead .sorting_asc_disabled::before,
table.dataTable thead .sorting_asc_disabled::after,
table.dataTable thead .sorting_desc_disabled::before,
table.dataTable thead .sorting_desc_disabled::after {
font-family: boxicons !important;
font-weight: 500 !important;
font-size: 1.2rem !important;
width: 10px;
height: 10px;
content: "" !important;
right: 0.7rem !important;
}
table.dataTable thead .sorting:before,
table.dataTable thead .sorting_asc:before,
table.dataTable thead .sorting_desc:before,
table.dataTable thead .sorting_asc_disabled:before,
table.dataTable thead .sorting_desc_disabled:before {
top: 0.5rem !important;
content: "\ea57" !important;
}
table.dataTable thead .sorting:after,
table.dataTable thead .sorting_asc:after,
table.dataTable thead .sorting_desc:after,
table.dataTable thead .sorting_asc_disabled:after,
table.dataTable thead .sorting_desc_disabled:after {
bottom: 0.8rem !important;
content: "\ea4a" !important;
}
[dir=rtl] table.dataTable thead .sorting::before,
[dir=rtl] table.dataTable thead .sorting_asc::before,
[dir=rtl] table.dataTable thead .sorting_desc::before,
[dir=rtl] table.dataTable thead .sorting_asc_disabled::before,
[dir=rtl] table.dataTable thead .sorting_desc_disabled::before {
right: auto !important;
left: 0.58em !important;
}
[dir=rtl] table.dataTable thead .sorting::after,
[dir=rtl] table.dataTable thead .sorting_asc::after,
[dir=rtl] table.dataTable thead .sorting_desc::after,
[dir=rtl] table.dataTable thead .sorting_asc_disabled::after,
[dir=rtl] table.dataTable thead .sorting_desc_disabled::after {
right: auto !important;
left: 0.58em !important;
}
div.card-datatable.dataTable,
div.card-datatable .dataTable {
border-right: 0;
border-left: 0;
}
@media screen and (max-width: 575.98px) {
div.dataTables_wrapper .card-header {
display: block;
}
.dtr-bs-modal.modal .modal-body {
padding: 0;
overflow: auto;
}
.dataTable_select div.dataTables_wrapper div.dataTables_info {
flex-direction: column;
}
}
@media screen and (max-width: 767.98px) {
div.dataTables_wrapper div.dataTables_info {
padding-bottom: 0.782rem;
}
}
div.dataTables_wrapper div.dataTables_length,
div.dataTables_wrapper .dataTables_filter {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item .page-link, div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.next .page-link, div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.previous .page-link, div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.first .page-link, div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-item.last .page-link {
border-radius: 0.375rem;
}
div.dataTables_wrapper div.dataTables_paginate ul.pagination .page-link div:not(.table-responsive) div.dataTables_wrapper .dataTables_paginate {
margin-right: 0;
}
@media (max-width: 767.98px) {
div.dataTables_wrapper div.dataTables_length label,
div.dataTables_wrapper div.dataTables_filter label,
div.dataTables_wrapper div.dataTables_info,
div.dataTables_wrapper div.dataTables_paginate {
justify-content: center;
}
}
@media (max-width: 575.98px) {
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
justify-content: start !important;
overflow-x: scroll;
}
}
div.card-datatable {
padding-bottom: 1rem;
}
div.card-datatable [class*=col-md-] {
padding-right: 1.5rem !important;
padding-left: 1.5rem !important;
}
div.card-datatable:not(.table-responsive) .dataTables_wrapper .row:first-child, div.card-datatable:not(.table-responsive) .dataTables_wrapper .row:last-child {
margin: 0;
}
html:not([dir=rtl]) div.card-datatable table.dataTable thead th:first-child,
html:not([dir=rtl]) div.card-datatable table.dataTable tfoot th:first-child {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
html:not([dir=rtl]) div.card-datatable table.dataTable thead th:last-child,
html:not([dir=rtl]) div.card-datatable table.dataTable tfoot th:last-child {
padding-right: 1rem;
}
html:not([dir=rtl]) div.card-datatable table.dataTable tbody td:first-child {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
[dir=rtl] table.dataTable.table-sm > thead > tr > th {
padding-right: 1.25rem;
}
[dir=rtl] table.table-bordered.dataTable tr td,
[dir=rtl] table.table-bordered.dataTable tr th,
[dir=rtl] table.table-bordered.dataTable tr th:first-child {
border-left-width: 0 !important;
}
[dir=rtl] table.dataTable thead th,
[dir=rtl] table.dataTable tbody td,
[dir=rtl] table.dataTable tfoot th {
padding-right: 1.25rem;
}
[dir=rtl] table.dataTable.table-sm thead th, [dir=rtl] table.dataTable.table-sm tbody td, [dir=rtl] table.dataTable.table-sm tfoot th {
padding-right: 1.25rem;
}
[dir=rtl] div.card-datatable table.dataTable thead th:first-child,
[dir=rtl] div.card-datatable table.dataTable tbody td:first-child,
[dir=rtl] div.card-datatable table.dataTable tfoot th:first-child {
padding-right: 1.5rem;
}
[dir=rtl] div.card-datatable table.dataTable thead th:last-child,
[dir=rtl] div.card-datatable table.dataTable tbody td:last-child,
[dir=rtl] div.card-datatable table.dataTable tfoot th:last-child {
padding-left: 1.5rem;
}
.light-style div.dataTables_wrapper div.dataTables_info {
color: #a7acb2;
}
.light-style div.dataTables_scrollBody table {
border-top-color: #e4e6e8;
}
.light-style table.dataTable th,
.light-style table.dataTable td {
border-color: #e4e6e8 !important;
}
.dark-style div.dataTables_wrapper div.dataTables_info {
color: #7e7f96;
}
.dark-style div.dataTables_scrollBody table {
border-top-color: #4e4f6c;
}
.dark-style table.dataTable th,
.dark-style table.dataTable td {
border-color: #4e4f6c !important;
}
@media print {
.dt-print-view h1 {
color: #232333;
}
.dt-print-view .table > :not(caption) > * > * {
color: #232333;
}
}

View File

@ -0,0 +1,509 @@
@charset "UTF-8";
@keyframes dtb-spinner {
100% {
transform: rotate(360deg);
}
}
@-o-keyframes dtb-spinner {
100% {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-ms-keyframes dtb-spinner {
100% {
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes dtb-spinner {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes dtb-spinner {
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
div.dataTables_wrapper {
position: relative;
}
div.dt-buttons {
position: initial;
}
div.dt-buttons .dt-button {
overflow: hidden;
text-overflow: ellipsis;
}
div.dt-button-info {
position: fixed;
top: 50%;
left: 50%;
width: 400px;
margin-top: -100px;
margin-left: -200px;
background-color: white;
border-radius: 0.75em;
box-shadow: 3px 4px 10px 1px rgba(0, 0, 0, 0.8);
text-align: center;
z-index: 2003;
overflow: hidden;
}
div.dt-button-info h2 {
padding: 2rem 2rem 1rem 2rem;
margin: 0;
font-weight: normal;
}
div.dt-button-info > div {
padding: 1em 2em 2em 2em;
}
div.dtb-popover-close {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
text-align: center;
border-radius: 3px;
cursor: pointer;
z-index: 2003;
}
button.dtb-hide-drop {
display: none !important;
}
div.dt-button-collection-title {
text-align: center;
padding: 0.3em 0 0.5em;
margin-left: 0.5em;
margin-right: 0.5em;
font-size: 0.9em;
}
div.dt-button-collection-title:empty {
display: none;
}
span.dt-button-spacer {
display: inline-block;
margin: 0.5em;
white-space: nowrap;
}
span.dt-button-spacer.bar {
border-left: 1px solid rgba(0, 0, 0, 0.3);
vertical-align: middle;
padding-left: 0.5em;
}
span.dt-button-spacer.bar:empty {
height: 1em;
width: 1px;
padding-left: 0;
}
div.dt-button-collection .dt-button-active {
padding-right: 3em;
}
div.dt-button-collection .dt-button-active:after {
position: absolute;
top: 50%;
margin-top: -10px;
right: 1em;
display: inline-block;
content: "✓";
color: inherit;
}
div.dt-button-collection .dt-button-active.dt-button-split {
padding-right: 0;
}
div.dt-button-collection .dt-button-active.dt-button-split:after {
display: none;
}
div.dt-button-collection .dt-button-active.dt-button-split > *:first-child {
padding-right: 3em;
}
div.dt-button-collection .dt-button-active.dt-button-split > *:first-child:after {
position: absolute;
top: 50%;
margin-top: -10px;
right: 1em;
display: inline-block;
content: "✓";
color: inherit;
}
div.dt-button-collection .dt-button-active-a a {
padding-right: 3em;
}
div.dt-button-collection .dt-button-active-a a:after {
position: absolute;
right: 1em;
display: inline-block;
content: "✓";
color: inherit;
}
div.dt-button-collection span.dt-button-spacer {
width: 100%;
font-size: 0.9em;
text-align: center;
margin: 0.5em 0;
}
div.dt-button-collection span.dt-button-spacer:empty {
height: 0;
width: 100%;
}
div.dt-button-collection span.dt-button-spacer.bar {
border-left: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding-left: 0;
}
html.dark div.dt-button-info {
background-color: var(--dt-html-background);
border: 1px solid rgba(255, 255, 255, 0.15);
}
div.dt-buttons div.btn-group {
position: initial;
}
div.dt-buttons div.dropdown-menu {
margin-top: 4px;
}
div.dt-buttons div.dropdown-menu .dt-button {
position: relative;
}
div.dt-buttons div.dropdown-menu div.dt-button-split {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-content: flex-start;
align-items: stretch;
}
div.dt-buttons div.dropdown-menu div.dt-button-split a:first-child {
min-width: auto;
flex: 1 0 50px;
padding-right: 0;
}
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child {
min-width: 33px;
flex: 0;
background: transparent;
border: none;
line-height: 1rem;
color: var(--bs-dropdown-link-color);
padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
}
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:hover {
color: var(--bs-dropdown-link-hover-color);
background-color: var(--bs-dropdown-link-hover-bg);
}
div.dt-buttons div.dropdown-menu div.dt-button-split button:last-child:after {
position: relative;
left: -3px;
}
div.dt-buttons div.dropdown-menu.fixed {
position: fixed;
display: block;
top: 50%;
left: 50%;
margin-left: -75px;
border-radius: 5px;
background-color: white;
padding: 0.5em;
}
div.dt-buttons div.dropdown-menu.fixed.two-column {
margin-left: -200px;
}
div.dt-buttons div.dropdown-menu.fixed.three-column {
margin-left: -225px;
}
div.dt-buttons div.dropdown-menu.fixed.four-column {
margin-left: -300px;
}
div.dt-buttons div.dropdown-menu.fixed.columns {
margin-left: -409px;
}
@media screen and (max-width: 1024px) {
div.dt-buttons div.dropdown-menu.fixed.columns {
margin-left: -308px;
}
}
@media screen and (max-width: 640px) {
div.dt-buttons div.dropdown-menu.fixed.columns {
margin-left: -203px;
}
}
@media screen and (max-width: 460px) {
div.dt-buttons div.dropdown-menu.fixed.columns {
margin-left: -100px;
}
}
div.dt-buttons div.dropdown-menu.fixed > :last-child {
max-height: 100vh;
overflow: auto;
}
div.dt-buttons div.dropdown-menu.two-column > :last-child, div.dt-buttons div.dropdown-menu.three-column > :last-child, div.dt-buttons div.dropdown-menu.four-column > :last-child {
display: block !important;
-webkit-column-gap: 8px;
-moz-column-gap: 8px;
-ms-column-gap: 8px;
-o-column-gap: 8px;
column-gap: 8px;
}
div.dt-buttons div.dropdown-menu.two-column > :last-child > *, div.dt-buttons div.dropdown-menu.three-column > :last-child > *, div.dt-buttons div.dropdown-menu.four-column > :last-child > * {
-webkit-column-break-inside: avoid;
break-inside: avoid;
}
div.dt-buttons div.dropdown-menu.two-column {
width: 400px;
}
div.dt-buttons div.dropdown-menu.two-column > :last-child {
padding-bottom: 1px;
column-count: 2;
}
div.dt-buttons div.dropdown-menu.three-column {
width: 450px;
}
div.dt-buttons div.dropdown-menu.three-column > :last-child {
padding-bottom: 1px;
column-count: 3;
}
div.dt-buttons div.dropdown-menu.four-column {
width: 600px;
}
div.dt-buttons div.dropdown-menu.four-column > :last-child {
padding-bottom: 1px;
column-count: 4;
}
div.dt-buttons div.dropdown-menu .dt-button {
border-radius: 0;
}
div.dt-buttons div.dropdown-menu.columns {
width: auto;
}
div.dt-buttons div.dropdown-menu.columns > :last-child {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
gap: 6px;
width: 818px;
padding-bottom: 1px;
}
div.dt-buttons div.dropdown-menu.columns > :last-child .dt-button {
min-width: 200px;
flex: 0 1;
margin: 0;
}
div.dt-buttons div.dropdown-menu.columns.dtb-b3 > :last-child, div.dt-buttons div.dropdown-menu.columns.dtb-b2 > :last-child, div.dt-buttons div.dropdown-menu.columns.dtb-b1 > :last-child {
justify-content: space-between;
}
div.dt-buttons div.dropdown-menu.columns.dtb-b3 .dt-button {
flex: 1 1 32%;
}
div.dt-buttons div.dropdown-menu.columns.dtb-b2 .dt-button {
flex: 1 1 48%;
}
div.dt-buttons div.dropdown-menu.columns.dtb-b1 .dt-button {
flex: 1 1 100%;
}
@media screen and (max-width: 1024px) {
div.dt-buttons div.dropdown-menu.columns > :last-child {
width: 612px;
}
}
@media screen and (max-width: 640px) {
div.dt-buttons div.dropdown-menu.columns > :last-child {
width: 406px;
}
div.dt-buttons div.dropdown-menu.columns.dtb-b3 .dt-button {
flex: 0 1 32%;
}
}
@media screen and (max-width: 460px) {
div.dt-buttons div.dropdown-menu.columns > :last-child {
width: 200px;
}
}
div.dt-buttons span.dt-button-spacer.empty {
margin: 1px;
}
div.dt-buttons span.dt-button-spacer.bar:empty {
height: inherit;
}
div.dt-buttons .btn.processing {
color: rgba(0, 0, 0, 0.2);
}
div.dt-buttons .btn.processing:after {
position: absolute;
top: 50%;
left: 50%;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
box-sizing: border-box;
display: block;
content: " ";
border: 2px solid #282828;
border-radius: 50%;
border-left-color: transparent;
border-right-color: transparent;
animation: dtb-spinner 1500ms infinite linear;
-o-animation: dtb-spinner 1500ms infinite linear;
-ms-animation: dtb-spinner 1500ms infinite linear;
-webkit-animation: dtb-spinner 1500ms infinite linear;
-moz-animation: dtb-spinner 1500ms infinite linear;
}
div.dt-button-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
}
@media screen and (max-width: 767px) {
div.dt-buttons {
float: none;
width: 100%;
text-align: center;
margin-bottom: 0.5em;
}
div.dt-buttons a.btn {
float: none;
}
}
:root[data-bs-theme=dark] div.dropdown-menu.dt-button-collection.fixed {
background-color: #212529;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 8px;
}
@media screen and (max-width: 767px) {
div.dt-buttons {
margin-bottom: 0;
}
}
div.dataTables_wrapper .dt-button-collection {
border: 0;
border-radius: var(--bs-border-radius);
padding: 0.5rem 0;
width: auto;
}
div.dataTables_wrapper .dt-button-collection > div[role=menu] {
text-align: left;
}
.dt-button-collection {
margin-top: 0.2rem;
}
div.dropdown-menu.dt-button-collection,
div.dt-button-collection .dt-button:not(.dt-btn-split-drop) {
min-width: 8rem;
}
.dt-down-arrow {
display: none;
}
.light-style div.dataTables_wrapper .dt-button-collection {
background-color: #fff;
}
.light-style .dataTable a:not([href]):not([tabindex]) {
color: #71dd37;
}
.light-style .dt-button-info {
box-shadow: 0 0.1875rem 0.5rem 0 rgba(34, 48, 62, 0.1);
}
.light-style .dt-button-collection .dropdown-item {
padding: 0.543rem 1.25rem;
}
.dark-style div.dataTables_wrapper .dt-button-collection {
background-color: #2b2c40;
}
.dark-style .dataTable a:not([href]):not([tabindex]) {
color: #71dd37;
}
.dark-style .dt-button-info {
box-shadow: 0 0.1875rem 0.5rem 0 rgba(20, 20, 29, 0.22);
}
.dark-style .dt-button-collection .dropdown-item {
padding: 0.543rem 1.25rem;
}
.dt-button-info {
border-width: 0 !important;
border-radius: 0.375rem !important;
}
.dt-button-info h2 {
font-size: 1.5rem !important;
}
.dt-buttons {
position: relative;
}
[dir=rtl] .dt-buttons .dt-button-collection .dropdown-item {
text-align: right;
}
.dt-buttons.btn-group button {
border-color: transparent !important;
border-radius: 0.375rem !important;
}

View File

@ -0,0 +1,23 @@
table.dataTable.dt-checkboxes-select tbody tr,
table.dataTable thead th.dt-checkboxes-select-all,
table.dataTable tbody td.dt-checkboxes-cell {
cursor: pointer;
}
table.dataTable thead th.dt-checkboxes-select-all,
table.dataTable tbody td.dt-checkboxes-cell {
text-align: center;
}
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0.5em;
}
@media screen and (max-width: 640px) {
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0;
display: block;
}
}

View File

@ -0,0 +1,185 @@
table.dataTable thead tr > .dtfc-fixed-left,
table.dataTable thead tr > .dtfc-fixed-right,
table.dataTable tfoot tr > .dtfc-fixed-left,
table.dataTable tfoot tr > .dtfc-fixed-right {
top: 0;
bottom: 0;
z-index: 3;
background-color: white;
}
table.dataTable tbody tr > .dtfc-fixed-left,
table.dataTable tbody tr > .dtfc-fixed-right {
z-index: 1;
background-color: white;
}
div.dtfc-left-top-blocker,
div.dtfc-right-top-blocker {
background-color: white;
}
html.dark table.dataTable thead tr > .dtfc-fixed-left,
html.dark table.dataTable thead tr > .dtfc-fixed-right,
html.dark table.dataTable tfoot tr > .dtfc-fixed-left,
html.dark table.dataTable tfoot tr > .dtfc-fixed-right {
background-color: var(--dt-html-background);
}
html.dark table.dataTable tbody tr > .dtfc-fixed-left,
html.dark table.dataTable tbody tr > .dtfc-fixed-right {
background-color: var(--dt-html-background);
}
html.dark div.dtfc-left-top-blocker,
html.dark div.dtfc-right-top-blocker {
background-color: var(--dt-html-background);
}
div.dtfc-right-top-blocker,
div.dtfc-left-top-blocker {
margin-top: 6px;
border-bottom: 0px solid #ddd !important;
}
table.dataTable.table-bordered.dtfc-has-left {
border-left: none;
}
div.dataTables_scroll.dtfc-has-left table.table-bordered {
border-left: none;
}
div.dataTables_scrollBody {
border-left: 1px solid #ddd !important;
}
div.dataTables_scrollFootInner table.table-bordered tr th:first-child,
div.dataTables_scrollHeadInner table.table-bordered tr th:first-child {
border-left: 1px solid #ddd !important;
}
html[data-bs-theme=dark] table.dataTable thead tr > .dtfc-fixed-left,
html[data-bs-theme=dark] table.dataTable thead tr > .dtfc-fixed-right,
html[data-bs-theme=dark] table.dataTable tfoot tr > .dtfc-fixed-left,
html[data-bs-theme=dark] table.dataTable tfoot tr > .dtfc-fixed-right {
background-color: var(--bs-body-bg);
}
html[data-bs-theme=dark] table.dataTable tbody tr > .dtfc-fixed-left,
html[data-bs-theme=dark] table.dataTable tbody tr > .dtfc-fixed-right {
background-color: var(--bs-body-bg);
}
html[data-bs-theme=dark] div.dtfc-left-top-blocker,
html[data-bs-theme=dark] div.dtfc-right-top-blocker {
background-color: var(--bs-body-bg);
}
html[data-bs-theme=dark] div.dataTables_scrollBody {
border-left-color: var(--bs-border-color) !important;
}
html[data-bs-theme=dark] div.dataTables_scrollFootInner table.table-bordered tr th:first-child,
html[data-bs-theme=dark] div.dataTables_scrollHeadInner table.table-bordered tr th:first-child {
border-left-color: var(--bs-border-color) !important;
}
div.dataTables_scrollBody thead tr,
div.DTFC_LeftBodyLiner thead tr {
border-top-width: 0;
border-bottom-width: 0;
}
div.dataTables_scrollBody {
border: 0 !important;
}
html:not([dir=rtl]) div.dataTables_scrollFootInner table.table-bordered tr th:first-child,
html:not([dir=rtl]) div.dataTables_scrollHeadInner table.table-bordered tr th:first-child {
border-left: 0 !important;
}
[dir=rtl] table.dataTable thead th,
[dir=rtl] table.dataTable thead td,
[dir=rtl] table.dataTable tfoot th,
[dir=rtl] table.dataTable tfoot td {
text-align: right !important;
}
.light-style table.DTFC_Cloned tr {
border-color: #e4e6e8;
}
.light-style div.dataTables_scrollFootInner table.table-bordered tr th:first-child,
.light-style div.dataTables_scrollHeadInner table.table-bordered tr th:first-child {
border-left: 1px solid #e4e6e8;
}
.light-style table.dataTable thead tr > .dtfc-fixed-left,
.light-style table.dataTable thead tr > .dtfc-fixed-right,
.light-style table.dataTable tbody tr > .dtfc-fixed-left,
.light-style table.dataTable tbody tr > .dtfc-fixed-right,
.light-style div.dtfc-right-top-blocker,
.light-style div.dtfc-left-top-blocker {
background-color: #fff;
margin-top: 1px !important;
height: 0px !important;
}
.light-style .dt-fixedcolumns thead {
border-top-color: #e4e6e8;
}
.light-style[dir=rtl] div.dataTables_scrollHead table,
.light-style[dir=rtl] div.dataTables_scrollBody table {
border-width: 0;
}
.light-style[dir=rtl] div.DTFC_LeftBodyLiner {
padding-right: 0 !important;
}
.light-style[dir=rtl] div.DTFC_RightHeadWrapper table,
.light-style[dir=rtl] div.DTFC_RightBodyWrapper table {
border: 0;
}
.light-style[dir=rtl] div.DTFC_RightBodyLiner {
padding-left: 0 !important;
}
.dark-style table.DTFC_Cloned tr {
background-color: #2b2c40;
border-color: #4e4f6c;
}
.dark-style div.dataTables_scrollHead table,
.dark-style div.DTFC_RightHeadWrapper table,
.dark-style table.dataTable.fixedHeader-floating,
.dark-style table.dataTable.fixedHeader-locked {
background-color: #2b2c40;
}
.dark-style div.dataTables_scrollFootInner table.table-bordered tr th:first-child,
.dark-style div.dataTables_scrollHeadInner table.table-bordered tr th:first-child {
border-left: 1px solid #4e4f6c !important;
}
.dark-style table.dataTable thead tr > .dtfc-fixed-left,
.dark-style table.dataTable thead tr > .dtfc-fixed-right,
.dark-style table.dataTable tbody tr > .dtfc-fixed-left,
.dark-style table.dataTable tbody tr > .dtfc-fixed-right,
.dark-style div.dtfc-right-top-blocker,
.dark-style div.dtfc-left-top-blocker {
background-color: #2b2c40;
margin-top: 1px !important;
height: 0px !important;
}
.dark-style .dt-fixedcolumns thead {
border-top-color: #4e4f6c;
}
.dark-style[dir=rtl] div.dataTables_scrollHead table,
.dark-style[dir=rtl] div.dataTables_scrollBody table {
border-width: 0;
}
.dark-style[dir=rtl] div.DTFC_LeftBodyLiner {
padding-right: 0 !important;
}
.dark-style[dir=rtl] div.DTFC_RightHeadWrapper table,
.dark-style[dir=rtl] div.DTFC_RightBodyWrapper table {
border: 0;
}
.dark-style[dir=rtl] div.DTFC_RightBodyLiner {
padding-left: 0 !important;
}

View File

@ -0,0 +1,46 @@
table.dataTable.fixedHeader-floating,
table.dataTable.fixedHeader-locked {
background-color: white;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
table.dataTable.fixedHeader-locked {
position: absolute !important;
}
@media print {
table.fixedHeader-floating {
display: none;
}
}
html[data-bs-theme=dark] table.dataTable.fixedHeader-floating,
html[data-bs-theme=dark] table.dataTable.fixedHeader-locked {
background-color: var(--bs-body-bg);
}
.dt-fixedheader.fixedHeader-floating.table.dataTable {
width: auto !important;
}
.dt-fixedheader.fixedHeader-locked.table.dataTable {
display: none;
}
.light-style .dtfh-floatingparenthead {
border-bottom: 1px solid #e4e6e8;
}
.light-style .table-bordered.dt-fixedheader.fixedHeader-floating.table.dataTable thead > tr > th,
.light-style .table-bordered.dt-fixedheader.fixedHeader-locked.table.dataTable thead > tr > th {
border-bottom-width: 1px;
border-color: #e4e6e8;
}
.dark-style .dtfh-floatingparenthead {
border-bottom: 1px solid #4e4f6c;
}
.dark-style .table-bordered.dt-fixedheader.fixedHeader-floating.table.dataTable thead > tr > th,
.dark-style .table-bordered.dt-fixedheader.fixedHeader-locked.table.dataTable thead > tr > th {
border-bottom-width: 1px;
border-color: #4e4f6c;
}

View File

@ -0,0 +1,241 @@
@charset "UTF-8";
table.dataTable.dtr-inline.collapsed > tbody > tr > td.child,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.child,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty {
cursor: default !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.child:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dataTables_empty:before {
display: none !important;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control {
cursor: pointer;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before {
margin-right: 0.5em;
display: inline-block;
color: rgba(0, 0, 0, 0.5);
content: "►";
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control.arrow-right::before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control.arrow-right::before {
content: "◄";
}
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before {
content: "▼";
}
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed.compact > tbody > tr > th.dtr-control {
padding-left: 0.333em;
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control,
table.dataTable.dtr-column > tbody > tr > th.dtr-control,
table.dataTable.dtr-column > tbody > tr > td.control,
table.dataTable.dtr-column > tbody > tr > th.control {
cursor: pointer;
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr > th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr > td.control:before,
table.dataTable.dtr-column > tbody > tr > th.control:before {
display: inline-block;
color: rgba(0, 0, 0, 0.5);
content: "►";
}
table.dataTable.dtr-column > tbody > tr > td.dtr-control.arrow-right::before,
table.dataTable.dtr-column > tbody > tr > th.dtr-control.arrow-right::before,
table.dataTable.dtr-column > tbody > tr > td.control.arrow-right::before,
table.dataTable.dtr-column > tbody > tr > th.control.arrow-right::before {
content: "◄";
}
table.dataTable.dtr-column > tbody > tr.parent td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent td.control:before,
table.dataTable.dtr-column > tbody > tr.parent th.control:before {
content: "▼";
}
table.dataTable > tbody > tr.child {
padding: 0.5em 1em;
}
table.dataTable > tbody > tr.child:hover {
background: transparent !important;
}
table.dataTable > tbody > tr.child ul.dtr-details {
display: inline-block;
list-style-type: none;
margin: 0;
padding: 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li {
border-bottom: 1px solid #efefef;
padding: 0.5em 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:first-child {
padding-top: 0;
}
table.dataTable > tbody > tr.child ul.dtr-details > li:last-child {
padding-bottom: 0;
border-bottom: none;
}
table.dataTable > tbody > tr.child span.dtr-title {
display: inline-block;
min-width: 75px;
font-weight: bold;
}
div.dtr-modal {
position: fixed;
box-sizing: border-box;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 100;
padding: 10em 1em;
}
div.dtr-modal div.dtr-modal-display {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 50%;
height: fit-content;
max-height: 75%;
overflow: auto;
margin: auto;
z-index: 102;
overflow: auto;
background-color: #f5f5f7;
border: 1px solid black;
border-radius: 0.5em;
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.6);
}
div.dtr-modal div.dtr-modal-content {
position: relative;
padding: 2.5em;
}
div.dtr-modal div.dtr-modal-content h2 {
margin-top: 0;
}
div.dtr-modal div.dtr-modal-close {
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
text-align: center;
border-radius: 3px;
cursor: pointer;
z-index: 12;
}
div.dtr-modal div.dtr-modal-background {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 101;
background: rgba(0, 0, 0, 0.6);
}
@media screen and (max-width: 767px) {
div.dtr-modal div.dtr-modal-display {
width: 95%;
}
}
html.dark table.dataTable > tbody > tr > td.dtr-control:before {
color: rgba(255, 255, 255, 0.5) !important;
}
html.dark table.dataTable > tbody > tr.child ul.dtr-details > li {
border-bottom-color: #404346;
}
html.dark div.dtr-modal div.dtr-modal-display {
background-color: #212529;
border: 1px solid rgba(255, 255, 255, 0.15);
}
div.dtr-bs-modal table.table tr:first-child td {
border-top: none;
}
table.dataTable.table-bordered th.dtr-control.dtr-hidden + *,
table.dataTable.table-bordered td.dtr-control.dtr-hidden + * {
border-left-width: 1px;
}
table.dataTable.dtr-column > tbody > tr > td.control,
table.dataTable.dtr-column > tbody > tr > th.control {
position: relative;
}
table.dataTable.dtr-column > tbody > tr > td.control:before, table.dataTable.dtr-column > tbody > tr > td.control:before,
table.dataTable.dtr-column > tbody > tr > th.control:before,
table.dataTable.dtr-column > tbody > tr > th.control:before {
position: absolute;
line-height: 0.9em;
font-weight: 500;
height: 0.85em;
width: 0.85em;
color: #fff;
border-radius: 1em;
box-sizing: content-box;
text-align: center;
font-family: "Courier New", Courier, monospace;
content: "+";
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
table.dataTable.dtr-column > tbody > tr.parent td.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent th.dtr-control:before,
table.dataTable.dtr-column > tbody > tr.parent td.control:before,
table.dataTable.dtr-column > tbody > tr.parent th.control:before {
content: "+";
}
@media screen and (max-width: 1399.98px) {
table.dataTable.table-responsive {
display: block;
}
}
.modal.dtr-bs-modal .modal-body {
padding: 0;
}
.modal.dtr-bs-modal .table tr:last-child > td {
border-bottom: 0;
}
.modal.dtr-bs-modal .table .btn {
box-shadow: none !important;
}
.modal.dtr-bs-modal .table .emp_name {
font-weight: 500;
}

View File

@ -0,0 +1,87 @@
table.dataTable tr.dtrg-group th {
background-color: rgba(0, 0, 0, 0.1);
text-align: left;
}
table.dataTable tr.dtrg-group.dtrg-level-0 th {
font-weight: bold;
}
table.dataTable tr.dtrg-group.dtrg-level-1 th,
table.dataTable tr.dtrg-group.dtrg-level-2 th,
table.dataTable tr.dtrg-group.dtrg-level-3 th,
table.dataTable tr.dtrg-group.dtrg-level-4 th,
table.dataTable tr.dtrg-group.dtrg-level-5 th {
background-color: rgba(0, 0, 0, 0.05);
padding-top: 0.25em;
padding-bottom: 0.25em;
padding-left: 2em;
font-size: 0.9em;
}
table.dataTable tr.dtrg-group.dtrg-level-2 th {
background-color: rgba(0, 0, 0, 0.01);
padding-left: 2.5em;
}
table.dataTable tr.dtrg-group.dtrg-level-3 th {
background-color: rgba(0, 0, 0, 0.01);
padding-left: 3em;
}
table.dataTable tr.dtrg-group.dtrg-level-4 th {
background-color: rgba(0, 0, 0, 0.01);
padding-left: 3.5em;
}
table.dataTable tr.dtrg-group.dtrg-level-5 th {
background-color: rgba(0, 0, 0, 0.01);
padding-left: 4em;
}
html.dark table.dataTable tr.dtrg-group th {
background-color: rgba(255, 255, 255, 0.1);
}
html.dark table.dataTable tr.dtrg-group.dtrg-level-1 th {
background-color: rgba(255, 255, 255, 0.05);
}
html.dark table.dataTable tr.dtrg-group.dtrg-level-2 th,
html.dark table.dataTable tr.dtrg-group.dtrg-level-3 th,
html.dark table.dataTable tr.dtrg-group.dtrg-level-4 th,
html.dark table.dataTable tr.dtrg-group.dtrg-level-5 th {
background-color: rgba(255, 255, 255, 0.01);
}
table.dataTable.table-striped tr.dtrg-level-0 {
background-color: rgba(0, 0, 0, 0.1);
}
table.dataTable.table-striped tr.dtrg-level-1 {
background-color: rgba(0, 0, 0, 0.05);
}
table.dataTable.table-striped tr.dtrg-level-2,
table.dataTable.table-striped tr.dtrg-level-3,
table.dataTable.table-striped tr.dtrg-level-4,
table.dataTable.table-striped tr.dtrg-level-5 {
background-color: rgba(0, 0, 0, 0.01);
}
table.dataTable.table-striped tr.dtrg-level-1 tr.dtrg-level-2 th,
table.dataTable.table-striped tr.dtrg-level-3 th,
table.dataTable.table-striped tr.dtrg-level-4 th,
table.dataTable.table-striped tr.dtrg-level-5 th {
background-color: transparent;
}
.light-style tr.group,
.light-style tr.group:hover {
background-color: rgba(34, 48, 62, 0.1) !important;
}
.dark-style tr.group,
.dark-style tr.group:hover {
background-color: rgba(230, 230, 241, 0.1) !important;
}

View File

@ -0,0 +1,108 @@
@charset "UTF-8";
table.dataTable > tbody > tr > .selected {
background-color: rgba(13, 110, 253, 0.9);
color: white;
}
table.dataTable > tbody > tr > td.select-checkbox,
table.dataTable > tbody > tr > th.select-checkbox {
position: relative;
}
table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after,
table.dataTable > tbody > tr > th.select-checkbox:before,
table.dataTable > tbody > tr > th.select-checkbox:after {
display: block;
position: absolute;
top: 50%;
left: 50%;
width: 12px;
height: 12px;
box-sizing: border-box;
}
table.dataTable > tbody > tr > td.select-checkbox:before,
table.dataTable > tbody > tr > th.select-checkbox:before {
content: " ";
margin-top: -6px;
margin-left: -6px;
border: 1px solid black;
border-radius: 3px;
}
table.dataTable > tbody > tr.selected > td.select-checkbox:before,
table.dataTable > tbody > tr.selected > th.select-checkbox:before {
border: 1px solid white;
}
table.dataTable > tbody > tr.selected > td.select-checkbox:after,
table.dataTable > tbody > tr.selected > th.select-checkbox:after {
content: "✓";
font-size: 20px;
margin-top: -12px;
margin-left: -6px;
text-align: center;
}
table.dataTable.compact > tbody > tr > td.select-checkbox:before,
table.dataTable.compact > tbody > tr > th.select-checkbox:before {
margin-top: -12px;
}
table.dataTable.compact > tbody > tr.selected > td.select-checkbox:after,
table.dataTable.compact > tbody > tr.selected > th.select-checkbox:after {
margin-top: -16px;
}
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0.5em;
}
html.dark table.dataTable > tbody > tr > td.select-checkbox:before,
html.dark table.dataTable > tbody > tr > th.select-checkbox:before,
html[data-bs-theme=dark] table.dataTable > tbody > tr > td.select-checkbox:before,
html[data-bs-theme=dark] table.dataTable > tbody > tr > th.select-checkbox:before {
border: 1px solid rgba(255, 255, 255, 0.6);
}
@media screen and (max-width: 640px) {
div.dataTables_wrapper span.select-info,
div.dataTables_wrapper span.select-item {
margin-left: 0;
display: block;
}
}
table.dataTable.table-sm tbody td.select-checkbox::before {
margin-top: -9px;
}
.light-style table.dataTable tbody > tr.selected,
.light-style table.dataTable tbody > tr > .selected {
background-color: #f8f9f9;
}
.light-style table.dataTable tbody > tr.selected > *,
.light-style table.dataTable tbody > tr > .selected > * {
box-shadow: inset 0 0 0 rgba(34, 48, 62, 0.06);
color: #646e78;
}
.light-style table.dataTable tbody tr.selected,
.light-style table.dataTable tbody th.selected,
.light-style table.dataTable tbody td.selected {
color: #646e78;
}
.dark-style table.dataTable tbody > tr.selected,
.dark-style table.dataTable tbody > tr > .selected {
background-color: rgba(230, 230, 241, 0.08);
}
.dark-style table.dataTable tbody > tr.selected > *,
.dark-style table.dataTable tbody > tr > .selected > * {
box-shadow: inset 0 0 0 rgba(230, 230, 241, 0.06);
color: #b2b2c4;
}
.dark-style table.dataTable tbody tr.selected,
.dark-style table.dataTable tbody th.selected,
.dark-style table.dataTable tbody td.selected {
color: inherit;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

BIN
public/img/avatars/male.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -43,3 +43,27 @@
.read-the-docs { .read-the-docs {
color: #888; color: #888;
} }
.assign-employee-card {
background-color: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.assign-employee-card:hover {
background-color: #e9ecef;
}
.employee-info p {
font-weight: bold;
}
@media (max-width: 768px) {
.assign-employee-card {
flex-direction: column;
align-items: flex-start;
}
}

View File

@ -6,7 +6,6 @@ import { convertShortTime } from '../../utils/dateUtils';
const AttendLogs = ({ Id }) => { const AttendLogs = ({ Id }) => {
const {logs,loading} = useEmployeeAttendacesLog(Id) const {logs,loading} = useEmployeeAttendacesLog(Id)
console.log(logs)
return ( return (
<div className="table-responsive"> <div className="table-responsive">
{loading && <p>Loading..</p>} {loading && <p>Loading..</p>}
@ -22,7 +21,7 @@ console.log(logs)
<thead> <thead>
<tr> <tr>
<th style={{ width: '20%' }}>Time</th> <th style={{ width: '20%' }}>Time</th>
<th style={{ width: '20%' }}>activityTime</th> <th style={{ width: '20%' }}>Date</th>
<th style={{ width: '60%' }}>Description</th> <th style={{ width: '60%' }}>Description</th>
</tr> </tr>
</thead> </thead>

View File

@ -7,15 +7,17 @@ import usePagination from "../../hooks/usePagination";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
const Attendance = ({attendance,getRole, handleModalData}) => { const Attendance = ( {attendance, getRole, handleModalData} ) =>
{
const { currentPage, totalPages, currentItems, paginate } = usePagination(attendance, 5); const { currentPage, totalPages, currentItems, paginate } = usePagination(attendance, 5);
const [loading,setLoading] = useState(false); const [loading,setLoading] = useState(false);
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<> <>
<div className="table-responsive text-nowrap"> <div className="table-responsive text-nowrap">
{attendance &&attendance.length > 0 ? (<> {attendance && attendance.length > 0 ? (<>
<table className="table "> <table className="table ">
<thead > <thead >
<tr> <tr>
@ -40,7 +42,6 @@ const Attendance = ({attendance,getRole, handleModalData}) => {
<a <a
// href="#" // href="#"
onClick={(e) =>navigate(`/employee/${item.employeeId}?for=attendance`)} onClick={(e) =>navigate(`/employee/${item.employeeId}?for=attendance`)}
className="text-heading text-truncate cursor-pointer" className="text-heading text-truncate cursor-pointer"
> >
<span className="fw-medium"> <span className="fw-medium">
@ -60,7 +61,7 @@ const Attendance = ({attendance,getRole, handleModalData}) => {
<td>{item.checkOutTime ? convertShortTime(item.checkOutTime):"--"}</td> <td>{item.checkOutTime ? convertShortTime(item.checkOutTime):"--"}</td>
<td className="mx-24" > <td className="mx-24" >
<RenderAttendanceStatus attendanceData={item} handleModalData={handleModalData} Tab={1}/> <RenderAttendanceStatus attendanceData={item} handleModalData={handleModalData} Tab={1} currentDate={null}/>
</td> </td>
</tr> </tr>
))} ))}

View File

@ -1,95 +1,105 @@
import React,{useEffect,useState} from 'react'; import React, { useEffect, useState } from 'react';
import Avatar from '../common/Avatar'; import Avatar from '../common/Avatar';
import { convertShortTime } from '../../utils/dateUtils'; import { convertShortTime } from '../../utils/dateUtils';
import RenderAttendanceStatus from './RenderAttendanceStatus'; import RenderAttendanceStatus from './RenderAttendanceStatus';
import { useSelector,useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { fetchAttendanceData } from '../../slices/apiSlice/attedanceLogsSlice'; import { fetchAttendanceData } from '../../slices/apiSlice/attedanceLogsSlice';
const AttendanceLog = ({attendance,handleModalData,projectId}) => { const AttendanceLog = ({ attendance, handleModalData, projectId }) => {
const[attendances,setAttendnaces] = useState() const [attendances, setAttendnaces] = useState([]);
const[selectedDate,setSelectedDate] = useState('') const [selectedDate, setSelectedDate] = useState('');
const dispatch = useDispatch() const dispatch = useDispatch();
const {data,loading,error} = useSelector((store)=>store.attendance) const { data, loading, error } = useSelector((store) => store.attendanceLogs);
const handleDateChange = (e) => { // Set the default selected date to the current date
const date = e.target.value; const currentDate = new Date().toISOString().split('T')[0]; // "YYYY-MM-DD"
console.log(date)
setSelectedDate(date); const handleDateChange = (e) => {
if (date) { const date = e.target.value;
dispatch(fetchAttendanceData({ projectId, date: date })); setSelectedDate(date);
} if (date) {
}; dispatch(fetchAttendanceData({ projectId, date: date }));
}
};
useEffect(() => {
// If attendance has check-in time, filter it
setAttendnaces(attendance?.filter((record) => record.checkInTime !== null));
setSelectedDate(currentDate); // Set default selected date to today
}, [attendance]);
const renderAttendanceData = selectedDate === currentDate ? attendances : data;
useEffect(()=>{
setAttendnaces(attendance?.filter((record)=>record.checkInTime !== null))
},[attendance])
return ( return (
<> <>
<div className="dataTables_length text-start py-2" id="DataTables_Table_0_length"> <div className="dataTables_length text-start py-2" id="DataTables_Table_0_length">
<div class="col-md-3"> <div className="col-md-3">
<input class="form-control form-control-sm" type="date" placeholder='Selecte Date' value={selectedDate} onChange={handleDateChange} id="html5-date-input" /> <input
</div> className="form-control form-control-sm"
</div> type="date"
<div className="table-responsive"> placeholder="Select Date"
{attendance && attendance.length > 0 ? ( value={selectedDate}
<table className="table mb-0"> onChange={handleDateChange}
<thead> id="html5-date-input"
<tr> />
<th>Name</th> </div>
{/* <th>Date</th> */} </div>
<th><i className='bx bxs-down-arrow-alt text-success' ></i>Check-In</th>
<th><i className='bx bxs-up-arrow-alt text-danger' ></i>Check-Out</th> <div className="table-responsive">
<th>Action</th> {attendance && attendance.length > 0 ? (
<table className="table mb-0">
<thead>
<tr>
<th>Name</th>
<th>
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
</th>
<th>
<i className="bx bxs-up-arrow-alt text-danger"></i> Check-Out
</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{loading && <td colSpan={5}>Loading...</td>}
{error && <td colSpan={5}>{error}</td>}
{selectedDate && renderAttendanceData.length === 0 && (
<td colSpan={5}>No Data Found</td>
)}
{renderAttendanceData?.map((attendance, index) => (
<tr key={index}>
<td>
<div className="d-flex justify-content-start align-items-center">
<Avatar firstName={attendance.firstName} lastName={attendance.lastName} />
<div className="d-flex flex-column">
<a href="#" className="text-heading text-truncate">
<span className="fw-medium">
{attendance.firstName} {attendance.lastName}
</span>
</a>
</div>
</div>
</td>
<td>{convertShortTime(attendance.checkInTime)}</td>
<td>{attendance.checkOutTime ? convertShortTime(attendance.checkOutTime) : '--'}</td>
<td className="text-center">
<RenderAttendanceStatus
attendanceData={attendance}
handleModalData={handleModalData}
Tab={2}
currentDate={currentDate}
/>
</td>
</tr> </tr>
</thead> ))}
<tbody> </tbody>
{loading && <td colSpan={5}>Loading...</td>} </table>
{error && <td colSpan={5}>{error}</td>} ) : (
{selectedDate && data.length == 0 && (<td colSpan={5}>No Data Found</td>)} <span>No employee logs</span>
)}
</div>
{ </>
(selectedDate ? data : attendances)?.map((attendance, index) => (
<tr key={index}>
<td>
<div className="d-flex justify-content-start align-items-center">
<Avatar
firstName={attendance.firstName}
lastName={attendance.lastName}
></Avatar>
<div className="d-flex flex-column">
<a
href="#"
className="text-heading text-truncate"
>
<span className="fw-medium">
{attendance.firstName} {attendance.lastName}
</span>
</a>
</div>
</div>
</td>
{/* <td>{attendance.date}</td> */}
<td>{convertShortTime(attendance.checkInTime)}</td>
<td>{attendance.checkOutTime ? convertShortTime(attendance.checkOutTime):"--"}</td>
<td className='text-center ' >
{/* <div className='d-flex justify-content-center align-items-center gap-3'> */}
<RenderAttendanceStatus attendanceData={attendance} handleModalData={handleModalData} Tab={2} />
{/* </div> */}
</td>
</tr>
))
}
</tbody>
</table>
):(
<span>No employees Logs</span>
)
}
</div>
</>
); );
}; };

View File

@ -7,6 +7,7 @@ import { usePositionTracker } from "../../hooks/usePositionTracker";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice"; import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import {checkIfCurrentDate} from "../../utils/dateUtils";
const schema = z.object({ const schema = z.object({
@ -15,7 +16,7 @@ const schema = z.object({
}); });
const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => { const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
console.log(modeldata)
const projectId = useSelector((store)=>store.localVariables.projectId) const projectId = useSelector((store)=>store.localVariables.projectId)
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const coords = usePositionTracker(); const coords = usePositionTracker();
@ -31,21 +32,32 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
resolver: zodResolver(schema), resolver: zodResolver(schema),
}); });
const onSubmit = (data) => { const onSubmit = ( data ) =>
{
console.log(data)
let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,action:modeldata.action} let record = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,action:modeldata.action}
if(modeldata.forWhichTab === 1){ if(modeldata.forWhichTab === 1){
handleSubmitForm(record) handleSubmitForm(record)
}else{ } else
let formData = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,projectId:projectId,action:modeldata.action} {
dispatch(markAttendance(formData)) console.log("is Date" ,checkIfCurrentDate(modeldata?.currentDate))
.unwrap() if ( modeldata?.currentDate && checkIfCurrentDate(modeldata?.currentDate) )
.then(() => { {
showToast("Attendance Marked Successfully", "success"); handleSubmitForm(record)
}) } else
.catch((error) => { {
showToast(error, "error"); let formData = {...data, date: new Date().toLocaleDateString(),latitude:coords.latitude,longitude:coords.longitude,employeeId:modeldata.employeeId,projectId:projectId,action:modeldata.action}
}); dispatch(markAttendance(formData))
} .unwrap()
.then(() => {
showToast("Attendance Marked Successfully", "success");
})
.catch((error) => {
showToast(error, "error");
});
}
}
closeModal() closeModal()
}; };
@ -56,7 +68,6 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
{/* <TimePicker label="Time" onChange={(e) => setValue("time", e)} /> */} {/* <TimePicker label="Time" onChange={(e) => setValue("time", e)} /> */}
<TimePicker <TimePicker
label="Choose a time" label="Choose a time"
@ -106,9 +117,6 @@ const CheckCheckOutmodel = ({modeldata,closeModal,handleSubmitForm,}) => {
export default CheckCheckOutmodel; export default CheckCheckOutmodel;
const schemaReg = z.object({ const schemaReg = z.object({
description:z.string().min(1,{message:"please give reason!"}) description:z.string().min(1,{message:"please give reason!"})
}); });
@ -120,8 +128,6 @@ export const Regularization = ({modeldata,closeModal,handleSubmitForm})=>{
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
reset,
setValue,
} = useForm({ } = useForm({
resolver: zodResolver(schemaReg), resolver: zodResolver(schemaReg),
}); });

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import useAttendanceStatus from "../../hooks/useAttendanceStatus"; import useAttendanceStatus from "../../hooks/useAttendanceStatus";
const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab }) => { const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab,currentDate}) => {
const { text, color, disabled, action,checkInTime } = useAttendanceStatus(attendanceData); const { text, color, disabled, action,checkInTime } = useAttendanceStatus(attendanceData);
@ -17,7 +17,7 @@ const RenderAttendanceStatus = ({ attendanceData, handleModalData,Tab }) => {
action, action,
employeeId: attendanceData?.employeeId, employeeId: attendanceData?.employeeId,
id: attendanceData?.id, id: attendanceData?.id,
currentDate
}); });
} }
}; };

View File

@ -0,0 +1,135 @@
import React from 'react'
const DemoTable = () => {
return (
<div class="content-wrapper">
<div class="container-xxl flex-grow-1 container-p-y">
<div class="card">
<div class="card-datatable table-responsive">
<table class="datatables-basic table border-top">
<thead>
<tr>
<th></th>
<th></th>
<th>id</th>
<th>Name</th>
<th>Email</th>
<th>Date</th>
<th>Salary</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="offcanvas offcanvas-end" id="add-new-record">
<div class="offcanvas-header border-bottom">
<h5 class="offcanvas-title" id="exampleModalLabel">New Record</h5>
<button
type="button"
class="btn-close text-reset"
data-bs-dismiss="offcanvas"
aria-label="Close"></button>
</div>
<div class="offcanvas-body flex-grow-1">
<form class="add-new-record pt-0 row g-2" id="form-add-new-record" onsubmit="return false">
<div class="col-sm-12">
<label class="form-label" for="basicFullname">Full Name</label>
<div class="input-group input-group-merge">
<span id="basicFullname2" class="input-group-text"><i class="bx bx-user"></i></span>
<input
type="text"
id="basicFullname"
class="form-control dt-full-name"
name="basicFullname"
placeholder="John Doe"
aria-label="John Doe"
aria-describedby="basicFullname2" />
</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicPost">Post</label>
<div class="input-group input-group-merge">
<span id="basicPost2" class="input-group-text"><i class="bx bxs-briefcase"></i></span>
<input
type="text"
id="basicPost"
name="basicPost"
class="form-control dt-post"
placeholder="Web Developer"
aria-label="Web Developer"
aria-describedby="basicPost2" />
</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicEmail">Email</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="bx bx-envelope"></i></span>
<input
type="text"
id="basicEmail"
name="basicEmail"
class="form-control dt-email"
placeholder="john.doe@example.com"
aria-label="john.doe@example.com" />
</div>
<div class="form-text">You can use letters, numbers & periods</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicDate">Joining Date</label>
<div class="input-group input-group-merge">
<span id="basicDate2" class="input-group-text"><i class="bx bx-calendar"></i></span>
<input
type="text"
class="form-control dt-date"
id="basicDate"
name="basicDate"
aria-describedby="basicDate2"
placeholder="MM/DD/YYYY"
aria-label="MM/DD/YYYY" />
</div>
</div>
<div class="col-sm-12">
<label class="form-label" for="basicSalary">Salary</label>
<div class="input-group input-group-merge">
<span id="basicSalary2" class="input-group-text"><i class="bx bx-dollar"></i></span>
<input
type="number"
id="basicSalary"
name="basicSalary"
class="form-control dt-salary"
placeholder="12000"
aria-label="12000"
aria-describedby="basicSalary2" />
</div>
</div>
<div class="col-sm-12">
<button type="submit" class="btn btn-primary data-submit me-sm-4 me-1">Submit</button>
<button type="reset" class="btn btn-outline-secondary" data-bs-dismiss="offcanvas">Cancel</button>
</div>
</form>
</div>
</div>
<hr class="my-12" />
<hr class="my-12" />
<hr class="my-12" />
</div>
<div class="content-backdrop fade"></div>
</div>
)
}
export default DemoTable

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,9 @@ import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { RolesRepository } from '../../repositories/MastersRepository'; import { RolesRepository } from '../../repositories/MastersRepository';
import { useEmployeeRoles } from '../../hooks/useEmployees'; import { useEmployeeRoles } from '../../hooks/useEmployees';
import {useDispatch} from 'react-redux';
import {changeMaster} from '../../slices/localVariablesSlice';
import showToast from '../../services/toastService';
const formSchema = z.object({ const formSchema = z.object({
@ -18,10 +21,11 @@ const formSchema = z.object({
const ManageRole = ({employeeId,onClosed}) => { const ManageRole = ({employeeId,onClosed}) => {
const disptach = useDispatch()
disptach(changeMaster("Role"))
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { employeeRoles,loading } = useEmployeeRoles(employeeId); const { employeeRoles,loading } = useEmployeeRoles(employeeId);
const {data,loading:roleLoading} = useMaster();
const { data } = useMaster();
const buildDefaultRoles = () => { const buildDefaultRoles = () => {
const defaults = {}; const defaults = {};
@ -51,7 +55,8 @@ const ManageRole = ({employeeId,onClosed}) => {
}); });
useEffect(() => { useEffect( () =>
{
if (Object.keys(initialRoles).length > 0) { if (Object.keys(initialRoles).length > 0) {
reset({ reset({
selectedRole: initialRoles, selectedRole: initialRoles,
@ -72,44 +77,51 @@ const ManageRole = ({employeeId,onClosed}) => {
} }
} }
RolesRepository.createEmployeeRoles(result).then((resp)=>{ RolesRepository.createEmployeeRoles( result ).then( ( resp ) =>
{
showToast( "Role assigned successfully", "success" )
setIsLoading(false)
onClosed() onClosed()
}).catch((err)=>{ }).catch((err)=>{
console.log(err) console.log( err )
setIsLoading(false)
showToast(err.message,"error")
}) })
setIsLoading(false) setIsLoading(false)
}; };
if(loading){
<div className='text-center'>Loading...</div>
}
return ( return (
<div className={`modal fade `} id="managerole-modal" tabindex="-1" aria-hidden="true"> <div className={`modal fade `} id="managerole-modal" tabindex="-1" aria-hidden="true" >
<div className="modal-dialog modal-simple modal-md d-flex align-items-center justify-content-center"> <div className="modal-dialog modal-simple modal-md d-flex align-items-center justify-content-center" >
<div className="modal-content "> <div className="modal-content " >
<div className="modal-body" > <div className="modal-body" >
<button <button
type="button" type="button"
className="btn-close" className="btn-close"
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"
></button> ></button>
<div className="container" >
<form onSubmit={handleSubmit(onSubmit)} > <form onSubmit={handleSubmit(onSubmit)} >
<div className='text-start my-0'>
<p className='lead'>Select Roles</p>
</div>
<div className='d-flex flex-wrap justify-content-between align-items-center pb-5 '> <div className='d-flex flex-wrap justify-content-between align-items-center pb-5 ' style={{height: "70vh", overflowY: "scroll"}} >
{data.map((item) => (
{(loading || roleLoading) && <p>Loading...</p>}
{data && data.map((item) => (
<> <>
<div className="d-flex mx-3 my-2">
<div className="form-check ms-2"> <div className="col-md-6 col-lg-4 mb-4">
<div className="form-check ms-2 text-start">
<input <input
className="form-check-input" className="form-check-input"
type="checkbox" type="checkbox"
@ -119,7 +131,7 @@ const ManageRole = ({employeeId,onClosed}) => {
})} })}
/> />
<label className="form-check-label" htmlFor={item.id}>{item.role}</label> <label className="form-check-label text-bold" htmlFor={item.id}><small>{item.role || "--"}</small></label>
</div> </div>
</div> </div>
@ -132,7 +144,7 @@ const ManageRole = ({employeeId,onClosed}) => {
<div className="col-12 text-center"> <div className="col-12 text-center">
<button type="submit" className="btn btn-primary me-3"> <button type="submit" className="btn btn-primary me-3">
{isLoading? "Please Wait...":"Submit"} { isLoading ? "Please Wait":"Submit"}
</button> </button>
<button <button
type="reset" type="reset"
@ -145,7 +157,7 @@ const ManageRole = ({employeeId,onClosed}) => {
</div> </div>
</form> </form>
</div>
</div> </div>

View File

@ -1,8 +1,24 @@
import getGreetingMessage from "../../utils/greetingHandler"; import getGreetingMessage from "../../utils/greetingHandler";
import { clearAllCache } from "../../slices/apiDataManager"; import { clearAllCache } from "../../slices/apiDataManager";
import AuthRepository from "../../repositories/AuthRepository"; import AuthRepository from "../../repositories/AuthRepository";
import {useDispatch, } from "react-redux";
import {changeMaster} from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster";
import {useProfile} from "../../hooks/useProfile";
import {useNavigate} from "react-router-dom";
const Header = () => { const Header = () =>
{
const {profile} = useProfile()
const dispatch = useDispatch( changeMaster( "Job Role" ) )
const {data, loading} = useMaster()
const navigate = useNavigate()
const getRole = (roles,joRoleId) =>
{
if (!Array.isArray(roles)) return "User";
let role = roles.find( role => role.id === joRoleId )
return role ? role.name : "User";
}
const handleLogout = (e) => { const handleLogout = (e) => {
e.preventDefault(); // Prevent default anchor behavior (e.g., page reload) e.preventDefault(); // Prevent default anchor behavior (e.g., page reload)
logout(); logout();
@ -32,44 +48,6 @@ const Header = () => {
window.location.href = "/auth/login"; window.location.href = "/auth/login";
}); });
// api
// .post("/api/auth/logout", data)
// .then((data) => {
// localStorage.removeItem("jwtToken");
// localStorage.removeItem("refreshToken");
// localStorage.removeItem("user");
// window.location.href = "/auth/login";
// localStorage.clear();
// clearAllCache();
// })
// .catch((error) => {
// localStorage.removeItem("jwtToken");
// localStorage.removeItem("refreshToken");
// localStorage.removeItem("user");
// window.location.href = "/auth/login";
// clearAllCache();
// });
// await axiosClient
// .post("/api/auth/logout", data, {
// headers: {
// Authorization: `Bearer ${localStorage.getItem("jwt")}`, // Pass the JWT token
// },
// })
// .then((response) => {
// // Clear all local storage items related to the session
// })
// .catch((error) => {
// localStorage.removeItem("jwtToken");
// localStorage.removeItem("refreshToken");
// localStorage.removeItem("user"); // Remove any user-related info
// });
// // Optionally clear all localStorage (uncomment if needed)
// // localStorage.clear();
// // Redirect to login page or home page
// window.location.href = "/auth/login"; // Adjust the route as needed
} catch (error) { } catch (error) {
console.error( console.error(
"Error during logout:", "Error during logout:",
@ -77,6 +55,11 @@ const Header = () => {
); );
} }
}; };
const handleProfilePage = ()=>{
navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
}
return ( return (
<nav <nav
className="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme" className="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
@ -95,7 +78,7 @@ const Header = () => {
className="navbar-nav-right d-flex align-items-center" className="navbar-nav-right d-flex align-items-center"
id="navbar-collapse" id="navbar-collapse"
> >
<marquee> {getGreetingMessage("Ramchandra")}</marquee> <marquee> {getGreetingMessage(profile?.employeeInfo?.firstName)}</marquee>
<ul className="navbar-nav flex-row align-items-center ms-auto"> <ul className="navbar-nav flex-row align-items-center ms-auto">
<li className="nav-item navbar-dropdown dropdown-user dropdown"> <li className="nav-item navbar-dropdown dropdown-user dropdown">
<a <a
@ -114,11 +97,11 @@ const Header = () => {
</div> </div>
</a> </a>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li onClick={handleProfilePage}>
<a <a
aria-label="go to profile" aria-label="go to profile"
className="dropdown-item" className="dropdown-item"
href="#"
> >
<div className="d-flex"> <div className="d-flex">
<div className="flex-shrink-0 me-3"> <div className="flex-shrink-0 me-3">
@ -132,8 +115,8 @@ const Header = () => {
</div> </div>
</div> </div>
<div className="flex-grow-1"> <div className="flex-grow-1">
<span className="fw-medium d-block">Ramchandra</span> <span className="fw-medium d-block">{profile?.employeeInfo?.firstName}</span>
<small className="text-muted">Admin</small> <small className="text-muted">{ getRole(data,profile?.employeeInfo?.joRoleId)}</small>
</div> </div>
</div> </div>
</a> </a>
@ -141,11 +124,11 @@ const Header = () => {
<li> <li>
<div className="dropdown-divider"></div> <div className="dropdown-divider"></div>
</li> </li>
<li> <li onClick={handleProfilePage} >
<a <a
aria-label="go to profile" aria-label="go to profile"
className="dropdown-item" className="dropdown-item cusor-pointer"
href="#"
> >
<i className="bx bx-user me-2"></i> <i className="bx bx-user me-2"></i>
<span className="align-middle">My Profile</span> <span className="align-middle">My Profile</span>
@ -155,7 +138,7 @@ const Header = () => {
<a <a
aria-label="go to setting" aria-label="go to setting"
className="dropdown-item" className="dropdown-item"
href="#"
> >
<i className="bx bx-cog me-2"></i> <i className="bx bx-cog me-2"></i>
<span className="align-middle">Settings</span> <span className="align-middle">Settings</span>
@ -165,7 +148,7 @@ const Header = () => {
<a <a
aria-label="go to billing" aria-label="go to billing"
className="dropdown-item" className="dropdown-item"
href="#"
> >
<span className="d-flex align-items-center align-middle"> <span className="d-flex align-items-center align-middle">
<i className="flex-shrink-0 bx bx-credit-card me-2"></i> <i className="flex-shrink-0 bx bx-credit-card me-2"></i>

View File

@ -1,14 +1,20 @@
import React from "react"; import React from "react";
import { Link, NavLink, useLocation } from "react-router-dom"; import { Link, NavLink, useLocation, useNavigate } from "react-router-dom";
import menuData from "../../data/menuData.json"; import menuData from "../../data/menuData.json";
import {getCachedProfileData} from "../../slices/apiDataManager";
const Sidebar = () => { const Sidebar = () => {
const logineUser = getCachedProfileData()
const navigate = useNavigate()
const handleLogout = (e) => { const handleLogout = (e) => {
e.preventDefault(); // Prevent default anchor behavior (e.g., page reload) e.preventDefault();
// logout(); // logout();
}; };
const handleProfilePage = ()=>{
console.log(profile?.employeeInfo?.id)
navigate(`/employee/${profile?.employeeInfo?.id}?for=account`)
}
return ( return (
<aside <aside
id="layout-menu" id="layout-menu"
@ -30,7 +36,7 @@ const Sidebar = () => {
</Link> </Link>
<a <a
href="javascript:void(0);"
className="layout-menu-toggle menu-link text-large ms-auto" className="layout-menu-toggle menu-link text-large ms-auto"
> >
<i className="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i> <i className="bx bx-chevron-left bx-sm d-flex align-items-center justify-content-center"></i>
@ -67,11 +73,11 @@ const Sidebar = () => {
height="28" height="28"
className="rounded-circle" className="rounded-circle"
/> />
<span className="d-none d-sm-inline mx-1">Ramchandra</span> <span className="d-none d-sm-inline mx-1">{ logineUser?.employeeInfo?.firstName}</span>
</a> </a>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li onClick={handleProfilePage}>
<a aria-label="go to profile" className="dropdown-item" href="#"> <a aria-label="go to profile" className="dropdown-item">
<div className="d-flex"> <div className="d-flex">
<div className="flex-shrink-0 me-3"> <div className="flex-shrink-0 me-3">
<div className="avatar avatar-online"> <div className="avatar avatar-online">
@ -84,7 +90,7 @@ const Sidebar = () => {
</div> </div>
</div> </div>
<div className="flex-grow-1"> <div className="flex-grow-1">
<span className="fw-medium d-block">Ramchandra</span> <span className="fw-medium d-block">{ logineUser?.employeeInfo?.firstName}</span>
<small className="text-muted">Admin</small> <small className="text-muted">Admin</small>
</div> </div>
</div> </div>
@ -93,8 +99,8 @@ const Sidebar = () => {
<li> <li>
<div className="dropdown-divider"></div> <div className="dropdown-divider"></div>
</li> </li>
<li> <li onClick={handleProfilePage}>
<a aria-label="go to profile" className="dropdown-item" href="#"> <a aria-label="go to profile" className="dropdown-item" >
<i className="bx bx-user me-2"></i> <i className="bx bx-user me-2"></i>
<span className="align-middle">My Profile</span> <span className="align-middle">My Profile</span>
</a> </a>

View File

@ -1,7 +1,10 @@
import React from "react"; import React,{useState} from "react";
import moment from "moment"; import moment from "moment";
import { ProjectStatus } from "../../utils/projectStatus"; import { ProjectStatus } from "../../utils/projectStatus";
const AboutProject = ({ data }) => { const AboutProject = ( {data} ) =>
{
const [CurrentProject,setCurrentProject] = useState(data)
return ( return (
<> <>
{data && ( {data && (

View File

@ -0,0 +1,60 @@
import React, { useState, useEffect } from 'react';
import RoleBadge from './RoleBadge';
import Avatar from '../common/Avatar';
const AssignEmployeeCard = ({
employee,
jobRoles,
isChecked,
onRoleChange,
onCheckboxChange,
}) => {
const [currentJobRole, setCurrentJobRole] = useState(employee.jobRoleId);
useEffect(() => {
setCurrentJobRole(employee.jobRoleId);
}, [employee.jobRoleId]);
const handleRoleChange = (newRoleId) => {
setCurrentJobRole(newRoleId);
onRoleChange(employee.id, newRoleId);
};
const handleCheckboxChange = () => {
if (!employee.isActive) {
onCheckboxChange(employee.id);
}
};
return (
<div className="assign-employee-card d-flex justify-content-between align-items-center px-2 py-1 border-bottom">
<div className="d-flex align-items-center flex-grow-1">
<div className="avatar me-3">
<Avatar firstName={employee.firstName} lastName={employee.l} />
</div>
<div className="employee-info">
<p className="mb-0 d-none d-sm-block">{employee.firstName} {employee.lastName} </p>
</div>
</div>
<div className="d-flex align-items-center">
<RoleBadge
JobRoles={jobRoles}
currentJobRole={currentJobRole}
onRoleChange={handleRoleChange}
/>
<input
className="form-check-input ms-3"
type="checkbox"
checked={isChecked}
disabled={employee.isActive}
onChange={handleCheckboxChange}
/>
</div>
</div>
);
};
export default AssignEmployeeCard;

View File

@ -2,7 +2,7 @@ import React, { useState,useEffect } from "react";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster"; import useMaster from "../../hooks/masterHook/useMaster";
import { jobRoles,employee } from "../../data/masters"; import { employee } from "../../data/masters";
import { useForm, Controller } from "react-hook-form"; import { useForm, Controller } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { getCachedData } from "../../slices/apiDataManager"; import { getCachedData } from "../../slices/apiDataManager";
@ -17,7 +17,7 @@ const AssignRoleModel = ( {assignData,onClose}) => {
const[target,setTraget] = useState("") const[target,setTraget] = useState("")
const dispatch = useDispatch() const dispatch = useDispatch()
const {data,loading} = useMaster() const {data,loading} = useMaster()
const jobRoleData = getCachedData("JobRole") const jobRoleData = getCachedData("Job Role")
const [selectedRole, setSelectedRole] = useState("all"); const [selectedRole, setSelectedRole] = useState("all");
@ -77,7 +77,7 @@ const onSubmit = (data) => {
}; };
useEffect(()=>{ useEffect(()=>{
dispatch(changeMaster("JobRole")) dispatch(changeMaster("Job Role"))
return ()=> setSelectedRole("all") return ()=> setSelectedRole("all")
},[dispatch]) },[dispatch])

View File

@ -1,44 +1,112 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useForm,Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {z} from 'zod';
const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
const [formData, setFormData] = useState({
id: project?.id || "",
name: project?.name || "",
contactPerson: project?.contactPerson || "",
projectAddress: project?.projectAddress || "",
startDate: project?.startDate || new Date(),
endDate: project?.endDate || new Date(),
projectStatusId: project?.projectStatusId || "1",
});
// Handle input change
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
// Handle form submission const currentDate = new Date().toISOString().split('T')[0];
const handleSubmit = (e) => { const formatDate = (date) => {
e.preventDefault(); if (!date) {
onSubmit(formData); // Pass the updated data to the parent return currentDate;
}; }
const d = new Date(date);
if (isNaN(d.getTime())) {
return currentDate;
}
return d.toISOString().split('T')[0];
};
const ManageProjectInfo = ( {project,handleSubmitForm, onClose} ) =>
{
debugger
const [CurrentProject,setCurrentProject] = useState()
const [ isloading, setLoading ] = useState( false )
const projectSchema = z.object( {
...(project?.id ? { id: z.number().optional() } : {}),
name: z.string().min( 1, {message: "Project Name is required"} ),
contactPerson: z.string().min( 1, {message: "Contact Person Name is required"} ),
projectAddress: z.string().min( 1, {message: "Address is required"} ),
startDate: z.string().min( 1, {message: "Start Date is required"} ).default(currentDate),
endDate: z.string().min( 1, {message: "End Date is required"} ).default(currentDate),
projectStatusId: z
.string()
.min(1, { message: "Status is required" })
.transform((val) => {
const num = Number(val);
if (isNaN(num)) {
throw new Error("Status must be a valid number");
}
return num;
}),
} )
const {register, control, handleSubmit, formState: {errors}, reset, getValues} = useForm( {
resolver: zodResolver( projectSchema ),
defaultValues: {
id:project?.id || "",
name:project?.name || "",
contactPerson:project?.contactPerson || "",
projectAddress:project?.projectAddress || "",
startDate: formatDate(project?.startDate )|| currentDate,
endDate: formatDate(project?.endDate ) || currentDate,
projectStatusId: String(project?.projectStatusId || "0"),
}
})
useEffect( () =>
{
setCurrentProject(project)
reset(
project ? {
id:project?.id || "",
name:project?.name || "",
contactPerson: project?.contactPerson || "",
projectAddress: project?.projectAddress || "",
startDate:formatDate(project?.startDate )|| "",
endDate: formatDate(project?.endDate ) || "",
projectStatusId: String(project.projectStatusId) || "0" ,
} :{}
)
},[project,reset,])
const onSubmitForm = (updatedProject) => {
setLoading( true )
handleSubmitForm( updatedProject )
};
useEffect( () =>
{
return ()=>setLoading(false)
} )
return ( return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <div className="modal-dialog modal-lg modal-simple edit-project-modal" role="document">
<div className="modal-content"> <div className="modal-content">
<div className="modal-body"> <div className="modal-body">
<button <button
type="button" type="button"
className="btn-close" className="btn-close"
data-bs-dismiss="modal"
onClick={onClose}
aria-label="Close" aria-label="Close"
></button> ></button>
<div className="text-center mb-2"> <div className="text-center mb-2">
<h5 className="mb-2"> <h5 className="mb-2">
{formData.name ? "Edit Project" : "Create Project"} {project?.id ? "Edit Project" : "Create Project"}
</h5> </h5>
</div> </div>
<form className="row g-2" onSubmit={handleSubmit}> <form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
<label className="form-label" htmlFor="name"> <label className="form-label" htmlFor="name">
Project Name Project Name
@ -47,11 +115,12 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
type="text" type="text"
id="name" id="name"
name="name" name="name"
className="form-control" className="form-control"
placeholder="Project Name" placeholder="Project Name"
onChange={handleChange} {...register("name")}
value={formData.name}
/> />
{errors.name && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.name.message}</div>}
</div> </div>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
<label className="form-label" htmlFor="contactPerson"> <label className="form-label" htmlFor="contactPerson">
@ -63,9 +132,10 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
name="contactPerson" name="contactPerson"
className="form-control" className="form-control"
placeholder="Contact Person" placeholder="Contact Person"
onChange={handleChange} {...register("contactPerson")}
value={formData.contactPerson}
/> />
{errors.contactPerson && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.contactPerson.message}</div>}
</div> </div>
<div className="col-12 col-md-6"> <div className="col-12 col-md-6">
@ -75,11 +145,13 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
<input <input
className="form-control form-control-sm" className="form-control form-control-sm"
type="date" type="date"
value={new Date(formData.startDate).toISOString().split("T")[0]}
onChange={handleChange}
name="startDate" name="startDate"
{...register("startDate")}
id="startDate" id="startDate"
/> />
{errors.startDate && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.startDate.message}</div>}
</div> </div>
<div className="col-12 col-md-6"> <div className="col-12 col-md-6">
<label className="form-label" htmlFor="endDate"> <label className="form-label" htmlFor="endDate">
@ -88,11 +160,13 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
<input <input
className="form-control form-control-sm" className="form-control form-control-sm"
type="date" type="date"
value={new Date(formData.endDate).toISOString().split("T")[0]}
onChange={handleChange}
name="endDate" name="endDate"
{...register("endDate")}
id="endDate" id="endDate"
/> />
{errors.endDate && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.endDate.message}</div>}
</div> </div>
<div className="col-12 col-md-6"> <div className="col-12 col-md-6">
<label className="form-label" htmlFor="modalEditUserStatus"> <label className="form-label" htmlFor="modalEditUserStatus">
@ -103,8 +177,10 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
name="modalEditUserStatus" name="modalEditUserStatus"
className="select2 form-select" className="select2 form-select"
aria-label="Default select example" aria-label="Default select example"
onChange={handleChange} {...register("projectStatusId", {
value={formData.projectStatusId} required: "Status is required",
valueAsNumber: false
})}
> >
<option disabled>Status</option> <option disabled>Status</option>
<option value="1">Active</option> <option value="1">Active</option>
@ -115,6 +191,8 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
<option value="5">Completed</option> <option value="5">Completed</option>
</select> </select>
{errors.projectStatusId && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.projectStatusId.message}</div>}
</div> </div>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
@ -127,20 +205,21 @@ const ManageProjectInfo = ({ project, onClose, onSubmit }) => {
id="projectAddress" id="projectAddress"
name="projectAddress" name="projectAddress"
className="form-control" className="form-control"
onChange={handleChange} {...register( "projectAddress" )}
value={formData.projectAddress}
/> />
</div> </div>
</div> {errors.projectAddress && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.projectAddress.message}</div>}
</div>
<div className="col-12 text-center"> <div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3"> <button type="submit" className="btn btn-sm btn-primary me-3" >
Submit {isloading ? "Please Wait" : project?.id ? "Update":"Submit"}
</button> </button>
<button <button
type="reset" type="button"
className="btn btn-sm btn-label-secondary" className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal" onClick={onClose}
aria-label="Close" aria-label="Close"
> >
Cancel Cancel

View File

@ -1,253 +1,149 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import EmployeeRepository from "../../repositories/EmployeeRepository"; import EmployeeRepository from "../../repositories/EmployeeRepository";
import AssignEmployeeCard from "./AssignEmployeeCard";
import {useAllEmployees} from "../../hooks/useEmployees";
import useSearch from "../../hooks/useSearch";
const MapUsers = ({ const MapUsers = ({
projectId,
onClose, onClose,
empRoles, empJobRoles,
onSubmit, onSubmit,
clearTrigger, allocation,
onClearComplete,
}) => { }) => {
const [searchQuery, setSearchQuery] = useState(""); const {employeesList, loading, error} = useAllEmployees();
const [searchResults, setSearchResults] = useState([]);
const [selectedEmployees, setSelectedEmployees] = useState([]); const [selectedEmployees, setSelectedEmployees] = useState([]);
const [currentEmployee, setCurrentEmployee] = useState(null); const [searchText, setSearchText] = useState("");
const [employeeRoles, setEmployeeRoles] = useState(null);
useEffect(() => { const handleAllocationData = Array.isArray( allocation ) ? allocation : [];
if (clearTrigger) {
setSearchQuery(""); const allocationEmployees = employeesList.map((employee) => {
setSearchResults([]); // Notify parent that clearing is done const allocationItem = handleAllocationData.find((alloc) => alloc.employeeId === employee.id);
setSelectedEmployees([]); return {
setCurrentEmployee(null); ...employee,
isActive: allocationItem ? allocationItem.isActive : false,
jobRoleId: allocationItem ? allocationItem.jobRoleId : employee.jobRoleId,
};
} );
function parseDate(dateStr) {
return new Date(dateStr.split('.')[0]);
}
const latestAllocations = handleAllocationData.reduce((acc, alloc) => {
const existingAlloc = acc[alloc.employeeId];
if (!existingAlloc) {
acc[alloc.employeeId] = alloc;
} else {
const existingDate = parseDate(existingAlloc.reAllocationDate || existingAlloc.allocationDate);
const newDate = parseDate(alloc.reAllocationDate || alloc.allocationDate);
if (newDate > existingDate) {
acc[alloc.employeeId] = alloc;
}
} }
}, [clearTrigger, onClearComplete]); return acc;
}, {});
useEffect(() => {
setEmployeeRoles(empRoles);
}, [empRoles]);
const getRole = (id) => { const allocationEmployeesData = employeesList
const role = employeeRoles.find((b) => b.id === id); .map((employee) => {
return role; const allocationItem = latestAllocations[employee.id];
return {
...employee,
isActive: allocationItem ? allocationItem.isActive : false,
};
})
.filter( ( employee ) => employee.isActive === false );
const { filteredData, setSearchQuery } = useSearch(allocationEmployeesData, searchText);
const handleRoleChange = (employeeId, newRoleId) => {
setSelectedEmployees((prevSelectedEmployees) =>
prevSelectedEmployees.map((emp) =>
emp.id === employeeId ? { ...emp, jobRoleId: newRoleId } : emp
)
);
}; };
const searchEmployees = async (query) => {
if (!query) { const handleCheckboxChange = (employeeId) => {
setSearchResults([]); setSelectedEmployees((prevSelectedEmployees) => {
return; const updatedEmployees = [...prevSelectedEmployees];
} const employeeIndex = updatedEmployees.findIndex((emp) => emp.id === employeeId);
try {
EmployeeRepository.getEmployeeListByproject(projectid) if (employeeIndex !== -1) {
.then((response) => { const isSelected = !updatedEmployees[employeeIndex].isSelected;
setSearchResults(response); updatedEmployees[employeeIndex].isSelected = isSelected;
}) } else {
.catch((error) => { updatedEmployees.push({
console.error(error); id: employeeId,
setError("Failed to fetch data."); isSelected: true,
}); });
}
// api return updatedEmployees;
// .get(`/api/employee/search/${query}`) });
// .then((data) => {
// setSearchResults(data.data);
// //dispatch(cacheApiResponse({ key: "projectslist", data: data }));
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// });
// // const response = await axios.get(`/api/employee/search/${query}`);
// let token = localStorage.getItem("jwtToken");
// const response = await axiosClient.get(`/api/employee/search/${query}`, {
// headers: {
// Authorization: `Bearer ${token}`,
// //"Content-Type": "multipart/form-data",
// },
// });
// setSearchResults(response.data.data);
} catch (error) {
console.error("Search failed", error);
}
}; };
const handleRoleChange = (e) => {
const { name, value } = e.target;
setCurrentEmployee({ ...currentEmployee, [name]: value });
};
const onSelectAction = (employee) => {
setCurrentEmployee(employee);
};
const handleSelectEmployee = (employee) => {
if (!selectedEmployees.some((emp) => emp.id === employee.id)) {
setSelectedEmployees([...selectedEmployees, employee]);
setCurrentEmployee(null);
setSearchQuery("");
setSearchResults([]);
}
};
const handleRemoveEmployee = (id) => { const handleSubmit = () => {
setSelectedEmployees(selectedEmployees.filter((emp) => emp.id !== id)); const selected = selectedEmployees
}; .filter((emp) => emp.isSelected)
.map((emp) => ({ empID: emp.id, jobRoleId: emp.jobRoleId }));
const handleSubmit = async (e) => { onSubmit(selected);
e.preventDefault();
//formData.projectId = project.id;
onSubmit(selectedEmployees);
}; };
return ( return (
<> <>
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <div className="modal-dialog modal-dialog-scrollable mx-sm-auto mx-1 modal-lg modal-simple modal-edit-user">
<div className="modal-content"> <div className="modal-content">
{/* <div className="modal-body"> <div className="modal-header">
<h2>Assign Employees to Project</h2> <div className="md-2 mb-1">
<input
type="text"
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
searchEmployees(e.target.value);
}}
placeholder="Search employees..."
/>
<ul>
{searchResults.map((emp) => (
<li key={emp.id}>
{emp.firstName} {emp.lastName}
<button onClick={() => handleSelectEmployee(emp)}>Add</button>
</li>
))}
</ul>
<h3>Selected Employees:</h3>
<ul>
{selectedEmployees.map((emp) => (
<li key={emp.id}>
{emp.firstName} {emp.lastName}
<button onClick={() => handleRemoveEmployee(emp.id)}>
Remove
</button>
</li>
))}
</ul>
<button onClick={handleSubmit}>Assign to Project</button>
<button onClick={onClose}>Cancel</button>
</div> */}
<div className="modal-body">
<div className="mb-3">
<div className="input-group"> <div className="input-group">
<input <input
type="text" type="search"
className="form-control" className="form-control form-control-sm"
value={searchQuery} placeholder="Search employees.."
onChange={(e) => { onChange={(e) => setSearchQuery(e.target.value)}
setSearchQuery(e.target.value);
searchEmployees(e.target.value);
}}
placeholder="Search employees..."
/> />
<button
className="btn btn-primary"
onClick={() => searchEmployees(searchQuery)}
>
Search
</button>
</div> </div>
</div> </div>
<ul className="list-group mb-3"> </div>
{searchResults.map((emp) => ( <div className="modal-body p-sm-4 p-0">
<li <ul className="list-group border-none mb-2">
{loading && !employeesList && <p>Loading...</p>}
{filteredData.map((emp) => (
<AssignEmployeeCard
key={emp.id} key={emp.id}
className="list-group-item d-flex justify-content-between align-items-center" employee={emp}
onClick={() => onSelectAction(emp)} jobRoles={empJobRoles}
> isChecked={emp.isSelected}
{emp.firstName} {emp.lastName} onRoleChange={handleRoleChange}
{/* <button onCheckboxChange={handleCheckboxChange}
className="btn btn-success btn-sm" />
onClick={() => onSelectAction(emp)} ) )}
> {filteredData.length == 0 && <p>No Data Found</p>}
Add
</button> */}
</li>
))}
</ul>
{currentEmployee && (
<div>
<div className="col-12 col-md-12">
<span>
{currentEmployee.firstName} {currentEmployee.lastName}
</span>
</div>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="roleId">
Select Role
</label>
<select
id="roleId"
name="roleId"
className="select2 form-select form-select-sm"
aria-label="Default select example"
onChange={handleRoleChange}
value={currentEmployee.roleId}
>
<option value="0">Select Role</option>
{employeeRoles.map((role) => (
<option key={role.id} value={role.id}>
{role.role}
</option>
))}
</select>
<div className="col-12 col-md-12">
<button
className="btn btn-success btn-sm"
onClick={() => handleSelectEmployee(currentEmployee)}
>
Add
</button>
</div>
</div>
</div>
// <div>
// <span>
// {currentEmployee.firstName} {currentEmployee.lastName}
// </span>
// </div>
)}
<h6 className="text-start">Selected Employees:</h6>
<ul className="list-group">
{selectedEmployees.map((emp) => (
<li
key={emp.id}
className="list-group-item d-flex justify-content-between align-items-center"
>
<div className="row"></div>
<div className="col-4">
{" "}
{emp.firstName} {emp.lastName}
</div>
<div className="col-4"> {getRole(emp.roleId).role}</div>
<div className="col-4">
{" "}
<button
className="btn btn-danger btn-sm"
onClick={() => handleRemoveEmployee(emp.id)}
>
Remove
</button>
</div>
</li>
))}
</ul> </ul>
</div> </div>
<div className="modal-footer"> <div className="modal-footer">
<button className="btn btn-success" onClick={handleSubmit}> <button className="btn btn-success" onClick={handleSubmit}>
Assign to Project Assign to Project
</button> </button>
<button type="button" className="btn btn-secondary" data-dismiss="modal" aria-label="Close" onClick={onClose}> <button
type="button"
className="btn btn-secondary"
data-dismiss="modal"
aria-label="Close"
onClick={onClose}
>
Cancel Cancel
</button> </button>
</div> </div>
@ -257,4 +153,4 @@ const MapUsers = ({
); );
}; };
export default MapUsers; export default MapUsers;

View File

@ -1,65 +1,80 @@
import React, { useState } from "react"; import React, { useState,useEffect } from "react";
import ManageProjectInfo from "./ManageProjectInfo"; import ManageProjectInfo from "./ManageProjectInfo";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { clearCacheKey } from "../../slices/apiDataManager"; import { cacheData,getCachedData } from "../../slices/apiDataManager";
import {hasUserPermission} from "../../utils/authUtils";
import moment from "moment";
const ProjectBanner = ({ data }) => {
if (data == null) { const ProjectBanner = ( {project_data} ) =>
{
const [showModal, setShowModal] = useState(false);
const [ CurrentProject, setCurrentProject ] = useState( project_data )
if (project_data == null) {
return <span>incomplete project information</span>; return <span>incomplete project information</span>;
} }
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const handleFormSubmit = ( updatedProject ) =>
{
if ( CurrentProject?.id )
{
ProjectRepository.updateProject(CurrentProject.id,updatedProject).then( ( response ) =>
{
const updatedProjectData = {
...CurrentProject,
...response.data,
building: CurrentProject.building,
};
setCurrentProject( updatedProject )
cacheData( `projectinfo-${ CurrentProject.id }`, updatedProjectData );
const projects_list = getCachedData("projectslist");
if ( projects_list )
{
const updatedProjectsList = projects_list.map(project =>
project.id === CurrentProject.id ? {
...project,
...response.data,
tenant:project.tenant
} : project
);
cacheData("projectslist",updatedProjectsList)
}
showToast( "Project updated successfully.", "success" );
setShowModal(false)
})
.catch((error) => {
showToast( error.message, "error" );
const [isModalOpen, setIsModalOpen] = useState(false); });
const [project, setProject] = useState(data); }
// Open the modal and set project data (if editing)
const openModal = (projectData) => {
setIsModalOpen(true);
};
// Close the modal
const closeModal = () => {
setIsModalOpen(false);
const modalBackdrop = document.querySelector(".modal-backdrop");
if (modalBackdrop) modalBackdrop.remove();
};
// Handle form submission
const handleFormSubmit = (updatedProject) => {
console.log("Form submitted:", updatedProject); // Replace this with an API call or state update
ProjectRepository.updateProject(updatedProject)
.then((response) => {
setProject({
...response,
});
clearCacheKey("projectslist");
showToast("Project updated successfully.", "success");
closeModal();
})
.catch((error) => {
showToast(error.message, "error");
});
}; };
return ( return (
<> <>
{isModalOpen && ( <div
<div className={`modal fade ${showModal ? 'show' : ''}`}
className={`modal fade `} tabIndex="-1"
id="editproject" role="dialog"
tabindex="-1" style={{ display: showModal ? 'block' : 'none' }}
aria-hidden="true" aria-hidden={!showModal}
> >
<ManageProjectInfo <ManageProjectInfo
project={project} project={CurrentProject}
onClose={closeModal} handleSubmitForm={handleFormSubmit}
onSubmit={handleFormSubmit} onClose={handleClose}
></ManageProjectInfo> ></ManageProjectInfo>
</div> </div>
)} {/* -------------------- */}
<div className="col-12"> <div className="col-12">
<div className="card mb-6 pb-0"> <div className="card mb-6 pb-0">
<div className="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-2 "> <div className="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-2 ">
@ -75,40 +90,38 @@ const ProjectBanner = ({ data }) => {
<div className="d-flex align-items-md-end align-items-sm-start align-items-center justify-content-md-between justify-content-start mx-5 flex-md-row flex-column gap-4"> <div className="d-flex align-items-md-end align-items-sm-start align-items-center justify-content-md-between justify-content-start mx-5 flex-md-row flex-column gap-4">
<div className="user-profile-info"> <div className="user-profile-info">
<h4 className="mb-2 mt-lg-1"> <h4 className="mb-2 mt-lg-1">
{project.name ? project.name : "N/A"} {CurrentProject.name ? CurrentProject.name : "N/A"}
</h4> </h4>
<h6 className="mb-1 mt-lg-1"> <h6 className="mb-1 mt-lg-1">
Address:{" "} Address:{" "}
{project.projectAddress ? project.projectAddress : "N/A"} {CurrentProject.projectAddress ? CurrentProject.projectAddress : "N/A"}
</h6> </h6>
<h6 className="mb-1 mt-lg-1"> <h6 className="mb-1 mt-lg-1">
Contact:{" "} Contact:{" "}
{project.contactPerson ? project.contactPerson : "N/A"} {CurrentProject.contactPerson ? CurrentProject.contactPerson : "N/A"}
</h6> </h6>
<h6 className="mb-1 mt-lg-1"> <h6 className="mb-1 mt-lg-1">
<span> <span>
{" "} {" "}
Start Date:{" "} Start Date:{" "}
{project.startDate {CurrentProject.startDate
? new Date(project.startDate) ? moment(CurrentProject.startDate).format("DD-MMM-YYYY")
.toISOString() : "N/A"}
.split("T")[0]
: "N/A"}
</span> </span>
<span className="ms-5"> <span className="ms-5">
End Date:{" "} End Date:{" "}
{project.endDate {CurrentProject.endDate
? new Date(project.endDate).toISOString().split("T")[0] ? moment(CurrentProject.endDate).format("DD-MMM-YYYY")
: "N/A"} : "N/A"}
</span> </span>
</h6> </h6>
</div> </div>
<button <button
type="button" type="button"
className="btn btn-sm btn-primary" className={`btn btn-sm btn-primary ${hasUserPermission("53176ebf-c75d-42e5-839f-4508ffac3def") ? "":"d-none"}`}
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#editproject" data-bs-target="#edit-project-modal"
onClick={openModal} onClick={handleShow}
> >
Modify Modify
</button> </button>

View File

@ -1,13 +1,26 @@
import React, { useEffect } from "react"; import React,{useState,useEffect} from "react";
import moment from "moment"; import moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils"; import { getDateDifferenceInDays } from "../../utils/dateUtils";
import { logInfo, logError } from "../../utils/errorUtil"; import { useNavigate } from "react-router-dom";
import {useProjectDetails} from "../../hooks/useProjects";
import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository";
import {cacheData, getCachedData} from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
const ProjectCard = ({ project }) => {
useEffect(() => {
// projectCardChart(project);
}, []);
const ProjectCard = ( {projectData} ) =>
{
const[projectInfo,setProjectInfo] = useState(projectData)
const navigate = useNavigate()
const {projects_Details, loading} = useProjectDetails( projectData.id )
const [showModal, setShowModal] = useState(false);
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal( false );
const getProjectStatusName = (statusId) => { const getProjectStatusName = (statusId) => {
switch (statusId) { switch (statusId) {
case 1: case 1:
@ -22,6 +35,7 @@ const ProjectCard = ({ project }) => {
return "Completed"; return "Completed";
} }
}; };
const getProjectStatusColor = (statusId) => { const getProjectStatusColor = (statusId) => {
switch (statusId) { switch (statusId) {
case 1: case 1:
@ -36,13 +50,75 @@ const ProjectCard = ({ project }) => {
return "bg-label-dark"; return "bg-label-dark";
} }
}; };
const handleViewProject = (e) => { const handleViewProject = (e) => {
e.preventDefault(); // Prevent default anchor behavior (e.g., page reload) navigate(`/projects/${projectData.id}`)
window.location.href = "/projects/" + project.id;
}; };
const handleFormSubmit = ( updatedProject ) =>
{
if ( projectInfo?.id )
{
ProjectRepository.updateProject(projectInfo.id,updatedProject).then( ( response ) =>
{
const updatedProjectData = {
...projectInfo,
...response.data,
building:projects_Details.building,
};
setProjectInfo( updatedProject )
if ( getCachedData( `projectinfo-${ projectInfo.id }` ) )
{
cacheData( `projectinfo-${ projectInfo.id }`, updatedProjectData );
}
const projects_list = getCachedData( "projectslist" );
if ( projects_list )
{
const updatedProjectsList = projects_list.map(project =>
project.id == projectInfo.id ? {
...project,
...response.data,
tenant:project.tenant
} : project
);
cacheData("projectslist",updatedProjectsList)
}
showToast( "Project updated successfully.", "success" );
setShowModal(false)
})
.catch((error) => {
showToast( error.message, "error" );
});
}
};
return ( return (
<>
<div
className={`modal fade ${showModal ? 'show' : ''}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? 'block' : 'none' }}
aria-hidden={!showModal}
>
<ManageProjectInfo
project={projects_Details}
handleSubmitForm={handleFormSubmit}
onClose={handleClose}
></ManageProjectInfo>
</div>
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4"> <div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<div className="card"> <div className="card cursor-pointer">
<div className="card-header pb-4"> <div className="card-header pb-4">
<div className="d-flex align-items-start"> <div className="d-flex align-items-start">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
@ -56,15 +132,15 @@ const ProjectCard = ({ project }) => {
<h5 className="mb-0"> <h5 className="mb-0">
<a <a
className="stretched-link text-heading" className="stretched-link text-heading"
href="#"
onClick={handleViewProject} onClick={handleViewProject}
> >
{project.name} {projectInfo.name}
</a> </a>
</h5> </h5>
<div className="client-info text-body"> <div className="client-info text-body">
<span className="fw-medium">Client: </span> <span className="fw-medium">Client: </span>
<span>{project.contactPerson}</span> <span>{projectInfo.contactPerson}</span>
</div> </div>
</div> </div>
</div> </div>
@ -80,9 +156,6 @@ const ProjectCard = ({ project }) => {
</button> </button>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li>
{/* <a className="dropdown-item" href="javascript:void(0);">
View details
</a> */}
<a <a
aria-label="click to View details" aria-label="click to View details"
className="dropdown-item" className="dropdown-item"
@ -92,67 +165,50 @@ const ProjectCard = ({ project }) => {
<span className="align-left">View details</span> <span className="align-left">View details</span>
</a> </a>
</li> </li>
<li> <li data-bs-toggle="modal"
data-bs-target="#edit-project-modal"
onClick={handleShow}>
<a <a
className="dropdown-item" className="dropdown-item"
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent default link behavior
window.location.href = `/project/manage/${project.id}`;
}}
> >
<i className="bx bx-pencil me-2"></i> <i className="bx bx-pencil me-2"></i>
<span className="align-left">Modify</span> <span className="align-left"
>Modify</span>
</a> </a>
</li> </li>
<li> <li>
<a className="dropdown-item" href="javascript:void(0);"> <a className="dropdown-item" >
<i className="bx bx-task me-2"></i> <i className="bx bx-task me-2"></i>
<span className="align-left">Activities</span> <span className="align-left">Activities</span>
</a> </a>
</li> </li>
{/* <li>
<hr className="dropdown-divider"></hr>
</li> */}
{/* <li>
<a
className="dropdown-item text-danger"
href="javascript:void(0);"
>
Leave Project
</a>
</li> */}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="card-body"> <div className="card-body pb-1">
<div className="d-flex align-items-center flex-wrap"> <div className="d-flex align-items-center flex-wrap">
{/* <div className="bg-lighter px-3 py-2 rounded me-auto mb-4">
<p className="mb-1">
<span className="fw-medium text-heading">$24.8k</span>/ $18.2k
</p>
<span className="text-body">Total Budget</span>
</div> */}
<div className="text-start mb-4"> <div className="text-start mb-4">
<p className="mb-1"> <p className="mb-1">
<span className="text-heading fw-medium">Start Date: </span> <span className="text-heading fw-medium">Start Date: </span>
{project.startDate {projectInfo.startDate
? moment(project.startDate).format("DD-MMM-YYYY") ? moment(projectInfo.startDate).format("DD-MMM-YYYY")
: "NA"} : "NA"}
</p> </p>
<p className="mb-1"> <p className="mb-1">
<span className="text-heading fw-medium">Deadline: </span> <span className="text-heading fw-medium">Deadline: </span>
{project.endDate {projectInfo.endDate
? moment(project.endDate).format("DD-MMM-YYYY") ? moment(projectInfo.endDate).format("DD-MMM-YYYY")
: "NA"} : "NA"}
</p> </p>
<p className="mb-0">{projectInfo.projectAddress}</p>
</div> </div>
</div> </div>
<p className="mb-0">{project.projectAddress}</p>
</div> </div>
<div className="card-body border-top"> <div className="card-body border-top">
<div className="d-flex align-items-center mb-4"> <div className="d-flex align-items-center mb-4">
@ -160,18 +216,18 @@ const ProjectCard = ({ project }) => {
<span <span
className={ className={
`badge rounded-pill ` + `badge rounded-pill ` +
getProjectStatusColor(project.projectStatusId) getProjectStatusColor(projectInfo.projectStatusId)
} }
> >
{getProjectStatusName(project.projectStatusId)} {getProjectStatusName(projectInfo.projectStatusId)}
</span> </span>
</p>{" "} </p>{" "}
<span className="badge bg-label-success ms-auto"> <span className="badge bg-label-success ms-auto">
{project.startDate && {projectInfo.startDate &&
project.endDate && projectInfo.endDate &&
getDateDifferenceInDays( getDateDifferenceInDays(
project.startDate, projectInfo.startDate,
project.endDate projectInfo.endDate
)}{" "} )}{" "}
Days left Days left
</span> </span>
@ -192,57 +248,10 @@ const ProjectCard = ({ project }) => {
</div> </div>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
{/* <ul className="list-unstyled d-flex align-items-center avatar-group mb-0 z-2">
<li
data-bs-toggle="tooltip"
data-popup="tooltip-custom"
data-bs-placement="top"
className="avatar avatar-sm pull-up"
aria-label="Vinnie Mostowy"
data-bs-original-title="Vinnie Mostowy"
>
<img
className="rounded-circle"
src="../../assets/img/avatars/5.png"
alt="Avatar"
></img>
</li>
<li
data-bs-toggle="tooltip"
data-popup="tooltip-custom"
data-bs-placement="top"
className="avatar avatar-sm pull-up"
aria-label="Allen Rieske"
data-bs-original-title="Allen Rieske"
>
<img
className="rounded-circle"
src="../../assets/img/avatars/00.jpg"
alt="Avatar"
></img>
</li>
<li
data-bs-toggle="tooltip"
data-popup="tooltip-custom"
data-bs-placement="top"
className="avatar avatar-sm pull-up me-3"
aria-label="Julee Rossignol"
data-bs-original-title="Julee Rossignol"
>
<img
className="rounded-circle"
src="../../assets/img/avatars/6.png"
alt="Avatar"
></img>
</li>
<li>
<small className="text-muted">280 Employees</small>
</li>
</ul> */}
</div> </div>
<div className="ms-auto"> <div className="ms-auto">
<a <a
href="javascript:void(0);"
className="text-muted d-flex align-items-center" className="text-muted d-flex align-items-center"
> >
<i className="bx bx-chat me-1"></i> 15 <i className="bx bx-chat me-1"></i> 15
@ -251,7 +260,8 @@ const ProjectCard = ({ project }) => {
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</>
); );
}; };

View File

@ -1,6 +1,12 @@
import React from "react"; import React from "react";
import {hasUserPermission} from "../../utils/authUtils";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {INFRASTRUCTURE} from "../../utils/constants";
const ProjectNav = ({ onPillClick, activePill }) => { const ProjectNav = ( {onPillClick, activePill} ) =>
{
const HasInfraStructure = useHasUserPermission( INFRASTRUCTURE )
return ( return (
<div className="col-md-12"> <div className="col-md-12">
<div className="nav-align-top"> <div className="nav-align-top">
@ -29,7 +35,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
<i className="bx bx-group bx-sm me-1_5"></i> Teams <i className="bx bx-group bx-sm me-1_5"></i> Teams
</a> </a>
</li> </li>
<li className="nav-item"> <li className={`nav-item ${HasInfraStructure ? "":"d-none"} `}>
<a <a
className={`nav-link ${activePill === "infra" ? "active" : ""}`} className={`nav-link ${activePill === "infra" ? "active" : ""}`}
href="#" href="#"

View File

@ -1,6 +1,11 @@
import React from "react"; import React from "react";
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
const ProjectOverview = ({project}) =>
{
const {projectEmployees} = useEmployeesByProjectAllocated( project.id );
let teamSize = projectEmployees.filter( ( emp ) => emp.isActive )
const ProjectOverview = () => {
return ( return (
<div className="card mb-6"> <div className="card mb-6">
<div className="card-body"> <div className="card-body">
@ -21,7 +26,7 @@ const ProjectOverview = () => {
<li className="d-flex align-items-center"> <li className="d-flex align-items-center">
<i className="bx bx-user"></i> <i className="bx bx-user"></i>
<span className="fw-medium mx-2">Current team Size:</span>{" "} <span className="fw-medium mx-2">Current team Size:</span>{" "}
<span>897</span> <span>{ teamSize?.length}</span>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,57 @@
import React from 'react';
const RoleBadge = ({ JobRoles, currentJobRole, onRoleChange }) => {
const handleRoleSelect = (newRoleId) => {
onRoleChange(newRoleId);
};
const validJobRoles = Array.isArray(JobRoles) ? JobRoles : [];
const selectedRole = validJobRoles.find((role) => role.id === currentJobRole);
const selectedRoleName = selectedRole ? selectedRole.name : 'Select Job Role';
if (validJobRoles.length === 0) {
return (
<div className="badge bg-label-warning d-flex align-items-center lead">
<span className="ms-1 px-2 py-1">No Roles Available</span>
</div>
);
}
return (
<div className="dropdown py-2 px-3 cursor-pointer">
<div
className=" dropdown-toggle d-flex align-items-center lead"
id="dropdownMenuButton"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span className="ms-1 px-2 py-1">
<small>{selectedRoleName}</small>
</span>
</div>
<ul
className="dropdown-menu"
aria-labelledby="dropdownMenuButton"
style={{ maxHeight: '200px', overflowY: 'auto' }}
>
{validJobRoles.map((role) => (
<li key={role.id}>
<a
className="dropdown-item"
onClick={(e) => {
e.preventDefault();
handleRoleSelect(role.id);
}}
>
{role.name}
</a>
</li>
))}
</ul>
</div>
);
};
export default RoleBadge;

View File

@ -3,25 +3,37 @@ import MapUsers from "./MapUsers";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import moment from "moment"; import moment from "moment";
import { RolesRepository } from "../../repositories/MastersRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import ProjectRepository from "../../repositories/ProjectRepository";
const Teams = ({ project }) => { import ProjectRepository from "../../repositories/ProjectRepository";
import {useDispatch} from "react-redux";
import {changeMaster} from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster"
import {useHasUserPermission} from "../../hooks/useHasUserPermission"
import {ASSIGN_USER_TO_PROJECT} from "../../utils/constants";
const Teams = ( {project} ) =>
{
const dispatch = useDispatch()
dispatch( changeMaster( 'Job Role' ) )
const {data,loading} = useMaster()
const [isModalOpen, setIsModelOpen] = useState(false); const [isModalOpen, setIsModelOpen] = useState(false);
const [error, setError] = useState(""); const [error, setError] = useState("");
const [empRoles, setEmpRoles] = useState(null); const [empJobRoles, setEmpJobRoles] = useState(null);
const [clearFormTrigger, setClearFormTrigger] = useState(false); const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [employees, setEmployees] = useState(null); const [employees, setEmployees] = useState([]);
const [filteredEmployees, setFilteredEmployees] = useState([]); const [ filteredEmployees, setFilteredEmployees ] = useState( [] );
const HasAssignUserPermission = useHasUserPermission( ASSIGN_USER_TO_PROJECT )
const fetchEmployees = async () => { const fetchEmployees = async () => {
try { try {
// if (!empRoles) { // if (!empRoles) {
ProjectRepository.getProjectAllocation(project.id) ProjectRepository.getProjectAllocation(project.id)
.then( ( response ) => .then((response) => {
{ setEmployees(response.data);
debugger
setEmployees( response.data );
setFilteredEmployees(response.data.filter((emp) => emp.isActive)); setFilteredEmployees(response.data.filter((emp) => emp.isActive));
}) })
.catch((error) => { .catch((error) => {
@ -33,163 +45,141 @@ const Teams = ({ project }) => {
} }
}; };
const fetchEmpRoles = async () => {
try {
const roles_cache = getCachedData("employeeRolesMaster");
if (!roles_cache) {
RolesRepository.getRoles()
.then((response) => {
setEmpRoles(response.data);
cacheData("employeeRolesMaster", response.data);
})
.catch((error) => {
console.error(error);
setError("Failed to fetch data.");
});
} else {
setEmpRoles(roles_cache);
}
} catch (err) {
setError("Failed to fetch activities.");
} finally {
// setLoading(false);
}
};
const submitAllocations = (items) => { const submitAllocations = (items) => {
ProjectRepository.manageProjectAllocation(items) ProjectRepository.manageProjectAllocation(items)
.then((response) => { .then((response) => {
showToast("Details updated successfully.", "success"); showToast("Details updated successfully.", "success");
setClearFormTrigger(true); // Set trigger to true setClearFormTrigger(true);
fetchEmployees(); fetchEmployees();
}) })
.catch((error) => { .catch((error) => {
showToast(error.message, "error"); showToast(error.message, "error");
}); });
// api
// .post("/api/project/allocation", items)
// .then((data) => {
// showToast("Details updated successfully.", "success");
// setClearFormTrigger(true); // Set trigger to true
// fetchEmployees();
// })
// .catch((error) => {
// showToast(error.message, "error");
// });
}; };
const removeAllocation = (item) => { const removeAllocation = (item) => {
submitAllocations([ submitAllocations([
{ {
empID: item.employeeId, empID: item.employeeId,
roleID: item.roleID, jobRoleId: item.jobRoleId,
projectId: project.id, projectId: project.id,
status: false, status: false,
}, },
]); ]);
}; };
const handleEmpAlicationFormSubmit = (allocaionObj) => { const handleEmpAlicationFormSubmit = (allocaionObj) => {
console.log("Form submitted:", allocaionObj); // Replace this with an API call or state update
debugger;
let items = allocaionObj.map((item) => { let items = allocaionObj.map((item) => {
return { return {
empID: item.id, empID: item.empID,
roleID: item.roleId, jobRoleId: item.jobRoleId,
projectId: project.id, projectId: project.id,
status: true, status: true,
}; };
}); });
submitAllocations(items);
submitAllocations(items)
}; };
const getRole = (roleId) => {
if (!empRoles) return "Unassigned"; const getRole = ( jobRoleId ) =>
if (!roleId) return "Unassigned"; {
const role = empRoles.find((b) => b.id == roleId); if (loading) return "Loading...";
return role ? role.role : "Unassigned"; if (!Array.isArray(empJobRoles)) return "Unassigned";
if (!jobRoleId) return "Unassigned";
const role = empJobRoles.find((b) => b.id == jobRoleId);
return role ? role.name : "Unassigned";
}; };
const openModel = () => { const openModel = () => {
setIsModelOpen(true); setIsModelOpen(true);
};
const onModelClose = () => {
setIsModelOpen(false);
const modalElement = document.getElementById("user-model");
if (modalElement) {
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop").remove();
}
const modalBackdropElement = document.querySelector('.modal-backdrop');
if (modalBackdropElement) {
modalBackdropElement.remove();
}
document.body.style.overflow = 'auto'
}; };
const onModelClose = () => {
setIsModelOpen(false);
const modalElement = document.getElementById('user-model');
if (modalElement) {
modalElement.classList.remove('show');
modalElement.style.display = 'none';
document.body.classList.remove('modal-open');
document.querySelector('.modal-backdrop').remove();
}
};
useEffect(() => { useEffect(() => {
fetchEmployees(); fetchEmployees();
fetchEmpRoles();
}, [] ); }, [] );
useEffect( () =>
{
if ( data )
{
setEmpJobRoles(data)
}
},[data])
const handleFilterEmployee = (e) => { const handleFilterEmployee = (e) => {
const filterValue = e.target.value; const filterValue = e.target.value;
if (filterValue === 'true') { if (filterValue === "true") {
setFilteredEmployees(employees.filter((emp) => emp.isActive)); setFilteredEmployees(employees.filter((emp) => emp.isActive));
} else { } else {
setFilteredEmployees(employees.filter((emp) => !emp.isActive)); setFilteredEmployees(employees.filter((emp) => !emp.isActive));
} }
}; };
return ( return (
<> <>
{ {isModalOpen && (
isModalOpen && (
<div <div
className={`modal fade `} className={`modal fade `}
id="user-model" id="user-model"
tabIndex="-1" tabIndex="-1"
aria-hidden="true" aria-hidden="true"
> >
<MapUsers <MapUsers
onClose={onModelClose} projectId={project.id}
empRoles={empRoles} onClose={onModelClose}
projectId={1} empJobRoles={empJobRoles}
onSubmit={handleEmpAlicationFormSubmit} onSubmit={handleEmpAlicationFormSubmit}
clearTrigger={clearFormTrigger} allocation={employees}
onClearComplete={() => setClearFormTrigger(false)} clearTrigger={clearFormTrigger}
></MapUsers> onClearComplete={() => setClearFormTrigger(false)}
</div> ></MapUsers>
</div>
)} )}
<div className="card card-action mb-6"> <div className="card card-action mb-6">
<div className="card-body"> <div className="card-body">
<div className="row"> <div className="row">
<div className="col-12 d-flex justify-content-between mb-1"> <div className="col-12 d-flex justify-content-between mb-1">
<div className="dataTables_length text-start py-2 px-2" id="DataTables_Table_0_length"> <div
<label> className="dataTables_length text-start py-2 px-2"
<select id="DataTables_Table_0_length"
name="DataTables_Table_0_length" >
aria-controls="DataTables_Table_0" <label>
className="form-select form-select-sm" <select
name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
className="form-select form-select-sm"
onChange={handleFilterEmployee} onChange={handleFilterEmployee}
// value={false} // value={false}
aria-label="" aria-label=""
defaultValue="true" defaultValue="true"
> >
<option value="true">Active Employee</option> <option value="true">Active Employee</option>
<option value="false">InActive Employee</option> <option value="false">In-Active Employee</option>
</select> </select>
</label> </label>
</div> </div>
<button <button
type="button" type="button"
className="link-button link-button-sm m-1" className={`link-button link-button-sm m-1 ${HasAssignUserPermission ? "":"d-none"}`}
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#user-model" data-bs-target="#user-model"
onClick={() => openModel()} onClick={() => openModel()}
@ -236,7 +226,6 @@ const Teams = ({ project }) => {
{item.firstName} {item.lastName} {item.firstName} {item.lastName}
</span> </span>
</a> </a>
{/* <small>{item.phoneNumber}</small> */}
</div> </div>
</div> </div>
</td> </td>
@ -255,10 +244,10 @@ const Teams = ({ project }) => {
</td> </td>
<td> <td>
<span className="badge bg-label-primary me-1"> <span className="badge bg-label-primary me-1">
{getRole(item.roleID)} {getRole(item.jobRoleId)}
</span> </span>
</td> </td>
<td> <td>
{item.isActive && ( {item.isActive && (
<button <button
aria-label="Delete" aria-label="Delete"
@ -271,7 +260,7 @@ const Teams = ({ project }) => {
<i className="bx bx-trash me-1 text-danger"></i>{" "} <i className="bx bx-trash me-1 text-danger"></i>{" "}
</button> </button>
)} )}
{!item.isActive && (<span>Not in project</span>)} {!item.isActive && <span>Not in project</span>}
</td> </td>
</tr> </tr>
))} ))}
@ -288,3 +277,260 @@ const Teams = ({ project }) => {
}; };
export default Teams; export default Teams;
// const Teams = ({ project }) => {
// const dispatch = useDispatch()
// const {data, loading} = useMaster()
// const navigate = useNavigate()
// const [isModalOpen, setIsModelOpen] = useState(false);
// const [error, setError] = useState("");
// const [empJobRoles, setEmpJobRoles] = useState([]);
// const [clearFormTrigger, setClearFormTrigger] = useState(false);
// const [employees, setEmployees] = useState([]);
// const [ filteredEmployees, setFilteredEmployees ] = useState( [] );
// useEffect( () =>
// {
// dispatch( changeMaster( 'Job Role' ) )
// },[dispatch])
// const fetchEmployees = useCallback(async () => {
// try {
// const response = await ProjectRepository.getProjectAllocation(project.id);
// setEmployees(response.data);
// setFilteredEmployees(response.data.filter((emp) => emp.isActive));
// } catch (error) {
// console.error(error);
// setError("Failed to fetch data.");
// }
// }, [project.id]);
// useEffect(() => {
// fetchEmployees();
// }, [fetchEmployees]);
// useEffect(() => {
// if (data) setEmpJobRoles(data);
// }, [ data ] );
// const submitAllocations = (items) => {
// ProjectRepository.manageProjectAllocation(items)
// .then((response) => {
// showToast("Details updated successfully.", "success");
// setClearFormTrigger(true);
// fetchEmployees();
// })
// .catch((error) => {
// showToast(error.message, "error");
// });
// };
// const removeAllocation = (item) => {
// submitAllocations([
// {
// empID: item.employeeId,
// jobRoleId: item.jobRoleId,
// projectId: project.id,
// status: false,
// },
// ]);
// };
// const handleEmpAlicationFormSubmit = (allocaionObj) => {
// console.log("Form submitted:", allocaionObj);
// let items = allocaionObj.map((item) => {
// return {
// empID: item.empID,
// jobRoleId: item.jobRoleId,
// projectId: project.id,
// status: true,
// };
// });
// submitAllocations(items)
// };
// const getRole = ( jobRoleId ) =>
// {
// if (loading) return "Loading...";
// if (!Array.isArray(empJobRoles)) return "Unassigned";
// if (!jobRoleId) return "Unassigned";
// const role = empJobRoles.find((b) => b.id == jobRoleId);
// return role ? role.name : "Unassigned";
// };
// const openModel = () => {
// setIsModelOpen(true);
// };
// const onModelClose = () => {
// setIsModelOpen(false);
// const modalElement = document.getElementById("user-model");
// if (modalElement) {
// modalElement.classList.remove("show");
// modalElement.style.display = "none";
// document.body.classList.remove("modal-open");
// document.querySelector(".modal-backdrop").remove();
// }
// const modalBackdropElement = document.querySelector('.modal-backdrop');
// if (modalBackdropElement) {
// modalBackdropElement.remove();
// }
// document.body.style.overflow = 'auto'
// };
// const handleFilterEmployee = (e) => {
// const filterValue = e.target.value === "true";
// setFilteredEmployees(
// employees.filter((emp) => emp.isActive === filterValue)
// );
// };
// return (
// <>
// {isModalOpen && (
// <div
// className={`modal fade `}
// id="user-model"
// tabIndex="-1"
// aria-hidden="true"
// >
// <MapUsers
// projectId={project.id}
// onClose={onModelClose}
// empJobRoles={empJobRoles}
// onSubmit={handleEmpAlicationFormSubmit}
// allocation={employees}
// clearTrigger={clearFormTrigger}
// onClearComplete={() => setClearFormTrigger(false)}
// ></MapUsers>
// </div>
// )}
// <div className="card card-action mb-6">
// <div className="card-body">
// <div className="row">
// <div className="col-12 d-flex justify-content-between mb-1">
// <div
// className="dataTables_length text-start py-2 px-2"
// id="DataTables_Table_0_length"
// >
// <label>
// <select
// name="DataTables_Table_0_length"
// aria-controls="DataTables_Table_0"
// className="form-select form-select-sm"
// onChange={handleFilterEmployee}
// aria-label=""
// defaultValue="true"
// >
// <option value="true">Active Employee</option>
// <option value="false">In-Active Employee</option>
// </select>
// </label>
// </div>
// <button
// type="button"
// className="link-button link-button-sm m-1"
// data-bs-toggle="modal"
// data-bs-target="#user-model"
// onClick={() => openModel()}
// >
// <i className="bx bx-plus-circle me-2"></i>
// Assign Users
// </button>
// </div>
// </div>
// <div className="table-responsive text-nowrap">
// {employees && employees.length > 0 ? (
// <table className="table ">
// <thead>
// <tr>
// <th>Name</th>
// <th>Assigned Date</th>
// <th>Release Date</th>
// <th>Role</th>
// <th>Actions</th>
// </tr>
// </thead>
// <tbody className="table-border-bottom-0">
// {filteredEmployees &&
// filteredEmployees.map((item) => (
// <tr key={item.id}>
// <td>
// <div className="d-flex justify-content-start align-items-center">
// <Avatar
// firstName={item.firstName}
// lastName={item.lastName}
// ></Avatar>
// <div className="d-flex flex-column">
// <a
// href="#"
// onClick={()=> navigate(`/employee/${item.employeeId}`)}
// className="text-heading text-truncate"
// >
// <span className="fw-medium">
// {item.firstName} {item.lastName}
// </span>
// </a>
// </div>
// </div>
// </td>
// <td>
// {" "}
// {moment(item.allocationDate).format(
// "DD-MMM-YYYY"
// )}{" "}
// </td>
// <td>
// {item.reAllocationDate
// ? moment(item.reAllocationDate).format("DD-MMM-YYYY"): "Present"}
// </td>
// <td>
// <span className="badge bg-label-primary me-1">
// {getRole(item.jobRoleId)}
// </span>
// </td>
// <td>
// {item.isActive && (
// <button
// aria-label="Delete"
// type="button"
// title="Remove from project"
// className="btn p-0 dropdown-toggle hide-arrow"
// onClick={() => removeAllocation(item)}
// >
// {" "}
// <i className="bx bx-trash me-1 text-danger"></i>{" "}
// </button>
// )}
// {!item.isActive && <span>Not in project</span>}
// </td>
// </tr>
// ))}
// </tbody>
// </table>
// ) : (
// <span>No employees assigned to the project</span>
// )}
// </div>
// </div>
// </div>
// </>
// );
// };
// export default Teams;

View File

@ -25,8 +25,8 @@ const Avatar = ({ firstName, lastName }) => {
return bgClasses[randomIndex]; return bgClasses[randomIndex];
} }
return ( return (
<div> <>
<div className="avatar-wrapper"> <div className="avatar-wrapper p-1">
<div className="avatar me-2"> <div className="avatar me-2">
<span <span
className={`avatar-initial rounded-circle ${getRandomBootstrapBgClass()}`} className={`avatar-initial rounded-circle ${getRandomBootstrapBgClass()}`}
@ -35,7 +35,7 @@ const Avatar = ({ firstName, lastName }) => {
</span> </span>
</div> </div>
</div> </div>
</div> </>
); );
}; };

View File

@ -8,16 +8,16 @@ const Breadcrumb = ({ data }) => {
<ol className="breadcrumb breadcrumb-style1"> <ol className="breadcrumb breadcrumb-style1">
{data.map((item) => {data.map((item) =>
item.link ? ( item.link ? (
<li className="breadcrumb-item"> <li className="breadcrumb-item cursor-pointer">
<a <a
aria-label="pagination link link-underline-primary" aria-label="pagination link link-underline-primary "
onClick={()=>navigate(item.link)} onClick={()=>navigate(item.link)}
> >
{item.label} {item.label}
</a> </a>
</li> </li>
) : ( ) : (
<li className="breadcrumb-item active"> {item.label}</li> <li className="breadcrumb-item active "> {item.label}</li>
) )
)} )}
</ol> </ol>

View File

@ -1,11 +1,10 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
const TimePicker = ({ label, onChange, interval = 10 }) => { const TimePicker = ({ label, onChange, interval = 10, value }) => {
const [time, setTime] = useState(""); const [time, setTime] = useState(value || "");
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null); const dropdownRef = useRef(null);
// Function to get the current time rounded to the nearest interval
const getCurrentTime = () => { const getCurrentTime = () => {
const now = new Date(); const now = new Date();
const minutes = now.getMinutes(); const minutes = now.getMinutes();
@ -13,11 +12,9 @@ const TimePicker = ({ label, onChange, interval = 10 }) => {
now.setMinutes(roundedMinutes); now.setMinutes(roundedMinutes);
now.setSeconds(0); now.setSeconds(0);
now.setMilliseconds(0); now.setMilliseconds(0);
return now; return now;
}; };
// Function to format time to hh:mm AM/PM
const formatTime = (date) => { const formatTime = (date) => {
let hours = date.getHours(); let hours = date.getHours();
const minutes = date.getMinutes(); const minutes = date.getMinutes();
@ -58,10 +55,12 @@ const TimePicker = ({ label, onChange, interval = 10 }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
// Set the default time to the nearest interval of current time if (!value) {
const defaultTime = formatTime(getCurrentTime()); const defaultTime = formatTime(getCurrentTime());
setTime(defaultTime); setTime(defaultTime);
}, [interval]); if (onChange) onChange(defaultTime);
}
}, [value, interval, onChange]);
return ( return (
<div className="position-relative w-100" ref={dropdownRef}> <div className="position-relative w-100" ref={dropdownRef}>

View File

@ -33,21 +33,16 @@ const CreateJobRole = ({onClose}) => {
const onSubmit = (data) => { const onSubmit = (data) => {
setIsLoading(true) setIsLoading(true)
const result = { const result = {
name: data.role, name: data.role,
description: data.description, description: data.description,
}; };
MasterRespository.createJobRole(result).then((resp)=>{ MasterRespository.createJobRole(result).then((resp)=>{
setIsLoading(false) setIsLoading(false)
resetForm() resetForm()
const cachedData = getCachedData("JobRole"); const cachedData = getCachedData("Job Role");
const updatedData = [...cachedData, resp?.data]; const updatedData = [...cachedData, resp?.data];
cacheData("Job Role", updatedData);
cacheData("JobRole", updatedData);
showToast("JobRole Added successfully.", "success"); showToast("JobRole Added successfully.", "success");
onClose() onClose()

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useFeatures } from "../../hooks/useMasterRole"; import { useFeatures } from "../../hooks/useMasterRole";
import { useForm ,Controller} from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { set, z } from 'zod'; import { set, z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { MasterRespository } from "../../repositories/MastersRepository"; import { MasterRespository } from "../../repositories/MastersRepository";
@ -56,13 +56,13 @@ const onSubmit = (values) => {
}; };
MasterRespository.createRole(result).then((resp)=>{ MasterRespository.createRole(result).then((resp)=>{
console.log(resp)
setIsLoading(false) setIsLoading(false)
const cachedData = getCachedData("JobRole"); const cachedData = getCachedData( "Role" );
const updatedData = [...cachedData, resp?.data]; console.log(cachedData)
const updatedData = [...cachedData, resp];
cacheData("Role", updatedData); cacheData("Role", updatedData);
showToast("Role Added successfully.", "success"); showToast("Role Added successfully.", "success");
onClose() onClose()
@ -70,7 +70,7 @@ const onSubmit = (values) => {
showToast(err.message, "error"); showToast(err.message, "error");
setIsLoading(false) setIsLoading(false)
}) })
NewRoleAdded(true)
}; };
@ -106,7 +106,7 @@ const onSubmit = (values) => {
{masterFeatures.map((feature) => ( {masterFeatures.map((feature) => (
<> <React.Fragment key={feature.id}>
<div className="row my-1" key={feature.id} style={{ marginLeft: "0px" }}> <div className="row my-1" key={feature.id} style={{ marginLeft: "0px" }}>
@ -136,7 +136,7 @@ const onSubmit = (values) => {
</div> </div>
<hr className="hr my-1 py-1" /> <hr className="hr my-1 py-1" />
</> </React.Fragment>
))} ))}
{errors.selectedPermissions && ( {errors.selectedPermissions && (

View File

@ -44,13 +44,13 @@ const EditJobRole = ({data,onClose}) => {
MasterRespository.updateJobRole(data?.id,result).then((resp)=>{ MasterRespository.updateJobRole(data?.id,result).then((resp)=>{
setIsLoading(false) setIsLoading(false)
showToast("JobRole Update successfully.", "success"); showToast("JobRole Update successfully.", "success");
const cachedData = getCachedData("JobRole"); const cachedData = getCachedData("Job Role");
if (cachedData) { if (cachedData) {
const updatedData = cachedData.map((role) => const updatedData = cachedData.map((role) =>
role.id === data?.id ? { ...role, ...resp.data } : role role.id === data?.id ? { ...role, ...resp.data } : role
); );
cacheData("JobRole", updatedData); cacheData("Job Role", updatedData);
} }
onClose() onClose()

View File

@ -96,20 +96,20 @@ const EditMaster=({master,onClose})=> {
description: data.description, description: data.description,
featuresPermission: updatedPermissions, featuresPermission: updatedPermissions,
}; };
MasterRespository.updateRoles(master?.item?.id, updatedRole).then((resp)=>{ MasterRespository.updateRoles(master?.item?.id, updatedRole).then((resp)=>{
setIsLoading(false) setIsLoading( false )
// const cachedData = getCachedData("Role");
// if (cachedData) {
// const updatedData = cachedData.map((role) =>
// role.id === data?.id ? { ...role, ...resp } : role const cachedData = getCachedData("Role");
// );
if (cachedData) {
const updatedData = cachedData.map((role) =>
role.id === resp.data?.id ? { ...role, ...resp.data } : role
);
// cacheData("Role", updatedData); cacheData("Role", updatedData);
// } }
showToast("Role Update successfully.", "success"); showToast("Role Update successfully.", "success");
onClose() onClose()
}).catch((Err)=>{ }).catch((Err)=>{

View File

@ -13,13 +13,14 @@ const MasterModal = ({ modaldata ,closeModal}) => {
<div <div
className="modal fade" className="modal fade"
id="master-modal" id="master-modal"
tabindex="-1" tabIndex="-1"
aria-hidden="true" aria-hidden="true"
role="dialog" role="dialog"
aria-labelledby="modalToggleLabel"
> >
<div <div
className={`modal-dialog mx-sm-auto mx-1 ${ className={`modal-dialog mx-sm-auto mx-1 ${
modaldata?.type === "delete" ? "modal-md" : "modal-lg" modaldata?.modalType === "delete" ? "modal-md" : "modal-lg"
} modal-simple ` } } modal-simple ` }
> >
<div className="modal-content"> <div className="modal-content">

View File

@ -1,4 +1,4 @@
export const List = [{id:1, name: "Role"},{id:2, name: "Job Role"}, {id:3, name: "Status"},{id:4,name:"Module"}] export const mastersList = [{id:1, name: "Role"},{id:2, name: "Job Role"}, {id:3, name: "Status"},{id:4,name:"Module"}]
export const dailyTask = [ export const dailyTask = [
{ {

View File

@ -5,36 +5,6 @@ import { useSelector } from "react-redux";
const useMasterData = (key, fetchFunction) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const fetchData = async () => {
try {
const cachedData = getCachedData(key);
if (cachedData) {
setData(cachedData);
} else {
const response = await fetchFunction();
setData(response);
cacheData(key, response);
}
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
};
fetchData();
}, [key, fetchFunction]);
return { data, loading, error };
};
@ -52,7 +22,6 @@ const useMaster = () => {
const [data, setData] = useState([]); const [data, setData] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
if (!selectedMaster) return; if (!selectedMaster) return;
@ -68,6 +37,7 @@ const useMaster = () => {
switch (selectedMaster) { switch (selectedMaster) {
case "Role": case "Role":
response = await MasterRespository.getRoles(); response = await MasterRespository.getRoles();
response = response.data;
break; break;
case "Job Role": case "Job Role":
response = await MasterRespository.getJobRole(); response = await MasterRespository.getJobRole();
@ -94,7 +64,11 @@ const useMaster = () => {
} }
}; };
fetchData(); if ( selectedMaster )
{
fetchData();
}
}, [selectedMaster]); }, [selectedMaster]);

View File

@ -5,7 +5,7 @@ import AttendanceRepository from "../repositories/AttendanceRepository";
export const useAttendace =(projectId)=>{ export const useAttendace =(projectId)=>{
const [attendance, setAttendance] = useState([]); const [attendance, setAttendance] = useState([]);
const[loading,setLoading] = useState(true) const[loading,setLoading] = useState(false)
const [error, setError] = useState(null); const [error, setError] = useState(null);
const fetchData = () => { const fetchData = () => {
@ -24,7 +24,7 @@ export const useAttendace =(projectId)=>{
}) })
} else { } else {
setAttendance(Attendance_cache.data); setAttendance(Attendance_cache.data);
setLoading(false)
} }
}; };
@ -51,7 +51,6 @@ export const useEmployeeAttendacesLog = (id) => {
setLoading(true) setLoading(true)
AttendanceRepository.getAttendanceLogs(id).then((response)=>{ AttendanceRepository.getAttendanceLogs(id).then((response)=>{
setLogs(response.data) setLogs(response.data)
console.log("logs",response)
cacheData("AttendanceLogs", { data: response.data, id }) cacheData("AttendanceLogs", { data: response.data, id })
setLoading(false) setLoading(false)
}).catch((error)=>{ }).catch((error)=>{

View File

@ -1,7 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { timeElapsed } from '../utils/dateUtils'; import { timeElapsed } from '../utils/dateUtils';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { THRESH_HOLD } from '../utils/constants'; import {THRESH_HOLD} from '../utils/constants';
export const ACTIONS = { export const ACTIONS = {
CHECK_IN: 0, CHECK_IN: 0,
CHECK_OUT: 1, CHECK_OUT: 1,

View File

@ -2,19 +2,60 @@ import { useEffect, useState } from "react";
import { cacheData, getCachedData } from "../slices/apiDataManager"; import { cacheData, getCachedData } from "../slices/apiDataManager";
import { RolesRepository } from "../repositories/MastersRepository"; import { RolesRepository } from "../repositories/MastersRepository";
import EmployeeRepository from "../repositories/EmployeeRepository"; import EmployeeRepository from "../repositories/EmployeeRepository";
import ProjectRepository from "../repositories/ProjectRepository";
export const useAllEmployees = () =>
{
const [employeesList, setEmployeeList] = useState([]);
const [ loading, setLoading ] = useState( false )
const [ error, setError ] = useState()
const fetchData = async () =>
{
try
{
let EmployeeList_cached = getCachedData( "AllEmployees" )
if ( ! EmployeeList_cached )
{
setLoading(true)
const response = await EmployeeRepository.getAllEmployeeList();
cacheData( "AllEmployees", response.data )
setEmployeeList(response.data)
} else
{
setEmployeeList( EmployeeList_cached )
setLoading(false)
}
} catch ( error )
{
console.error(error);
setError( "Failed to fetch data." );
setLoading(false)
}
}
useEffect( () =>
{
fetchData()
}, [] )
return {employeesList,loading,error}
}
export const useEmployees =(selectedProject)=>{ export const useEmployees =(selectedProject)=>{
const [employees, setEmployeeList] = useState([]); const [employees, setEmployeeList] = useState([]);
const[loading,setLoading] = useState(false) const[loading,setLoading] = useState(true)
const [projects, setProjects] = useState([]); const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => { const fetchData = async (projectid) => {
try { try {
let EmployeeByProject_Cache = getCachedData("employeeListByProject") let EmployeeByProject_Cache = getCachedData("employeeListByProject")
if(!EmployeeByProject_Cache || !EmployeeByProject_Cache.projectId === projectid) { if(!EmployeeByProject_Cache || !EmployeeByProject_Cache.projectId === projectid) {
setLoading(true)
EmployeeRepository.getEmployeeListByproject(projectid) EmployeeRepository.getEmployeeListByproject(projectid)
.then((response) => { .then((response) => {
setEmployeeList(response); setEmployeeList(response);
@ -23,11 +64,12 @@ export const useEmployees =(selectedProject)=>{
.catch((error) => { .catch((error) => {
setError("Failed to fetch data."); setError("Failed to fetch data.");
}); });
setLoading(false)
}else{ }else{
setEmployeeList(EmployeeByProject_Cache.data) setEmployeeList(EmployeeByProject_Cache.data)
setLoading(false);
} }
setLoading(false)
} catch (err) { } catch (err) {
setError("Failed to fetch data."); setError("Failed to fetch data.");
@ -43,13 +85,13 @@ export const useEmployees =(selectedProject)=>{
},[selectedProject]) },[selectedProject])
return {employees,loading,projects} return {employees,loading,projects,reCallAllEmployee}
} }
export const useEmployeeRoles = (employeeId)=>{ export const useEmployeeRoles = (employeeId)=>{
const [loading,setLoading] = useState(true) const [loading,setLoading] = useState(true)
const[error, setError] =useState() const[error, setError] =useState()
const [employeeRoles,setEmployeeRoles] = useState() const [employeeRoles,setEmployeeRoles] = useState([])
const fetchData = async (employeeid) => { const fetchData = async (employeeid) => {
try { try {
@ -78,41 +120,144 @@ return {employeeRoles,loading,error}
} }
export const useEmployeesByProject=(projectId)=>{ export const useEmployeesByProject=(projectId)=>{
const [loading,setLoading] = useState(true) const [loading,setLoading] = useState(false)
const[error, setError] =useState() const[error, setError] =useState()
const [employees,setEmployees] = useState() const [employees,setEmployees] = useState([])
const fetchData = async () => { const fetchData = async () => {
const Employees_cache = getCachedData("employeeListByProject"); const Employees_cache = getCachedData("employeeListByProject");
if(!Employees_cache || Employees_cache.projectId !== projectId){ if(!Employees_cache || Employees_cache.projectId !== projectId){
setEmployees(true)
EmployeeRepository.getEmployeeListByproject(projectId) ProjectRepository.getEmployeesByProject(projectId)
.then((response) => { .then( ( response ) =>
setEmployees(response); {
cacheData("employeeListByProject", { data: response, projectId }) setEmployees(response.data);
cacheData("employeeListByProject", { data: response.data, projectId })
setLoading(false) setLoading(false)
}) })
.catch((error) => { .catch((error) => {
setError("Failed to fetch data."); setError("Failed to fetch data.");
setLoading(false) setLoading(false)
}); });
}else{ }else{
setEmployees(Employees_cache.data) setEmployees(Employees_cache.data)
setLoading(false) setLoading(false)
} }
}; };
useEffect(()=>{ useEffect(()=>{
fetchData(projectId); fetchData(projectId);
},[projectId]) },[projectId])
return {employees,loading,error,recallProjectEmplloyee:fetchData}
return {employees,loading,error} }
export const useEmployeesAllOrByProjectId = (projectId) => {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () =>
{
if (projectId) {
const Employees_cache = getCachedData("employeeListByProject");
if (!Employees_cache || Employees_cache.projectId !== projectId) {
setLoading(true);
setError(null);
try {
const response = await ProjectRepository.getEmployeesByProject(projectId);
setEmployees(response.data);
cacheData("employeeListByProject", { data: response.data, projectId });
setLoading(false);
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
} else {
setEmployees(Employees_cache.data);
setLoading(false);
}
} else {
const employeesCache = getCachedData("allEmployeeList");
if (!employeesCache) {
setLoading(true);
setError(null);
try {
const response = await EmployeeRepository.getAllEmployeeList();
setEmployees(response.data);
cacheData("allEmployeeList", { data: response.data });
setLoading(false);
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
} else {
setEmployees(employeesCache.data);
setLoading(false);
}
}
};
useEffect(() => {
fetchData(); // Fetch data when the component mounts or projectId changes
}, [projectId]); // Re-fetch when projectId changes
return {
employees,
loading,
error,
recallEmployeeData: fetchData,
};
};
export const useEmployeeProfile =(employeeId)=>{
const [loading,setLoading] = useState(true)
const[error, setError] =useState()
const [employee,setEmployees] = useState()
const fetchData = async () => {
const Employee_cache = getCachedData("employeeProfile");
if(!Employee_cache || Employee_cache.employeeId !== employeeId){
EmployeeRepository.getEmployeeProfile(employeeId)
.then((response) => {
setEmployees(response.data);
cacheData("employeeProfile", { data: response.data, employeeId })
setLoading(false)
})
.catch((error) => {
setError("Failed to fetch data.");
setLoading(false)
});
}else{
setEmployees(Employee_cache.data)
setLoading(false)
}
};
useEffect(()=>{
fetchData(employeeId);
},[employeeId])
return {employee,loading,error}
} }

View File

@ -0,0 +1,11 @@
import { useProfile } from "./useProfile"
export const useHasUserPermission = (permission) => {
const { profile } = useProfile();
if (profile && permission && typeof permission === "string") {
return profile?.featurePermissions.includes(permission);
}
return false;
};

42
src/hooks/useProfile.js Normal file
View File

@ -0,0 +1,42 @@
import {useState,useEffect} from "react";
import AuthRepository from "../repositories/AuthRepository";
import {cacheProfileData, getCachedProfileData} from "../slices/apiDataManager";
export const useProfile =()=>{
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect( () => {
const fetchData = async () => {
const profile_cache = getCachedProfileData()
// if (!profile_cache) {
setLoading(true)
AuthRepository.profile()
.then((response) => {
setProfile(response)
cacheProfileData(response)
setLoading(false);
})
.catch((error) => {
setLoading(false)
console.error(error);
setError("Failed to fetch data.");
});
// } else {
// setProfile(profile_cache);
// }
};
fetchData()
},[])
return { profile,loading,error}
}

View File

@ -16,9 +16,7 @@ export const useProjects =()=>{
const projects_cache = getCachedData("projectslist"); const projects_cache = getCachedData("projectslist");
if (!projects_cache) { if (!projects_cache) {
setLoading(true) setLoading(true)
ProjectRepository.getProjectList() ProjectRepository.getProjectList()
.then((response) => { .then((response) => {
setProjects(response); setProjects(response);
@ -35,14 +33,88 @@ export const useProjects =()=>{
} else { } else {
if (!projects.length) setProjects(projects_cache); if (!projects.length) setProjects(projects_cache);
} }
}; };
useEffect(()=>{ useEffect(()=>{
fetchData() fetchData()
},[]) },[])
return { projects,loading,error} return { projects,loading,error,refetch:fetchData}
}
export const useEmployeesByProjectAllocated = ( selectedProject ) =>
{
const [projectEmployees, setEmployeeList] = useState([]);
const[loading,setLoading] = useState(true)
const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => {
try {
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated")
if(!EmployeeByProject_Cache || !EmployeeByProject_Cache.projectId === projectid) {
let response = await ProjectRepository.getProjectAllocation(projectid)
setEmployeeList(response.data);
cacheData("empListByProjectAllocated",{data:response.data,projectId:projectid});
setLoading(false)
}else{
setEmployeeList(EmployeeByProject_Cache.data)
setLoading(false)
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false)
}
};
useEffect(()=>{
if(selectedProject){
fetchData(selectedProject);
}
},[selectedProject])
return {projectEmployees,loading,projects}
}
export const useProjectDetails =(projectId)=>{
const [projects_Details, setProject_Details] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchData = async () => {
setLoading(true)
const project_cache = getCachedData(`projectinfo-${projectId}`);
if (!project_cache) {
ProjectRepository.getProjectByprojectId(projectId)
.then( ( response ) =>
{
setProject_Details(response);
cacheData( `projectinfo-${ projectId }`, response );
setLoading(false)
})
.catch((error) => {
console.error(error);
setError( "Failed to fetch data." );
setLoading(false)
});
} else {
setProject_Details( project_cache );
setLoading(false)
}
};;
useEffect(()=>{
fetchData()
},[projectId])
return { projects_Details,loading,error,refetch:fetchData}
} }

View File

@ -1,9 +1,9 @@
import { useState, useMemo } from "react"; import { useState } from "react";
const useSearch = (data, query) => { const useSearch = (data, query) => {
const [searchQuery, setSearchQuery] = useState(query); const [searchQuery, setSearchQuery] = useState(query);
const filteredData = useMemo(() => { const filteredData = () => {
if (!searchQuery) { if (!searchQuery) {
return data; return data;
} }
@ -14,10 +14,10 @@ const useSearch = (data, query) => {
item.lastName.toLowerCase().includes(searchQuery.toLowerCase()) || item.lastName.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.phoneNumber.toLowerCase().includes(searchQuery.toLowerCase()) item.phoneNumber.toLowerCase().includes(searchQuery.toLowerCase())
); );
}, [data, searchQuery]); };
return { return {
filteredData, filteredData: filteredData(),
setSearchQuery, setSearchQuery,
}; };
}; };

View File

@ -80,4 +80,23 @@ button:focus-visible {
} }
.danger-text { .danger-text {
color: #f08080; color: #f08080;
} }
/* for breadcrumb It Important*/
.breadcrumb-style1 .breadcrumb-item + .breadcrumb-item::before {
content: "";
font-size: 11.8px;
line-height: 1.4;
}
.li-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
.name-column,
.role-column,
.checkbox-column {
flex: 1;
}

View File

@ -10,12 +10,12 @@ const HomeLayout = () => {
Main(); Main();
}, []); }, []);
return ( return (
<div className="layout-wrapper layout-content-navbar"> <div className="layout-wrapper layout-content-navbar" >
<div className="layout-container"> <div className="layout-container" >
<Sidebar /> <Sidebar />
<div className="layout-page "> <div className="layout-page ">
<Header /> <Header />
<div className="content-wrapper"> <div className="content-wrapper" >
<Outlet /> <Outlet />
<Footer /> <Footer />
</div> </div>

View File

@ -1,67 +1,24 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData, getCachedProfileData } from "../../slices/apiDataManager";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import AttendanceLog from "../../components/Activities/AttendcesLogs"; import AttendanceLog from "../../components/Activities/AttendcesLogs";
import Attendance from "../../components/Activities/Attendance"; import Attendance from "../../components/Activities/Attendance";
import AttendanceModel from "../../components/Activities/AttendanceModel"; import AttendanceModel from "../../components/Activities/AttendanceModel";
import AttendanceRepository from "../../repositories/AttendanceRepository";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import Regularization from "../../components/Activities/Regularization"; import Regularization from "../../components/Activities/Regularization";
import { useAttendace } from "../../hooks/useAttendance"; import { useAttendace } from "../../hooks/useAttendance";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import {markCurrentAttendance} from "../../slices/apiSlice/attendanceAllSlice";
import { hasUserPermission } from "../../utils/authUtils";
const AttendancePage = () =>
// const attendance1 = [ {
const loginUser = getCachedProfileData()
// {
// "id": 0,
// "employeeId": 4,
// "firstName": "Umesh",
// "lastName": "Desai",
// "employeeAvatar": null,
// "checkInTime": null,
// "checkOutTime": null,
// "activity": 0
// },
// {
// "id": 0,
// "employeeId": 7,
// "firstName": "ramesh",
// "lastName": "sadsad",
// "employeeAvatar": null,
// "checkInTime": null,
// "checkOutTime": null,
// "activity": 0
// },
// {
// "id": 0,
// "employeeId": 1,
// "firstName": "Administrator",
// "lastName": "",
// "employeeAvatar": null,
// "checkInTime": "2025-03-08T08:55:00",
// "checkOutTime":"2025-03-08T08:55:00",
// "activity": 2
// },
// {
// "id": 0,
// "employeeId": 2,
// "firstName": "ssda",
// "lastName": "sadsad",
// "employeeAvatar": null,
// "checkInTime": null,
// "checkOutTime": null,
// "activity": 0
// }
// ]
const AttendancePage = () => {
const selectedProject = useSelector((store)=>store.localVariables.projectId) const selectedProject = useSelector((store)=>store.localVariables.projectId)
const {projects,loading:projectLoading} = useProjects() const {projects,loading:projectLoading} = useProjects()
const {attendance} = useAttendace(selectedProject) const {attendance,loading:attLoading} = useAttendace(selectedProject)
const[attendances,setAttendances] = useState() const[attendances,setAttendances] = useState()
const [empRoles, setEmpRoles] = useState(null); const [empRoles, setEmpRoles] = useState(null);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
@ -74,6 +31,7 @@ const AttendancePage = () => {
date: new Date().toLocaleDateString(), date: new Date().toLocaleDateString(),
}); });
const getRole = (roleId) => { const getRole = (roleId) => {
if (!empRoles) return "Unassigned"; if (!empRoles) return "Unassigned";
if (!roleId) return "Unassigned"; if (!roleId) return "Unassigned";
@ -81,8 +39,6 @@ const AttendancePage = () => {
return role ? role.role : "Unassigned"; return role ? role.role : "Unassigned";
}; };
const openModel = () => { const openModel = () => {
setIsCreateModalOpen(true); setIsCreateModalOpen(true);
}; };
@ -106,43 +62,23 @@ const AttendancePage = () => {
}; };
const handleSubmit = (formData) => { const handleSubmit = ( formData ) =>{
let newRecordAttendance = { dispatch( markCurrentAttendance( formData ) ).then( ( action ) =>
id: null, {
comment:formData.description,
employeeID: formData.employeeId ,
projectId:selectedProject,
date:new Date().toISOString(),
markTime:formData.time,
latitude: formData.latitude.toString(),
longitude: formData.longitude.toString(),
action:formData.action,
image:null
}
console.log(newRecordAttendance)
AttendanceRepository.markAttendance(newRecordAttendance).then((response) => {
console.log(response)
const updatedAttendance = attendances.map(item => const updatedAttendance = attendances.map(item =>
item.employeeId === response.data.employeeId item.employeeId === action.payload.employeeId
? { ...item, ...response.data } ? { ...item, ...action.payload }
: item : item
); );
cacheData("Attendance", { data: updatedAttendance, projectId: selectedProject }) cacheData("Attendance", { data: updatedAttendance, projectId: selectedProject })
setAttendances(updatedAttendance) setAttendances(updatedAttendance)
showToast("Attedance Marked Successfully","success") showToast("Attedance Marked Successfully","success")
console.log(updatedAttendance)
}) })
.catch((error) => { .catch( ( error ) =>
console.error(error); {
showToast(error.message,"error") showToast(error.message,"error")
setError("Failed to fetch data.");
}); });
}; };
@ -155,6 +91,7 @@ const AttendancePage = () => {
setAttendances(attendance) setAttendances(attendance)
},[attendance]) },[attendance])
return ( return (
<> <>
{isCreateModalOpen && modelConfig && ( {isCreateModalOpen && modelConfig && (
@ -182,21 +119,24 @@ const AttendancePage = () => {
className="dataTables_length text-start py-2 px-2" className="dataTables_length text-start py-2 px-2"
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
> >
<label> {
<select ((loginUser && loginUser?.projects.length > 1) ) && (<label>
name="DataTables_Table_0_length" <select
aria-controls="DataTables_Table_0" name="DataTables_Table_0_length"
className="form-select form-select-sm" aria-controls="DataTables_Table_0"
value={selectedProject} className="form-select form-select-sm"
onChange={(e)=>dispatch(setProjectId(e.target.value))} value={selectedProject}
aria-label="" onChange={(e)=>dispatch(setProjectId(e.target.value))}
> aria-label=""
{!projectLoading && projects?.map((project)=>( >
<option value={project.id}>{project.name}</option> {!projectLoading && projects?.filter(project =>
))} loginUser?.projects?.map(Number).includes(project.id)).map((project)=>(
{projectLoading && <option disabled>Loading...</option> } <option value={project.id}>{project.name}</option>
</select> ))}
</label> {projectLoading && <option value="Loading..." disabled>Loading...</option> }
</select>
</label>)
}
</div> </div>
</ul> </ul>
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
@ -224,25 +164,30 @@ const AttendancePage = () => {
Logs Logs
</button> </button>
</li> </li>
<li class="nav-item"> {hasUserPermission("52c9cf54-1eb2-44d2-81bb-524cf29c0a94") && (
<button <li class="nav-item">
type="button" <button
className="nav-link " type="button"
role="tab" className="nav-link "
data-bs-toggle="tab" role="tab"
data-bs-target="#navs-top-messages" data-bs-toggle="tab"
aria-controls="navs-top-messages" data-bs-target="#navs-top-messages"
aria-selected="false"> aria-controls="navs-top-messages"
Regularization aria-selected="false">
</button> Regularization
</button>
</li>
</li>
)}
</ul> </ul>
<div class="tab-content attedanceTabs py-2"> <div class="tab-content attedanceTabs py-2">
{ projects && projects.length > 0 ? ( {projectLoading && (<span>Loading..</span>)}
{(!projectLoading && !attendances) && <span>Not Found</span>}
{ (projects && projects.length > 0 ) && (
<> <>
<div class="tab-pane fade show active py-0" id="navs-top-home" role="tabpanel"> <div className="tab-pane fade show active py-0" id="navs-top-home" role="tabpanel">
<Attendance attendance={attendances} handleModalData={handleModalData} getRole={getRole}/>
<Attendance attendance={attendances} handleModalData={handleModalData} getRole={getRole} />
</div> </div>
<div class="tab-pane fade" id="navs-top-profile" role="tabpanel"> <div class="tab-pane fade" id="navs-top-profile" role="tabpanel">
@ -252,20 +197,14 @@ const AttendancePage = () => {
projectId={selectedProject} projectId={selectedProject}
/> />
</div> </div>
<div class="tab-pane fade" id="navs-top-messages" role="tabpanel"> <div className="tab-pane fade" id="navs-top-messages" role="tabpanel">
<Regularization <Regularization
attendance={attendances} attendance={attendances}
handleRequest ={handleSubmit} handleRequest ={handleSubmit}
/> />
</div> </div>
</> </>
):( )}
<>
{projectLoading && (<span>Loading..</span>)}
{!projectLoading && (<span>Not Found</span>)}
</>
)
}
</div> </div>
</div> </div>

View File

@ -1,19 +1,40 @@
import { useState } from "react"; import { useState } from "react";
import { Link } from "react-router-dom"; import {Link} from "react-router-dom";
import { AuthWrapper } from "./AuthWrapper"
import "./page-auth.css"; import "./page-auth.css";
import { AuthWrapper } from "./AuthWrapper"; import AuthRepository from "../../repositories/AuthRepository";
import showToast from "../../services/toastService";
const ForgotPasswordPage = () => { const ForgotPasswordPage = () => {
const [email, setEmail] = useState(""); const [ email, setEmail ] = useState( "" );
const[loding,setLoading] = useState(false)
const handleChange = (e) => { const handleChange = (e) => {
setEmail(e.target.value); setEmail(e.target.value);
}; };
const handleSubmit = (e) => { const handleSubmit = async ( e ) =>
{
setLoading(true)
e.preventDefault(); e.preventDefault();
// Add logic to handle the form submission (e.g., send reset link) try
console.log("Email submitted:", email); {
const response = await AuthRepository.forgotPassword({email})
if ( response.data && response.success )
{
showToast( response.message, "success" )
} else
{
showToast( response.message, "warning" )
}
setLoading( false )
setEmail("")
} catch ( error )
{
showToast( "User Not Found", "error" )
setLoading(false)
}
}; };
return ( return (
<AuthWrapper> <AuthWrapper>
@ -38,7 +59,7 @@ const ForgotPasswordPage = () => {
/> />
</div> </div>
<button aria-label="Click me" className="btn btn-primary d-grid w-100"> <button aria-label="Click me" className="btn btn-primary d-grid w-100">
Send Reset Link {loding ? "Please Wait...":"Send Reset Link"}
</button> </button>
</form> </form>
<div className="text-center"> <div className="text-center">

View File

@ -4,9 +4,11 @@ import { AuthWrapper } from "./AuthWrapper";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import "./page-auth.css"; import "./page-auth.css";
import AuthRepository from "../../repositories/AuthRepository"; import AuthRepository from "../../repositories/AuthRepository";
import showToast from "../../services/toastService";
const LoginPage = () => { const LoginPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
password: "", password: "",
@ -25,6 +27,7 @@ const LoginPage = () => {
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setLoading(true);
try { try {
let data = { let data = {
@ -32,44 +35,18 @@ const LoginPage = () => {
password: formData.password, password: formData.password,
}; };
AuthRepository.login(data) const response = await AuthRepository.login(data);
.then((response) => { localStorage.setItem("jwtToken", response.data.token);
//console.log("Login Success:", response); localStorage.setItem("refreshToken", response.data.refreshToken);
localStorage.setItem("jwtToken", response.token); setLoading(false);
localStorage.setItem("refreshToken", response.refreshToken); navigate("/dashboard");
// Redirect to dashboard
navigate("/dashboard");
})
.catch((error) => {
console.log("Invalid credentials. Please try again.");
});
// api
// .postPublic("/api/auth/login", data)
// .then((data) => {
// console.log("Login Success:", data);
// localStorage.setItem("jwtToken", data.token);
// localStorage.setItem("refreshToken", data.refreshToken);
// // Redirect to dashboard
// navigate("/dashboard");
// })
// .catch((error) =>
// console.log("Invalid credentials. Please try again.")
// );
// const response = await axiosClient.post("/api/auth/login", data);
// //console.log(response.data);
// // Store tokens in localStorage or cookies
// localStorage.setItem("jwtToken", response.data.token);
// localStorage.setItem("refreshToken", response.data.refreshToken);
// // Redirect to dashboard
// navigate("/employees");
} catch (err) { } catch (err) {
console.log("Unable to proceed. Please try again."); console.log("Unable to proceed. Please try again.");
setLoading(false);
if (err.status === 401) {
showToast(err.response.data);
}
} }
}; };
return ( return (
@ -144,7 +121,7 @@ const LoginPage = () => {
className="btn btn-primary d-grid w-100" className="btn btn-primary d-grid w-100"
type="submit" type="submit"
> >
Sign in {loading ? "Please Wait" : " Sign in"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -5,6 +5,8 @@ import { AuthWrapper } from "./AuthWrapper";
import { useForm,Controller } from 'react-hook-form'; import { useForm,Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import showToast from "../../services/toastService";
import AuthRepository from "../../repositories/AuthRepository";
const mobileNumberRegex = /^(?:\d{10}|\d{3}[-\s]?\d{3}[-\s]?\d{4})$/; const mobileNumberRegex = /^(?:\d{10}|\d{3}[-\s]?\d{3}[-\s]?\d{4})$/;
@ -18,15 +20,25 @@ const registerSchema = z.object({
terms: z.boolean().refine((val) => val === true, { terms: z.boolean().refine((val) => val === true, {
message: "Please accept the terms and conditions.", message: "Please accept the terms and conditions.",
}), }),
}) } )
const RegisterPage = () => { const RegisterPage = () => {
const {register,handleSubmit,formState: { errors }} = useForm({ const {register,handleSubmit,formState: { errors }} = useForm({
resolver:zodResolver(registerSchema) resolver:zodResolver(registerSchema)
}) })
const onSubmit=(data)=>{ const onSubmit= async(data)=>{
console.log(data) try
{
// const response = await AuthRepository.register( data );
showToast("Your Registration SuccessFully !")
} catch ( error )
{
console.log(error)
showToast(error.message,"error")
}
} }
return ( return (
<AuthWrapper> <AuthWrapper>

View File

@ -0,0 +1,139 @@
import { useState } from "react";
import { Link, useSearchParams } from "react-router-dom";
import "./page-auth.css";
import { AuthWrapper } from "./AuthWrapper";
import showToast from "../../services/toastService";
import AuthRepository from "../../repositories/AuthRepository";
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useNavigate } from "react-router-dom";
import {clearAllCache} from "../../slices/apiDataManager";
const resetPasswordSchema = z.object( {
email:z.string().email(),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[a-z]/, 'Password must contain at least one lowercase letter')
.regex(/\d/, 'Password must contain at least one number')
.regex(/[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/, 'Password must contain at least one special character'),
confirmPassword: z.string().min(8, 'Password must be at least 8 characters'),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
} );
const ResetPasswordPage = () =>
{
const [ searchParams ] = useSearchParams();
const [loading,setLoading] = useState(false)
const token = searchParams.get('token');
const navigate = useNavigate()
const {register,handleSubmit,formState: { errors }} = useForm({
resolver:zodResolver(resetPasswordSchema)
})
const onSubmitResetPassword = async(data) => {
try
{
setLoading(true)
const {email, password, confirmPassword} = data;
let reqObject = {
email,
token: token,
newPassword: password
}
let response = await AuthRepository.resetPassword( reqObject );
showToast( "Password Reseted", "success" )
clearAllCache()
setLoading(false)
navigate("/auth/login",{replace:true})
} catch ( error )
{
setLoading(false)
showToast("Token is expries or Invalid ","error")
}
};
return (
<AuthWrapper>
<h4 className="mb-2">Reset Password? 🔒</h4>
<p className="mb-4">Enter your email and new password to update.</p>
<form id="formAuthentication" className="mb-3" onSubmit={handleSubmit(onSubmitResetPassword)}>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email
</label>
<input
type="text"
className="form-control"
id="email"
{...register("email")}
placeholder="Enter your email"
autoFocus
/>
{errors.email && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.email.message}</div>}
</div>
<div className="mb-3">
<label htmlFor="email" className="form-label">
New Password
</label>
<input
type="password"
autoComplete="true"
id="password"
className="form-control"
name="password"
{...register('password')}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
{errors.password && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.password.message}</div>}
</div>
<div className=" mb-3">
<label htmlFor="email" className="form-label">
Repeat New Password
</label>
<input
type="password"
autoComplete="true"
id="password"
className="form-control"
name="confirmPassword"
{...register('confirmPassword')}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
{errors.confirmPassword && <div className="danger-text text-start" style={{fontSize:"12px"}}>{errors.confirmPassword.message}</div>}
</div>
<div className="mb-3 text-start ">
<p className="p-0 m-0"style={{fontSize:'9px'}}>Password must be at least 8 characters</p>
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one uppercase letter</p>
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one number</p>
<p className="p-0 m-0" style={{fontSize:'9px'}}>Password must contain at least one special character</p>
</div>
<button aria-label="Click me" className="btn btn-primary d-grid w-100">
{loading ? "Please Wait...":"Update Password"}
</button>
</form>
<div className="text-center">
<Link
aria-label="Go to Login Page"
to="/auth/login"
className="d-flex align-items-center justify-content-center"
>
<i className="bx bx-chevron-left scaleX-n1-rtl bx-sm"></i>
Back to login
</Link>
</div>
</AuthWrapper>
);
};
export default ResetPasswordPage;

View File

@ -3,105 +3,118 @@ import moment from "moment";
import { Link, NavLink, useNavigate } from "react-router-dom"; import { Link, NavLink, useNavigate } from "react-router-dom";
import Avatar from "../../components/common/Avatar"; import Avatar from "../../components/common/Avatar";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import ProjectRepository from "../../repositories/ProjectRepository";
import EmployeeRepository from "../../repositories/EmployeeRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import ManageEmp from "../../components/Employee/ManageRole"; import ManageEmp from "../../components/Employee/ManageRole";
import {useEmployeesAllOrByProjectId,useAllEmployees} from "../../hooks/useEmployees";
import {useDispatch, useSelector} from "react-redux";
import {setProjectId} from "../../slices/localVariablesSlice";
import { useProjects } from "../../hooks/useProjects";
import { useProfile } from "../../hooks/useProfile";
import {hasUserPermission} from "../../utils/authUtils";
const EmployeeList = () => { const EmployeeList = () =>
const [employees, setEmployeeList] = useState([]); {
const [projects, setProjects] = useState([]);
const [selectedProject, setSelectedProject] = useState(0);
const {profile:loginUser}= useProfile()
const [selectedProject, setSelectedProject] = useState("");
const [ projectsList, setProjectsList ] = useState( [] );
const {projects, loading: projectLoading} = useProjects()
const {employees, loading,setLoading, error} = useEmployeesAllOrByProjectId( selectedProject );
const dispatch = useDispatch()
const [employeeList,setEmployeeList] = useState([])
const [modelConfig, setModelConfig] = useState(); const [modelConfig, setModelConfig] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [data, setData] = useState([]);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(7); const [itemsPerPage] = useState(5);
const [isCreateModalOpen, setIsCreateModalOpen ] = useState( false );
const [searchText,setSearchText] = useState("")
const [filteredData, setFilteredData] = useState([]);
const navigate = useNavigate()
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const openModal = () => { const handleSearch = (e) => {
setIsCreateModalOpen(true); const value = e.target.value.toLowerCase();
}; setSearchText(value);
const projects_cache = getCachedData("projectslist"); if (!employeeList.length) return;
if (!projects_cache) {
ProjectRepository.getProjectList().then((response) => { const results = employeeList.filter((item) =>
setProjects(response); Object.values(item).some((field) =>
cacheData("projectslist", response); field && field.toString().toLowerCase().includes(value)
}); )
} else { );
if (!projects.length) setProjects(projects_cache);
} setFilteredData(results);
const fetchData = async (projectid) => {
try {
setLoading(true);
EmployeeRepository.getEmployeeListByproject(projectid)
.then((response) => {
setEmployeeList(response);
setData(response);
cacheData("employeelist", response);
})
.catch((error) => {
setError("Failed to fetch data.");
});
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
};
const handleManageEmployee = (id) => {
if (id != null) {
window.location.href = "/employee/manage/" + id;
} else {
window.location.href = "/employee/manage";
}
};
const handleProjectChange = (e) => {
const { name, value } = e.target;
setSelectedProject(value);
fetchData(value);
}; };
useEffect(() => { useEffect(() => {
fetchData(selectedProject); if ( loginUser && projects )
}, []); {
const filteredProjects = projects.filter((project) =>
const closeModal = () => { loginUser?.projects?.map(Number).includes(project.id)
setIsCreateModalOpen(false); );
setProjectsList(filteredProjects);
const modalElement = document.getElementById("managerole-modal");
if (modalElement) {
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop").remove();
} }
}; }, [ loginUser, projects ] );
const handleConfigData = (config) => {
setModelConfig(config);
};
useEffect(() => { useEffect(() => {
if (modelConfig !== null) { setCurrentPage( 1 )
openModal();
if (!loading && Array.isArray(employees)) {
setEmployeeList(employees);
setFilteredData(employees);
} }
}, [modelConfig, isCreateModalOpen]);
}, [loading, employees, selectedProject]);
const displayData = searchText ? filteredData :employeeList
const indexOfLastItem = currentPage * itemsPerPage; const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = employees.slice(indexOfFirstItem, indexOfLastItem); const currentItems = Array.isArray(displayData)
? displayData.slice(indexOfFirstItem, indexOfLastItem)
: [];
const paginate = (pageNumber) => setCurrentPage(pageNumber); const paginate = (pageNumber) => setCurrentPage(pageNumber);
const totalPages = Math.ceil(employees.length / itemsPerPage); const totalPages = Array.isArray(displayData)
? Math.ceil(displayData.length / itemsPerPage)
: 0;
const openModal = () => {
setIsCreateModalOpen(true);
};
const closeModal = () => {
setIsCreateModalOpen(false);
const modalElement = document.getElementById("managerole-modal");
if (modalElement) {
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop").remove();
}
};
const handleConfigData = (config) => {
setModelConfig(config);
};
useEffect(() => {
if (modelConfig !== null) {
openModal();
}
}, [ modelConfig, isCreateModalOpen ] );
return ( return (
<> <>
@ -130,19 +143,33 @@ const EmployeeList = () => {
className="dataTables_length text-start" className="dataTables_length text-start"
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
> >
<label>
<label>
<select <select
onChange={handleProjectChange} id="project-select"
name="DataTables_Table_0_length" onChange={(e)=>setSelectedProject(e.target.value)}
aria-controls="DataTables_Table_0" name="DataTables_Table_0_length"
className="form-select form-select-sm" aria-controls="DataTables_Table_0"
> className="form-select form-select-sm"
<option value="0">All Projects</option> value={selectedProject || ""}
{projects.map((item) => ( >
<option value={item.id}>{item.name}</option> {projectLoading ? (
))} <option value="Loading" >
Loading...
</option>
) : (
<>
<option value="">All Employees</option>
{Array.isArray(projectsList) && projectsList.map((item) => (
<option key={item.id} value={item.id}>
{item.name}
</option>
))}
</>
)}
</select> </select>
</label> </label>
</div> </div>
</div> </div>
</div> </div>
@ -155,6 +182,8 @@ const EmployeeList = () => {
<label> <label>
<input <input
type="search" type="search"
value={searchText}
onChange={handleSearch}
className="form-control form-control-sm" className="form-control form-control-sm"
placeholder="Search User" placeholder="Search User"
aria-controls="DataTables_Table_0" aria-controls="DataTables_Table_0"
@ -213,12 +242,10 @@ const EmployeeList = () => {
</li> </li>
</ul> </ul>
<button <button
className="btn btn-sm add-new btn-primary" className={`btn btn-sm add-new btn-primary `}
// ${hasUserPermission("81ab8a87-8ccd-4015-a917-0627cee6a100")?"":"d-none"}
tabIndex="0" tabIndex="0"
// aria-controls="DataTables_Table_0"
type="button" type="button"
// data-bs-toggle="offcanvas"
// data-bs-target="#offcanvasAddUser"
> >
<span> <span>
<Link <Link
@ -280,17 +307,7 @@ const EmployeeList = () => {
> >
Role Role
</th> </th>
<th
className="sorting d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
// style={{ width: "148px" }}
aria-label="Role: activate to sort column ascending"
>
Current Project
</th>
<th <th
className="sorting d-none d-md-table-cell" className="sorting d-none d-md-table-cell"
tabIndex="0" tabIndex="0"
@ -324,8 +341,17 @@ const EmployeeList = () => {
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{currentItems.map((item) => ( {loading && <tr>
<td colSpan={8}>
<p>Loading...</p>
</td>
</tr>}
{( !loading && employeeList?.length === 0 ) && <td colSpan={8}>Not Data Found </td>}
{( !loading && employeeList && currentItems.length === 0 && employeeList.length !==0 ) && <td colSpan={8}><small className="muted">'{searchText}' employee not found</small> </td>}
{(currentItems && !loading) && currentItems.map((item) => (
<tr className="odd" key={item.id}> <tr className="odd" key={item.id}>
<td className="sorting_1" colSpan={2}> <td className="sorting_1" colSpan={2}>
<div className="d-flex justify-content-start align-items-center user-name"> <div className="d-flex justify-content-start align-items-center user-name">
@ -335,12 +361,9 @@ const EmployeeList = () => {
></Avatar> ></Avatar>
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<a <a
href="#"
onClick={(e) => { onClick={()=>navigate(`/employee/${item.id}?for=account`)}
e.preventDefault(); // Prevent default link behavior className="text-heading text-truncate cursor-pointer"
window.location.href = "/employee/" + item.id;
}}
className="text-heading text-truncate"
> >
<span className="fw-medium"> <span className="fw-medium">
{item.firstName} {item.lastName} {item.firstName} {item.lastName}
@ -355,17 +378,12 @@ const EmployeeList = () => {
{item.phoneNumber} {item.phoneNumber}
</span> </span>
</td> </td>
<td className=" d-none d-sm-table-cell"> <td className=" d-none d-sm-table-cell text-start">
<span className="text-truncate"> <span className="text-truncate">
<i className="bx bxs-wrench text-success me-2"></i> <i className="bx bxs-wrench text-success me-2"></i>{item.jobRole || "Not Assign Yet"}
</span>
</td>
<td className=" d-none d-sm-table-cell">
<span className="text-heading d-none d-sm-table-cell">
<i className="bx bx-home text-primary me-2"></i>
Assigned Project
</span> </span>
</td> </td>
<td className=" d-none d-md-table-cell"> <td className=" d-none d-md-table-cell">
{moment(item.birthDate).format("DD-MMM-YYYY")} {moment(item.birthDate).format("DD-MMM-YYYY")}
</td> </td>
@ -378,9 +396,8 @@ const EmployeeList = () => {
</span> </span>
</td> </td>
<td className="d-flex justify-content-end justify-content-sm-center"> <td className="d-flex justify-content-end justify-content-sm-center">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center ">
<a <a
href="javascript:;"
className="btn btn-icon dropdown-toggle hide-arrow" className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
> >
@ -390,11 +407,8 @@ const EmployeeList = () => {
<div className="dropdown-menu dropdown-menu-end m-0"> <div className="dropdown-menu dropdown-menu-end m-0">
{" "} {" "}
<a <a
href="#"
onClick={(e) => { onClick={()=> navigate(`/employee/${item.id}`)}
e.preventDefault(); // Prevent default link behavior
window.location.href = "/employee/" + item.id;
}}
className="dropdown-item" className="dropdown-item"
> >
View View
@ -403,13 +417,14 @@ const EmployeeList = () => {
> */} > */}
<Link <Link
className="dropdown-item" className={`dropdown-item `}
// ${hasUserPermission("81ab8a87-8ccd-4015-a917-0627cee6a100")?"":"d-none"}
to={`/employee/manage/${item.id}`} to={`/employee/manage/${item.id}`}
> >
Edit Edit
</Link> </Link>
{/* </a> */} {/* </a> */}
<a href="javascript:;" className="dropdown-item"> <a className="dropdown-item">
Suspend Suspend
</a> </a>
<a <a
@ -421,13 +436,14 @@ const EmployeeList = () => {
handleConfigData(item.id); handleConfigData(item.id);
}} }}
> >
ManageRole Manage Role
</a> </a>
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
))} ) )}
</tbody> </tbody>
</table> </table>
@ -449,7 +465,7 @@ const EmployeeList = () => {
&laquo; &laquo;
</button> </button>
</li> </li>
{[...Array(totalPages)].map((_, index) => ( {[...Array(totalPages)]?.map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${ className={`page-item ${

View File

@ -5,46 +5,54 @@ import Breadcrumb from "../../components/common/Breadcrumb";
import EmployeeNav from "../../components/Employee/EmployeeNav"; import EmployeeNav from "../../components/Employee/EmployeeNav";
import { useSearchParams,useParams } from "react-router-dom"; import { useSearchParams,useParams } from "react-router-dom";
import { getCachedData } from "../../slices/apiDataManager"; import { getCachedData } from "../../slices/apiDataManager";
import { useEmployees, useEmployeesByProject } from "../../hooks/useEmployees"; import { useEmployeeProfile, useEmployees, useEmployeesByProject } from "../../hooks/useEmployees";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { base64ToFile } from "../../utils/dateUtils"; import EmployeeRepository from "../../repositories/EmployeeRepository";
const EmployeeProfile = () => { const EmployeeProfile = () => {
// const employeeAPI = (url = "http://localhost:5032/api/File/manage1") => {
// return {
// fetchAll: () => axios.get(url),
// create: (newRecord) => axios.post(url, newRecord),
// update: (id, updatedRecord) => axios.put(url + id, updatedRecord),
// delete: (id) => axios.delete(id),
// };
// };
// const addOrEdit = (formData, onSuccess) => {
// employeeAPI()
// .create(formData)
// .then((res) => {
// onSuccess();
// })
// .catch((error) => {
// console.log(error);
// });
// };
const projectID = useSelector((store)=>store.localVariables.projectId) const projectID = useSelector((store)=>store.localVariables.projectId)
const {employees} = useEmployeesByProject(projectID) const {employeeId} = useParams();
const { employeeId,loading } = useParams(); // const {employee,loading} = useEmployeeProfile(employeeId)
const [loading,setLoading] = useState(true)
const [SearchParams] = useSearchParams() const [SearchParams] = useSearchParams()
const tab = SearchParams.get("for") const tab = SearchParams.get( "for" )
const [activePill, setActivePill] = useState(tab); const [activePill, setActivePill] = useState(tab);
const[currentEmployee,setCurrentEmployee] = useState() const[currentEmployee,setCurrentEmployee] = useState()
const handlePillClick = (pillKey) => { const handlePillClick = (pillKey) => {
setActivePill(pillKey); setActivePill(pillKey);
}; };
const fetchEmployeeProfile = async( employeeID ) =>
{
try
{
const resp = await EmployeeRepository.getEmployeeProfile( employeeID )
setCurrentEmployee( resp.data )
setLoading(false)
} catch ( err )
{
console.log( "Faild to fetch employee data,", err )
setLoading(false)
}
}
useEffect(() => {
if ( employeeId )
{
fetchEmployeeProfile(employeeId)
}
}, [employeeId]);
const renderContent = () => { const renderContent = () => {
// if (loading) return <Loader></Loader>; if (loading) return <div>Loading</div>;
switch (activePill) { switch (activePill) {
case "account": { case "account": {
return ( return (
@ -80,14 +88,13 @@ const EmployeeProfile = () => {
} }
}; };
useEffect(()=>{ if (loading) {
if(employees && employees?.length > 0){ return <div>Loading...</div>;
setCurrentEmployee(employees?.find((emp)=>emp.id == employeeId)) }
}
},[employees])
console.log(currentEmployee)
return ( return (
<div className="container-xxl flex-grow-1 container-p-y"> <div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb <Breadcrumb
@ -98,120 +105,88 @@ console.log(currentEmployee)
]} ]}
></Breadcrumb> ></Breadcrumb>
{/* <div> <div className="row">
<EmpProfile employee={null} addOrEdit={addOrEdit}></EmpProfile> <div className="col-12 col-md-8 col-lg-4 order-1 order-lg-1">
</div> */} <div className="row">
{ currentEmployee && ( <div className="col-12 mb-4">
<div className="card">
<div className="row"> <div className="card-body">
<div className="col-12 col-md-8 col-lg-4 order-1 order-lg-1"> <div className="d-flex flex-row flex-lg-column">
<div className="row"> <div className="d-flex flex-column justify-content-center align-items-center text-center">
<img
<div className="col-12 mb-4"> src={`../../../public/img/avatars/${currentEmployee.gender}.jpg`}
<div className="card"> alt="user-avatar"
<div className="card-body"> className="d-block rounded"
<div className="d-flex flex-row flex-lg-column"> height="100"
<div className="d-flex flex-column justify-content-center align-items-center text-center " > width="100"
<img aria-label="Account image"
src="../../../public/img/avatars/00.png" id="uploadedAvatar"
alt="user-avatar" />
className="d-block rounded" <div className="py-2">
height="100" <p className="h6">{`${currentEmployee?.firstName} ${currentEmployee?.lastName}`}</p>
width="100" </div>
aria-label="Account image" <hr className="my-2" />
id="uploadedAvatar" </div>
/> <div className="w-100 d-flex flex-row flex-sm-column justify-content-sm-start justify-content-around">
<div className="py-2"> <div className="text-wrap">
<p className="h6">{currentEmployee?.firstName + " "+ currentEmployee?.lastName}</p> <small className="card-text text-uppercase text-muted small">Contacts</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="bx bx-phone"></i>
<span className="fw-medium mx-2">Contact Number:</span>
<span className={`${currentEmployee?.emergencyPhoneNumber ? "" : "text-muted"}`}>
{currentEmployee?.emergencyPhoneNumber || <em>NA</em>}
</span>
</li>
<li className="d-flex align-items-center mb-4 text-start">
<i className="bx bx-envelope"></i>
<span className="fw-medium mx-2">Email:</span>
<span className={`text-break text-wrap ${currentEmployee?.email ? "" : "text-muted"}`}>
{currentEmployee?.email || <em className="muted">NA</em>}
</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact Person:</span>
<span className="">
{currentEmployee?.emergencyContactPerson}
</span>
</li>
<li className="d-flex align-items-center text-wrap ">
<i className="bx bx-flag"></i>
<span className="fw-medium mx-2">Address:</span>
</li>
<li className="d-flex align-items-start test-start mb-2">
<span className={`${currentEmployee?.permanentAddress ? "" : "ms-4"}`}>
{currentEmployee?.peramnentAddress}
</span>
</li>
</ul>
</div>
</div>
</div>
</div> </div>
<hr className="my-2 " />
</div>
<div className="w-100 d-flex flex-row flex-sm-column justify-content-sm-start justify-content-around">
<div className="text-wrap">
<small className="card-text text-uppercase text-muted small">
Contacts
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>{" "}
<span className="text-muted"><em>NA</em></span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-phone"></i>
<span className="fw-medium mx-2">Contact Number:</span>{" "}
<span className={`${currentEmployee.emergencyPhoneNumber ? "":"text-muted"}`}>{currentEmployee.emergencyPhoneNumber || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4 ">
<i className="bx bx-envelope"></i>
<span className="fw-medium mx-2">Email:</span>
<span lassName={`text-break text-wrap ${currentEmployee.email ? "":"text-muted"}`}>{currentEmployee?.email || <em className="muted">NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-flag"></i>
<span className="fw-medium mx-2">Address:</span>{" "}
</li>
<li className="d-flex align-items-start test-start mb-4">
<span className={`${currentEmployee?.peramnentAddress ? "":"text-muted"}`}>{currentEmployee?.peramnentAddress || <em>NA</em>}</span>
</li>
</ul>
</div>
</div> </div>
<div>
</div>
</div> </div>
</div>
</div>
<div className="col-12 col-lg-8 order-2 order-lg-2 mb-4">
<div className="row">
<EmployeeNav onPillClick={handlePillClick} activePill={activePill} />
</div>
<div className="card">
<div className="row row-bordered g-0">
{renderContent()}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div className="col-12 col-lg-8 order-2 order-lg-2 mb-4">
<div className="row">
<EmployeeNav
onPillClick={handlePillClick}
activePill={activePill}
/>
</div>
<div className="card">
<div className="row row-bordered g-0">
{renderContent()}
</div>
</div>
</div>
</div>
)
}
{loading && (<p>Loading...</p>)}
</div> </div>
); );
}; };
export default EmployeeProfile; export default EmployeeProfile;
{/* <div>
<small className="card-text text-uppercase text-muted small">
Profile
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="bx bx-check"></i>
<span className="fw-medium mx-2">Start Date:</span>{" "}
<span>
</span>
</li>
<li className="d-flex align-items-center mb-4">
<i class="bx bx-stop-circle"></i>{" "}
<span className="fw-medium mx-2">End Date:</span>{" "}
<span>
</span>
</li>
<li className="d-flex align-items-center mb-2">
<i class="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>{" "}
<span>active</span>
</li>
</ul>
</div> */}

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import MasterModal from "../../components/master/MasterModal"; import MasterModal from "../../components/master/MasterModal";
import { List} from "../../data/masters"; import { mastersList} from "../../data/masters";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster from "../../hooks/masterHook/useMaster" import useMaster from "../../hooks/masterHook/useMaster"
@ -32,14 +32,14 @@ const MasterPage = () => {
const modalElement = document.getElementById('master-modal'); const modalElement = document.getElementById('master-modal');
if (modalElement) { if (modalElement) {
modalElement.classList.remove('show'); modalElement.classList.remove('show');
modalElement.style.display = 'none'; // Hide modal visually modalElement.style.display = 'none';
document.body.classList.remove('modal-open'); // Unlock body scroll document.body.classList.remove('modal-open');
const backdropElement = document.querySelector('.modal-backdrop'); const backdropElement = document.querySelector('.modal-backdrop');
if (backdropElement) { if (backdropElement) {
backdropElement.classList.remove('modal-backdrop'); // Remove backdrop class backdropElement.classList.remove('modal-backdrop');
backdropElement.style.display = 'none'; // Hide the backdrop element backdropElement.style.display = 'none';
} }
} }
const modalBackdropElement = document.querySelector('.modal-backdrop'); const modalBackdropElement = document.querySelector('.modal-backdrop');
@ -84,10 +84,16 @@ const MasterPage = () => {
if (modalConfig !== null) { if (modalConfig !== null) {
openModal(); openModal();
} }
}, [modalConfig,isCreateModalOpen]);
}, [ modalConfig, isCreateModalOpen ] );
useEffect(() => {
return () => {
setIsCreateModalOpen(false)
closeModal();
};
}, [])
return ( return (
<> <>
@ -121,13 +127,15 @@ const MasterPage = () => {
> >
<label> <label>
<select <select
select onChange={(e) => dispatch(changeMaster(e.target.value))} value={selectedMaster} onChange={(e) => dispatch(changeMaster(e.target.value))}
name="DataTables_Table_0_length" name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0" aria-controls="DataTables_Table_0"
className="form-select form-select-sm" className="form-select form-select-sm"
value={selectedMaster}
> >
<option value="select" disabled selected>Select Master</option> <option value="" disabled >Select Master</option>
{List.map((item) => ( {mastersList.map( ( item ) => (
<option key={item.id} value={item.name}>{item.name}</option> <option key={item.id} value={item.name}>{item.name}</option>
))} ))}
</select> </select>
@ -157,7 +165,8 @@ const MasterPage = () => {
<div className="input-group"> <div className="input-group">
<button <button
className="btn btn-sm add-new btn-primary" className={`btn btn-sm add-new btn-primary `}
// ${hasUserPermission('660131a4-788c-4739-a082-cbbf7879cbf2') ? "":"d-none"}
tabIndex="0" tabIndex="0"
aria-controls="DataTables_Table_0" aria-controls="DataTables_Table_0"
type="button" type="button"

View File

@ -1,26 +1,34 @@
import React, { useEffect,useState } from "react"; import React, { useEffect,useState } from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import {MASTER_MANAGE} from "../../utils/constants";
const MasterTable = ( {data, columns, loading, handleModalData} ) =>
const MasterTable = ({ data, columns, loading, handleModalData }) => { {
const hasMasterPermission = useHasUserPermission(MASTER_MANAGE) // for master manage permission id
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster) const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster)
const hiddenColumns = ["id", "featurePermission"]; const hiddenColumns = ["id", "featurePermission","tenant","tenantId"];
const safeData = Array.isArray(data) ? data : [];
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(10); const [itemsPerPage] = useState(10);
const indexOfLastItem = currentPage * itemsPerPage; const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = data.slice(indexOfFirstItem, indexOfLastItem); const currentItems = safeData.slice( indexOfFirstItem, indexOfLastItem );
useEffect(() => {
setCurrentPage(1);
}, [ safeData ] )
const paginate = (pageNumber) => setCurrentPage(pageNumber); const paginate = (pageNumber) => setCurrentPage(pageNumber);
const totalPages = Math.ceil(data.length / itemsPerPage); const totalPages = Math.ceil(safeData.length / itemsPerPage);
const updatedColumns = columns const updatedColumns = columns
.filter((col) => !hiddenColumns.includes(col.key)) .filter((col) => !hiddenColumns.includes(col.key))
.map((col) => ({ .map((col) => ({
...col, ...col,
label: col.key === "role" || "module" || "status" ? "Name" : col.label, label:
col.key === "role" || col.key === "module" || col.key === "status" ? "Name" : col.label,
})); }));
return ( return (
@ -28,31 +36,36 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
{loading ? ( {loading ? (
<p>Loading...</p> <p>Loading...</p>
) : ( ) : (
<table className="datatables-users table border-top dataTable no-footer dtr-column" <table
id="DataTables_Table_0" className="datatables-users table border-top dataTable no-footer dtr-column"
aria-describedby="DataTables_Table_0_info" id="DataTables_Table_0"
style={{ width: "100%" }}> aria-describedby="DataTables_Table_0_info"
style={{ width: "100%" }}
>
<thead> <thead>
<tr> <tr>
{/* {updatedColumns.map((col) => ( {/* Example of using updatedColumns */}
<th key={col.key}>{col.label}</th> <th></th>
))} */} <th>Name</th>
<th></th> <th>Description</th>
<th>Name</th> <th className={` ${hasMasterPermission? "":"d-none"}`}>Actions</th>
<th>Description</th> {/* ${hasUserPermission('660131a4-788c-4739-a082-cbbf7879cbf2') ? "":"d-none"} */}
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{currentItems.length > 0 ? ( {currentItems.length > 0 ? (
currentItems.map((item, index) => ( currentItems.map((item, index) => (
<tr key={index}> <tr key={index}>
<td></td> <td></td>
{updatedColumns.map((col) => ( {updatedColumns.map((col) => (
<td className="text-start mx-2" key={col.key}>{item[col.key] || " --- "}</td> <td className="text-start mx-2" key={col.key}>
{item[col.key] !== undefined && item[col.key] !== null
? item[col.key]
: " --- "}
</td>
))} ))}
<td> <td >
{/* className={` ${hasUserPermission('660131a4-788c-4739-a082-cbbf7879cbf2') ? "":"d-none"}`}> */}
<button <button
aria-label="Modify" aria-label="Modify"
type="button" type="button"
@ -72,6 +85,7 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
data-bs-target="#master-modal" data-bs-target="#master-modal"
onClick={() => handleModalData("delete", item)} onClick={() => handleModalData("delete", item)}
> >
<i className="bx bx-trash me-1 text-danger"></i> <i className="bx bx-trash me-1 text-danger"></i>
</button> </button>
</td> </td>
@ -86,28 +100,30 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
</table> </table>
)} )}
{/* Pagination */} {/* Pagination */}
{ {!loading && (
!loading && ( <nav aria-label="Page ">
<nav aria-label="Page " > <ul className="pagination pagination-sm justify-content-end py-1">
<ul className="pagination pagination-sm justify-content-end py-1"> <li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}> <button className="page-link btn-xs" onClick={() => paginate(currentPage - 1)}>
<button className="page-link btn-xs" onClick={() => paginate(currentPage - 1)}>&laquo;</button> &laquo;
</li> </button>
{[...Array(totalPages)].map((_, index) => ( </li>
<li key={index} className={`page-item ${currentPage === index + 1 ? 'active' : ''}`}> {[...Array(totalPages)].map((_, index) => (
<button className="page-link " onClick={() => paginate(index + 1)}> <li key={index} className={`page-item ${currentPage === index + 1 ? "active" : ""}`}>
{index + 1} <button className="page-link" onClick={() => paginate(index + 1)}>
</button> {index + 1}
</li> </button>
))} </li>
<li className={`page-item ${currentPage === totalPages ? 'disabled' : ''}`}> ))}
<button className="page-link " onClick={() => paginate(currentPage + 1)}>&raquo;</button> <li className={`page-item ${currentPage === totalPages ? "disabled" : ""}`}>
</li> <button className="page-link" onClick={() => paginate(currentPage + 1)}>
</ul> &raquo;
</nav> </button>
) </li>
} </ul>
</nav>
)}
</div> </div>
); );
}; };

View File

@ -11,25 +11,25 @@ import ProjectInfra from "../../components/Project/ProjectInfra";
import Loader from "../../components/common/Loader"; import Loader from "../../components/common/Loader";
import WorkPlan from "../../components/Project/WorkPlan"; import WorkPlan from "../../components/Project/WorkPlan";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { ActivityeRepository } from "../../repositories/MastersRepository"; import { ActivityeRepository } from "../../repositories/MastersRepository";
import "./ProjectDetails.css"; import "./ProjectDetails.css";
import {useEmployeesByProjectAllocated} from "../../hooks/useProjects";
const ProjectDetails = ({ props }) => {
const ProjectDetails = () => {
let { projectId } = useParams(); let { projectId } = useParams();
const [project, setProject] = useState(null); const [project, setProject] = useState(null);
const [projectDetails, setProjectDetails] = useState(null); const [ projectDetails, setProjectDetails ] = useState( null );
const [activities, setActivities] = useState(null); const [activities, setActivities] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
const fetchActivities = async () => { const fetchActivities = async () => {
try {
const activities_cache = getCachedData("activitiesMaster"); const activities_cache = getCachedData("activitiesMaster");
if (!activities_cache) { if (!activities_cache) {
@ -41,68 +41,41 @@ const ProjectDetails = ({ props }) => {
.catch((error) => { .catch((error) => {
setError("Failed to fetch data."); setError("Failed to fetch data.");
}); });
// api
// .get("/api/task/activities")
// .then((data) => {
// setActivities(data);
// dispatch(cacheApiResponse({ key: "activitiesMaster", data: data }));
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// });
} else { } else {
setActivities(activities_cache); setActivities(activities_cache);
} }
} catch (err) {
setError("Failed to fetch activities.");
} finally {
// setLoading(false);
}
}; };
const fetchData = async () => { const fetchData = async () => {
try {
const project_cache = getCachedData(`projectinfo-${projectId}`); const project_cache = getCachedData(`projectinfo-${projectId}`);
if (!project_cache) { if (!project_cache) {
ProjectRepository.getProjectByprojectId(projectId) ProjectRepository.getProjectByprojectId(projectId)
.then((response) => { .then( ( response ) =>
{
setProjectDetails(response); setProjectDetails(response);
setProject(response); setProject(response);
cacheData(`projectinfo-${projectId}`, response); cacheData( `projectinfo-${ projectId }`, response );
setLoading(false)
}) })
.catch((error) => { .catch((error) => {
console.error(error); console.error(error);
setError("Failed to fetch data."); setError( "Failed to fetch data." );
setLoading(false)
}); });
} else { } else {
setProjectDetails(project_cache); setProjectDetails( project_cache );
} setProject( project_cache );
setLoading(false)
// api }
// .get(`/api/project/details/${projectId}`)
// .then((data) => {
// setProjectDetails(data);
// setProject(data);
// dispatch(
// cacheApiResponse({ key: `projectinfo-${projectId}`, data: data })
// );
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// });
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
}; };
const [activePill, setActivePill] = useState("profile"); const [activePill, setActivePill] = useState("profile");
// Event handler to set the active pill
const handlePillClick = (pillKey) => { const handlePillClick = (pillKey) => {
setActivePill(pillKey); setActivePill(pillKey);
}; };
@ -111,7 +84,7 @@ const ProjectDetails = ({ props }) => {
fetchData(); fetchData();
}; };
// Dynamically render content based on the active pill
const renderContent = () => { const renderContent = () => {
if (loading) return <Loader></Loader>; if (loading) return <Loader></Loader>;
switch (activePill) { switch (activePill) {
@ -125,7 +98,7 @@ const ProjectDetails = ({ props }) => {
</div> </div>
<div className="col-xl-4 col-lg-5 col-md-5"> <div className="col-xl-4 col-lg-5 col-md-5">
{/* Profile Overview */} {/* Profile Overview */}
<ProjectOverview></ProjectOverview> <ProjectOverview project={ project} />
{/* Profile Overview */} {/* Profile Overview */}
</div> </div>
</div> </div>
@ -167,14 +140,12 @@ const ProjectDetails = ({ props }) => {
return ( return (
<div className="row"> <div className="row">
<div className="col-lg-12 col-xl-12"> <div className="col-lg-12 col-xl-12">
{/* Activity Timeline */}
<ActivityTimeline></ActivityTimeline> <ActivityTimeline></ActivityTimeline>
{/* Activity Timeline */}
</div> </div>
</div> </div>
); );
} }
//return <ComponentC />;
default: default:
return <div>Select a pill to display content here.</div>; return <div>Select a pill to display content here.</div>;
} }
@ -197,19 +168,18 @@ const ProjectDetails = ({ props }) => {
]} ]}
></Breadcrumb> ></Breadcrumb>
{/* Header */}
<div className="row"> <div className="row">
<ProjectBanner data={project}></ProjectBanner> {loading && <p>Loading....</p>}
{!loading && <ProjectBanner project_data={project} ></ProjectBanner>}
</div> </div>
{/* Header */}
{/* Navbar pills */}
<div className="row"> <div className="row">
<ProjectNav <ProjectNav
onPillClick={handlePillClick} onPillClick={handlePillClick}
activePill={activePill} activePill={activePill}
></ProjectNav> ></ProjectNav>
</div> </div>
{/* Navbar pills */}
{renderContent()} {renderContent()}
</div> </div>
</> </>

View File

@ -1,122 +1,99 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import ProjectCard from "../../components/Project/ProjectCard"; import ProjectCard from "../../components/Project/ProjectCard";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import showToast from "../../services/toastService";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../../slices/apiDataManager";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import showToast from "../../services/toastService";
import {
getCachedData, cacheData
} from "../../slices/apiDataManager";
import {useHasUserPermission} from "../../hooks/useHasUserPermission"
import { useProfile } from "../../hooks/useProfile";
import {MANAGE_PROJECT} from "../../utils/constants";
const ProjectList = () =>
{
const {profile: loginUser} = useProfile();
const [showModal, setShowModal] = useState(false);
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT)
const { projects, loading, error, refetch } = useProjects();
const [refresh, setRefresh] = useState(false);
const [projectList, setProjectList] = useState([]);
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(5);
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal( false );
const ProjectList = () => {
const { projects,loading,error} = useProjects()
const dispatch = useDispatch()
// -------hook----- useEffect(() => {
// const [projects, setProjects] = useState([]); if (projects && projects.length > 0) {
// const [loading, setLoading] = useState(true); setProjectList(
// const [error, setError] = useState(""); projects?.filter((project) =>
loginUser?.projects?.map(Number).includes(project.id)
)
);
}
}, [projects, loginUser?.projects, loading]);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const handleSubmitForm = (newProject) => {
ProjectRepository.manageProject(newProject)
//const projects_cache = getCachedData("projectslist");
const openModal = () => {
console.log("clicked")
setIsCreateModalOpen(true);
};
// Close the modal
const closeModal = () => {
setIsCreateModalOpen(false);
const modalBackdrop = document.querySelector(".modal-backdrop");
if (modalBackdrop) modalBackdrop.remove();
};
// const fetchData = async () => {
// const projects_cache = getCachedData("projectslist");
// if (!projects_cache) {
// ProjectRepository.getProjectList()
// .then((response) => {
// setProjects(response);
// cacheData("projectslist", response);
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// });
// } else {
// if (!projects.length) setProjects(projects_cache);
// }
// };
const handleFormSubmit = (updatedProject) => {
delete updatedProject.id;
//console.log("Form submitted:", updatedProject);
ProjectRepository.manageProject(updatedProject)
.then((response) => { .then((response) => {
clearCacheKey("projectslist"); const cachedProjects_list = getCachedData("projectslist") || [];
fetchData(); const updated_Projects_list = [...cachedProjects_list, response];
showToast("Project updated successfully.", "success"); cacheData("projectslist", updated_Projects_list);
closeModal(); setProjectList(updated_Projects_list);
showToast("Project Created successfully.", "success");
setShowModal(false)
}) })
.catch((error) => { .catch((error) => {
closeModal();
showToast(error.message, "error"); showToast(error.message, "error");
}); });
// api
// .post("/api/project", updatedProject)
// .then((data) => {
// clearCacheKey("projectslist");
// fetchData();
// showToast("Project updated successfully.", "success");
// closeModal();
// })
// .catch((error) => {
// showToast(error.message, "error");
// });
}; };
// useEffect(() => { const handleReFresh = () => {
// fetchData(); if (!projects || projects.length === 0) {
// }, []); refetch();
useEffect(() => {
if (isCreateModalOpen) {
const modalElement = document.getElementById("create-project-model");
const modal = new window.bootstrap.Modal(modalElement);
modal.show();
} }
dispatch(changeMaster("JobRole")) setRefresh((prev) => !prev);
}, [isCreateModalOpen]); };
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = Array.isArray(projectList)
? projectList.slice(indexOfFirstItem, indexOfLastItem)
: [];
const paginate = (pageNumber) => setCurrentPage(pageNumber);
const totalPages = Array.isArray(projectList)
? Math.ceil(projectList.length / itemsPerPage)
: 0;
return ( return (
<> <>
{isCreateModalOpen && ( <div
<div className={`modal fade ${showModal ? 'show' : ''}`}
className={`modal fade `} tabIndex="-1"
id="create-project-model" role="dialog"
tabIndex="-1" style={{ display: showModal ? 'block' : 'none' }}
aria-hidden="true" aria-hidden={!showModal}
> >
<ManageProjectInfo <ManageProjectInfo
project={null} project={null}
onClose={closeModal} handleSubmitForm={handleSubmitForm}
onSubmit={handleFormSubmit} onClose={handleClose}
></ManageProjectInfo> ></ManageProjectInfo>
</div> </div>
)}
<div className="container-xxl flex-grow-1 container-p-y"> <div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb <Breadcrumb
data={[ data={[
@ -126,26 +103,101 @@ const ProjectList = () => {
></Breadcrumb> ></Breadcrumb>
<div className="row"> <div className="row">
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4 text-end"> <div
className={`col-md-12 col-lg-12 col-xl-12 order-0 mb-4 ${
!error && !projects ? "text-center" : "text-end"
}`}
>
{" "} {" "}
<button <button
type="button" type="button"
className="btn btn-sm btn-primary" className={`btn btn-sm btn-primary ${
HasManageProjectPermission
? ""
: "d-none"
}`}
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#create-project-model" data-bs-target="#create-project-model"
onClick={openModal} onClick={handleShow}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Create New Project Create New Project
</button> </button>
</div> </div>
</div> </div>
{((error && !loading) || !projects) && (
<p className="text-center text-body-secondary">
There was an error loading the projects. Please try again.
</p>
)}
{(!projects || projects.length === 0 || projectList.length == 0) &&
!loading &&
error && (
<div className="text-center">
<button
className="btn btn-xs btn-label-secondary"
onClick={handleReFresh}
>
Retry Fetching Projects
</button>
</div>
)}
<div className="row"> <div className="row">
{loading && (<p class="text-center">Loading...</p>)} {loading && <p className="text-center">Loading...</p>}
{projects?.map((item) => ( {currentItems &&
<ProjectCard project={item} key={item.id}></ProjectCard> currentItems?.map((item) => (
))} <ProjectCard projectData={item} key={item.id}></ProjectCard>
))}
</div> </div>
{/* Pagination */}
{!loading && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1">
<li
className={`page-item ${
currentPage === 1 ? "disabled" : ""
}`}
>
<button
className="page-link btn-xs"
onClick={() => paginate(currentPage - 1)}
>
&laquo;
</button>
</li>
{[...Array(totalPages)]?.map((_, index) => (
<li
key={index}
className={`page-item ${
currentPage === index + 1 ? "active" : ""
}`}
>
<button
className="page-link "
onClick={() => paginate(index + 1)}
>
{index + 1}
</button>
</li>
))}
<li
className={`page-item ${
currentPage === totalPages ? "disabled" : ""
}`}
>
<button
className="page-link "
onClick={() => paginate(currentPage + 1)}
>
&raquo;
</button>
</li>
</ul>
</nav>
)}
</div> </div>
</> </>
); );

View File

@ -2,8 +2,14 @@ import { api } from "../utils/axiosClient";
const AuthRepository = { const AuthRepository = {
login: (data) => api.post("/api/auth/login", data), login: (data) => api.post("/api/auth/login", data),
refreshToken: (data) => api.post("/api/auth/refresh-token", data), refreshToken: ( data ) => api.post( "/api/auth/refresh-token", data ),
logout: (data) => api.post("/api/auth/logout", data),
logout: ( data ) => api.post( "/api/auth/logout", data ),
profile: () => api.get( `/api/user/profile` ),
register: ( data ) => api.post( 'api/auth/register', data ),
resetPassword: ( data ) => api.post( '/api/auth/reset-password', data ),
forgotPassword: (data) => api.post( '/api/auth/forgot-password', data),
sendMail:(data)=>api.post("/api/auth/sendmail",data)
}; };
export default AuthRepository; export default AuthRepository;

View File

@ -1,12 +1,18 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const EmployeeRepository = { const EmployeeRepository = {
getAllEmployeeList:()=>api.get(`api/employee/list`),
getEmployeeListByproject: (projectid) => getEmployeeListByproject: (projectid) =>
api.get(`/api/employee/get/${projectid}`), api.get(`/api/employee/list/${projectid}`),
searchEmployees: (query) => api.get(`/api/employee/search/${query}`), searchEmployees: (query) =>
manageEmployee: (data) => api.post("/api/employee/manage", data,{"Content-Type": "multipart/form-data",}), api.get(`/api/employee/search/${query}`),
manageEmployee: (data) =>
api.post("/api/employee/manage", data, {
"Content-Type": "multipart/form-data",
}),
updateEmployee: (id, data) => api.put(`/users/${id}`, data), updateEmployee: (id, data) => api.put(`/users/${id}`, data),
deleteEmployee: (id) => api.delete(`/users/${id}`), deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
getEmployeeProfile:(id)=>api.get(`/api/Employee/profile/get/${id}`)
}; };
export default EmployeeRepository; export default EmployeeRepository;

View File

@ -6,16 +6,18 @@ const ProjectRepository = {
api.get(`/api/project/details/${projetid}`), api.get(`/api/project/details/${projetid}`),
getProjectAllocation: (projetid) => getProjectAllocation: (projetid) =>
api.get(`api/project/allocation/${projetid}`), api.get( `api/project/allocation/${ projetid }` ),
getEmployeesByProject:(projectId)=>api.get(`/api/Project/employees/get/${projectId}`),
manageProject: (data) => api.post("/api/project", data), manageProject: (data) => api.post("/api/project", data),
updateProject: (data) => api.post("/api/project/update", data), // updateProject: (data) => api.post("/api/project/update", data),
manageProjectAllocation: (data) => api.post("/api/project/allocation", data), manageProjectAllocation: (data) => api.post("/api/project/allocation", data),
manageProjectInfra: (data) => api.post("/api/project/manage-infra", data), manageProjectInfra: (data) => api.post("/api/project/manage-infra", data),
manageProjectTasks: (data) => api.post("/api/project/manage-infra", data), manageProjectTasks: (data) => api.post("/api/project/manage-infra", data),
updateProject: (id, data) => api.put(`/projects/${id}`, data), updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
deleteProject: (id) => api.delete(`/projects/${id}`), deleteProject: (id) => api.delete(`/projects/${id}`),
}; };

View File

@ -5,6 +5,7 @@ import HomeLayout from "../Layouts/HomeLayout";
import LoginPage from "../pages/Authentication/LoginPage"; import LoginPage from "../pages/Authentication/LoginPage";
import RegisterPage from "../pages/Authentication/RegisterPage"; import RegisterPage from "../pages/Authentication/RegisterPage";
import ForgotPasswordPage from "../pages/Authentication/ForgotPasswordPage"; import ForgotPasswordPage from "../pages/Authentication/ForgotPasswordPage";
import ResetPasswordPage from "../pages/Authentication/ResetPasswordPage";
import Dashboard from "../components/Dashboard/Dashboard"; import Dashboard from "../components/Dashboard/Dashboard";
import ProjectList from "../pages/project/ProjectList"; import ProjectList from "../pages/project/ProjectList";
@ -28,13 +29,9 @@ import DailyTask from "../pages/Activities/DailyTask";
import AttendancePage from "../pages/Activities/AttendancePage"; import AttendancePage from "../pages/Activities/AttendancePage";
const AppRoutes = () => { const AppRoutes = () => {
return ( return (
<Router> <Router>
<Routes> <Routes>
{/* Authentication Routes */} {/* Authentication Routes */}
<Route element={<AuthLayout />}> <Route element={<AuthLayout />}>
@ -44,9 +41,10 @@ const AppRoutes = () => {
path="/auth/forgot-password" path="/auth/forgot-password"
element={<ForgotPasswordPage />} element={<ForgotPasswordPage />}
/> />
<Route path="/reset-password" element={<ResetPasswordPage />} />
</Route> </Route>
{/* Protected Routes */} {/* Protected Routes */}
<Route element={<ProtectedRoute />}> <Route element={<ProtectedRoute />}>
<Route element={<HomeLayout />}> <Route element={<HomeLayout />}>
@ -54,7 +52,10 @@ const AppRoutes = () => {
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard" element={<Dashboard />} />
<Route path="/projects" element={<ProjectList />} /> <Route path="/projects" element={<ProjectList />} />
<Route path="/projects/:projectId" element={<ProjectDetails />} /> <Route path="/projects/:projectId" element={<ProjectDetails />} />
<Route path="/project/manage/:projectId" element={<ManageProject />}/> <Route
path="/project/manage/:projectId"
element={<ManageProject />}
/>
<Route path="/employees" element={<EmployeeList />} /> <Route path="/employees" element={<EmployeeList />} />
<Route path="/employee/:employeeId" element={<EmployeeProfile />} /> <Route path="/employee/:employeeId" element={<EmployeeProfile />} />
@ -66,7 +67,7 @@ const AppRoutes = () => {
<Route path="/inventory" element={<Inventory />} /> <Route path="/inventory" element={<Inventory />} />
<Route path="/activities/attendance" element={<AttendancePage />} /> <Route path="/activities/attendance" element={<AttendancePage />} />
<Route path="/activities/records" element={<DailyTask />} /> <Route path="/activities/records" element={<DailyTask />} />
<Route path="/activities/task" element={<TaskPlannng />} /> <Route path="/activities/task" element={<TaskPlannng />} />
@ -74,17 +75,16 @@ const AppRoutes = () => {
<Route path="/activities/gallary" element={<ImageGallary />} /> <Route path="/activities/gallary" element={<ImageGallary />} />
<Route path="/masters" element={<MasterPage />} /> <Route path="/masters" element={<MasterPage />} />
<Route path="/help/support" element={<Support />} /> <Route path="/help/support" element={<Support />} />
<Route path="/help/docs" element={<Documentation />} /> <Route path="/help/docs" element={<Documentation />} />
<Route path="/help/connect" element={<Connect />} /> <Route path="/help/connect" element={<Connect />} />
</Route> </Route>
</Route> </Route>
{/* Fallback Route */} {/* Fallback Route */}
<Route path="*" element={<ErrorPage />} /> <Route path="*" element={<ErrorPage />} />
</Routes> </Routes>
</Router> </Router>
); );
}; };

View File

@ -6,10 +6,9 @@ import AuthRepository from "../repositories/AuthRepository";
const ProtectedRoute = () => { const ProtectedRoute = () => {
// const isAuthenticated = localStorage.getItem("jwtToken"); // Example authentication check // const isAuthenticated = localStorage.getItem("jwtToken"); // Example authentication check
// // const isAuthenticated = true; // // const isAuthenticated = true;
// isTokenValid(); // isTokenValid();
// return isAuthenticated ? <Outlet /> : <Navigate to="/auth/login" />; // return isAuthenticated ? <Outlet /> : <Navigate to="/auth/login" />
const [isAuthenticated, setIsAuthenticated] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(null);
useEffect(() => { useEffect(() => {

View File

@ -4,6 +4,8 @@ import {
clearApiCacheKey, clearApiCacheKey,
flushApiCache, flushApiCache,
} from "../slices/apiCacheSlice"; } from "../slices/apiCacheSlice";
import {setLoginUserPermmisions} from "./globalVariablesSlice";
// Cache data // Cache data
export const cacheData = (key, data) => { export const cacheData = (key, data) => {
@ -24,3 +26,14 @@ export const clearCacheKey = (key) => {
export const clearAllCache = () => { export const clearAllCache = () => {
store.dispatch(flushApiCache()); store.dispatch(flushApiCache());
}; };
export const cacheProfileData = ( data) => {
store.dispatch(setLoginUserPermmisions(data));
};
// Get cached data
export const getCachedProfileData = () => {
return store.getState().globalVariables.loginUser;
};

View File

@ -3,7 +3,7 @@ import AttendanceRepository from '../../repositories/AttendanceRepository';
// Fetch attendance data // Fetch attendance data
export const fetchAttendanceData = createAsyncThunk( export const fetchAttendanceData = createAsyncThunk(
'attendance/fetchAttendanceData', 'attendanceLogs/fetchAttendanceData', // Updated action type prefix
async ({ projectId, date }, thunkAPI) => { async ({ projectId, date }, thunkAPI) => {
try { try {
const response = await AttendanceRepository.getAttendanceFilteredByDate(projectId, date); const response = await AttendanceRepository.getAttendanceFilteredByDate(projectId, date);
@ -14,35 +14,35 @@ export const fetchAttendanceData = createAsyncThunk(
} }
); );
// This method for marking attendance if a date filter is applied
export const markAttendance = createAsyncThunk( export const markAttendance = createAsyncThunk(
'attendance/markAttendance', 'attendanceLogs/markAttendance', // Updated action type prefix
async (formData, thunkAPI) => { async (formData, thunkAPI) => {
try { try {
let newRecordAttendance = { let newRecordAttendance = {
id: formData.id || null, id: formData.id || null,
comment: formData.description, comment: formData.description,
employeeID: formData.employeeId, employeeID: formData.employeeId,
projectID: formData.projectId , projectID: formData.projectId,
date: new Date().toISOString(), date: new Date().toISOString(),
markTime: formData.time, markTime: formData.time,
latitude: formData.latitude.toString(), latitude: formData.latitude.toString(),
longitude: formData.longitude.toString(), longitude: formData.longitude.toString(),
action: formData.action, action: formData.action,
image: null image: null,
}; };
const response = await AttendanceRepository.markAttendance(newRecordAttendance); const response = await AttendanceRepository.markAttendance(newRecordAttendance);
return response.data; // Return the newly created or updated record return response.data; // Return the newly created or updated record
} catch (error) { } catch (error) {
return thunkAPI.rejectWithValue(error.message); return thunkAPI.rejectWithValue(error.message);
} }
} }
); );
// Attendance Slice // Attendance Logs Slice
const attendanceSlice = createSlice({ const attendanceLogsSlice = createSlice({
name: 'attendance', name: 'attendanceLogs', // Updated slice name
initialState: { initialState: {
data: [], data: [],
loading: false, loading: false,
@ -55,7 +55,7 @@ const attendanceSlice = createSlice({
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
// Fetch attendance // Fetch attendance data
.addCase(fetchAttendanceData.pending, (state) => { .addCase(fetchAttendanceData.pending, (state) => {
state.loading = true; state.loading = true;
}) })
@ -74,13 +74,13 @@ const attendanceSlice = createSlice({
const index = state.data.findIndex(item => item.employeeId === updatedRecord.employeeId); const index = state.data.findIndex(item => item.employeeId === updatedRecord.employeeId);
if (index !== -1) { if (index !== -1) {
state.data[index] = { ...state.data[index], ...updatedRecord }; // Update existing record state.data[index] = { ...state.data[index], ...updatedRecord }; // Update existing record
} else { } else {
state.data.push(updatedRecord); // Add new record if not found state.data.push(updatedRecord); // Add new record if not found
} }
}); });
}, },
}); });
export const { setAttendanceData } = attendanceSlice.actions; export const { setAttendanceData } = attendanceLogsSlice.actions;
export default attendanceSlice.reducer; export default attendanceLogsSlice.reducer;

View File

@ -0,0 +1,32 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import AttendanceRepository from '../../repositories/AttendanceRepository';
export const markCurrentAttendance = createAsyncThunk(
'attendanceCurrentDate/markAttendance',
async (formData, { getState, dispatch, rejectWithValue }) => {
const { projectId } = getState().localVariables
try {
// Create the new attendance record
const newRecordAttendance = {
id: null,
comment: formData.description,
employeeID: formData.employeeId,
projectId: projectId,
date: new Date().toISOString(),
markTime: formData.time,
latitude: formData.latitude.toString(),
longitude: formData.longitude.toString(),
action: formData.action,
image: null,
};
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
const markedAttendance = response.data
return markedAttendance;
} catch (error) {
console.error('Error marking attendance:', error);
return rejectWithValue(error.message); // Reject with error message
}
}
);

View File

@ -2,14 +2,20 @@ import { createSlice } from "@reduxjs/toolkit";
const globalVariablesSlice = createSlice({ const globalVariablesSlice = createSlice({
name: "globalVariables", name: "globalVariables",
initialState: {}, initialState: {
loginUser:null
},
reducers: { reducers: {
setGlobalVariable: (state, action) => { setGlobalVariable: (state, action) => {
const { key, value } = action.payload; const { key, value } = action.payload;
state[key] = value; state[key] = value;
}, },
setLoginUserPermmisions: ( state, action ) =>
{
state.loginUser = action.payload
}
}, },
}); });
export const { setGlobalVariable } = globalVariablesSlice.actions; export const { setGlobalVariable,setLoginUserPermmisions } = globalVariablesSlice.actions;
export default globalVariablesSlice.reducer; export default globalVariablesSlice.reducer;

View File

@ -9,6 +9,6 @@ export const store = configureStore({
apiCache: apiCacheReducer, apiCache: apiCacheReducer,
globalVariables: globalVariablesReducer, globalVariables: globalVariablesReducer,
localVariables:localVariableRducer, localVariables:localVariableRducer,
attendance: attendanceReducer, attendanceLogs: attendanceReducer,
}, },
}); });

12
src/utils/authUtils.js Normal file
View File

@ -0,0 +1,12 @@
import { useProfile } from "../hooks/useProfile";
export const hasUserPermission = (permission) => {
const { profile } = useProfile();
if (profile) {
if (!permission || typeof permission !== "string") {
return false;
}
return profile?.featurePermissions.includes(permission);
}
return false;
};

View File

@ -83,11 +83,11 @@ axiosClient.interceptors.response.use(
refreshToken: refreshToken, refreshToken: refreshToken,
}); });
const { token } = response.data; const { token } = response.data.data;
// Save the new access token // Save the new access token
localStorage.setItem("jwtToken", token); localStorage.setItem("jwtToken", token);
localStorage.setItem("refreshToken", response.data.refreshToken); localStorage.setItem("refreshToken", response.data.data.refreshToken);
// Update the original request with the new token // Update the original request with the new token
originalRequest.headers["Authorization"] = `Bearer ${token}`; originalRequest.headers["Authorization"] = `Bearer ${token}`;

View File

@ -1,2 +1,10 @@
export const THRESH_HOLD = 12; // hours export const THRESH_HOLD = 12; // hours
export const DURATION_TIME=10; // minutes export const DURATION_TIME = 10; // minutes
export const MASTER_MANAGE = "660131a4-788c-4739-a082-cbbf7879cbf2";
export const ASSIGN_USER_TO_PROJECT = "81ab8a87-8ccd-4015-a917-0627cee6a100";
export const INFRASTRUCTURE = "9666de86-d7c7-4d3d-acaa-fcd6d6b81f3c";
export const MANAGE_PROJECT = "53176ebf-c75d-42e5-839f-4508ffac3def"

View File

@ -54,25 +54,15 @@ export const convertShortTime=(dateString)=> {
return timeDifferenceInHours >= timeElapsedInHours; return timeDifferenceInHours >= timeElapsedInHours;
} }
export const base64ToFile = (base64String, fileName) => {
// Ensure the string is valid and clean
const cleanedBase64 = base64String.replace(/^data:.*,/, '').replace(/\s/g, '');
// Decode Base64 to binary data
const byteString = atob(cleanedBase64);
// Get MIME type from Base64 string
const mimeTypeMatch = base64String.match(/^data:(.*);base64,/);
const mimeType = mimeTypeMatch ? mimeTypeMatch[1] : 'application/octet-stream';
// Convert binary data to Uint8Array
const byteArray = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
return new File([byteArray], fileName, { type: mimeType });
};
// Example usage
export const checkIfCurrentDate = (dateString) => {
const currentDate = new Date();
const inputDate = new Date(dateString);
currentDate.setHours(0, 0, 0, 0);
inputDate.setHours(0, 0, 0, 0);
return currentDate.getTime() === inputDate.getTime();
};