Created new layout for employee profile page

This commit is contained in:
Vikas Nale 2025-08-05 18:31:59 +05:30
parent 5a209ae24e
commit e76284598e
9 changed files with 787 additions and 641 deletions

View File

@ -3,10 +3,6 @@
--bs-nav-link-font-size: 0.7375rem; --bs-nav-link-font-size: 0.7375rem;
} }
.nav {
--bs-nav-link-font-size: 0.7375rem;
}
.card-header { .card-header {
padding: 0.5rem var(--bs-card-cap-padding-x); padding: 0.5rem var(--bs-card-cap-padding-x);
} }

View File

@ -1,24 +1,30 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import moment from "moment"; import moment from "moment";
import DateRangePicker from "../../components/common/DateRangePicker"; import DateRangePicker from "../common/DateRangePicker";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice"; import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import Avatar from "../../components/common/Avatar"; import Avatar from "../common/Avatar";
import { convertShortTime } from "../../utils/dateUtils"; import { convertShortTime } from "../../utils/dateUtils";
import RenderAttendanceStatus from "../../components/Activities/RenderAttendanceStatus"; import RenderAttendanceStatus from "../Activities/RenderAttendanceStatus";
import AttendLogs from "../../components/Activities/AttendLogs"; import AttendLogs from "../Activities/AttendLogs";
import { useAttendanceByEmployee } from "../../hooks/useAttendance"; import { useAttendanceByEmployee } from "../../hooks/useAttendance";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../common/GlobalModel";
import { ITEMS_PER_PAGE } from "../../utils/constants"; import { ITEMS_PER_PAGE } from "../../utils/constants";
const AttendancesEmployeeRecords = ({ employee }) => { const EmpAttendance = ({ employee }) => {
const [attendances, setAttendnaces] = useState([]); const [attendances, setAttendnaces] = useState([]);
const [selectedDate, setSelectedDate] = useState(""); const [selectedDate, setSelectedDate] = useState("");
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [attendanceId, setAttendanecId] = useState(); const [attendanceId, setAttendanecId] = useState();
const {data =[],isLoading:loading,isFetching,error,refetch} = useAttendanceByEmployee(employee,dateRange.startDate, dateRange.endDate) const {
data = [],
isLoading: loading,
isFetching,
error,
refetch,
} = useAttendanceByEmployee(employee, dateRange.startDate, dateRange.endDate);
const dispatch = useDispatch(); const dispatch = useDispatch();
// const { data, loading, error } = useSelector( // const { data, loading, error } = useSelector(
@ -75,16 +81,24 @@ const AttendancesEmployeeRecords = ({ employee }) => {
const uniqueMap = new Map(); const uniqueMap = new Map();
[...group1, ...group2, ...group3, ...group4, ...group5].forEach((rec) => { [...group1, ...group2, ...group3, ...group4, ...group5].forEach((rec) => {
const date = moment(rec.checkInTime || rec.checkOutTime).format("YYYY-MM-DD"); const date = moment(rec.checkInTime || rec.checkOutTime).format(
"YYYY-MM-DD"
);
const key = `${rec.employeeId}-${date}`; const key = `${rec.employeeId}-${date}`;
const existing = uniqueMap.get(key); const existing = uniqueMap.get(key);
if (!existing || new Date(rec.checkInTime || rec.checkOutTime) > new Date(existing.checkInTime || existing.checkOutTime)) { if (
!existing ||
new Date(rec.checkInTime || rec.checkOutTime) >
new Date(existing.checkInTime || existing.checkOutTime)
) {
uniqueMap.set(key, rec); uniqueMap.set(key, rec);
} }
}); });
const sortedFinalList = [...uniqueMap.values()].sort((a, b) => const sortedFinalList = [...uniqueMap.values()].sort(
new Date(b.checkInTime || b.checkOutTime) - new Date(a.checkInTime || a.checkOutTime) (a, b) =>
new Date(b.checkInTime || b.checkOutTime) -
new Date(a.checkInTime || a.checkOutTime)
); );
const currentDate = new Date().toLocaleDateString("en-CA"); const currentDate = new Date().toLocaleDateString("en-CA");
@ -93,8 +107,6 @@ const AttendancesEmployeeRecords = ({ employee }) => {
ITEMS_PER_PAGE ITEMS_PER_PAGE
); );
const openModal = (id) => { const openModal = (id) => {
setAttendanecId(id); setAttendanecId(id);
setIsModalOpen(true); setIsModalOpen(true);
@ -103,25 +115,28 @@ const AttendancesEmployeeRecords = ({ employee }) => {
return ( return (
<> <>
{isModalOpen && ( {isModalOpen && (
<GlobalModel size="lg" isOpen={isModalOpen} closeModal={closeModal}> <GlobalModel size="lg" isOpen={isModalOpen} closeModal={closeModal}>
<AttendLogs Id={attendanceId} /> <AttendLogs Id={attendanceId} />
</GlobalModel> </GlobalModel>
)} )}
<div className="px-4 py-2 " style={{ minHeight: "500px" }}> <div className="card px-4 mt-0 py-2 " style={{ minHeight: "500px" }}>
<div <div
className="dataTables_length text-start py-2 d-flex justify-content-between " className="dataTables_length text-start py-2 d-flex justify-content-between "
id="DataTables_Table_0_length" id="DataTables_Table_0_length"
> >
<div className="col-md-3 my-0 "> <div className="col-md-3 my-0 ">
<DateRangePicker onRangeChange={setDateRange} endDateMode="yesterday" /> <DateRangePicker
DateDifference="30"
onRangeChange={setDateRange}
endDateMode="today"
/>
</div> </div>
<div className="col-md-2 m-0 text-end"> <div className="col-md-2 m-0 text-end">
<i <i
className={`bx bx-refresh cursor-pointer fs-4 ${isFetching ? "spin" : "" className={`bx bx-refresh cursor-pointer fs-4 ${
}`} isFetching ? "spin" : ""
}`}
data-toggle="tooltip" data-toggle="tooltip"
title="Refresh" title="Refresh"
onClick={() => refetch()} onClick={() => refetch()}
@ -214,8 +229,9 @@ const AttendancesEmployeeRecords = ({ employee }) => {
{[...Array(totalPages)].map((_, index) => ( {[...Array(totalPages)].map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${currentPage === index + 1 ? "active" : "" className={`page-item ${
}`} currentPage === index + 1 ? "active" : ""
}`}
> >
<button <button
className="page-link " className="page-link "
@ -226,8 +242,9 @@ const AttendancesEmployeeRecords = ({ employee }) => {
</li> </li>
))} ))}
<li <li
className={`page-item ${currentPage === totalPages ? "disabled" : "" className={`page-item ${
}`} currentPage === totalPages ? "disabled" : ""
}`}
> >
<button <button
className="page-link " className="page-link "
@ -244,4 +261,4 @@ const AttendancesEmployeeRecords = ({ employee }) => {
); );
}; };
export default AttendancesEmployeeRecords; export default EmpAttendance;

View File

@ -0,0 +1,93 @@
import React, { useState, useEffect } from "react";
import { useChangePassword } from "../../components/Context/ChangePasswordContext";
import GlobalModel from "../common/GlobalModel";
import ManageEmployee from "./ManageEmployee";
const EmpBanner = ({ profile, loggedInUser }) => {
const { openChangePassword } = useChangePassword();
const [showModal, setShowModal] = useState(false);
return (
<>
{showModal && (
<GlobalModel
size="lg"
isOpen={showModal}
closeModal={() => setShowModal(false)}
>
<ManageEmployee
employeeId={profile.id}
onClosed={() => setShowModal(false)}
/>
</GlobalModel>
)}
<div class="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-8">
<div class="flex-shrink-0 mt-1 mx-sm-0 mx-auto">
<img
width={125}
src="../../assets/img/avatars/00.jpg"
alt="user image"
class="d-block h-auto ms-0 ms-sm-6 rounded-3 user-profile-img"
/>
</div>
<div class="flex-grow-1 mt-1 mt-lg-1">
<div class="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 class="user-profile-info">
<h4 class="mb-2">{`${profile?.firstName} ${profile?.middleName} ${profile?.lastName}`}</h4>
<ul class="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center gap-4 mt-4">
<li class="list-inline-item">
<i class="icon-base bx bx-palette me-2 align-top"></i>
<span class="fw-medium">
{profile?.jobRole || <em>NA</em>}
</span>
</li>
<li class="list-inline-item">
<i class="icon-base bx bx-phone me-2 align-top"></i>
<span class="fw-medium">
{" "}
{profile?.phoneNumber || <em>NA</em>}
</span>
</li>
<li class="list-inline-item">
<i class="icon-base bx bx-calendar me-2 align-top"></i>
<span class="fw-medium">
{" "}
Joined on{" "}
{profile?.joiningDate ? (
new Date(profile.joiningDate).toLocaleDateString()
) : (
<em>NA</em>
)}
</span>
</li>
</ul>
<ul class="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center mt-4">
<li class="list-inline-item">
<button
className="btn btn-sm btn-primary btn-block"
onClick={() => setShowModal(true)}
>
Edit Profile
</button>
</li>
<li class="list-inline-item">
{profile?.id == loggedInUser?.employeeInfo?.id && (
<button
className="btn btn-sm btn-outline-primary btn-block"
onClick={() => openChangePassword()}
>
Change Password
</button>
)}
</li>
</ul>
</div>
</div>
</div>
</div>
</>
);
};
export default EmpBanner;

View File

@ -0,0 +1,37 @@
import React from "react";
import Avatar from "../common/Avatar";
import EmpOverview from "./EmpOverview";
const EmpDashboard = ({ profile }) => {
return (
<>
<div className="row">
<div className="col col-4 pt-5">
{" "}
<EmpOverview profile={profile}></EmpOverview>
</div>
<div className="col col-4 pt-5">
<div className="card">
{" "}
<div class="card-body">
<small class="card-text text-uppercase text-body-secondary small">
My Projects
</small>{" "}
</div>
</div>
</div>
<div className="col col-4 pt-5">
<div className="card">
<div class="card-body">
<small class="card-text text-uppercase text-body-secondary small">
My Expences
</small>{" "}
</div>{" "}
</div>
</div>
</div>
</>
);
};
export default EmpDashboard;

View File

@ -0,0 +1,111 @@
import React from "react";
import Avatar from "../common/Avatar";
import { useProfile } from "../../hooks/useProfile";
const EmpOverview = ({ profile }) => {
const { loggedInUserProfile } = useProfile();
console.log(loggedInUserProfile);
console.log(profile);
return (
<>
{" "}
<div className="row">
<div className="col-12 mb-4">
<div className="card">
<div className="card-body">
<small className="card-text text-uppercase text-body-secondary small">
About
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-user"></i>
<span className="fw-medium mx-2">Full Name:</span>{" "}
<span>{`${profile?.firstName} ${profile?.lastName}`}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-check"></i>
<span className="fw-medium mx-2">Status:</span>{" "}
<span>Active</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-crown"></i>
<span className="fw-medium mx-2">Role:</span>{" "}
<span> {profile?.jobRole || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-flag"></i>
<span className="fw-medium mx-2">Gender:</span>{" "}
<span> {profile?.gender || <em>NA</em>}</span>
</li>{" "}
<li className="d-flex align-items-center mb-2">
<i className="icon-base bx bx-calendar "></i>
<span className="fw-medium mx-2">Birth Date:</span>{" "}
<span>
{profile?.birthDate ? (
new Date(profile.birthDate).toLocaleDateString()
) : (
<em>NA</em>
)}
</span>
</li>
<li className="d-flex align-items-center mb-2">
<i className="icon-base bx bx-calendar "></i>
<span className="fw-medium mx-2">Joining Date:</span>{" "}
<span>
{profile?.joiningDate ? (
new Date(profile.joiningDate).toLocaleDateString()
) : (
<em>NA</em>
)}
</span>
</li>
</ul>
<small className="card-text text-uppercase text-body-secondary small">
Contacts
</small>
<ul className="list-unstyled my-3 py-1">
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-phone"></i>
<span className="fw-medium mx-2">Contact:</span>{" "}
<span> {profile?.phoneNumber || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-envelope"></i>
<span className="fw-medium mx-2">Email:</span>{" "}
<a href={`${profile?.email}`}>
{" "}
{profile?.email || <em>NA</em>}
</a>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-user"></i>
<span className="fw-medium mx-2">
{" "}
Emergency Contact:
</span>{" "}
<span> {profile?.emergencyContactPerson || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="icon-base bx bx-phone"></i>
<span className="fw-medium mx-2"> Emergency Phone:</span>{" "}
<span> {profile?.emergencyPhoneNumber || <em>NA</em>}</span>
</li>
<li className="d-flex align-items-center ">
<div className="container p-0">
<div className="fw-medium text-start">
<i className="icon-base bx bx-map "></i> Address:
</div>
<div className="text-start ms-7">
{profile?.currentAddress || <em>NA</em>}
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</>
);
};
export default EmpOverview;

View File

@ -5,6 +5,20 @@ const EmployeeNav = ({ onPillClick, activePill }) => {
<div className="col-md-12"> <div className="col-md-12">
<div className="nav-align-top "> <div className="nav-align-top ">
<ul className="nav nav-tabs"> <ul className="nav nav-tabs">
<li className="nav-item" style={{ padding: "10px 10px 0 10px" }}>
<a
className={`nav-link py-1 px-2 small ${
activePill === "profile" ? "active" : ""
}`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
onPillClick("profile");
}}
>
<i className="icon-base bx bx-user icon-sm me-1_5"></i> Profile
</a>
</li>
<li className="nav-item" style={{ padding: "10px 10px 0 10px" }}> <li className="nav-item" style={{ padding: "10px 10px 0 10px" }}>
<a <a
className={`nav-link py-1 px-2 small ${ className={`nav-link py-1 px-2 small ${

View File

@ -82,7 +82,7 @@ const Header = () => {
}; };
const handleProfilePage = () => { const handleProfilePage = () => {
navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`); navigate(`/employee/${profile?.employeeInfo?.id}`);
}; };
const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const { projectNames, loading: projectLoading, fetchData } = useProjectName();
@ -91,7 +91,7 @@ const Header = () => {
const projectsForDropdown = isDashboard const projectsForDropdown = isDashboard
? projectNames ? projectNames
: projectNames?.filter(project => : projectNames?.filter((project) =>
allowedProjectStatusIds.includes(project.projectStatusId) allowedProjectStatusIds.includes(project.projectStatusId)
); );
@ -109,7 +109,9 @@ const Header = () => {
const selectedProjectObj = projectNames.find( const selectedProjectObj = projectNames.find(
(p) => p?.id === selectedProject (p) => p?.id === selectedProject
); );
currentProjectDisplayName = selectedProjectObj ? selectedProjectObj.name : "All Projects"; currentProjectDisplayName = selectedProjectObj
? selectedProjectObj.name
: "All Projects";
} }
} }
@ -128,14 +130,15 @@ const Header = () => {
if (isDashboard) { if (isDashboard) {
dispatch(setProjectId(null)); dispatch(setProjectId(null));
} else { } else {
const firstAllowedProject = projectNames.find(project => allowedProjectStatusIds.includes(project.projectStatusId)); const firstAllowedProject = projectNames.find((project) =>
allowedProjectStatusIds.includes(project.projectStatusId)
);
dispatch(setProjectId(firstAllowedProject?.id || null)); dispatch(setProjectId(firstAllowedProject?.id || null));
} }
} }
} }
}, [projectNames, selectedProject, dispatch, isDashboard]); }, [projectNames, selectedProject, dispatch, isDashboard]);
const handler = useCallback( const handler = useCallback(
async (data) => { async (data) => {
if (!HasManageProjectPermission) { if (!HasManageProjectPermission) {
@ -223,40 +226,42 @@ const Header = () => {
</span> </span>
)} )}
{shouldShowDropdown && projectsForDropdown && projectsForDropdown.length > 0 && ( {shouldShowDropdown &&
<ul projectsForDropdown &&
className="dropdown-menu" projectsForDropdown.length > 0 && (
style={{ overflow: "auto", maxHeight: "300px" }} <ul
> className="dropdown-menu"
{isDashboard && ( style={{ overflow: "auto", maxHeight: "300px" }}
<li> >
<button {isDashboard && (
className="dropdown-item" <li>
onClick={() => handleProjectChange(null)}
>
All Projects
</button>
</li>
)}
{[...projectsForDropdown]
.sort((a, b) => a?.name?.localeCompare(b.name))
.map((project) => (
<li key={project?.id}>
<button <button
className="dropdown-item" className="dropdown-item"
onClick={() => handleProjectChange(project?.id)} onClick={() => handleProjectChange(null)}
> >
{project?.name} All Projects
{project?.shortName && (
<span className="text-primary fw-semibold ms-1">
({project?.shortName})
</span>
)}
</button> </button>
</li> </li>
))} )}
</ul> {[...projectsForDropdown]
)} .sort((a, b) => a?.name?.localeCompare(b.name))
.map((project) => (
<li key={project?.id}>
<button
className="dropdown-item"
onClick={() => handleProjectChange(project?.id)}
>
{project?.name}
{project?.shortName && (
<span className="text-primary fw-semibold ms-1">
({project?.shortName})
</span>
)}
</button>
</li>
))}
</ul>
)}
</div> </div>
</div> </div>
)} )}
@ -457,4 +462,4 @@ const Header = () => {
</nav> </nav>
); );
}; };
export default Header; export default Header;

