Merge pull request 'Issues_Sep_1W_V2' (#402) from Issues_Sep_1W_V2 into Refactor_Directory

Reviewed-on: #402
Merged
This commit is contained in:
pramod.mahajan 2025-09-16 05:11:27 +00:00
commit d2288ea967
14 changed files with 372 additions and 351 deletions

View File

@ -470,9 +470,8 @@ const ManageContact = ({ contactId, closeModal }) => {
)} )}
</div> </div>
{/* Actions */} <div className="d-flex justify-content-end gap-2 py-2 mt-3">
<div className="d-flex justify-content-end gap-3 py-2"> <button
<button
className="btn btn-sm btn-label-secondary" className="btn btn-sm btn-label-secondary"
type="button" type="button"
onClick={handleClosed} onClick={handleClosed}

View File

@ -34,9 +34,13 @@ const EmpDashboard = ({ profile }) => {
<div className="d-flex flex-wrap align-items-center justify-content-between gap-2"> <div className="d-flex flex-wrap align-items-center justify-content-between gap-2">
<div className="d-flex"> <div className="d-flex">
<div className="avatar flex-shrink-0 me-3"> <div className="avatar flex-shrink-0 me-3">
<span className="avatar-initial rounded bg-label-primary"> <span
className={`avatar-initial rounded ${project.removedDate ? "bg-label-warning" : "bg-label-success"
}`}
>
<i className="icon-base bx bx-buildings icon-lg"></i> <i className="icon-base bx bx-buildings icon-lg"></i>
</span> </span>
</div> </div>
<div> <div>
<h6 className="mb-0">{project.projectShortName}</h6> <h6 className="mb-0">{project.projectShortName}</h6>
@ -49,6 +53,14 @@ const EmpDashboard = ({ profile }) => {
<em>NA</em> <em>NA</em>
)} )}
</div> </div>
{project.removedDate && (
<div className="mt-0 d-flex flex-column flex-sm-row justify-content-between">
<div className="label-secondary">
Unassigned:{" "}
{new Date(project.removedDate).toLocaleDateString()}
</div>
</div>
)}
</div> </div>
</div> </div>
<div> <div>
@ -59,14 +71,7 @@ const EmpDashboard = ({ profile }) => {
</div> </div>
{/* Dates */} {/* Dates */}
{project.removedDate && (
<div className="mt-2 d-flex flex-column flex-sm-row justify-content-between">
<div className="label-secondary">
Unassigned:{" "}
{new Date(project.removedDate).toLocaleDateString()}
</div>
</div>
)}
</div> </div>
</li> </li>
))} ))}

View File

@ -187,7 +187,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
<div className="d-flex justify-content-end py-3 gap-2"> <div className="d-flex justify-content-end py-3 gap-2">
<button <button
type="button" type="button"
className="btn btn-secondary btn-xs" className="btn btn-label-secondary btn-xs"
onClick={onClear} onClick={onClear}
> >
Clear Clear

View File

