Compare commits
38 Commits
164f3cc02f
...
250c1bf2e1
| Author | SHA1 | Date | |
|---|---|---|---|
| 250c1bf2e1 | |||
| 909a1f72d9 | |||
| 73e5254fbb | |||
| 1c0be858e9 | |||
| a03358dd14 | |||
| 732a6901ee | |||
| 053dfaf197 | |||
| b0fd14d0bb | |||
| e349d2622c | |||
| 6b9670b942 | |||
| 3305c0b0d9 | |||
| 8e7d99d111 | |||
| 32fdc39e5c | |||
|
|
deaf10c302 | ||
|
|
e5b34eb1ba | ||
|
|
573d7828ae | ||
|
|
7932f847f6 | ||
|
|
f14644b3b4 | ||
|
|
23bd307a37 | ||
|
|
e404b5bc50 | ||
|
|
38960e9132 | ||
|
|
4bb4f8fc17 | ||
|
|
ce1bdc3df7 | ||
|
|
cf75f3c1fb | ||
|
|
195ebd4f9b | ||
|
|
ad0dfdda55 | ||
|
|
def2a9b6f3 | ||
|
|
18d6916fb8 | ||
|
|
78894918c0 | ||
|
|
49f15f574c | ||
|
|
558b663f6b | ||
|
|
1e51fabaa1 | ||
|
|
488a92e5d1 | ||
|
|
c1da916ca7 | ||
|
|
28c995bbc0 | ||
|
|
2b8764b402 | ||
|
|
7f1ce2b863 | ||
|
|
4df293488e |
@ -199,3 +199,17 @@
|
|||||||
.dropdown-menu[data-bs-popper] {
|
.dropdown-menu[data-bs-popper] {
|
||||||
inset-block-start: 100%;
|
inset-block-start: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ql-toolbar.ql-snow {
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
}
|
||||||
|
.ql-container.ql-snow {
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const InfraPlanning = () =>
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||||
<div className="align-items-center">
|
<div className="align-items-center">
|
||||||
<div className="row ">
|
{/* <div className="row ">
|
||||||
<div className="col-sm-3 col-8 text-start mb-1">
|
<div className="col-sm-3 col-8 text-start mb-1">
|
||||||
<select name="DataTables_Table_0_length"
|
<select name="DataTables_Table_0_length"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
@ -65,7 +65,7 @@ const InfraPlanning = () =>
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div> */}
|
||||||
<div className="row ">
|
<div className="row ">
|
||||||
{project_deatilsLoader && ( <p>Loading...</p> )}
|
{project_deatilsLoader && ( <p>Loading...</p> )}
|
||||||
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
|
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
|
||||||
|
|||||||
@ -2,10 +2,13 @@ import React, { useState } from "react";
|
|||||||
import LineChart from "../Charts/LineChart";
|
import LineChart from "../Charts/LineChart";
|
||||||
import { useProjects } from "../../hooks/useProjects";
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
import { useDashboard_Data } from "../../hooks/useDashboard_Data";
|
import { useDashboard_Data } from "../../hooks/useDashboard_Data";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
|
||||||
const ProjectProgressChart = () => {
|
const ProjectProgressChart = () =>
|
||||||
|
{
|
||||||
|
const selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
const [selectedProjectId, setSelectedProjectId] = useState("all");
|
// const [selectedProjectId, setSelectedProjectId] = useState("all");
|
||||||
const [range, setRange] = useState("1W");
|
const [range, setRange] = useState("1W");
|
||||||
|
|
||||||
const getDaysFromRange = (range) => {
|
const getDaysFromRange = (range) => {
|
||||||
@ -28,7 +31,7 @@ const ProjectProgressChart = () => {
|
|||||||
const { dashboard_data, loading: isLineChartLoading } = useDashboard_Data({
|
const { dashboard_data, loading: isLineChartLoading } = useDashboard_Data({
|
||||||
days,
|
days,
|
||||||
FromDate,
|
FromDate,
|
||||||
projectId: selectedProjectId === "all" ? null : selectedProjectId,
|
projectId: selectedProject === " " ? "all" : selectedProject// selectedProjectId === "all" ? null : selectedProjectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sortedDashboardData = [...dashboard_data].sort(
|
const sortedDashboardData = [...dashboard_data].sort(
|
||||||
@ -63,7 +66,7 @@ const ProjectProgressChart = () => {
|
|||||||
<p className="card-subtitle">Progress Overview by Project</p>
|
<p className="card-subtitle">Progress Overview by Project</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="btn-group">
|
{/* <div className="btn-group">
|
||||||
<button
|
<button
|
||||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
@ -92,7 +95,7 @@ const ProjectProgressChart = () => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Row 2: Time Range Buttons */}
|
{/* Row 2: Time Range Buttons */}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const CardViewDirectory = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { buckets } = useBuckets();
|
const { buckets } = useBuckets();
|
||||||
const { dirActions, setDirActions } = useDir();
|
const { dirActions, setDirActions } = useDir();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="card text-start border-1"
|
className="card text-start border-1"
|
||||||
@ -24,9 +25,8 @@ const CardViewDirectory = ({
|
|||||||
<div className="card-body px-1 py-2 pb-0">
|
<div className="card-body px-1 py-2 pb-0">
|
||||||
<div className="d-flex justify-content-between">
|
<div className="d-flex justify-content-between">
|
||||||
<div
|
<div
|
||||||
className={`d-flex align-items-center ${
|
className={`d-flex align-items-center ${IsActive && "cursor-pointer"
|
||||||
IsActive && "cursor-pointer"
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (IsActive) {
|
if (IsActive) {
|
||||||
setIsOpenModalNote(true);
|
setIsOpenModalNote(true);
|
||||||
@ -89,11 +89,10 @@ const CardViewDirectory = ({
|
|||||||
)}
|
)}
|
||||||
{!IsActive && (
|
{!IsActive && (
|
||||||
<i
|
<i
|
||||||
className={`bx ${
|
className={`bx ${dirActions.action && dirActions.id === contact.id
|
||||||
dirActions.action && dirActions.id === contact.id
|
|
||||||
? "bx-loader-alt bx-spin"
|
? "bx-loader-alt bx-spin"
|
||||||
: "bx-recycle"
|
: "bx-recycle"
|
||||||
} me-1 text-primary cursor-pointer`}
|
} me-1 text-primary cursor-pointer`}
|
||||||
title="Restore"
|
title="Restore"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDirActions({ action: false, id: contact.id });
|
setDirActions({ action: false, id: contact.id });
|
||||||
@ -114,9 +113,8 @@ const CardViewDirectory = ({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`card-footer text-start px-1 py-1 ${
|
className={`card-footer text-start px-1 py-1 ${IsActive && "cursor-pointer"
|
||||||
IsActive && "cursor-pointer"
|
}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (IsActive) {
|
if (IsActive) {
|
||||||
setIsOpenModalNote(true);
|
setIsOpenModalNote(true);
|
||||||
@ -151,17 +149,19 @@ const CardViewDirectory = ({
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{contact?.contactCategory?.name ? (
|
{contact?.tags?.length > 0 ? (
|
||||||
<ul className="list-inline m-0 ms-2">
|
<ul className="list-inline m-0 ms-2">
|
||||||
<li className="list-inline-item me-2 my-1">
|
<li className="list-inline-item me-2 my-1">
|
||||||
<i className="fa-solid fa-tag fs-6 ms-1"></i>
|
<i className="fa-solid fa-tag fs-6 ms-1"></i>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item text-small active">
|
{contact.tags.map((tag, index) => (
|
||||||
{contact?.contactCategory?.name}
|
<li key={index} className="list-inline-item text-small active">
|
||||||
</li>
|
{tag.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<ul className="list-inline m-0 ms-2">
|
<ul className="list-inline m-0 ms-2">
|
||||||
<li className="list-inline-item me-2 my-1">
|
<li className="list-inline-item me-2 my-1">
|
||||||
<i className="fa-solid fa-tag fs-6 ms-1"></i>
|
<i className="fa-solid fa-tag fs-6 ms-1"></i>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -39,7 +39,7 @@ export const ContactSchema = z
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, { message: "At least one tag is required" }),
|
.min(1, { message: "At least one tag is required" }),
|
||||||
bucketIds: z.array(z.string()).nonempty({ message: "At least one label is required" })
|
bucketIds: z.array(z.string()).nonempty({ message: "At least one bucket is required" })
|
||||||
})
|
})
|
||||||
|
|
||||||
// .refine((data) => {
|
// .refine((data) => {
|
||||||
|
|||||||
@ -66,7 +66,6 @@ const ManageBucket = () => {
|
|||||||
const cache_buckets = getCachedData("buckets") || [];
|
const cache_buckets = getCachedData("buckets") || [];
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
// Utility: Compare arrays regardless of order
|
|
||||||
const arraysAreEqual = (a, b) => {
|
const arraysAreEqual = (a, b) => {
|
||||||
if (a.length !== b.length) return false;
|
if (a.length !== b.length) return false;
|
||||||
const setA = new Set(a);
|
const setA = new Set(a);
|
||||||
@ -74,11 +73,9 @@ const ManageBucket = () => {
|
|||||||
return [...setA].every((id) => setB.has(id));
|
return [...setA].every((id) => setB.has(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
// UPDATE existing bucket
|
|
||||||
if (selected_bucket) {
|
if (selected_bucket) {
|
||||||
const payload = { ...data, id: selected_bucket.id };
|
const payload = { ...data, id: selected_bucket.id };
|
||||||
|
|
||||||
// 1. Update bucket details
|
|
||||||
response = await DirectoryRepository.UpdateBuckets(
|
response = await DirectoryRepository.UpdateBuckets(
|
||||||
selected_bucket.id,
|
selected_bucket.id,
|
||||||
payload
|
payload
|
||||||
@ -91,14 +88,12 @@ const ManageBucket = () => {
|
|||||||
cacheData("buckets", updatedBuckets);
|
cacheData("buckets", updatedBuckets);
|
||||||
setBucketList(updatedBuckets);
|
setBucketList(updatedBuckets);
|
||||||
|
|
||||||
// 2. Update employee assignments if they changed
|
|
||||||
const existingEmployeeIds = selected_bucket?.employeeIds || [];
|
const existingEmployeeIds = selected_bucket?.employeeIds || [];
|
||||||
const employeesToUpdate = selectedEmployee.filter((emp) => {
|
const employeesToUpdate = selectedEmployee.filter((emp) => {
|
||||||
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
||||||
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
|
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a filtered list of active employee IDs to compare
|
|
||||||
const newActiveEmployeeIds = selectedEmployee
|
const newActiveEmployeeIds = selectedEmployee
|
||||||
.filter((emp) => {
|
.filter((emp) => {
|
||||||
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
const isExisting = existingEmployeeIds.includes(emp.employeeId);
|
||||||
@ -110,7 +105,7 @@ const ManageBucket = () => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!arraysAreEqual(newActiveEmployeeIds, existingEmployeeIds) &&
|
!arraysAreEqual(newActiveEmployeeIds, existingEmployeeIds) &&
|
||||||
employeesToUpdate.length != 0
|
employeesToUpdate.length !== 0
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
response = await DirectoryRepository.AssignedBuckets(
|
response = await DirectoryRepository.AssignedBuckets(
|
||||||
@ -133,10 +128,7 @@ const ManageBucket = () => {
|
|||||||
|
|
||||||
setBucketList(updatedData);
|
setBucketList(updatedData);
|
||||||
showToast("Bucket Updated Successfully", "success");
|
showToast("Bucket Updated Successfully", "success");
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// CREATE new bucket
|
|
||||||
else {
|
|
||||||
response = await DirectoryRepository.CreateBuckets(data);
|
response = await DirectoryRepository.CreateBuckets(data);
|
||||||
|
|
||||||
const updatedBuckets = [...cache_buckets, response?.data];
|
const updatedBuckets = [...cache_buckets, response?.data];
|
||||||
@ -163,7 +155,7 @@ const ManageBucket = () => {
|
|||||||
const resp = await DirectoryRepository.DeleteBucket(deleteBucket);
|
const resp = await DirectoryRepository.DeleteBucket(deleteBucket);
|
||||||
const cache_buckets = getCachedData("buckets") || [];
|
const cache_buckets = getCachedData("buckets") || [];
|
||||||
const updatedBuckets = cache_buckets.filter(
|
const updatedBuckets = cache_buckets.filter(
|
||||||
(bucket) => bucket.id != deleteBucket
|
(bucket) => bucket.id !== deleteBucket
|
||||||
);
|
);
|
||||||
cacheData("buckets", updatedBuckets);
|
cacheData("buckets", updatedBuckets);
|
||||||
setBucketList(updatedBuckets);
|
setBucketList(updatedBuckets);
|
||||||
@ -193,6 +185,8 @@ const ManageBucket = () => {
|
|||||||
select_bucket(null);
|
select_bucket(null);
|
||||||
setAction_bucket(false);
|
setAction_bucket(false);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
reset({ name: "", description: "" });
|
||||||
|
setSelectEmployee([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
|
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
|
||||||
@ -200,11 +194,12 @@ const ManageBucket = () => {
|
|||||||
const name = bucket.name?.toLowerCase();
|
const name = bucket.name?.toLowerCase();
|
||||||
return name?.includes(term);
|
return name?.includes(term);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{deleteBucket && (
|
{deleteBucket && (
|
||||||
<div
|
<div
|
||||||
className={`modal fade ${deleteBucket ? "show" : ""}`}
|
className={`modal fade ${deleteBucket ? "show" : ""}`}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
style={{
|
style={{
|
||||||
@ -215,15 +210,14 @@ const ManageBucket = () => {
|
|||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
type={"delete"}
|
type={"delete"}
|
||||||
header={"Delete Bucket"}
|
header={"Delete Bucket"}
|
||||||
message={"Are you sure you want delete?"}
|
message={"Are you sure you want to delete this bucket?"}
|
||||||
onSubmit={handleDeleteContact}
|
onSubmit={handleDeleteContact}
|
||||||
onClose={() => setDeleteBucket(null)}
|
onClose={() => setDeleteBucket(null)}
|
||||||
// loading={IsDeleting}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container m-0 p-0" style={{ minHeight: "200px" }}>
|
<div className="container m-0 p-0" style={{ minHeight: "00px" }}>
|
||||||
<div className="d-flex justify-content-center">
|
<div className="d-flex justify-content-center">
|
||||||
<p className="fs-6 fw-semibold m-0">Manage Buckets</p>
|
<p className="fs-6 fw-semibold m-0">Manage Buckets</p>
|
||||||
</div>
|
</div>
|
||||||
@ -254,10 +248,15 @@ const ManageBucket = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm btn-primary ms-auto ${
|
className={`btn btn-sm btn-primary ms-auto ${
|
||||||
action_bucket ? "d-none" : ""
|
action_bucket ? "d-none" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setAction_bucket(true)}
|
onClick={() => {
|
||||||
|
setAction_bucket(true);
|
||||||
|
select_bucket(null);
|
||||||
|
reset({ name: "", description: "" });
|
||||||
|
setSelectEmployee([]);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
Add Bucket
|
Add Bucket
|
||||||
@ -265,113 +264,88 @@ const ManageBucket = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{!action_bucket ? (
|
{!action_bucket ? (
|
||||||
<div className="table-responsive text-nowrap pt-1 px-2 px-sm-0 mt-3">
|
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
|
||||||
<table className="table px-2">
|
{loading && (
|
||||||
<thead className="p-0">
|
<div className="col-12">
|
||||||
<tr className="p-0">
|
<div className="d-flex justify-content-center align-items-center py-5">
|
||||||
<th
|
Loading...
|
||||||
colSpan={2}
|
</div>
|
||||||
className="cursor-pointer"
|
</div>
|
||||||
onClick={() => requestSort((e) => `${e.name} `)}
|
)}
|
||||||
>
|
{!loading && buckets.length === 0 && (
|
||||||
<div className="d-flex justify-content-start align-items-center gap-1 mx-2">
|
<div className="col-12">
|
||||||
<span>Name {getSortIcon()}</span>
|
<div className="d-flex justify-content-center align-items-center py-5">
|
||||||
</div>
|
No Buckets Available.
|
||||||
</th>
|
</div>
|
||||||
<th className="text-start d-none d-sm-table-cell">
|
</div>
|
||||||
<div className="d-flex align-items-center justify-content-center gap-1">
|
)}
|
||||||
<span>Description</span>
|
{!loading && sortedBucktesList.length === 0 && (
|
||||||
</div>
|
<div className="col-12">
|
||||||
</th>
|
<div className="d-flex justify-content-center align-items-center py-5">
|
||||||
<th>Contacts</th>
|
No Matching Bucket Found.
|
||||||
<th>
|
</div>
|
||||||
<div className="d-flex align-items-center justify-content-center gap-1">
|
</div>
|
||||||
<span>Action</span>
|
)}
|
||||||
</div>
|
{!loading &&
|
||||||
</th>
|
sortedBucktesList.map((bucket) => (
|
||||||
</tr>
|
<div className="col" key={bucket.id}>
|
||||||
</thead>
|
<div className="card h-100">
|
||||||
|
<div className="card-body p-4">
|
||||||
<tbody className="table-border-bottom-0 overflow-auto">
|
<h6 className="card-title d-flex justify-content-between align-items-center">
|
||||||
{loading && (
|
<span>{bucket.name}</span>
|
||||||
<tr className="mt-10">
|
|
||||||
<td colSpan={5}>
|
|
||||||
{" "}
|
|
||||||
<div className="d-flex justify-content-center align-items-center py-5">
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!loading && buckets.length == 0 && (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={5}>
|
|
||||||
<div className="d-flex justify-content-center align-items-center py-5">
|
|
||||||
Bucket Not Available.
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!loading && sortedBucktesList.length == 0 && (
|
|
||||||
<tr>
|
|
||||||
<td className="text-center py-4 h-25" colSpan={5}>
|
|
||||||
<div className="d-flex justify-content-center align-items-center py-5">
|
|
||||||
No Matching Bucket Found.
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!loading &&
|
|
||||||
sortedBucktesList.map((bucket) => (
|
|
||||||
<tr key={bucket.id}>
|
|
||||||
<td colSpan={2} className="text-start text-wrap">
|
|
||||||
<i className="bx bx-right-arrow-alt me-1"></i>{" "}
|
|
||||||
{bucket.name}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className="text-start d-none d-sm-table-cell text-truncate"
|
|
||||||
style={{
|
|
||||||
maxWidth: "300px",
|
|
||||||
whiteSpace: "wrap",
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
}}
|
|
||||||
title={bucket.description}
|
|
||||||
>
|
|
||||||
{bucket.description}
|
|
||||||
</td>
|
|
||||||
<td>{bucket.numberOfContacts}</td>
|
|
||||||
<td className="justify-content-center">
|
|
||||||
{(DirManager ||
|
{(DirManager ||
|
||||||
DirAdmin ||
|
DirAdmin ||
|
||||||
bucket?.createdBy?.id ===
|
bucket?.createdBy?.id ===
|
||||||
profile?.employeeInfo?.id) && (
|
profile?.employeeInfo?.id) && (
|
||||||
<div className="d-flex justify-content-center align-items-center gap-2">
|
<div className="d-flex gap-2">
|
||||||
<i
|
<i
|
||||||
className="bx bx-edit bx-sm text-primary cursor-pointer "
|
className="bx bx-edit bx-sm text-primary cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
select_bucket(bucket);
|
select_bucket(bucket);
|
||||||
setAction_bucket(true);
|
setAction_bucket(true);
|
||||||
|
const initialSelectedEmployees = employeesList
|
||||||
|
.filter((emp) =>
|
||||||
|
bucket.employeeIds?.includes(
|
||||||
|
emp.employeeId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((emp) => ({ ...emp, isActive: true }));
|
||||||
|
setSelectEmployee(initialSelectedEmployees);
|
||||||
}}
|
}}
|
||||||
></i>
|
></i>
|
||||||
<i
|
<i
|
||||||
className="bx bx-trash bx-sm text-danger cursor-pointer"
|
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
|
||||||
onClick={() => setDeleteBucket(bucket?.id)}
|
onClick={() => setDeleteBucket(bucket?.id)}
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</h6>
|
||||||
</tr>
|
<h6 className="card-subtitle mb-2 text-muted text-start">
|
||||||
))}
|
Contacts:{" "}
|
||||||
</tbody>
|
{bucket.numberOfContacts
|
||||||
</table>
|
? bucket.numberOfContacts
|
||||||
|
: 0}
|
||||||
|
</h6>
|
||||||
|
<p
|
||||||
|
className="card-text text-start"
|
||||||
|
title={bucket.description}
|
||||||
|
>
|
||||||
|
{bucket.description || "No description available."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
|
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
|
||||||
<div className="">
|
<div className="mb-3">
|
||||||
<label className="form-label">Bucket Name</label>
|
<label htmlFor="bucketName" className="form-label">
|
||||||
|
Bucket Name
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
|
id="bucketName"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("name")}
|
{...register("name")}
|
||||||
/>
|
/>
|
||||||
@ -379,9 +353,12 @@ const ManageBucket = () => {
|
|||||||
<small className="danger-text">{errors.name.message}</small>
|
<small className="danger-text">{errors.name.message}</small>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="mb-3">
|
||||||
<label className="form-label">Bucket Discription</label>
|
<label htmlFor="bucketDescription" className="form-label">
|
||||||
|
Bucket Description
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
|
id="bucketDescription"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
rows="3"
|
rows="3"
|
||||||
{...register("description")}
|
{...register("description")}
|
||||||
@ -401,9 +378,10 @@ const ManageBucket = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-2 d-flex justify-content-center gap-3">
|
<div className="mt-4 d-flex justify-content-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleBack()}
|
type="button"
|
||||||
|
onClick={handleBack}
|
||||||
className="btn btn-sm btn-secondary"
|
className="btn btn-sm btn-secondary"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -362,7 +362,7 @@ const ManageDirectory = ({ submitContact, onCLosed }) => {
|
|||||||
{buckets?.map((item) => (
|
{buckets?.map((item) => (
|
||||||
<li
|
<li
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="list-inline-item flex-shrink-0 me-6 mb-2"
|
className="list-inline-item flex-shrink-0 me-6 mb-1"
|
||||||
>
|
>
|
||||||
<div className="form-check ">
|
<div className="form-check ">
|
||||||
<input
|
<input
|
||||||
@ -381,12 +381,13 @@ const ManageDirectory = ({ submitContact, onCLosed }) => {
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
</ul>
|
||||||
{errors.bucketIds && (
|
{errors.bucketIds && (
|
||||||
<small className="danger-text mt-0">
|
<small className="danger-text mt-0">
|
||||||
{errors.bucketIds.message}
|
{errors.bucketIds.message}
|
||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -132,17 +132,17 @@ const NoteCardDirectory = ({
|
|||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="xs"
|
size="xs"
|
||||||
firstName={noteItem.createdBy.firstName}
|
firstName={noteItem?.createdBy?.firstName}
|
||||||
lastName={noteItem.createdBy.lastName}
|
lastName={noteItem?.createdBy?.lastName}
|
||||||
className="m-0"
|
className="m-0"
|
||||||
/>
|
/>
|
||||||
<div className="d-flex flex-column ms-2">
|
<div className="d-flex flex-column ms-2">
|
||||||
<span className="fw-semibold small">
|
<span className="fw-semibold small">
|
||||||
{noteItem.createdBy.firstName} {noteItem.createdBy.lastName}
|
{noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-muted" style={{ fontSize: "10px" }}>
|
<span className="text-muted" style={{ fontSize: "10px" }}>
|
||||||
{moment
|
{moment
|
||||||
.utc(noteItem.createdAt)
|
.utc(noteItem?.createdAt)
|
||||||
.add(5, "hours")
|
.add(5, "hours")
|
||||||
.add(30, "minutes")
|
.add(30, "minutes")
|
||||||
.format("MMMM DD, YYYY [at] hh:mm A")}
|
.format("MMMM DD, YYYY [at] hh:mm A")}
|
||||||
@ -160,7 +160,7 @@ const NoteCardDirectory = ({
|
|||||||
{!isDeleting ? (
|
{!isDeleting ? (
|
||||||
<i
|
<i
|
||||||
className="bx bx-trash bx-sm me-1 text-secondary cursor-pointer"
|
className="bx bx-trash bx-sm me-1 text-secondary cursor-pointer"
|
||||||
onClick={() => handleDeleteNote(!noteItem.isActive)}
|
onClick={() => handleDeleteNote(!noteItem?.isActive)}
|
||||||
></i>
|
></i>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
@ -176,7 +176,7 @@ const NoteCardDirectory = ({
|
|||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="bx bx-recycle me-1 text-primary cursor-pointer"
|
className="bx bx-recycle me-1 text-primary cursor-pointer"
|
||||||
onClick={() => handleDeleteNote(!noteItem.isActive)}
|
onClick={() => handleDeleteNote(!noteItem?.isActive)}
|
||||||
title="Restore"
|
title="Restore"
|
||||||
></i>
|
></i>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -101,49 +101,85 @@ const NotesDirectory = ({
|
|||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
<div className="d-flex align-items-center justify-content-between">
|
<div className="d-flex align-items-center justify-content-between">
|
||||||
<p className="fw-semibold m-0">Notes :</p>
|
<p className="fw-semibold m-0">Notes :</p>
|
||||||
<div className="m-0 d-flex aligin-items-center">
|
</div>
|
||||||
<label className="switch switch-primary">
|
<div className="d-flex align-items-center justify-content-between mb-5">
|
||||||
<input
|
{contactNotes?.length > 0 && (
|
||||||
type="checkbox"
|
<div className="m-0 d-flex aligin-items-center">
|
||||||
className="switch-input"
|
<label className="switch switch-primary">
|
||||||
onChange={() => handleSwitch(!IsActive)}
|
<input
|
||||||
value={IsActive}
|
type="checkbox"
|
||||||
/>
|
className="switch-input"
|
||||||
<span className="switch-toggle-slider">
|
onChange={() => handleSwitch(!IsActive)}
|
||||||
<span className="switch-on">
|
value={IsActive}
|
||||||
{/* <i class="icon-base bx bx-check"></i> */}
|
/>
|
||||||
</span>
|
<span className="switch-toggle-slider">
|
||||||
<span className="switch-off">
|
<span className="switch-on">
|
||||||
{/* <i class="icon-base bx bx-x"></i> */}
|
{/* <i class="icon-base bx bx-check"></i> */}
|
||||||
|
</span>
|
||||||
|
<span className="switch-off">
|
||||||
|
{/* <i class="icon-base bx bx-x"></i> */}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span className="switch-label ">Include Deleted Notes</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!addNote && (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
${
|
||||||
|
contactNotes?.length > 0
|
||||||
|
? "d-flex justify-content-center px-2"
|
||||||
|
: "d-flex justify-content-center px-2w-100"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`btn btn-sm ${
|
||||||
|
addNote ? "btn-danger" : "btn-primary"
|
||||||
|
}`}
|
||||||
|
onClick={() => setAddNote(!addNote)}
|
||||||
|
>
|
||||||
|
{addNote ? "Hide Editor" : "Add a Note"}
|
||||||
</span>
|
</span>
|
||||||
<span className="switch-label ">Show Including Inactive Notes</span>
|
</div>
|
||||||
</label>
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{addNote && (
|
{addNote && (
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<div className="card m-2 mb-5">
|
||||||
<Editor
|
<button
|
||||||
value={noteValue}
|
type="button"
|
||||||
loading={IsSubmitting}
|
class="btn btn-close btn-secondary position-absolute top-0 end-0 m-2 mt-3 rounded-circle"
|
||||||
onChange={handleEditorChange}
|
aria-label="Close"
|
||||||
onCancel={onCancel}
|
style={{ backgroundColor: "#eee", color: "white" }}
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onClick={() => setAddNote(!addNote)}
|
||||||
/>
|
></button>
|
||||||
{errors.notes && (
|
{/* <div className="d-flex justify-content-end px-2">
|
||||||
<p className="text-danger small mt-1">{errors.note.message}</p>
|
<span
|
||||||
)}
|
className={`btn btn-sm ${
|
||||||
</form>
|
addNote ? "btn-danger" : "btn-primary"
|
||||||
|
}`}
|
||||||
|
onClick={() => setAddNote(!addNote)}
|
||||||
|
>
|
||||||
|
{addNote ? "Hide Editor" : "Add Note"}
|
||||||
|
</span>
|
||||||
|
</div> */}
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Editor
|
||||||
|
value={noteValue}
|
||||||
|
loading={IsSubmitting}
|
||||||
|
onChange={handleEditorChange}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
/>
|
||||||
|
{errors.notes && (
|
||||||
|
<p className="text-danger small mt-1">{errors.note.message}</p>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="d-flex justify-content-end px-2">
|
|
||||||
<span
|
|
||||||
className={`btn btn-sm ${addNote ? "btn-danger" : "btn-primary"}`}
|
|
||||||
onClick={() => setAddNote(!addNote)}
|
|
||||||
>
|
|
||||||
{addNote ? "Hide Editor" : "Add Note"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className=" justify-content-start px-1 mt-1">
|
<div className=" justify-content-start px-1 mt-1">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|||||||
@ -159,6 +159,23 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{contactProfile?.tags?.length > 0 && (
|
||||||
|
<div className="d-flex mb-2">
|
||||||
|
<div style={{ width: "100px", minWidth: "100px" }}>
|
||||||
|
<p className="m-0">Tags : </p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ul className="list-inline mb-0">
|
||||||
|
{contactProfile.tags.map((tag, index) => (
|
||||||
|
<li key={index} className="list-inline-item">
|
||||||
|
<i className="fa-solid fa-tag me-1"></i>
|
||||||
|
{tag.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{contactProfile?.buckets?.length > 0 && (
|
{contactProfile?.buckets?.length > 0 && (
|
||||||
<div className="d-flex ">
|
<div className="d-flex ">
|
||||||
|
|||||||
@ -1,18 +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 { useDispatch, useSelector } from "react-redux";
|
||||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
import { changeMaster, setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import useMaster from "../../hooks/masterHook/useMaster";
|
import useMaster from "../../hooks/masterHook/useMaster";
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import Avatar from "../../components/common/Avatar";
|
import Avatar from "../../components/common/Avatar";
|
||||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||||
|
import { useProjects } from "../../hooks/useProjects";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
|
const location = useLocation();
|
||||||
const dispatch = useDispatch(changeMaster("Job Role"));
|
const dispatch = useDispatch(changeMaster("Job Role"));
|
||||||
const { data, loading } = useMaster();
|
const { data, loading } = useMaster();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const getRole = (roles, joRoleId) => {
|
const getRole = (roles, joRoleId) => {
|
||||||
if (!Array.isArray(roles)) return "User";
|
if (!Array.isArray(roles)) return "User";
|
||||||
let role = roles.find((role) => role.id === joRoleId);
|
let role = roles.find((role) => role.id === joRoleId);
|
||||||
@ -49,7 +55,7 @@ const Header = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"Error during logout:",
|
"Error during logout:",
|
||||||
error.response?.data || error.message
|
error?.response?.data || error.message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -57,11 +63,36 @@ const Header = () => {
|
|||||||
const handleProfilePage = () => {
|
const handleProfilePage = () => {
|
||||||
navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`);
|
navigate(`/employee/${profile?.employeeInfo?.id}?for=attendance`);
|
||||||
};
|
};
|
||||||
// const ChangePasswordPage = () => {
|
// const { projects, loading: projectLoading } = useProjects();
|
||||||
// navigate(`/auth/changepassword`);
|
const { projectNames, loading: projectLoading } = useProjectName();
|
||||||
// };
|
|
||||||
|
const selectedProject = useSelector(
|
||||||
|
(store) => store.localVariables.projectId
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedProjectName = projectNames?.find(
|
||||||
|
(p) => p?.id === selectedProject
|
||||||
|
)?.name;
|
||||||
|
|
||||||
|
let displayText = "";
|
||||||
|
if (selectedProjectName) {
|
||||||
|
displayText = selectedProjectName;
|
||||||
|
} else if (projectLoading && selectedProject) {
|
||||||
|
displayText = selectedProject;
|
||||||
|
} else if (projectLoading) {
|
||||||
|
displayText = "Loading...";
|
||||||
|
}
|
||||||
|
|
||||||
const { openChangePassword } = useChangePassword();
|
const { openChangePassword } = useChangePassword();
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectNames && selectedProject !== " ") {
|
||||||
|
dispatch(setProjectId(projectNames[0]?.id));
|
||||||
|
}
|
||||||
|
}, [projectNames]);
|
||||||
|
|
||||||
|
/** Check if current page id project details page */
|
||||||
|
const isProjectPath = /^\/projects\/[a-f0-9-]{36}$/.test(location.pathname);
|
||||||
|
|
||||||
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"
|
||||||
@ -79,47 +110,46 @@ const Header = () => {
|
|||||||
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
||||||
id="navbar-collapse"
|
id="navbar-collapse"
|
||||||
>
|
>
|
||||||
{/* Search */}
|
{projectNames?.length > 0 && (
|
||||||
<div className="navbar-nav align-items-center">
|
<div className=" align-items-center">
|
||||||
<div className="nav-item navbar-search-wrapper mb-0">
|
{!isProjectPath && (
|
||||||
<a className="nav-item nav-link search-toggler px-0" href="#">
|
<>
|
||||||
<span
|
<i
|
||||||
className="d-inline-block text-body-secondary fw-normal"
|
className="rounded-circle bx bx-building-house"
|
||||||
id="autocomplete"
|
style={{ fontSize: "xx-large" }}
|
||||||
>
|
></i>
|
||||||
<div
|
<div className="btn-group">
|
||||||
className="aa-Autocomplete"
|
|
||||||
role="combobox"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-haspopup="listbox"
|
|
||||||
aria-labelledby="autocomplete-0-label"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
|
className={`btn btn-sm-sm btn-xl ${
|
||||||
|
projectNames?.length > 1 && "dropdown-toggle"
|
||||||
|
} px-1`}
|
||||||
type="button"
|
type="button"
|
||||||
className="aa-DetachedSearchButton"
|
data-bs-toggle="dropdown"
|
||||||
title="Search"
|
aria-expanded="false"
|
||||||
id="autocomplete-0-label"
|
|
||||||
>
|
>
|
||||||
<div className="aa-DetachedSearchButtonIcon"></div>
|
{displayText}
|
||||||
<div className="aa-DetachedSearchButtonPlaceholder">
|
|
||||||
Search -{" "}
|
|
||||||
<span className="font-italic">Coming Soon...</span>
|
|
||||||
</div>
|
|
||||||
<div className="aa-DetachedSearchButtonQuery"></div>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{projectNames?.length > 1 && (
|
||||||
|
<ul className="dropdown-menu">
|
||||||
|
{projectNames?.map((project) => (
|
||||||
|
<li key={project?.id}>
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => dispatch(setProjectId(project?.id))}
|
||||||
|
>
|
||||||
|
{project?.name}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</>
|
||||||
</a>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* marquee */}
|
|
||||||
{/* <marquee>
|
|
||||||
{" "}
|
|
||||||
{getGreetingMessage(profile?.employeeInfo?.firstName)}
|
|
||||||
</marquee> */}
|
|
||||||
|
|
||||||
{/* icon list */}
|
|
||||||
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
<ul className="navbar-nav flex-row align-items-center ms-md-auto">
|
||||||
<li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">
|
<li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">
|
||||||
<a
|
<a
|
||||||
@ -163,7 +193,7 @@ const Header = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="dropdown-shortcuts-item col">
|
<div className="dropdown-shortcuts-item col">
|
||||||
<a
|
<a
|
||||||
onClick={() => navigate(`/projects`)}
|
onClick={() => navigate(`/projectNames`)}
|
||||||
className="text-heading text-truncate cursor-pointer"
|
className="text-heading text-truncate cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="dropdown-shortcuts-icon rounded-circle mb-3">
|
<span className="dropdown-shortcuts-icon rounded-circle mb-3">
|
||||||
@ -602,7 +632,7 @@ const Header = () => {
|
|||||||
<span className="align-middle">My Profile</span>
|
<span className="align-middle">My Profile</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li onClick={handleProfilePage}>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-label="go to setting "
|
aria-label="go to setting "
|
||||||
className="dropdown-item cusor-pointer"
|
className="dropdown-item cusor-pointer"
|
||||||
@ -611,7 +641,7 @@ const Header = () => {
|
|||||||
<span className="align-middle">Settings</span>
|
<span className="align-middle">Settings</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/* <li>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-label="go to billing "
|
aria-label="go to billing "
|
||||||
className="dropdown-item cusor-pointer"
|
className="dropdown-item cusor-pointer"
|
||||||
@ -626,7 +656,7 @@ const Header = () => {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li> */}
|
</li>
|
||||||
<li onClick={openChangePassword}>
|
<li onClick={openChangePassword}>
|
||||||
{" "}
|
{" "}
|
||||||
{/* Use the function from the context */}
|
{/* Use the function from the context */}
|
||||||
|
|||||||
@ -108,7 +108,10 @@ const InfraTable = ({ buildings }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (buildings && buildings.length > 0) {
|
||||||
setProjectBuilding(buildings);
|
setProjectBuilding(buildings);
|
||||||
|
setExpandedBuildings([buildings[0].id]);
|
||||||
|
}
|
||||||
}, [buildings]);
|
}, [buildings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -33,7 +33,7 @@ const Editor = ({
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="editor-wrapper m-5">
|
<div className="editor-wrapper m-2 mt-5">
|
||||||
<div id="custom-toolbar" className="ql-toolbar ql-snow custom-toolbar">
|
<div id="custom-toolbar" className="ql-toolbar ql-snow custom-toolbar">
|
||||||
<div className="d-flex justify-content-between align-items-center w-100">
|
<div className="d-flex justify-content-between align-items-center w-100">
|
||||||
{/* Left: Quill Format Buttons */}
|
{/* Left: Quill Format Buttons */}
|
||||||
|
|||||||
@ -10,21 +10,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Projects",
|
"text": "Projects",
|
||||||
"icon": "bx bx-layout",
|
"icon": "bx bx-building-house",
|
||||||
"available": true,
|
"available": true,
|
||||||
"link": "/ui",
|
"link": "/projects"
|
||||||
"submenu": [
|
},
|
||||||
{
|
{
|
||||||
"text": "Project List",
|
"text": "Employees",
|
||||||
"available": true,
|
"icon": "bx bx-user",
|
||||||
"link": "/projects"
|
"available": true,
|
||||||
},
|
"link": "/employees"
|
||||||
{
|
|
||||||
"text": "Employees",
|
|
||||||
"available": true,
|
|
||||||
"link": "/employees"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Activities",
|
"text": "Activities",
|
||||||
@ -64,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Directory",
|
"text": "Directory",
|
||||||
"icon": "bx bx-group",
|
"icon": "bx bx-group",
|
||||||
"available": true,
|
"available": true,
|
||||||
|
|||||||
@ -7,8 +7,7 @@ import { setProjectId } from "../slices/localVariablesSlice";
|
|||||||
import EmployeeList from "../components/Directory/EmployeeList";
|
import EmployeeList from "../components/Directory/EmployeeList";
|
||||||
|
|
||||||
export const useProjects = () => {
|
export const useProjects = () => {
|
||||||
|
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
||||||
const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser )
|
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@ -129,39 +128,34 @@ export const useProjectDetails = (projectId) => {
|
|||||||
}
|
}
|
||||||
}, [projectId, profile]);
|
}, [projectId, profile]);
|
||||||
|
|
||||||
return { projects_Details, loading, error, refetch: fetchData }
|
return { projects_Details, loading, error, refetch: fetchData };
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const useProjectsByEmployee = (employeeId) => {
|
||||||
export const useProjectsByEmployee = ( employeeId ) =>
|
|
||||||
{
|
|
||||||
const [projectList, setProjectList] = useState([]);
|
const [projectList, setProjectList] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const fetchProjects = async (id) => {
|
const fetchProjects = async (id) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(''); // clear previous error
|
setError(""); // clear previous error
|
||||||
const res = await ProjectRepository.getProjectsByEmployee(id);
|
const res = await ProjectRepository.getProjectsByEmployee(id);
|
||||||
setProjectList(res.data);
|
setProjectList(res.data);
|
||||||
cacheData( 'ProjectsByEmployee', {data: res.data, employeeId: id} );
|
cacheData("ProjectsByEmployee", { data: res.data, employeeId: id });
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError( err?.message || 'Failed to fetch projects' );
|
setError(err?.message || "Failed to fetch projects");
|
||||||
setLoading(false)
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!employeeId) return;
|
if (!employeeId) return;
|
||||||
|
|
||||||
const cache_project = getCachedData('ProjectsByEmployee');
|
const cache_project = getCachedData("ProjectsByEmployee");
|
||||||
|
|
||||||
if (
|
if (!cache_project?.data || cache_project?.employeeId !== employeeId) {
|
||||||
!cache_project?.data ||
|
|
||||||
cache_project?.employeeId !== employeeId
|
|
||||||
) {
|
|
||||||
fetchProjects(employeeId);
|
fetchProjects(employeeId);
|
||||||
} else {
|
} else {
|
||||||
setProjectList(cache_project.data);
|
setProjectList(cache_project.data);
|
||||||
@ -172,7 +166,29 @@ export const useProjectsByEmployee = ( employeeId ) =>
|
|||||||
projectList,
|
projectList,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
refetch : fetchProjects
|
refetch: fetchProjects,
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useProjectName = () => {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [projectNames, setProjectName] = useState([]);
|
||||||
|
const [Error, setError] = useState();
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
let response = await ProjectRepository.projectNameList();
|
||||||
|
setProjectName(response.data);
|
||||||
|
cacheData("basicProjectNameList", response.data);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
setError("Failed to fetch data.");
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { projectNames, loading, Error };
|
||||||
|
};
|
||||||
|
|||||||
@ -167,7 +167,7 @@ const AttendancePage = () => {
|
|||||||
]}
|
]}
|
||||||
></Breadcrumb>
|
></Breadcrumb>
|
||||||
<div className="nav-align-top nav-tabs-shadow">
|
<div className="nav-align-top nav-tabs-shadow">
|
||||||
<ul className="nav nav-tabs align-items-center" role="tablist">
|
{/* <ul className="nav nav-tabs" role="tablist">
|
||||||
<div
|
<div
|
||||||
className="dataTables_length text-start py-2 px-2 d-flex "
|
className="dataTables_length text-start py-2 px-2 d-flex "
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
@ -201,26 +201,7 @@ const AttendancePage = () => {
|
|||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<li
|
</ul> */}
|
||||||
className={`nav-item ms-auto ${
|
|
||||||
activeTab === "regularization" ? "d-none" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<label className="switch switch-primary">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="switch-input"
|
|
||||||
checked={showOnlyCheckout}
|
|
||||||
onChange={handleToggle}
|
|
||||||
/>
|
|
||||||
<span className="switch-toggle-slider">
|
|
||||||
<span className="switch-on"></span>
|
|
||||||
<span className="switch-off"></span>
|
|
||||||
</span>
|
|
||||||
<span className="switch-label m-2">Pending Actions</span>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul className="nav nav-tabs" role="tablist">
|
<ul className="nav nav-tabs" role="tablist">
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
@ -259,7 +240,7 @@ const AttendancePage = () => {
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="tab-content attedanceTabs py-2 px-1 px-sm-3">
|
<div className="tab-content attedanceTabs py-0 px-1 px-sm-3">
|
||||||
{projectLoading && <span>Loading..</span>}
|
{projectLoading && <span>Loading..</span>}
|
||||||
{!projectLoading && !attendances && <span>Not Found</span>}
|
{!projectLoading && !attendances && <span>Not Found</span>}
|
||||||
|
|
||||||
|
|||||||
@ -167,7 +167,7 @@ const DailyTask = () => {
|
|||||||
closeModal={closeModal}
|
closeModal={closeModal}
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
/>
|
/>
|
||||||
{isModalOpen && <div className="modal-backdrop fade show"></div>}{" "}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -181,7 +181,7 @@ const DailyTask = () => {
|
|||||||
commentsData={comments}
|
commentsData={comments}
|
||||||
closeModal={closeCommentModal}
|
closeModal={closeCommentModal}
|
||||||
/>
|
/>
|
||||||
{isModalOpenComment && <div className="modal-backdrop fade show"></div>}{" "}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container-xxl flex-grow-1 container-p-y">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
@ -194,7 +194,7 @@ const DailyTask = () => {
|
|||||||
<div className="card card-action mb-6 ">
|
<div className="card card-action mb-6 ">
|
||||||
<div className="card-body p-1 p-sm-2">
|
<div className="card-body p-1 p-sm-2">
|
||||||
<div className="row d-flex justify-content-between align-items-center">
|
<div className="row d-flex justify-content-between align-items-center">
|
||||||
<div className="col-md-6 d-flex gap-3 align-items-center col-12 text-start mb-2 mb-md-0">
|
<div className="col-md-12 d-flex gap-3 align-items-center col-12 text-start mb-2 mb-md-0">
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
onRangeChange={setDateRange}
|
onRangeChange={setDateRange}
|
||||||
endDateMode="today"
|
endDateMode="today"
|
||||||
@ -209,7 +209,7 @@ const DailyTask = () => {
|
|||||||
currentSelectedActivities={filters.selectedActivities}
|
currentSelectedActivities={filters.selectedActivities}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-4 col-12 text-center mb-2 mb-md-0">
|
{/* <div className="col-md-4 col-12 text-center mb-2 mb-md-0">
|
||||||
<select
|
<select
|
||||||
name="project_select"
|
name="project_select"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
@ -232,7 +232,7 @@ const DailyTask = () => {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="table-responsive text-nowrap mt-3">
|
<div className="table-responsive text-nowrap mt-3">
|
||||||
<table className="table">
|
<table className="table">
|
||||||
|
|||||||
@ -112,7 +112,7 @@ const TaskPlannng = () => {
|
|||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
{ label: "Daily Task Planning", link: "/task" },
|
{ label: "Daily Task Planning", link: "/activities/task" },
|
||||||
]}
|
]}
|
||||||
></Breadcrumb>
|
></Breadcrumb>
|
||||||
{project_listLoader && <p>Loading..</p>}
|
{project_listLoader && <p>Loading..</p>}
|
||||||
|
|||||||
@ -22,18 +22,19 @@ import {
|
|||||||
import EmployeeRepository from "../../repositories/EmployeeRepository";
|
import EmployeeRepository from "../../repositories/EmployeeRepository";
|
||||||
import ManageEmployee from "../../components/Employee/ManageEmployee";
|
import ManageEmployee from "../../components/Employee/ManageEmployee";
|
||||||
import ConfirmModal from "../../components/common/ConfirmModal";
|
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||||
import GlobalModel from "../../components/common/GlobalModel";
|
import { useSelector } from "react-redux";
|
||||||
import AssignToProject from "./AssignToProject";
|
|
||||||
|
|
||||||
const EmployeeList = () => {
|
const EmployeeList = () => {
|
||||||
const { profile: loginUser } = useProfile();
|
const selectedProjectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const [selectedProject, setSelectedProject] = useState("");
|
const [selectedProject, setSelectedProject] = useState(() => selectedProjectId || "");
|
||||||
const { projects, loading: projectLoading } = useProjects();
|
const { projects, loading: projectLoading } = useProjects();
|
||||||
const [showInactive, setShowInactive] = useState(false);
|
const [showInactive, setShowInactive] = useState(false);
|
||||||
|
const [showAllEmployees, setShowAllEmployees] = useState(false); // New state for "All Employee"
|
||||||
const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES);
|
const Manage_Employee = useHasUserPermission(MANAGE_EMPLOYEES);
|
||||||
|
|
||||||
|
// Modify the hook to conditionally pass selectedProject or null based on showAllEmployees
|
||||||
const { employees, loading, setLoading, error, recallEmployeeData } =
|
const { employees, loading, setLoading, error, recallEmployeeData } =
|
||||||
useEmployeesAllOrByProjectId(selectedProject, showInactive);
|
useEmployeesAllOrByProjectId(showAllEmployees ? null : selectedProject, showInactive);
|
||||||
const [projectsList, setProjectsList] = useState(projects || []);
|
const [projectsList, setProjectsList] = useState(projects || []);
|
||||||
|
|
||||||
const [employeeList, setEmployeeList] = useState([]);
|
const [employeeList, setEmployeeList] = useState([]);
|
||||||
@ -49,8 +50,7 @@ const EmployeeList = () => {
|
|||||||
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
|
const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
|
||||||
const [employeeLodaing, setemployeeLodaing] = useState(false);
|
const [employeeLodaing, setemployeeLodaing] = useState(false);
|
||||||
const [ selectedEmployee, setSelectEmployee ] = useState( null )
|
|
||||||
const [IsOpenAsssingModal,setOpenAssignModal] = useState(false)
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSearch = (e) => {
|
const handleSearch = (e) => {
|
||||||
@ -73,19 +73,17 @@ const EmployeeList = () => {
|
|||||||
if (!loading && Array.isArray(employees)) {
|
if (!loading && Array.isArray(employees)) {
|
||||||
// Sort by full name (firstName + lastName)
|
// Sort by full name (firstName + lastName)
|
||||||
const sorted = [...employees].sort((a, b) => {
|
const sorted = [...employees].sort((a, b) => {
|
||||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${
|
const nameA = `${a.firstName || ""}${a.middleName || ""}${b.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);
|
||||||
});
|
});
|
||||||
|
|
||||||
setEmployeeList(sorted);
|
setEmployeeList(sorted);
|
||||||
setFilteredData(sorted);
|
setFilteredData(sorted);
|
||||||
}
|
}
|
||||||
}, [loading, employees, selectedProject]);
|
}, [loading, employees, selectedProject, showAllEmployees]); // Add showAllEmployees to dependencies
|
||||||
|
|
||||||
const displayData = searchText ? filteredData : employeeList;
|
const displayData = searchText ? filteredData : employeeList;
|
||||||
const indexOfLastItem = currentPage * itemsPerPage;
|
const indexOfLastItem = currentPage * itemsPerPage;
|
||||||
@ -167,7 +165,7 @@ const EmployeeList = () => {
|
|||||||
exportToExcel(currentItems, "employees");
|
exportToExcel(currentItems, "employees");
|
||||||
break;
|
break;
|
||||||
case "pdf":
|
case "pdf":
|
||||||
exportToPDF(currentItems, "employees"); // Pass the employeeList directly
|
exportToPDF(currentItems, "employees");
|
||||||
break;
|
break;
|
||||||
case "print":
|
case "print":
|
||||||
printTable(tableRef.current);
|
printTable(tableRef.current);
|
||||||
@ -182,6 +180,15 @@ const EmployeeList = () => {
|
|||||||
recallEmployeeData(e.target.checked);
|
recallEmployeeData(e.target.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// New handler for "All Employee" toggle
|
||||||
|
const handleAllEmployeesToggle = (e) => {
|
||||||
|
const isChecked = e.target.checked;
|
||||||
|
setShowAllEmployees(isChecked);
|
||||||
|
// If "All Employees" is checked, we don't want to filter by project,
|
||||||
|
// so we pass null for selected project. Otherwise, use the currently selected project.
|
||||||
|
recallEmployeeData(showInactive, isChecked ? null : selectedProject);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEmployeeModel = (id) => {
|
const handleEmployeeModel = (id) => {
|
||||||
setSelecedEmployeeId(id);
|
setSelecedEmployeeId(id);
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
@ -192,11 +199,20 @@ const EmployeeList = () => {
|
|||||||
setSelectedEmpFordelete(employee);
|
setSelectedEmpFordelete(employee);
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
};
|
};
|
||||||
const handleCloseAssignModal = () =>
|
|
||||||
{
|
const handleProjectSelection = (e) => {
|
||||||
setOpenAssignModal( false )
|
const newProjectId = e.target.value;
|
||||||
setSelectEmployee(null)
|
setSelectedProject(newProjectId);
|
||||||
}
|
// If a specific project is selected, uncheck "All Employees"
|
||||||
|
if (newProjectId) {
|
||||||
|
setShowAllEmployees(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedProject(selectedProjectId || "");
|
||||||
|
}, [selectedProjectId]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCreateModalOpen && (
|
{isCreateModalOpen && (
|
||||||
@ -220,8 +236,7 @@ const EmployeeList = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> )}
|
</div>)}
|
||||||
|
|
||||||
|
|
||||||
{IsDeleteModalOpen && (
|
{IsDeleteModalOpen && (
|
||||||
<div
|
<div
|
||||||
@ -248,11 +263,6 @@ const EmployeeList = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{IsOpenAsssingModal && ( <GlobalModel isOpen={IsOpenAsssingModal} closeModal={()=>setOpenAssignModal(false)}>
|
|
||||||
<AssignToProject employee={selectedEmployee} onClose={() => setOpenAssignModal( false )} />
|
|
||||||
</GlobalModel>)}
|
|
||||||
|
|
||||||
|
|
||||||
<div className="container-xxl flex-grow-1 container-p-y">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
@ -263,49 +273,25 @@ const EmployeeList = () => {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="card ">
|
<div className="card ">
|
||||||
<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="row mb-2 ">
|
<div className="row mb-2 ">
|
||||||
<div className="col-md-3 col-sm-6 ">
|
<div className="col-md-3 col-sm-6 ">
|
||||||
<div className="ms-0">
|
<div className="form-check text-start">
|
||||||
<div
|
<input
|
||||||
className="dataTables_length text-start"
|
type="checkbox"
|
||||||
id="DataTables_Table_0_length"
|
className="form-check-input"
|
||||||
>
|
id="allEmployeesCheckbox"
|
||||||
<label>
|
checked={showAllEmployees}
|
||||||
<select
|
onChange={handleAllEmployeesToggle}
|
||||||
id="project-select"
|
/>
|
||||||
onChange={(e) => setSelectedProject(e.target.value)}
|
<label className="form-check-label" htmlFor="allEmployeesCheckbox">
|
||||||
name="DataTables_Table_0_length"
|
All Employees
|
||||||
aria-controls="DataTables_Table_0"
|
</label>
|
||||||
className="form-select form-select-sm"
|
|
||||||
value={selectedProject || ""}
|
|
||||||
>
|
|
||||||
{projectLoading ? (
|
|
||||||
<option value="Loading">Loading...</option>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<option value="">All Employees</option>
|
|
||||||
{Array.isArray(projects) &&
|
|
||||||
projects
|
|
||||||
.filter((item) =>
|
|
||||||
loginUser?.projects?.includes(
|
|
||||||
String(item.id)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((item) => (
|
|
||||||
<option key={item.id} value={item.id}>
|
|
||||||
{item.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-9 col-sm-6">
|
<div className="col-md-9 col-sm-6">
|
||||||
@ -377,9 +363,8 @@ const EmployeeList = () => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm add-new btn-primary ${
|
className={`btn btn-sm add-new btn-primary ${!Manage_Employee && "d-none"
|
||||||
!Manage_Employee && "d-none"
|
}`}
|
||||||
}`}
|
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -398,23 +383,24 @@ const EmployeeList = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`text-end mb-2 ${selectedProject ? 'd-none' : ''}`}>
|
<div className="d-flex justify-content-end mb-2">
|
||||||
<label className="switch switch-primary">
|
{/* Show Inactive Employees Checkbox */}
|
||||||
<input
|
<div className={`${showAllEmployees ? '' : selectedProject ? 'd-none' : ''}`}>
|
||||||
type="checkbox"
|
<div className="form-check">
|
||||||
className="switch-input"
|
<input
|
||||||
checked={showInactive}
|
type="checkbox"
|
||||||
onChange={handleToggle}
|
className="form-check-input"
|
||||||
/>
|
id="inactiveEmployeesCheckbox"
|
||||||
<span className="switch-toggle-slider">
|
checked={showInactive}
|
||||||
<span className="switch-on"></span>
|
onChange={handleToggle}
|
||||||
<span className="switch-off"></span>
|
/>
|
||||||
</span>
|
<label className="form-check-label" htmlFor="inactiveEmployeesCheckbox">
|
||||||
<span className="switch-label">
|
Show Inactive Employees
|
||||||
Show Inactive Employees
|
</label>
|
||||||
</span>
|
</div>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table
|
<table
|
||||||
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
|
className="datatables-users table border-top dataTable no-footer dtr-column text-nowrap"
|
||||||
id="DataTables_Table_0"
|
id="DataTables_Table_0"
|
||||||
@ -490,9 +476,8 @@ const EmployeeList = () => {
|
|||||||
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" }}
|
||||||
@ -655,19 +640,6 @@ const EmployeeList = () => {
|
|||||||
<i className="bx bx-cog bx-sm"></i>{" "}
|
<i className="bx bx-cog bx-sm"></i>{" "}
|
||||||
Manage Role
|
Manage Role
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className="dropdown-item py-1"
|
|
||||||
|
|
||||||
onClick={() =>
|
|
||||||
{
|
|
||||||
setSelectEmployee( item ),
|
|
||||||
setOpenAssignModal(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className='bx bx-select-multiple'></i>{" "}
|
|
||||||
Assign Project
|
|
||||||
</button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -682,13 +654,11 @@ const EmployeeList = () => {
|
|||||||
<div style={{ width: "1%" }}></div>
|
<div style={{ width: "1%" }}></div>
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{!loading && (
|
{!loading && displayData.length > itemsPerPage && (
|
||||||
<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"
|
||||||
@ -697,28 +667,28 @@ const EmployeeList = () => {
|
|||||||
«
|
«
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{[...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"
|
||||||
onClick={() => paginate(index + 1)}
|
onClick={() => paginate(index + 1)}
|
||||||
>
|
>
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<li
|
<li
|
||||||
className={`page-item ${
|
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
||||||
currentPage === totalPages ? "disabled" : ""
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link "
|
className="page-link"
|
||||||
onClick={() => paginate(currentPage + 1)}
|
onClick={() => paginate(currentPage + 1)}
|
||||||
>
|
>
|
||||||
»
|
»
|
||||||
@ -727,6 +697,7 @@ const EmployeeList = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -736,4 +707,4 @@ const EmployeeList = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmployeeList;
|
export default EmployeeList;
|
||||||
@ -22,7 +22,8 @@ const ProjectRepository = {
|
|||||||
updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
|
updateProject: (id, data) => api.put(`/api/project/update/${id}`, data),
|
||||||
deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
|
deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
|
||||||
getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
|
getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
|
||||||
updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data)
|
updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data),
|
||||||
|
projectNameList:()=>api.get("/api/project/list/basic")
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TasksRepository = {
|
export const TasksRepository = {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ const localVariablesSlice = createSlice({
|
|||||||
initialState: {
|
initialState: {
|
||||||
selectedMaster:"Application Role",
|
selectedMaster:"Application Role",
|
||||||
regularizationCount:0,
|
regularizationCount:0,
|
||||||
projectId: 1,
|
projectId: "",
|
||||||
reload:false
|
reload:false
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|||||||
@ -39,103 +39,67 @@ axiosClient.interceptors.response.use(
|
|||||||
async (error) => {
|
async (error) => {
|
||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
|
|
||||||
if (!originalRequest) {
|
// Prevent infinite loop
|
||||||
|
if (!originalRequest || originalRequest._retry) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent multiple toasts for the same request
|
// Only show one toast per request
|
||||||
if (!originalRequest._toastShown) {
|
if (!originalRequest._toastShown) {
|
||||||
originalRequest._toastShown = true;
|
originalRequest._toastShown = true;
|
||||||
|
|
||||||
if (error.code === "ERR_CONNECTION_REFUSED") {
|
if (error.code === "ERR_CONNECTION_REFUSED") {
|
||||||
console.error(
|
showToast("Unable to connect to the server. Please try again later.", "error");
|
||||||
"Connection refused. Please ensure the server is running."
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error.config.url.indexOf("refresh-token") != -1) {
|
|
||||||
//showToast("Server is unreachable. Try again later!", "error");
|
|
||||||
} else {
|
|
||||||
showToast(
|
|
||||||
"Unable to connect to the server. Please try again later.",
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (error.code === "ERR_NETWORK") {
|
} else if (error.code === "ERR_NETWORK") {
|
||||||
console.error("Network error: Unable to reach the server.");
|
showToast("Network error. Please check your connection.", "error");
|
||||||
if (error.config.url.indexOf("refresh-token") != -1) {
|
|
||||||
//showToast("Server is unreachable. Try again later!", "error");
|
|
||||||
}
|
|
||||||
redirectToLogin();
|
redirectToLogin();
|
||||||
} else if (error.code === "ECONNABORTED") {
|
} else if (error.code === "ECONNABORTED") {
|
||||||
console.error("Request timed out.");
|
showToast("Request timed out. Please try again.", "error");
|
||||||
|
|
||||||
if (error.config.url.indexOf("refresh-token") != -1) {
|
|
||||||
//showToast("Server is unreachable. Try again later!", "error");
|
|
||||||
} else {
|
|
||||||
showToast(
|
|
||||||
"The request took too long. Please try again later.",
|
|
||||||
"error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (error.response) {
|
} else if (error.response) {
|
||||||
if (error.config.url.indexOf("refresh-token") != -1) {
|
const status = error.response.status;
|
||||||
//showToast("Server is unreachable. Try again later!", "error");
|
const isRefreshRequest = error.config.url.includes("refresh-token");
|
||||||
}
|
|
||||||
// else {
|
if (status === 401 && !isRefreshRequest) {
|
||||||
// // showToast(error.response.data.message, "error"); // repeted toast
|
// Mark as retried to avoid loops
|
||||||
// }
|
|
||||||
if (error.response.status === 401) {
|
|
||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
|
|
||||||
|
const refreshToken = localStorage.getItem("refreshToken");
|
||||||
|
|
||||||
|
if (!refreshToken || error.response.data?.errors === "Invalid or expired refresh token.") {
|
||||||
|
redirectToLogin();
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSignalR();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the refresh token from secure storage
|
// Refresh token
|
||||||
const refreshToken = localStorage.getItem("refreshToken");
|
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
||||||
|
token: localStorage.getItem("jwtToken"),
|
||||||
|
refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
if (!refreshToken || error.response.data.errors === "Invalid or expired refresh token.") {
|
const { token, refreshToken: newRefreshToken } = res.data.data;
|
||||||
// Redirect to login if refresh token is not available
|
|
||||||
redirectToLogin();
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
stopSignalR();
|
|
||||||
// Make a request to refresh the access token
|
|
||||||
const response = await axiosClient
|
|
||||||
.post("/api/Auth/refresh-token", {
|
|
||||||
token: localStorage.getItem("jwtToken"),
|
|
||||||
refreshToken,
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
// console.log("7 - error fetching refresh token :", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
const { token, refreshToken: newRefreshToken } = response.data.data;
|
// Save new tokens
|
||||||
localStorage.setItem("jwtToken", token);
|
localStorage.setItem("jwtToken", token);
|
||||||
localStorage.setItem("refreshToken", newRefreshToken);
|
localStorage.setItem("refreshToken", newRefreshToken);
|
||||||
|
|
||||||
startSignalR()
|
startSignalR()
|
||||||
// Retry the original request with the new token
|
// Set Authorization header
|
||||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
|
||||||
// Retry the original request
|
// Optional: Instead of retrying, you may choose to reload app or go to home
|
||||||
return axiosClient(originalRequest);
|
return axiosClient(originalRequest); // <== only retry once
|
||||||
} catch (err) {
|
} catch (refreshError) {
|
||||||
// Redirect to login if token refresh fails
|
|
||||||
redirectToLogin();
|
redirectToLogin();
|
||||||
return Promise.reject(err);
|
return Promise.reject(refreshError);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (error.config.url.indexOf("refresh-token") != -1) {
|
|
||||||
//showToast("Server is unreachable. Try again later!", "error");
|
|
||||||
// console.log("6 - error fetching refresh token :", error);
|
|
||||||
}
|
|
||||||
// showToast(
|
|
||||||
// error.response.data?.message ||
|
|
||||||
// "An error occurred. Please try again.",
|
|
||||||
// "error"
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("An unknown error occurred:", error.message);
|
showToast("An unknown error occurred.", "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user