View File

@ -51,7 +51,8 @@ const EmployeeList = () => {
const { employees, loading, setLoading, error, recallEmployeeData } = const { employees, loading, setLoading, error, recallEmployeeData } =
useEmployeesAllOrByProjectId( useEmployeesAllOrByProjectId(
showAllEmployees ,selectedProjectId, showAllEmployees,
selectedProjectId,
showInactive showInactive
); );
@ -153,8 +154,6 @@ const EmployeeList = () => {
} }
}; };
const handleAllEmployeesToggle = (e) => { const handleAllEmployeesToggle = (e) => {
const isChecked = e.target.checked; const isChecked = e.target.checked;
setShowInactive(false); setShowInactive(false);
@ -295,450 +294,449 @@ const EmployeeList = () => {
{ViewTeamMember ? ( {ViewTeamMember ? (
// <div className="row"> // <div className="row">
<div className="card p-1"> <div className="card p-1">
<div className="card-datatable table-responsive pt-2"> <div className="card-datatable table-responsive pt-2">
<div <div
id="DataTables_Table_0_wrapper" id="DataTables_Table_0_wrapper"
className="dataTables_wrapper dt-bootstrap5 no-footer" className="dataTables_wrapper dt-bootstrap5 no-footer"
style={{ width: "98%" }} style={{ width: "98%" }}
> >
<div className="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3"> <div className="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
{/* Switches: All Employees + Inactive */} {/* Switches: All Employees + Inactive */}
<div className="d-flex flex-wrap align-items-center gap-3"> <div className="d-flex flex-wrap align-items-center gap-3">
{/* All Employees Switch */} {/* All Employees Switch */}
{ViewAllEmployee && ( {ViewAllEmployee && (
<div className="form-check form-switch text-start"> <div className="form-check form-switch text-start">
<input <input
type="checkbox" type="checkbox"
className="form-check-input" className="form-check-input"
role="switch" role="switch"
id="allEmployeesCheckbox" id="allEmployeesCheckbox"
checked={showAllEmployees} checked={showAllEmployees}
onChange={handleAllEmployeesToggle} onChange={handleAllEmployeesToggle}
/> />
<label <label
className="form-check-label ms-0" className="form-check-label ms-0"
htmlFor="allEmployeesCheckbox" htmlFor="allEmployeesCheckbox"
> >
All Employees All Employees
</label>
</div>
)}
{/* Show Inactive Employees Switch */}
{showAllEmployees && (
<div className="form-check form-switch text-start">
<input
type="checkbox"
className="form-check-input"
role="switch"
id="inactiveEmployeesCheckbox"
checked={showInactive}
onChange={(e)=> setShowInactive(e.target.checked)}
/>
<label
className="form-check-label ms-0"
htmlFor="inactiveEmployeesCheckbox"
>
Show Inactive Employees
</label>
</div>
)}
</div>
{/* Right side: Search + Export + Add Employee */}
<div className="d-flex flex-wrap align-items-center justify-content-end gap-3 flex-grow-1">
{/* Search Input - ALWAYS ENABLED */}
<div className="dataTables_filter">
<label className="mb-0">
<input
type="search"
value={searchText}
onChange={handleSearch}
className="form-control form-control-sm"
placeholder="Search User"
aria-controls="DataTables_Table_0"
/>
</label> </label>
</div> </div>
)}
{/* Export Dropdown */} {/* Show Inactive Employees Switch */}
<div className="dropdown"> {showAllEmployees && (
<button <div className="form-check form-switch text-start">
aria-label="Click me" <input
className="btn btn-sm btn-label-secondary dropdown-toggle" type="checkbox"
type="button" className="form-check-input"
data-bs-toggle="dropdown" role="switch"
aria-expanded="false" id="inactiveEmployeesCheckbox"
checked={showInactive}
onChange={(e) => setShowInactive(e.target.checked)}
/>
<label
className="form-check-label ms-0"
htmlFor="inactiveEmployeesCheckbox"
> >
<i className="bx bx-export me-2 bx-sm"></i>Export Show Inactive Employees
</button> </label>
<ul className="dropdown-menu">
<li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("print")}
>
<i className="bx bx-printer me-1"></i> Print
</a>
</li>
<li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("csv")}
>
<i className="bx bx-file me-1"></i> CSV
</a>
</li>
<li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("excel")}
>
<i className="bx bxs-file-export me-1"></i> Excel
</a>
</li>
<li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("pdf")}
>
<i className="bx bxs-file-pdf me-1"></i> PDF
</a>
</li>
</ul>
</div> </div>
)}
{/* Add Employee Button */}
{Manage_Employee && (
<button
className="btn btn-sm btn-primary"
type="button"
onClick={() => handleEmployeeModel(null)}
>
<i className="bx bx-plus-circle me-2"></i>
<span className="d-none d-md-inline-block">
Add New Employee
</span>
</button>
)}
</div>
</div> </div>
<table {/* Right side: Search + Export + Add Employee */}
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap" <div className="d-flex flex-wrap align-items-center justify-content-end gap-3 flex-grow-1">
id="DataTables_Table_0" {/* Search Input - ALWAYS ENABLED */}
aria-describedby="DataTables_Table_0_info" <div className="dataTables_filter">
style={{ width: "100%" }} <label className="mb-0">
ref={tableRef} <input
> type="search"
<thead> value={searchText}
<tr> onChange={handleSearch}
<th className="form-control form-control-sm"
className="sorting sorting_desc" placeholder="Search User"
tabIndex="0"
aria-controls="DataTables_Table_0" aria-controls="DataTables_Table_0"
rowSpan="1" />
colSpan="2" </label>
aria-label="User: activate to sort column ascending" </div>
aria-sort="descending"
>
<div className="text-start ms-6">Name</div>
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-5">Email</div>
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-5">Contact</div>
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-5">Official Designation</div>
</th>
<th {/* Export Dropdown */}
className="sorting d-none d-md-table-cell" <div className="dropdown">
tabIndex="0" <button
aria-controls="DataTables_Table_0" aria-label="Click me"
rowSpan="1" className="btn btn-sm btn-label-secondary dropdown-toggle"
colSpan="1" type="button"
aria-label="Plan: activate to sort column ascending" data-bs-toggle="dropdown"
> aria-expanded="false"
Joining Date >
</th> <i className="bx bx-export me-2 bx-sm"></i>Export
<th </button>
className="sorting" <ul className="dropdown-menu">
tabIndex="0" <li>
aria-controls="DataTables_Table_0" <a
rowSpan="1" className="dropdown-item"
colSpan="1" href="#"
aria-label="Billing: activate to sort column ascending" onClick={() => handleExport("print")}
>
Status
</th>
<th
className={`sorting_disabled ${
!Manage_Employee && "d-none"
}`}
rowSpan="1"
colSpan="1"
style={{ width: "50px" }}
aria-label="Actions"
>
Actions
</th>
</tr>
</thead>
<tbody>
{loading && (
<tr>
<td colSpan={8}>
<p>Loading...</p>
</td>
</tr>
)}
{/* Conditional messages for no data or no search results */}
{!loading &&
displayData?.length === 0 &&
searchText &&
!showAllEmployees ? (
<tr>
<td colSpan={8}>
<small className="muted">
'{searchText}' employee not found
</small>{" "}
</td>
</tr>
) : null}
{!loading &&
displayData?.length === 0 &&
(!searchText || showAllEmployees) ? (
<tr>
<td
colSpan={8}
style={{ paddingTop: "20px", textAlign: "center" }}
> >
No Data Found <i className="bx bx-printer me-1"></i> Print
</td> </a>
</tr> </li>
) : null} <li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("csv")}
>
<i className="bx bx-file me-1"></i> CSV
</a>
</li>
<li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("excel")}
>
<i className="bx bxs-file-export me-1"></i> Excel
</a>
</li>
<li>
<a
className="dropdown-item"
href="#"
onClick={() => handleExport("pdf")}
>
<i className="bx bxs-file-pdf me-1"></i> PDF
</a>
</li>
</ul>
</div>
{/* Render current items */} {/* Add Employee Button */}
{currentItems && {Manage_Employee && (
!loading && <button
currentItems.map((item) => ( className="btn btn-sm btn-primary"
<tr className="odd" key={item.id}> type="button"
<td className="sorting_1" colSpan={2}> onClick={() => handleEmployeeModel(null)}
<div className="d-flex justify-content-start align-items-center user-name"> >
<Avatar <i className="bx bx-plus-circle me-2"></i>
firstName={item.firstName} <span className="d-none d-md-inline-block">
lastName={item.lastName} Add New Employee
></Avatar> </span>
<div className="d-flex flex-column"> </button>
<a )}
</div>
</div>
<table
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
id="DataTables_Table_0"
aria-describedby="DataTables_Table_0_info"
style={{ width: "100%" }}
ref={tableRef}
>
<thead>
<tr>
<th
className="sorting sorting_desc"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="2"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-6">Name</div>
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-5">Email</div>
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-5">Contact</div>
</th>
<th
className="sorting sorting_desc d-none d-sm-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="User: activate to sort column ascending"
aria-sort="descending"
>
<div className="text-start ms-5">
Official Designation
</div>
</th>
<th
className="sorting d-none d-md-table-cell"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="Plan: activate to sort column ascending"
>
Joining Date
</th>
<th
className="sorting"
tabIndex="0"
aria-controls="DataTables_Table_0"
rowSpan="1"
colSpan="1"
aria-label="Billing: activate to sort column ascending"
>
Status
</th>
<th
className={`sorting_disabled ${
!Manage_Employee && "d-none"
}`}
rowSpan="1"
colSpan="1"
style={{ width: "50px" }}
aria-label="Actions"
>
Actions
</th>
</tr>
</thead>
<tbody>
{loading && (
<tr>
<td colSpan={8}>
<p>Loading...</p>
</td>
</tr>
)}
{/* Conditional messages for no data or no search results */}
{!loading &&
displayData?.length === 0 &&
searchText &&
!showAllEmployees ? (
<tr>
<td colSpan={8}>
<small className="muted">
'{searchText}' employee not found
</small>{" "}
</td>
</tr>
) : null}
{!loading &&
displayData?.length === 0 &&
(!searchText || showAllEmployees) ? (
<tr>
<td
colSpan={8}
style={{ paddingTop: "20px", textAlign: "center" }}
>
No Data Found
</td>
</tr>
) : null}
{/* Render current items */}
{currentItems &&
!loading &&
currentItems.map((item) => (
<tr className="odd" key={item.id}>
<td className="sorting_1" colSpan={2}>
<div className="d-flex justify-content-start align-items-center user-name">
<Avatar
firstName={item.firstName}
lastName={item.lastName}
></Avatar>
<div className="d-flex flex-column">
<a
onClick={() =>
navigate(`/employee/${item.id}`)
}
className="text-heading text-truncate cursor-pointer"
>
<span className="fw-normal">
{item.firstName} {item.middleName}{" "}
{item.lastName}
</span>
</a>
</div>
</div>
</td>
<td className="text-start d-none d-sm-table-cell">
{item.email ? (
<span className="text-truncate">
<i className="bx bxs-envelope text-primary me-2"></i>
{item.email}
</span>
) : (
<span className="text-truncate text-italic">
NA
</span>
)}
</td>
<td className="text-start d-none d-sm-table-cell">
<span className="text-truncate">
<i className="bx bxs-phone-call text-primary me-2"></i>
{item.phoneNumber}
</span>
</td>
<td className=" d-none d-sm-table-cell text-start">
<span className="text-truncate">
<i className="bx bxs-wrench text-success me-2"></i>
{item.jobRole || "Not Assign Yet"}
</span>
</td>
<td className=" d-none d-md-table-cell">
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
</td>
<td>
{showInactive ? (
<span
className="badge bg-label-danger"
text-capitalized=""
>
Inactive
</span>
) : (
<span
className="badge bg-label-success"
text-capitalized=""
>
Active
</span>
)}
</td>
{Manage_Employee && (
<td className="text-end">
<div className="dropdown">
<button
className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<div className="dropdown-menu dropdown-menu-end">
<button
onClick={() => onClick={() =>
navigate( navigate(`/employee/${item.id}`)
`/employee/${item.id}?for=attendance`
)
} }
className="text-heading text-truncate cursor-pointer" className="dropdown-item py-1"
> >
<span className="fw-normal"> <i className="bx bx-detail bx-sm"></i> View
{item.firstName} {item.middleName}{" "} </button>
{item.lastName} <button
</span> className="dropdown-item py-1"
</a> onClick={() => {
handleEmployeeModel(item.id);
}}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
{!item.isSystem && (
<>
<button
className="dropdown-item py-1"
onClick={() =>
handleOpenDelete(item.id)
}
>
<i className="bx bx-task-x bx-sm"></i>{" "}
Suspend
</button>
<button
className="dropdown-item py-1"
type="button"
data-bs-toggle="modal"
data-bs-target="#managerole-modal"
onClick={() =>
setEmpForManageRole(item.id)
}
>
<i className="bx bx-cog bx-sm"></i>{" "}
Manage Role
</button>
</>
)}
</div> </div>
</div> </div>
</td> </td>
<td className="text-start d-none d-sm-table-cell"> )}
{item.email ? ( </tr>
<span className="text-truncate"> ))}
<i className="bx bxs-envelope text-primary me-2"></i> </tbody>
{item.email} </table>
</span>
) : (
<span className="text-truncate text-italic">
NA
</span>
)}
</td>
<td className="text-start d-none d-sm-table-cell">
<span className="text-truncate">
<i className="bx bxs-phone-call text-primary me-2"></i>
{item.phoneNumber}
</span>
</td>
<td className=" d-none d-sm-table-cell text-start">
<span className="text-truncate">
<i className="bx bxs-wrench text-success me-2"></i>
{item.jobRole || "Not Assign Yet"}
</span>
</td>
<td className=" d-none d-md-table-cell"> <div style={{ width: "1%" }}></div>
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
</td>
<td>
{showInactive ? (
<span
className="badge bg-label-danger"
text-capitalized=""
>
Inactive
</span>
) : (
<span
className="badge bg-label-success"
text-capitalized=""
>
Active
</span>
)}
</td>
{Manage_Employee && (
<td className="text-end">
<div className="dropdown">
<button
className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<div className="dropdown-menu dropdown-menu-end">
<button
onClick={() =>
navigate(`/employee/${item.id}`)
}
className="dropdown-item py-1"
>
<i className="bx bx-detail bx-sm"></i>{" "}
View
</button>
<button
className="dropdown-item py-1"
onClick={() => {
handleEmployeeModel(item.id);
}}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
{!item.isSystem && (
<>
<button
className="dropdown-item py-1"
onClick={() =>
handleOpenDelete(item.id)
}
>
<i className="bx bx-task-x bx-sm"></i>{" "}
Suspend
</button>
<button
className="dropdown-item py-1"
type="button"
data-bs-toggle="modal"
data-bs-target="#managerole-modal"
onClick={() =>
setEmpForManageRole(item.id)
}
>
<i className="bx bx-cog bx-sm"></i>{" "}
Manage Role
</button>
</>
)}
</div>
</div>
</td>
)}
</tr>
))}
</tbody>
</table>
<div style={{ width: "1%" }}></div> {/* Pagination */}
{!loading && displayData.length > ITEMS_PER_PAGE && (
{/* Pagination */} <nav aria-label="Page">
{!loading && displayData.length > ITEMS_PER_PAGE && ( <ul className="pagination pagination-sm justify-content-end py-1">
<nav aria-label="Page"> <li
<ul className="pagination pagination-sm justify-content-end py-1"> className={`page-item ${
<li currentPage === 1 ? "disabled" : ""
className={`page-item ${ }`}
currentPage === 1 ? "disabled" : "" >
}`} <button
className="page-link btn-xs"
onClick={() => paginate(currentPage - 1)}
> >
<button &laquo;
className="page-link btn-xs" </button>
onClick={() => paginate(currentPage - 1)} </li>
>
&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>
))}
{[...Array(totalPages)]?.map((_, index) => (
<li <li
key={index}
className={`page-item ${ className={`page-item ${
currentPage === totalPages ? "disabled" : "" currentPage === index + 1 ? "active" : ""
}`} }`}
> >
<button <button
className="page-link" className="page-link"
onClick={() => paginate(currentPage + 1)} onClick={() => paginate(index + 1)}
> >
&raquo; {index + 1}
</button> </button>
</li> </li>
</ul> ))}
</nav>
)} <li
</div> className={`page-item ${
currentPage === totalPages ? "disabled" : ""
}`}
>
<button
className="page-link"
onClick={() => paginate(currentPage + 1)}
>
&raquo;
</button>
</li>
</ul>
</nav>
)}
</div> </div>
</div> </div>
// </div> </div>
) : ( ) : (
// </div>
<div className="card"> <div className="card">
<div className="text-center"> <div className="text-center">
<i className="fa-solid fa-triangle-exclamation fs-5"></i> <i className="fa-solid fa-triangle-exclamation fs-5"></i>

View File

@ -1,26 +1,25 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import EmpProfile from "../../components/Employee/EmpProfile"; import { useSearchParams, useParams, useNavigate } from "react-router-dom";
import axios from "axios"; import { useSelector } from "react-redux";
import Breadcrumb from "../../components/common/Breadcrumb";
import EmployeeNav from "../../components/Employee/EmployeeNav";
import { useSearchParams, useParams } from "react-router-dom";
import { getCachedData } from "../../slices/apiDataManager";
import { import {
useEmployeeProfile, useEmployeeProfile,
useEmployees, useEmployees,
useEmployeesByProject, useEmployeesByProject,
} from "../../hooks/useEmployees"; } from "../../hooks/useEmployees";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { getCachedData } from "../../slices/apiDataManager";
import { useSelector } from "react-redux";
import EmployeeRepository from "../../repositories/EmployeeRepository"; import EmployeeRepository from "../../repositories/EmployeeRepository";
import { ComingSoonPage } from "../Misc/ComingSoonPage"; import { ComingSoonPage } from "../Misc/ComingSoonPage";
import { useNavigate } from "react-router-dom";
import Avatar from "../../components/common/Avatar"; import Breadcrumb from "../../components/common/Breadcrumb";
import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords"; import EmployeeNav from "../../components/Employee/EmployeeNav";
import EmpProfile from "../../components/Employee/EmpProfile";
import EmpAttendance from "../../components/Employee/EmpAttendance";
import ManageEmployee from "../../components/Employee/ManageEmployee"; import ManageEmployee from "../../components/Employee/ManageEmployee";
import { useChangePassword } from "../../components/Context/ChangePasswordContext"; import EmpBanner from "../../components/Employee/EmpBanner";
import GlobalModel from "../../components/common/GlobalModel"; import EmpDashboard from "../../components/Employee/EmpDashboard";
const EmployeeProfile = () => { const EmployeeProfile = () => {
const { profile } = useProfile(); const { profile } = useProfile();
@ -32,7 +31,7 @@ const EmployeeProfile = () => {
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 || "profile");
const [currentEmployee, setCurrentEmployee] = useState(); const [currentEmployee, setCurrentEmployee] = useState();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
@ -40,8 +39,6 @@ const EmployeeProfile = () => {
setActivePill(pillKey); setActivePill(pillKey);
}; };
const fetchEmployeeProfile = async (employeeID) => { const fetchEmployeeProfile = async (employeeID) => {
try { try {
const resp = await EmployeeRepository.getEmployeeProfile(employeeID); const resp = await EmployeeRepository.getEmployeeProfile(employeeID);
@ -62,10 +59,17 @@ const EmployeeProfile = () => {
const renderContent = () => { const renderContent = () => {
if (loading) return <div>Loading</div>; if (loading) return <div>Loading</div>;
switch (activePill) { switch (activePill) {
case "profile": {
return (
<>
<EmpDashboard profile={currentEmployee} />
</>
);
}
case "attendance": { case "attendance": {
return ( return (
<> <>
<AttendancesEmployeeRecords employee={employeeId} /> <EmpAttendance employee={employeeId} />
</> </>
); );
} }
@ -98,14 +102,8 @@ const EmployeeProfile = () => {
if (loading) { if (loading) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
const { openChangePassword } = useChangePassword();
return ( return (
<> <>
{showModal && (
<GlobalModel size="lg" isOpen={showModal} closeModal={() => setShowModal(false)}>
<ManageEmployee employeeId={employeeId} onClosed={() => setShowModal(false)} />
</GlobalModel>
)}
<div className="container-fluid"> <div className="container-fluid">
<Breadcrumb <Breadcrumb
data={[ data={[
@ -114,148 +112,25 @@ const EmployeeProfile = () => {
{ label: "Profile", link: null }, { label: "Profile", link: null },
]} ]}
></Breadcrumb> ></Breadcrumb>
<div className="row"> <div className="row">
<div className="col-12 col-md-8 col-lg-4 order-1 order-lg-1"> <div className="col-12 ">
<div className="row"> <EmpBanner
<div className="col-12 mb-4"> profile={currentEmployee}
<div className="card"> loggedInUser={profile}
<div className="card-body"> ></EmpBanner>
<div className="d-flex flex-row flex-lg-column">
<div className="d-flex flex-column justify-content-center align-items-center text-center">
<Avatar
firstName={`${currentEmployee?.firstName}`}
lastName={`${currentEmployee?.lastName}`}
size={"lg"}
/>
<div className="py-2">
<p className="h6">{`${currentEmployee?.firstName} ${currentEmployee?.lastName}`}</p>
</div>
</div>
<div className="w-100 d-flex flex-column justify-content-start">
<div className="mt-3 w-100">
<h6 className="mb-2 text-muted text-start">
Employee Info
</h6>
<table className="table table-borderless mb-3">
<tbody>
<tr>
<td className="fw-medium text-start">Email:</td>
<td className="text-start">
{currentEmployee?.email || <em>NA</em>}
</td>
</tr>
<tr>
<td className="fw-medium text-start text-nowrap">
Phone Number:
</td>
<td className="text-start">
{currentEmployee?.phoneNumber || <em>NA</em>}
</td>
</tr>
<tr>
<td className="fw-medium text-start" style={{ width: '120px' }}>
Emergency Contact Person:
</td>
<td className="text-start align-bottom">
{currentEmployee?.emergencyContactPerson || <em>NA</em>}
</td>
</tr>
<tr>
<td className="fw-medium text-start">
Emergency Contact Number:
</td>
<td className="text-start align-bottom">
{currentEmployee?.emergencyPhoneNumber || <em>NA</em>}
</td>
</tr>
<tr>
<td className="fw-medium text-start">
Gender:
</td>
<td className="text-start">
{currentEmployee?.gender || <em>NA</em>}
</td>
</tr>
<tr>
<td className="fw-medium text-start">
Birth Date:
</td>
<td className="text-start">
{currentEmployee?.birthDate ? (
new Date(
currentEmployee.birthDate
).toLocaleDateString()
) : (
<em>NA</em>
)}
</td>
</tr>
<tr>
<td className="fw-medium text-start">
Joining Date:
</td>
<td className="text-start">
{currentEmployee?.joiningDate ? (
new Date(
currentEmployee.joiningDate
).toLocaleDateString()
) : (
<em>NA</em>
)}
</td>
</tr>
<tr>
<td className="fw-medium text-start">
Job Role:
</td>
<td className="text-start">
{currentEmployee?.jobRole || <em>NA</em>}
</td>
</tr>
<tr>
<td className="fw-medium text-start align-top" >
Address:
</td>
<td className="text-start">
{currentEmployee?.currentAddress || <em>NA</em>}
</td>
</tr>
</tbody>
</table>
</div>
<button
className="btn btn-primary btn-block"
onClick={() => setShowModal(true)}
>
Edit Profile
</button>
{currentEmployee?.id == profile?.employeeInfo?.id && (
<button
className="btn btn-outline-primary btn-block mt-2"
onClick={() => openChangePassword()}
>
Change Password
</button>
)}
</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">
<div className="row"> <div className="col-12 ">
<EmployeeNav <EmployeeNav
onPillClick={handlePillClick} onPillClick={handlePillClick}
activePill={activePill} activePill={activePill}
/> />
</div> </div>
<div className="card"> </div>
<div className="row">
<div className="col-12">
<div>
<div className="row row-bordered g-0">{renderContent()}</div> <div className="row row-bordered g-0">{renderContent()}</div>
</div> </div>
</div> </div>