@ -47,6 +47,20 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
}, },
}); });
const dropdownRef = useRef(null);
const [open, setOpen] = useState(false);
// Close dropdown on outside click
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const infoRef = useRef(null); const infoRef = useRef(null);
const infoRef1 = useRef(null); const infoRef1 = useRef(null);
@ -255,19 +269,20 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
<div className="row mb-1"> <div className="row mb-1">
<div className="col-12"> <div className="col-12">
<div className="form-text text-start"> <div className="form-text text-start">
<div className="d-flex align-items-center form-text fs-7"> <div
className="d-flex align-items-center form-text fs-7"
ref={dropdownRef}
>
<span className="text-dark">Select Team</span> <span className="text-dark">Select Team</span>
{/* Dropdown */} {/* Dropdown */}
<div className="dropdown position-relative d-inline-block"> <div className="dropdown position-relative d-inline-block">
<a <a
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0 className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0
? "text-secondary" ? "text-secondary"
: "text-primary" : "text-primary"
}`} }`}
data-bs-toggle="dropdown" onClick={() => setOpen(!open)}
role="button"
aria-expanded="false"
> >
<i className="bx bx-slider-alt ms-2"></i> <i className="bx bx-slider-alt ms-2"></i>
</a> </a>
@ -290,57 +305,55 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
</span> </span>
)} )}
{/* Dropdown Menu with Scroll */} {/* Dropdown Menu */}
<ul {open && (
className="dropdown-menu p-2 text-capitalize" <ul
style={{ maxHeight: "300px", overflowY: "auto" }} className="dropdown-menu show p-2 text-capitalize"
> style={{ maxHeight: "300px", overflowY: "auto" }}
{/* All Roles */} >
<li key="all"> {/* All Roles */}
<div className="form-check dropdown-item py-0"> <li key="all">
<input
className="form-check-input"
type="checkbox"
id="checkboxAllRoles"
value="all"
checked={selectedRoles.includes("all")}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor="checkboxAllRoles"
>
All Roles
</label>
</div>
</li>
{/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0"> <div className="form-check dropdown-item py-0">
<input <input
className="form-check-input" className="form-check-input"
type="checkbox" type="checkbox"
id={`checkboxRole-${role.id}`} id="checkboxAllRoles"
value={role.id} value="all"
checked={selectedRoles.includes(String(role.id))} checked={selectedRoles.includes("all")}
onChange={(e) => onChange={(e) => handleRoleChange(e, e.target.value)}
handleRoleChange(e, e.target.value)
}
/> />
<label <label
className="form-check-label ms-2" className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`} htmlFor="checkboxAllRoles"
> >
{role.name} All Roles
</label> </label>
</div> </div>
</li> </li>
))}
</ul> {/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id={`checkboxRole-${role.id}`}
value={role.id}
checked={selectedRoles.includes(String(role.id))}
onChange={(e) => handleRoleChange(e, e.target.value)}
/>
<label
className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`}
>
{role.name}
</label>
</div>
</li>
))}
</ul>
)}
</div> </div>
{/* Search Box */} {/* Search Box */}

View File

