removed unused code
This commit is contained in:
parent
c35eacca5a
commit
97dca1a10b
@ -1,17 +1,20 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useOrganizationModal } from "./hooks/useOrganization";
|
||||
import OrganizationModal from "./components/Organization/OrganizationModal";
|
||||
import { useAuthModal } from "./hooks/useAuth";
|
||||
import { useAuthModal, useModal } from "./hooks/useAuth";
|
||||
import SwitchTenant from "./pages/authentication/SwitchTenant";
|
||||
import ChangePasswordPage from "./pages/authentication/ChangePassword";
|
||||
|
||||
const ModalProvider = () => {
|
||||
const { isOpen, onClose } = useOrganizationModal();
|
||||
const { isOpen: isAuthOpen } = useAuthModal();
|
||||
const {isOpen:isChangePass} = useModal("ChangePassword")
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && <OrganizationModal />}
|
||||
{isAuthOpen && <SwitchTenant />}
|
||||
{isChangePass && <ChangePasswordPage /> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,42 +0,0 @@
|
||||
import React, { createContext, useState, useContext } from "react";
|
||||
import ChangePasswordPage from "../../pages/authentication/ChangePassword";
|
||||
|
||||
const ChangePasswordContext = createContext();
|
||||
|
||||
export const ChangePasswordProvider = ({ children }) => {
|
||||
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
|
||||
|
||||
const openChangePassword = () => setIsChangePasswordOpen(true);
|
||||
const closeChangePassword = () => setIsChangePasswordOpen(false);
|
||||
|
||||
return (
|
||||
<ChangePasswordContext.Provider
|
||||
value={{ isChangePasswordOpen, openChangePassword, closeChangePassword }}
|
||||
>
|
||||
{children}
|
||||
|
||||
{isChangePasswordOpen && (
|
||||
<>
|
||||
{/* This is the main Bootstrap modal container */}
|
||||
{/* It provides the fixed positioning and high z-index */}
|
||||
<div
|
||||
className="modal fade show" // 'fade' for animation, 'show' to make it visible
|
||||
style={{ display: 'block' }} // Explicitly set display: block for immediate visibility
|
||||
tabIndex="-1" // Makes the modal focusable
|
||||
role="dialog" // ARIA role for accessibility
|
||||
aria-labelledby="changePasswordModalLabel" // Link to a heading for accessibility
|
||||
aria-modal="true" // Indicate it's a modal dialog
|
||||
>
|
||||
{/* The ChangePasswordPage component itself contains the modal-dialog and modal-content */}
|
||||
<ChangePasswordPage onClose={closeChangePassword} />
|
||||
</div>
|
||||
|
||||
{/* The modal backdrop */}
|
||||
<div className="modal-backdrop fade show"></div>
|
||||
</>
|
||||
)}
|
||||
</ChangePasswordContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useChangePassword = () => useContext(ChangePasswordContext);
|
@ -1,12 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import { useChangePassword } from "../../components/Context/ChangePasswordContext";
|
||||
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
import ManageEmployee from "./ManageEmployee";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import { useModal } from "../../hooks/useAuth";
|
||||
|
||||
const EmpBanner = ({ profile, loggedInUser }) => {
|
||||
const { openChangePassword } = useChangePassword();
|
||||
const {onOpen} = useModal("ChangePassword")
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
return (
|
||||
@ -86,7 +87,7 @@ const EmpBanner = ({ profile, loggedInUser }) => {
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center mt-4">
|
||||
{profile?.isActive && ( // ✅ show only if active
|
||||
{profile?.isActive && (
|
||||
<li className="list-inline-item">
|
||||
<button
|
||||
className="btn btn-sm btn-primary btn-block"
|
||||
@ -101,7 +102,7 @@ const EmpBanner = ({ profile, loggedInUser }) => {
|
||||
{profile?.id === loggedInUser?.employeeInfo?.id && (
|
||||
<button
|
||||
className="btn btn-sm btn-outline-primary btn-block"
|
||||
onClick={() => openChangePassword()}
|
||||
onClick={onOpen}
|
||||
>
|
||||
Change Password
|
||||
</button>
|
||||
|
@ -12,14 +12,13 @@ import useMaster from "../../hooks/masterHook/useMaster";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import Avatar from "../../components/common/Avatar";
|
||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
import { useAuthModal, useLogout } from "../../hooks/useAuth";
|
||||
import { useAuthModal, useLogout, useModal } from "../../hooks/useAuth";
|
||||
|
||||
const Header = () => {
|
||||
const { profile } = useProfile();
|
||||
@ -28,6 +27,7 @@ const Header = () => {
|
||||
const { data, loading } = useMaster();
|
||||
const navigate = useNavigate();
|
||||
const {onOpen} = useAuthModal()
|
||||
const { onOpen:changePass } = useModal("ChangePassword");
|
||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||
const { mutate : logout,isPending:logouting} = useLogout()
|
||||
|
||||
@ -97,7 +97,7 @@ const Header = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const { openChangePassword } = useChangePassword();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -422,7 +422,7 @@ const Header = () => {
|
||||
<span className="align-middle">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
<li onClick={openChangePassword}>
|
||||
<li onClick={changePass}>
|
||||
{" "}
|
||||
<a
|
||||
aria-label="go to profile"
|
||||
|
@ -207,7 +207,7 @@ export const useMarkAttendance = () => {
|
||||
onSuccess: (data, variables) => {
|
||||
if (variables.forWhichTab == 1) {
|
||||
queryClient.setQueryData(["attendance", selectedProject,selectedOrganization], (oldData) => {
|
||||
|
||||
debugger
|
||||
if (!oldData) return oldData;
|
||||
return oldData.map((emp) =>
|
||||
emp.employeeId === data.employeeId ? { ...emp, ...data } : emp
|
||||
|
@ -10,10 +10,30 @@ import AuthRepository from "../repositories/AuthRepository.jsx";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
closeAuthModal,
|
||||
closeModal,
|
||||
openAuthModal,
|
||||
openModal,
|
||||
toggleModal,
|
||||
} from "../slices/localVariablesSlice.jsx";
|
||||
import { removeSession } from "../utils/authUtils.js";
|
||||
|
||||
// ----------------------------Modal--------------------------
|
||||
|
||||
export const useModal = (modalType) => {
|
||||
const dispatch = useDispatch();
|
||||
const isOpen = useSelector(
|
||||
(state) => state.localVariables.modals[modalType]?.isOpen
|
||||
);
|
||||
|
||||
const onOpen = (data = {}) => dispatch(openModal({ modalType, data }));
|
||||
const onClose = () => dispatch(closeModal({ modalType }));
|
||||
const onToggle = () => dispatch(toggleModal({ modalType }));
|
||||
|
||||
return { isOpen, onOpen, onClose, onToggle };
|
||||
};
|
||||
|
||||
// -------------------APIHook-------------------------------------
|
||||
|
||||
export const useTenants = () => {
|
||||
return useQuery({
|
||||
queryKey: ["tenantlist"],
|
||||
@ -62,18 +82,21 @@ export const useAuthModal = () => {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export const useLogout = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
let payload = { refreshToken: localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken") };
|
||||
let payload = {
|
||||
refreshToken:
|
||||
localStorage.getItem("refreshToken") ||
|
||||
sessionStorage.getItem("refreshToken"),
|
||||
};
|
||||
return await AuthRepository.logout(payload);
|
||||
},
|
||||
|
||||
onSuccess: (data) => {
|
||||
removeSession()
|
||||
removeSession();
|
||||
|
||||
window.location.href = "/auth/login";
|
||||
if (onSuccessCallBack) onSuccessCallBack();
|
||||
@ -81,7 +104,7 @@ export const useLogout = () => {
|
||||
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while creating project", "error");
|
||||
removeSession()
|
||||
removeSession();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -5,19 +5,13 @@ import App from './App.tsx'
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './store/store';
|
||||
import { ChangePasswordProvider } from './components/Context/ChangePasswordContext.jsx';
|
||||
import { ModalProvider1 } from './pages/Gallary/ModalContext.jsx';
|
||||
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
// <StrictMode>
|
||||
// <MasterDataProvider>
|
||||
<Provider store={ store }>
|
||||
<ChangePasswordProvider >
|
||||
<ModalProvider1>
|
||||
<App />
|
||||
</ModalProvider1>
|
||||
</ChangePasswordProvider>
|
||||
</Provider>
|
||||
|
||||
// </StrictMode>,
|
||||
|
@ -12,7 +12,7 @@ import Attendance from "../../components/Activities/Attendance";
|
||||
import Regularization from "../../components/Activities/Regularization";
|
||||
import { useAttendance } from "../../hooks/useAttendance";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { setOrganization, setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
|
||||
import eventBus from "../../services/eventBus";
|
||||
@ -59,7 +59,8 @@ const AttendancePage = () => {
|
||||
if (selectedProject == null) {
|
||||
dispatch(setProjectId(projectNames[0]?.id));
|
||||
}
|
||||
}, []);
|
||||
dispatch(setOrganization(appliedFilters?.selectedOrganization))
|
||||
}, [appliedFilters?.selectedOrganization]);
|
||||
|
||||
const getRole = (roleId) => {
|
||||
if (!empRoles) return "Unassigned";
|
||||
|
@ -1,499 +0,0 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
// import "./ImageGallery.css";
|
||||
import moment from "moment";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useModal } from "./ModalContext";
|
||||
import ImagePop from "./ImagePop";
|
||||
import Avatar from "../../components/common/Avatar";
|
||||
import DateRangePicker from "../../components/common/DateRangePicker";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import useImageGallery from "../../hooks/useImageGallery";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
|
||||
const SCROLL_THRESHOLD = 5;
|
||||
|
||||
const ImageGallery = () => {
|
||||
const selectedProjectId = useSelector(
|
||||
(store) => store.localVariables.projectId
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
const { projectNames } = useProjectName();
|
||||
|
||||
// Auto-select a project on mount
|
||||
useEffect(() => {
|
||||
if (!selectedProjectId && projectNames?.length) {
|
||||
dispatch(setProjectId(projectNames[0].id));
|
||||
}
|
||||
}, [selectedProjectId, projectNames, dispatch]);
|
||||
|
||||
// Filter states
|
||||
const [selectedFilters, setSelectedFilters] = useState({
|
||||
building: [],
|
||||
floor: [],
|
||||
activity: [],
|
||||
uploadedBy: [],
|
||||
workCategory: [],
|
||||
workArea: [],
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
});
|
||||
const [appliedFilters, setAppliedFilters] = useState({
|
||||
buildingIds: null,
|
||||
floorIds: null,
|
||||
activityIds: null,
|
||||
uploadedByIds: null,
|
||||
workCategoryIds: null,
|
||||
workAreaIds: null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
});
|
||||
const [collapsedFilters, setCollapsedFilters] = useState({
|
||||
dateRange: false,
|
||||
building: false,
|
||||
floor: false,
|
||||
activity: false,
|
||||
uploadedBy: false,
|
||||
workCategory: false,
|
||||
workArea: false,
|
||||
});
|
||||
const [isFilterPanelOpen, setIsFilterPanelOpen] = useState(false);
|
||||
const [hoveredImage, setHoveredImage] = useState(null);
|
||||
|
||||
const imageGroupRefs = useRef({});
|
||||
const loaderRef = useRef(null);
|
||||
const filterPanelRef = useRef(null);
|
||||
const filterButtonRef = useRef(null);
|
||||
const { openModal } = useModal();
|
||||
|
||||
const {
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
refetch,
|
||||
} = useImageGallery(selectedProjectId, appliedFilters);
|
||||
|
||||
const images = data?.pages.flatMap((page) => page.data) || [];
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e) => {
|
||||
if (
|
||||
filterPanelRef.current &&
|
||||
!filterPanelRef.current.contains(e.target) &&
|
||||
filterButtonRef.current &&
|
||||
!filterButtonRef.current.contains(e.target)
|
||||
) {
|
||||
setIsFilterPanelOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
return () => document.removeEventListener("mousedown", handleClick);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProjectId) refetch();
|
||||
}, [selectedProjectId, appliedFilters, refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (data) => {
|
||||
if (data.projectId === selectedProjectId) refetch();
|
||||
};
|
||||
eventBus.on("image_gallery", handler);
|
||||
return () => eventBus.off("image_gallery", handler);
|
||||
}, [selectedProjectId, refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loaderRef.current) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage && !isLoading) {
|
||||
fetchNextPage();
|
||||
}
|
||||
},
|
||||
{ rootMargin: "200px", threshold: 0.1 }
|
||||
);
|
||||
|
||||
observer.observe(loaderRef.current);
|
||||
|
||||
return () => {
|
||||
if (loaderRef.current) {
|
||||
observer.unobserve(loaderRef.current);
|
||||
}
|
||||
};
|
||||
}, [hasNextPage, isFetchingNextPage, isLoading, fetchNextPage]);
|
||||
|
||||
|
||||
// Utility: derive filter options
|
||||
const getUniqueValues = useCallback(
|
||||
(idKey, nameKey) => {
|
||||
const m = new Map();
|
||||
images.forEach((batch) => {
|
||||
const id = idKey === "floorIds" ? batch.floorIds : batch[idKey];
|
||||
if (id && batch[nameKey] && !m.has(id)) {
|
||||
m.set(id, batch[nameKey]);
|
||||
}
|
||||
});
|
||||
return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1]));
|
||||
},
|
||||
[images]
|
||||
);
|
||||
|
||||
const getUploadedBy = useCallback(() => {
|
||||
const m = new Map();
|
||||
images.forEach((batch) => {
|
||||
batch.documents.forEach((doc) => {
|
||||
const name = `${doc.uploadedBy?.firstName || ""} ${
|
||||
doc.uploadedBy?.lastName || ""
|
||||
}`.trim();
|
||||
if (doc.uploadedBy?.id && name && !m.has(doc.uploadedBy.id)) {
|
||||
m.set(doc.uploadedBy.id, name);
|
||||
}
|
||||
});
|
||||
});
|
||||
return [...m.entries()].sort((a, b) => a[1].localeCompare(b[1]));
|
||||
}, [images]);
|
||||
|
||||
const buildings = getUniqueValues("buildingId", "buildingName");
|
||||
const floors = getUniqueValues("floorIds", "floorName");
|
||||
const activities = getUniqueValues("activityId", "activityName");
|
||||
const workAreas = getUniqueValues("workAreaId", "workAreaName");
|
||||
const workCategories = getUniqueValues("workCategoryId", "workCategoryName");
|
||||
const uploadedByUsers = getUploadedBy();
|
||||
|
||||
const toggleFilter = useCallback((type, id, name) => {
|
||||
setSelectedFilters((prev) => {
|
||||
const arr = prev[type];
|
||||
const exists = arr.some(([x]) => x === id);
|
||||
const updated = exists
|
||||
? arr.filter(([x]) => x !== id)
|
||||
: [...arr, [id, name]];
|
||||
return { ...prev, [type]: updated };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setDateRange = useCallback(({ startDate, endDate }) => {
|
||||
setSelectedFilters((prev) => ({
|
||||
...prev,
|
||||
startDate: startDate || "",
|
||||
endDate: endDate || "",
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const toggleCollapse = useCallback((type) => {
|
||||
setCollapsedFilters((prev) => ({ ...prev, [type]: !prev[type] }));
|
||||
}, []);
|
||||
|
||||
const handleApplyFilters = useCallback(() => {
|
||||
const payload = {
|
||||
buildingIds: selectedFilters.building.map(([x]) => x) || null,
|
||||
floorIds: selectedFilters.floor.map(([x]) => x) || null,
|
||||
activityIds: selectedFilters.activity.map(([x]) => x) || null,
|
||||
uploadedByIds: selectedFilters.uploadedBy.map(([x]) => x) || null,
|
||||
workCategoryIds: selectedFilters.workCategory.map(([x]) => x) || null,
|
||||
workAreaIds: selectedFilters.workArea.map(([x]) => x) || null,
|
||||
startDate: selectedFilters.startDate || null,
|
||||
endDate: selectedFilters.endDate || null,
|
||||
};
|
||||
const changed = Object.keys(payload).some((key) => {
|
||||
const oldVal = appliedFilters[key],
|
||||
newVal = payload[key];
|
||||
return Array.isArray(oldVal)
|
||||
? oldVal.length !== newVal.length ||
|
||||
oldVal.some((x) => !newVal.includes(x))
|
||||
: oldVal !== newVal;
|
||||
});
|
||||
if (changed) setAppliedFilters(payload);
|
||||
}, [selectedFilters, appliedFilters]);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
setSelectedFilters({
|
||||
building: [],
|
||||
floor: [],
|
||||
activity: [],
|
||||
uploadedBy: [],
|
||||
workCategory: [],
|
||||
workArea: [],
|
||||
startDate: "",
|
||||
endDate: "",
|
||||
});
|
||||
setAppliedFilters({
|
||||
buildingIds: null,
|
||||
floorIds: null,
|
||||
activityIds: null,
|
||||
uploadedByIds: null,
|
||||
workCategoryIds: null,
|
||||
workAreaIds: null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const scrollLeft = useCallback(
|
||||
(key) =>
|
||||
imageGroupRefs.current[key]?.scrollBy({ left: -200, behavior: "smooth" }),
|
||||
[]
|
||||
);
|
||||
const scrollRight = useCallback(
|
||||
(key) =>
|
||||
imageGroupRefs.current[key]?.scrollBy({ left: 200, behavior: "smooth" }),
|
||||
[]
|
||||
);
|
||||
|
||||
const renderCategory = (label, items, type) => (
|
||||
<div
|
||||
className={`dropdown my-2 ${collapsedFilters[type] ? "collapsed" : ""}`}
|
||||
>
|
||||
<div
|
||||
className="dropdown-header bg-label-primary"
|
||||
onClick={() => toggleCollapse(type)}
|
||||
>
|
||||
<strong>{label}</strong>
|
||||
<div className="header-controls">
|
||||
{((type === "dateRange" &&
|
||||
(selectedFilters.startDate || selectedFilters.endDate)) ||
|
||||
(type !== "dateRange" && selectedFilters[type]?.length > 0)) && (
|
||||
<button
|
||||
className="clear-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedFilters((prev) => ({
|
||||
...prev,
|
||||
[type]: type === "dateRange" ? "" : [],
|
||||
...(type === "dateRange" && { startDate: "", endDate: "" }),
|
||||
}));
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
<span className="collapse-icon">
|
||||
{collapsedFilters[type] ? "+" : "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{!collapsedFilters[type] && (
|
||||
<div className="dropdown-content">
|
||||
{type === "dateRange" ? (
|
||||
<DateRangePicker
|
||||
onRangeChange={setDateRange}
|
||||
endDateMode="today"
|
||||
startDate={selectedFilters.startDate}
|
||||
endDate={selectedFilters.endDate}
|
||||
/>
|
||||
) : (
|
||||
items.map(([id, name]) => (
|
||||
<label key={id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedFilters[type].some(([x]) => x === id)}
|
||||
onChange={() => toggleFilter(type, id, name)}
|
||||
/>
|
||||
{name}
|
||||
</label>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`gallery-container container-fluid ${
|
||||
isFilterPanelOpen ? "filter-panel-open-end" : ""
|
||||
}`}
|
||||
>
|
||||
<Breadcrumb data={[{ label: "Home", link: "/" }, { label: "Gallery" }]} />
|
||||
<div className="main-content">
|
||||
<button
|
||||
className={`filter-button btn-primary ${
|
||||
isFilterPanelOpen ? "closed-icon" : ""
|
||||
}`}
|
||||
onClick={() => setIsFilterPanelOpen((p) => !p)}
|
||||
ref={filterButtonRef}
|
||||
>
|
||||
{isFilterPanelOpen ? (
|
||||
<i className="fa-solid fa-times fs-5" />
|
||||
) : (
|
||||
<i className="bx bx-slider-alt ms-1" />
|
||||
)}
|
||||
</button>
|
||||
<div className="activity-section">
|
||||
{isLoading ? (
|
||||
<div className="text-center">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
) : images.length ? (
|
||||
images.map((batch) => {
|
||||
const doc = batch.documents[0];
|
||||
const userName = `${doc.uploadedBy?.firstName || ""} ${
|
||||
doc.uploadedBy?.lastName || ""
|
||||
}`.trim();
|
||||
const date = formatUTCToLocalTime(doc.uploadedAt);
|
||||
const hasArrows = batch.documents.length > SCROLL_THRESHOLD;
|
||||
return (
|
||||
<div key={batch.batchId} className="grouped-section">
|
||||
<div className="group-heading">
|
||||
{/* Uploader Info */}
|
||||
<div className="d-flex align-items-center mb-1">
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={doc.uploadedBy?.firstName}
|
||||
lastName={doc.uploadedBy?.lastName}
|
||||
className="me-2"
|
||||
/>
|
||||
<div className="d-flex flex-column align-items-start">
|
||||
<strong className="user-name-text">{userName}</strong>
|
||||
<span className="text-muted small">{date}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Location Info */}
|
||||
<div className="location-line text-secondary">
|
||||
<div className="d-flex align-items-center flex-wrap gap-1 text-secondary">
|
||||
{" "}
|
||||
<span className="d-flex align-items-center">
|
||||
<span>{batch.buildingName}</span>
|
||||
<i className="bx bx-chevron-right " />
|
||||
</span>
|
||||
<span className="d-flex align-items-center">
|
||||
<span>{batch.floorName}</span>
|
||||
<i className="bx bx-chevron-right m" />
|
||||
</span>
|
||||
<span className="d-flex align-items-center ">
|
||||
<span>{batch.workAreaName || "Unknown"}</span>
|
||||
<i className="bx bx-chevron-right " />
|
||||
<span>{batch.activityName}</span>
|
||||
</span>
|
||||
</div>
|
||||
{batch.workCategoryName && (
|
||||
<span className="badge bg-label-primary ms-2">
|
||||
{batch.workCategoryName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="image-group-wrapper">
|
||||
{hasArrows && (
|
||||
<button
|
||||
className="scroll-arrow left-arrow"
|
||||
onClick={() => scrollLeft(batch.batchId)}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className="image-group-horizontal"
|
||||
ref={(el) => (imageGroupRefs.current[batch.batchId] = el)}
|
||||
>
|
||||
{batch.documents.map((d, i) => {
|
||||
const hoverDate = moment(d.uploadedAt).format(
|
||||
"DD MMMM, YYYY"
|
||||
);
|
||||
const hoverTime = moment(d.uploadedAt).format(
|
||||
"hh:mm A"
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={d.id}
|
||||
className="image-card"
|
||||
onClick={() =>
|
||||
openModal(
|
||||
<ImagePop batch={batch} initialIndex={i} />
|
||||
)
|
||||
}
|
||||
onMouseEnter={() => setHoveredImage(d)}
|
||||
onMouseLeave={() => setHoveredImage(null)}
|
||||
>
|
||||
<div className="image-wrapper">
|
||||
<img src={d.url} alt={`Image ${i + 1}`} />
|
||||
</div>
|
||||
{hoveredImage === d && (
|
||||
<div className="image-hover-description">
|
||||
<p>
|
||||
<strong>Date:</strong> {hoverDate}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Time:</strong> {hoverTime}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Activity:</strong>{" "}
|
||||
{batch.activityName}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{hasArrows && (
|
||||
<button
|
||||
className="scroll-arrow right-arrow"
|
||||
onClick={() => scrollRight(batch.batchId)}
|
||||
>
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<p className="text-center text-muted mt-5">
|
||||
No images match the selected filters.
|
||||
</p>
|
||||
)}
|
||||
<div ref={loaderRef}>
|
||||
{isFetchingNextPage && hasNextPage && <p>Loading...</p>}
|
||||
{!hasNextPage && !isLoading && images.length > 0 && (
|
||||
<p className="text-muted">
|
||||
You've reached the end of the images.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`offcanvas offcanvas-end ${isFilterPanelOpen ? "show" : ""}`}
|
||||
ref={filterPanelRef}
|
||||
>
|
||||
<div className="offcanvas-header">
|
||||
<h5>Filters</h5>
|
||||
<button
|
||||
className="btn-close"
|
||||
onClick={() => setIsFilterPanelOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
<div className="filter-actions mt-auto mx-2">
|
||||
<button className="btn btn-secondary btn-xs" onClick={handleClear}>
|
||||
Clear All
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-xs"
|
||||
onClick={handleApplyFilters}
|
||||
>
|
||||
Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
<div className="offcanvas-body d-flex flex-column">
|
||||
{renderCategory("Date Range", [], "dateRange")}
|
||||
{renderCategory("Building", buildings, "building")}
|
||||
{renderCategory("Floor", floors, "floor")}
|
||||
{renderCategory("Work Area", workAreas, "workArea")}
|
||||
{renderCategory("Activity", activities, "activity")}
|
||||
{renderCategory("Uploaded By (User)", uploadedByUsers, "uploadedBy")}
|
||||
{renderCategory("Work Category", workCategories, "workCategory")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageGallery;
|
@ -1,111 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import "./ImagePop.css";
|
||||
import { useModal } from "./ModalContext";
|
||||
import moment from "moment";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
|
||||
const ImagePop = ({ batch, initialIndex = 0 }) => {
|
||||
const { closeModal } = useModal();
|
||||
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
||||
|
||||
// Effect to update currentIndex if the initialIndex prop changes
|
||||
useEffect(() => {
|
||||
setCurrentIndex(initialIndex);
|
||||
}, [initialIndex, batch]);
|
||||
|
||||
// If no batch or documents are provided, don't render
|
||||
if (!batch || !batch.documents || batch.documents.length === 0) return null;
|
||||
|
||||
// Get the current image document from the batch's documents array
|
||||
const image = batch.documents[currentIndex];
|
||||
|
||||
// Fallback if for some reason the image at the current index doesn't exist
|
||||
if (!image) return null;
|
||||
|
||||
// Format details for display from the individual image document
|
||||
const fullName = `${image.uploadedBy?.firstName || ""} ${
|
||||
image.uploadedBy?.lastName || ""
|
||||
}`.trim();
|
||||
const date = formatUTCToLocalTime(image.uploadedAt);
|
||||
|
||||
// Location and category details from the 'batch' object (as previously corrected)
|
||||
const buildingName = batch.buildingName;
|
||||
const floorName = batch.floorName;
|
||||
const workAreaName = batch.workAreaName;
|
||||
const activityName = batch.activityName;
|
||||
const batchComment = batch.comment;
|
||||
|
||||
// Handler for navigating to the previous image
|
||||
const handlePrev = () => {
|
||||
setCurrentIndex((prevIndex) => Math.max(0, prevIndex - 1));
|
||||
};
|
||||
|
||||
// Handler for navigating to the next image
|
||||
const handleNext = () => {
|
||||
setCurrentIndex((prevIndex) =>
|
||||
Math.min(batch.documents.length - 1, prevIndex + 1)
|
||||
);
|
||||
};
|
||||
|
||||
// Determine if previous/next buttons should be enabled/visible
|
||||
const hasPrev = currentIndex > 0;
|
||||
const hasNext = currentIndex < batch.documents.length - 1;
|
||||
|
||||
return (
|
||||
<div className="image-modal-overlay">
|
||||
<div className="image-modal-content">
|
||||
<i className="bx bx-x close-button" onClick={closeModal}></i>
|
||||
|
||||
{hasPrev && (
|
||||
<button className="nav-button prev-button" onClick={handlePrev}>
|
||||
<i className="bx bx-chevron-left"></i>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="image-container">
|
||||
<img src={image.url} alt="Preview" className="modal-image" />
|
||||
</div>
|
||||
|
||||
{hasNext && (
|
||||
<button className="nav-button next-button" onClick={handleNext}>
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="image-details">
|
||||
<div className="flex alig-items-center">
|
||||
{" "}
|
||||
<i className="bx bxs-user"></i>{" "}
|
||||
<span className="text-muted">Uploaded By : </span>{" "}
|
||||
<span className="text-secondary">{fullName}</span>
|
||||
</div>
|
||||
<div className="flex alig-items-center">
|
||||
{" "}
|
||||
<i className="bx bxs-calendar"></i>{" "}
|
||||
<span className="text-muted">Date : </span>{" "}
|
||||
<span className="text-secondary"> {date}</span>
|
||||
</div>
|
||||
<div className="flex alig-items-center">
|
||||
{" "}
|
||||
<i className="bx bx-map"></i>{" "}
|
||||
<span className="text-muted">Uploaded By : </span>{" "}
|
||||
<span className="text-secondary">
|
||||
{buildingName} <i className="bx bx-chevron-right"></i> {floorName}{" "}
|
||||
<i className="bx bx-chevron-right"></i>
|
||||
{workAreaName || "Unknown"}{" "}
|
||||
<i className="bx bx-chevron-right"></i> {activityName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex alig-items-center">
|
||||
{" "}
|
||||
<i className="bx bx-comment-dots"></i>{" "}
|
||||
<span className="text-muted">comment : </span>{" "}
|
||||
<span className="text-secondary">{batchComment}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImagePop;
|
@ -1,23 +0,0 @@
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
|
||||
const ModalContext = createContext();
|
||||
|
||||
export const ModalProvider1 = ({ children }) => {
|
||||
const [modalContent, setModalContent] = useState(null);
|
||||
|
||||
const openModal = (content) => setModalContent(content);
|
||||
const closeModal = () => setModalContent(null);
|
||||
|
||||
return (
|
||||
<ModalContext.Provider value={{ openModal, closeModal }}>
|
||||
{children}
|
||||
{modalContent && (
|
||||
<div className="global-modal-wrapper">
|
||||
{modalContent}
|
||||
</div>
|
||||
)}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
@ -6,6 +6,8 @@ import showToast from "../../services/toastService";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import "./page-auth.css";
|
||||
import { useSelector } from "react-redux";
|
||||
import Modal from "../../components/common/Modal";
|
||||
import { useModal } from "../../hooks/useAuth";
|
||||
|
||||
// --- Zod Schema for validation ---
|
||||
const ChangePasswordSchema = z
|
||||
@ -28,13 +30,15 @@ const ChangePasswordSchema = z
|
||||
path: ["confirmPassword"],
|
||||
});
|
||||
|
||||
const ChangePasswordPage = ({ onClose }) => {
|
||||
const ChangePasswordPage = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hideOldPass, setHideOldPass] = useState(true);
|
||||
const [hideNewPass, setHideNewPass] = useState(true);
|
||||
const [hideConfirmPass, setHideConfirmPass] = useState(true);
|
||||
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
|
||||
|
||||
const { isOpen, onClose } = useModal("ChangePassword");
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -64,25 +68,14 @@ const ChangePasswordPage = ({ onClose }) => {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal d-flex align-items-center justify-content-center show"
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||
>
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content p-10 rounded shadow bg-white position-relative">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
data-bs-dismiss="modal" aria-label="Close"
|
||||
onClick={onClose} // Use onClick to trigger onClose function
|
||||
style={{ position: "absolute", top: "15px", right: "15px" }} // Example positioning
|
||||
></button>
|
||||
const bodyContxt = (
|
||||
|
||||
|
||||
<div className="row">
|
||||
|
||||
|
||||
<h5 className="mb-2">Change Password</h5>
|
||||
<p className="mb-4" style={{ fontSize: "14px" }}>
|
||||
<p className="mb-4 text-black">
|
||||
Enter old and new password to update.
|
||||
</p>
|
||||
|
||||
@ -175,7 +168,7 @@ const ChangePasswordPage = ({ onClose }) => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-center pt-2 text-muted small">
|
||||
<div className="d-flex justify-content-center pt-2 text-muted small text-black">
|
||||
Your password must have at least 8 characters and include a lower
|
||||
case letter, an uppercase letter, a number, and a special
|
||||
character.
|
||||
@ -200,9 +193,11 @@ const ChangePasswordPage = ({ onClose }) => {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
);
|
||||
|
||||
return <Modal isOpen={isOpen} onClose={onClose} body={bodyContxt} />;
|
||||
};
|
||||
|
||||
export default ChangePasswordPage;
|
@ -22,7 +22,6 @@ import Inventory from "../pages/project/Inventory";
|
||||
import AttendancePage from "../pages/Activities/AttendancePage";
|
||||
import TaskPlannng from "../pages/Activities/TaskPlannng";
|
||||
import Reports from "../pages/reports/Reports";
|
||||
import ImageGallary from "../pages/Gallary/ImageGallary";
|
||||
import MasterPage from "../pages/master/MasterPage";
|
||||
import Support from "../pages/support/Support";
|
||||
import Documentation from "../pages/support/Documentation";
|
||||
|
@ -8,7 +8,14 @@ const localVariablesSlice = createSlice({
|
||||
attendance: {
|
||||
regularizationCount: 0,
|
||||
defaultDateRange: { startDate: null, endDate: null },
|
||||
SelectedOrg:null,
|
||||
SelectedOrg: "",
|
||||
},
|
||||
|
||||
// Modal for all simple pass Name
|
||||
|
||||
modals: {
|
||||
auth: { isOpen: false },
|
||||
organization: { isOpen: false },
|
||||
},
|
||||
projectId: null,
|
||||
reload: false,
|
||||
@ -30,7 +37,6 @@ const localVariablesSlice = createSlice({
|
||||
state.selectedMaster = action.payload;
|
||||
},
|
||||
|
||||
|
||||
// ─── ATTENDANCE ─────────────────────────
|
||||
updateRegularizationCount: (state, action) => {
|
||||
state.attendance.regularizationCount = action.payload;
|
||||
@ -38,11 +44,10 @@ const localVariablesSlice = createSlice({
|
||||
setDefaultDateRange: (state, action) => {
|
||||
state.attendance.defaultDateRange = action.payload;
|
||||
},
|
||||
setOrganization:(state,action)=>{
|
||||
setOrganization: (state, action) => {
|
||||
state.attendance.SelectedOrg = action.payload;
|
||||
},
|
||||
|
||||
|
||||
setProjectId: (state, action) => {
|
||||
localStorage.setItem("project", null);
|
||||
state.projectId = action.payload;
|
||||
@ -78,6 +83,19 @@ const localVariablesSlice = createSlice({
|
||||
closeAuthModal: (state, action) => {
|
||||
state.AuthModal.isOpen = false;
|
||||
},
|
||||
|
||||
openModal: (state, action) => {
|
||||
const { modalType, data } = action.payload;
|
||||
state.modals[modalType] = { isOpen: true, ...data };
|
||||
},
|
||||
closeModal: (state, action) => {
|
||||
const { modalType } = action.payload;
|
||||
state.modals[modalType] = { ...state.modals[modalType], isOpen: false };
|
||||
},
|
||||
toggleModal: (state, action) => {
|
||||
const { modalType } = action.payload;
|
||||
state.modals[modalType].isOpen = !state.modals[modalType].isOpen;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -91,6 +109,7 @@ export const {
|
||||
closeOrgModal,
|
||||
toggleOrgModal,
|
||||
openAuthModal,
|
||||
closeAuthModal,setOrganization
|
||||
closeAuthModal,
|
||||
setOrganization,openModal, closeModal, toggleModal
|
||||
} = localVariablesSlice.actions;
|
||||
export default localVariablesSlice.reducer;
|
||||
|
Loading…
x
Reference in New Issue
Block a user