@ -106,7 +106,7 @@ const TenantFilterPanel = ({ onApply }) => {
<div className="d-flex justify-content-end py-3 gap-2"> <div className="d-flex justify-content-end py-3 gap-2">
<button <button
type="button" type="button"
className="btn btn-secondary btn-xs" className="btn btn-label-secondary btn-xs"
onClick={onClear} onClick={onClear}
> >

View File

@ -273,7 +273,7 @@ const FilterIcon = ({
<li className="d-flex justify-content-end gap-2 px-2 mt-2 mb-2"> <li className="d-flex justify-content-end gap-2 px-2 mt-2 mb-2">
<button <button
type="button" type="button"
className="btn btn-secondary btn-sm py-0 px-2" className="btn btn-label-secondary btn-sm py-0 px-2"
onClick={clearAllFilters} onClick={clearAllFilters}
> >
Clear Clear

View File

@ -63,6 +63,7 @@
border: none; border: none;
outline: none; outline: none;
background-color: #f8f9fa; background-color: #f8f9fa;
color: black;
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
} }

View File

@ -65,9 +65,9 @@ const handleChange = (e) => {
return ( return (
<> <>
<Label htmlFor={name} className="form-label" required> <label htmlFor={name} className="form-label">
{label} {label}
</Label> </label>
<div <div
className="form-control form-control-sm p-1" className="form-control form-control-sm p-1"

View File

@ -11,6 +11,17 @@ import { queryClient } from "../layouts/AuthLayout";
// Query --------------------------------------------------------------------------- // Query ---------------------------------------------------------------------------
export const useEmployee = (employeeId) => {
return useQuery({
queryKey: ["employeeProfile", employeeId],
queryFn: async () => {
const res = await EmployeeRepository.getEmployeeProfile(employeeId)
return res.data;
},
enabled:!!employeeId
});
};
export const useAllEmployees = (showInactive) => { export const useAllEmployees = (showInactive) => {
const { const {
data = [], data = [],
@ -197,6 +208,9 @@ export const useEmployeesNameByProject = (projectId) => {
// Mutation------------------------------------------------------------------ // Mutation------------------------------------------------------------------
export const useUpdateEmployee = () => { export const useUpdateEmployee = () => {
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
@ -211,6 +225,7 @@ export const useUpdateEmployee = () => {
const isAllEmployee = variables.IsAllEmployee; const isAllEmployee = variables.IsAllEmployee;
// Cache invalidation // Cache invalidation
queryClient.invalidateQueries({ queryKey: ["employeeProfile",id] });
queryClient.invalidateQueries({ queryKey: ["allEmployees"] }); queryClient.invalidateQueries({ queryKey: ["allEmployees"] });
// queryClient.invalidateQueries(['employeeProfile', id]); // queryClient.invalidateQueries(['employeeProfile', id]);
queryClient.invalidateQueries({ queryKey: ["projectEmployees"] }); queryClient.invalidateQueries({ queryKey: ["projectEmployees"] });
@ -232,40 +247,6 @@ export const useUpdateEmployee = () => {
}); });
}; };
// export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing }) => {
// const queryClient = useQueryClient();
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
// return useMutation({
// mutationFn: (id) => {
// setemployeeLodaing(true);
// return EmployeeRepository.deleteEmployee(id);
// },
// onSuccess: () => {
// // queryClient.invalidateQueries( ['allEmployee',false]);
// queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
// queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject' ,selectedProject]} );
// showToast("Employee deleted successfully.", "success");
// setIsDeleteModalOpen(false);
// },
// onError: (error) => {
// const message =
// error.response?.data?.message ||
// error.message ||
// "An unexpected error occurred";
// showToast(message, "error");
// setIsDeleteModalOpen(false);
// },
// onSettled: () => {
// setemployeeLodaing(false);
// },
// });
// };
// Manage Role
export const useSuspendEmployee = ({ export const useSuspendEmployee = ({
setIsDeleteModalOpen, setIsDeleteModalOpen,
@ -353,4 +334,4 @@ export const useUpdateEmployeeRoles = ({
isError: mutation.isError, isError: mutation.isError,
error: mutation.error, error: mutation.error,
}; };
}; };

View File

@ -10,7 +10,7 @@ import { z } from "zod";
const forgotPassSceham = z.object({ const forgotPassSceham = z.object({
email: z.string().email(), email: z.string().trim().email(),
}) })
const ForgotPasswordPage = () => { const ForgotPasswordPage = () => {

View File

@ -16,13 +16,13 @@ const LoginPage = () => {
const loginSchema = IsLoginWithOTP const loginSchema = IsLoginWithOTP
? z.object({ ? z.object({
username: z.string().email({ message: "Valid email required" }), username: z.string().trim().email({ message: "Valid email required" }),
}) })
: z.object({ : z.object({
username: z.string().email({ message: "Valid email required" }), username: z.string().trim().email({ message: "Valid email required" }),
password: z.string().min(1, { message: "Password required" }), password: z.string().trim().min(1, { message: "Password required" }),
rememberMe: z.boolean(), rememberMe: z.boolean(),
}); });
const { const {
register, register,
@ -106,7 +106,7 @@ const LoginPage = () => {
{/* Password */} {/* Password */}
{!IsLoginWithOTP && ( {!IsLoginWithOTP && (
<> <>
<div className="mb-3 text-start"> {/* <div className="mb-3 text-start">
<label htmlFor="password" className="form-label"> <label htmlFor="password" className="form-label">
Password Password
</label> </label>
@ -114,9 +114,8 @@ const LoginPage = () => {
<input <input
type={hidepass ? "password" : "text"} type={hidepass ? "password" : "text"}
id="password" id="password"
className={`form-control ${ className={`form-control ${errors.password ? "is-invalid" : ""
errors.password ? "is-invalid" : "" }`}
}`}
placeholder="••••••••" placeholder="••••••••"
{...register("password")} {...register("password")}
/> />
@ -135,6 +134,38 @@ const LoginPage = () => {
{errors.password.message} {errors.password.message}
</div> </div>
)} )}
</div> */}
<div className="mb-3 form-password-toggle text-start">
<label htmlFor="password" className="form-label">
Password
</label>
<div className="input-group input-group-merge">
<input
type={hidepass ? "password" : "text"}
autoComplete="new-password"
id="password"
className="form-control form-control-xl shadow-none"
name="password"
{...register("password")}
placeholder="••••••••••••"
aria-describedby="password"
/>
<span className="input-group-text cursor-pointer border-start-0">
<button
type="button"
className="btn btn-link p-0"
onClick={() => setHidepass(!hidepass)}
>
{hidepass ? (
<i className="bx bx-hide fs-5" />
) : (
<i className="bx bx-show fs-5" />
)}
</button>
</span>
</div>
</div> </div>
{/* Remember Me + Forgot Password */} {/* Remember Me + Forgot Password */}
@ -169,8 +200,8 @@ const LoginPage = () => {
{loading {loading
? "Please Wait..." ? "Please Wait..."
: IsLoginWithOTP : IsLoginWithOTP
? "Send OTP" ? "Send OTP"
: "Sign In"} : "Sign In"}
</button> </button>
{/* Login With OTP Button */} {/* Login With OTP Button */}

View File

@ -93,153 +93,155 @@ const ResetPassword = () => {
); );
} }
return ( return (
<div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center justify-content-center p-4 p-sm-5 bg-gray-60 h-100"> <div className="col-12 col-lg-5 col-xl-4 d-flex align-items-center justify-content-center p-4 p-sm-5 bg-gray-60 h-100">
<div className="block p-4 p-sm-5 bg-gray-60"> <div className="block p-4 p-sm-5 bg-gray-60">
<h4 className="mb-2">Reset Password? 🔒</h4> <h4 className="mb-2">Reset Password? 🔒</h4>
<p className="mb-4">Enter your email and new password to update.</p> <p className="mb-4">Enter your email and new password to update.</p>
<form <form
id="formAuthentication" id="formAuthentication"
className="mb-3" className="mb-3"
onSubmit={handleSubmit(onSubmitResetPassword)} onSubmit={handleSubmit(onSubmitResetPassword)}
>
<div className="mb-3 text-start">
<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-2 form-password-toggle text-start">
<div className="mt-2">
<label htmlFor="email" className="form-label">
New Password
</label>
</div>
<div className=" input-group input-group-merge">
<input
type={hidepass ? "password" : "text"}
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"
/>
<button
type="button"
className="btn btn-outline-secondy border-top border-end border-bottom "
onClick={() => setHidepass(!hidepass)}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
borderLeft: 0,
}}
>
{hidepass ? (
<i className="bx bx-hide" />
) : (
<i className="bx bx-show" />
)}
</button>
</div>
{errors.password && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.password.message}
</div>
)}
<div className="mt-2">
{" "}
<label htmlFor="email" className="form-label">
Repeat New Password
</label>{" "}
</div>
<div className=" input-group input-group-merge">
<input
type={hidepass1 ? "password" : "text"}
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"
/>
<button
type="button"
className="btn border-top border-end border-bottom "
onClick={() => setHidepass1(!hidepass1)}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
borderLeft: 0,
}}
>
{hidepass1 ? (
<i className="bx bx-hide" />
) : (
<i className="bx bx-show" />
)}
</button>
</div>
{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> <div className="mb-3 text-start">
Back to login <label htmlFor="email" className="form-label">
</Link> Email
</label>
<input
type="text"
className="form-control border border-secondary"
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-2 form-password-toggle text-start">
<div className="mt-2">
<label htmlFor="email" className="form-label">
New Password
</label>
</div>
<div className="input-group input-group-merge d-flex align-items-center border border-secondary rounded px-2">
<input
type={hidepass ? "password" : "text"}
autoComplete="true"
id="password"
className="form-control form-control-xl border-0 shadow-none"
name="password"
{...register("password")}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
<button
type="button"
className="btn btn-link-secondary p-0 ms-2"
onClick={() => setHidepass(!hidepass)}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
borderLeft: 0,
}}
>
{hidepass ? (
<i className="bx bx-hide" />
) : (
<i className="bx bx-show" />
)}
</button>
</div>
{errors.password && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.password.message}
</div>
)}
<div className="mt-2">
{" "}
<label htmlFor="email" className="form-label">
Repeat New Password
</label>{" "}
</div>
<div className="input-group input-group-merge d-flex align-items-center border border-secondary rounded px-2">
<input
type={hidepass1 ? "password" : "text"}
autoComplete="true"
id="password"
className="form-control form-control-xl border-0 shadow-none"
name="confirmPassword"
{...register("confirmPassword")}
placeholder="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
aria-describedby="password"
/>
<button
type="button"
className="btn btn-link-secondary p-0 ms-2"
onClick={() => setHidepass1(!hidepass1)}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
borderLeft: 0,
}}
>
{hidepass1 ? (
<i className="bx bx-hide" />
) : (
<i className="bx bx-show" />
)}
</button>
</div>
{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>
</div> </div>
</div> </div>
</div>
); );
}; };

View File

@ -18,7 +18,7 @@ import {
VIEW_ALL_EMPLOYEES, VIEW_ALL_EMPLOYEES,
VIEW_TEAM_MEMBERS, VIEW_TEAM_MEMBERS,
} from "../../utils/constants"; } from "../../utils/constants";
import { clearCacheKey, useSelectedProject } from "../../slices/apiDataManager"; import { clearCacheKey } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
import { import {
@ -38,10 +38,9 @@ import usePagination from "../../hooks/usePagination";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
const EmployeeList = () => { const EmployeeList = () => {
// const selectedProjectId = useSelector( const selectedProjectId = useSelector(
// (store) => store.localVariables.projectId (store) => store.localVariables.projectId
// ); );
const selectedProjectId = useSelectedProject();
const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const { projectNames, loading: projectLoading, fetchData } = useProjectName();
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -79,11 +78,11 @@ const EmployeeList = () => {
} }
); );
useEffect(() => { useEffect(() => {
if (!selectedProjectId && projectNames?.length > 0) { if (selectedProjectId === null) {
dispatch(setProjectId(projectNames[0].id)); dispatch(setProjectId(projectNames[0]?.id));
} }
}, [selectedProjectId, projectNames, dispatch]); }, [selectedProjectId]);
const navigate = useNavigate(); const navigate = useNavigate();
const applySearchFilter = (data, text) => { const applySearchFilter = (data, text) => {
@ -177,12 +176,10 @@ useEffect(() => {
useEffect(() => { useEffect(() => {
if (!loading && Array.isArray(employees)) { if (!loading && Array.isArray(employees)) {
const sorted = [...employees].sort((a, b) => { const sorted = [...employees].sort((a, b) => {
const nameA = `${a.firstName || ""}${a.middleName || ""}${ const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
a.lastName || "" }`.toLowerCase();
}`.toLowerCase(); const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
const nameB = `${b.firstName || ""}${b.middleName || ""}${ }`.toLowerCase();
b.lastName || ""
}`.toLowerCase();
return nameA?.localeCompare(nameB); return nameA?.localeCompare(nameB);
}); });
@ -221,10 +218,10 @@ useEffect(() => {
recallEmployeeData( recallEmployeeData(
showInactive, showInactive,
showAllEmployees ? null : selectedProjectId showAllEmployees ? null : selectedProjectId
); ); // Use selectedProjectId here
} }
}, },
[employees, showInactive, showAllEmployees, selectedProjectId] [employees, showInactive, showAllEmployees, selectedProjectId] // Add all relevant dependencies
); );
useEffect(() => { useEffect(() => {
@ -247,7 +244,7 @@ useEffect(() => {
)} )}
{showModal && ( {showModal && (
<GlobalModel <GlobalModel
isOpen={showModal} isOpen={showModal}
size="lg" size="lg"
closeModal={() => setShowModal(false)} closeModal={() => setShowModal(false)}
@ -259,18 +256,24 @@ useEffect(() => {
/> />
</GlobalModel> </GlobalModel>
)} )}
{IsDeleteModalOpen && (
{IsDeleteModalOpen && (
<ConfirmModal <ConfirmModal
isOpen={IsDeleteModalOpen} isOpen={IsDeleteModalOpen}
type="delete" type="delete"
header="Suspend Employee" header={
message="Are you sure you want suspend?" selectedEmpFordelete?.isActive
? "Suspend Employee"
: "Reactivate Employee"
}
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
} this employee?`}
onSubmit={(id) => onSubmit={(id) =>
suspendEmployee({ suspendEmployee({
employeeId:id , employeeId: id,
active: !selectedEmpFordelete.isActive, active: !selectedEmpFordelete.isActive,
}) })
} }
onClose={() => setIsDeleteModalOpen(false)} onClose={() => setIsDeleteModalOpen(false)}
loading={employeeLodaing} loading={employeeLodaing}
paramData={selectedEmpFordelete.id} paramData={selectedEmpFordelete.id}
@ -496,9 +499,8 @@ useEffect(() => {
Status Status
</th> </th>
<th <th
className={`sorting_disabled ${ className={`sorting_disabled ${!Manage_Employee && "d-none"
!Manage_Employee && "d-none" }`}
}`}
rowSpan="1" rowSpan="1"
colSpan="1" colSpan="1"
style={{ width: "50px" }} style={{ width: "50px" }}
@ -518,9 +520,9 @@ useEffect(() => {
)} )}
{/* Conditional messages for no data or no search results */} {/* Conditional messages for no data or no search results */}
{!loading && {!loading &&
displayData?.length === 0 && displayData?.length === 0 &&
searchText && searchText &&
!showAllEmployees ? ( !showAllEmployees ? (
<tr> <tr>
<td colSpan={8}> <td colSpan={8}>
<small className="muted"> <small className="muted">
@ -530,14 +532,14 @@ useEffect(() => {
</tr> </tr>
) : null} ) : null}
{!loading && {!loading &&
displayData?.length === 0 && displayData?.length === 0 &&
(!searchText || showAllEmployees) ? ( (!searchText || showAllEmployees) ? (
<tr> <tr>
<td <td
colSpan={8} colSpan={8}
style={{ paddingTop: "20px", textAlign: "center" }} style={{ paddingTop: "20px", textAlign: "center" }}
> >
No team members assigned yet No Data Found
</td> </td>
</tr> </tr>
) : null} ) : null}
@ -556,9 +558,7 @@ useEffect(() => {
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<a <a
onClick={() => onClick={() =>
navigate( navigate(`/employee/${item.id}`)
`/employee/${item.id}?for=attendance`
)
} }
className="text-heading text-truncate cursor-pointer" className="text-heading text-truncate cursor-pointer"
> >
@ -594,10 +594,9 @@ useEffect(() => {
{item.jobRole || "Not Assign Yet"} {item.jobRole || "Not Assign Yet"}
</span> </span>
</td> </td>
<td className=" d-none d-md-table-cell"> <td className=" d-none d-md-table-cell">
{item.joiningDate {moment(item.joiningDate)?.format("DD-MMM-YYYY")}
? moment(item.joiningDate).format("DD-MMM-YYYY")
: "NA"}
</td> </td>
<td> <td>
{showInactive ? ( {showInactive ? (
@ -626,47 +625,56 @@ useEffect(() => {
<i className="bx bx-dots-vertical-rounded bx-md"></i> <i className="bx bx-dots-vertical-rounded bx-md"></i>
</button> </button>
<div className="dropdown-menu dropdown-menu-end"> <div className="dropdown-menu dropdown-menu-end">
{/* View always visible */}
<button <button
onClick={() => onClick={() => navigate(`/employee/${item.id}`)}
navigate(`/employee/${item.id}`)
}
className="dropdown-item py-1" className="dropdown-item py-1"
> >
<i className="bx bx-detail bx-sm"></i> View <i className="bx bx-detail bx-sm"></i> View
</button> </button>
<button
className="dropdown-item py-1" {/* If ACTIVE employee */}
onClick={() => { {item.isActive && (
handleEmployeeModel(item.id);
}}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
{!item.isSystem && (
<> <>
<button <button
className="dropdown-item py-1" className="dropdown-item py-1"
onClick={() => onClick={() => handleEmployeeModel(item.id)}
handleOpenDelete(item)
}
> >
<i className="bx bx-task-x bx-sm"></i>{" "} <i className="bx bx-edit bx-sm"></i> Edit
Suspend
</button> </button>
{/* Suspend only when active */}
{item.isActive && (
<button
className="dropdown-item py-1"
onClick={() => handleOpenDelete(item)}
>
<i className="bx bx-task-x bx-sm"></i> Suspend
</button>
)}
<button <button
className="dropdown-item py-1" className="dropdown-item py-1"
type="button" type="button"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#managerole-modal" data-bs-target="#managerole-modal"
onClick={() => onClick={() => setEmpForManageRole(item.id)}
setEmpForManageRole(item.id)
}
> >
<i className="bx bx-cog bx-sm"></i>{" "} <i className="bx bx-cog bx-sm"></i> Manage Role
Manage Role
</button> </button>
</> </>
)} )}
{/* If INACTIVE employee AND inactive toggle is ON */}
{!item.isActive && showInactive && (
<button
className="dropdown-item py-1"
onClick={() => handleOpenDelete(item)}
>
<i className="bx bx-refresh bx-sm me-1"></i> Re-activate
</button>
)}
</div> </div>
</div> </div>
</td> </td>
@ -683,9 +691,8 @@ useEffect(() => {
<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 <li
className={`page-item ${ className={`page-item ${currentPage === 1 ? "disabled" : ""
currentPage === 1 ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link btn-xs" className="page-link btn-xs"
@ -698,9 +705,8 @@ useEffect(() => {
{[...Array(totalPages)]?.map((_, index) => ( {[...Array(totalPages)]?.map((_, index) => (
<li <li
key={index} key={index}
className={`page-item ${ className={`page-item ${currentPage === index + 1 ? "active" : ""
currentPage === index + 1 ? "active" : "" }`}
}`}
> >
<button <button
className="page-link" className="page-link"
@ -712,9 +718,8 @@ useEffect(() => {
))} ))}
<li <li
className={`page-item ${ className={`page-item ${currentPage === totalPages ? "disabled" : ""
currentPage === totalPages ? "disabled" : "" }`}
}`}
> >
<button <button
className="page-link" className="page-link"

View File

@ -3,6 +3,7 @@ import { useSearchParams, useParams, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { import {
useEmployee,
useEmployeeProfile, useEmployeeProfile,
useEmployees, useEmployees,
useEmployeesByProject, useEmployeesByProject,
@ -28,38 +29,20 @@ const EmployeeProfile = () => {
const projectID = useSelector((store) => store.localVariables.projectId); const projectID = useSelector((store) => store.localVariables.projectId);
const { employeeId } = useParams(); const { employeeId } = 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 || "profile"); const [activePill, setActivePill] = useState(tab || "profile");
const [currentEmployee, setCurrentEmployee] = useState();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const {data:currentEmployee,isLoading,isError,error} = useEmployee(employeeId)
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) {
setLoading(false);
}
};
useEffect(() => {
if (employeeId) {
fetchEmployeeProfile(employeeId);
}
}, [employeeId]);
const navigate = useNavigate(); const navigate = useNavigate();
const renderContent = () => { const renderContent = () => {
if (loading) return <div>Loading</div>; if (isLoading) return <div>Loading</div>;
switch (activePill) { switch (activePill) {
case "profile": { case "profile": {
return ( return (
@ -101,9 +84,10 @@ const EmployeeProfile = () => {
} }
}; };
if (loading) { if (isLoading) {
return <div>Loading...</div>; return <div>Loading...</div>;
} }
if(isError) return <div >{error.message}</div>
return ( return (
<> <>
<div className="container-fluid"> <div className="container-fluid">
@ -138,4 +122,4 @@ const EmployeeProfile = () => {
); );
}; };
export default EmployeeProfile; export default EmployeeProfile;