Merge branch 'Document_Manag' of https://git.marcoaiot.com/admin/marco.pms.web into UI_Changes_PMS
This commit is contained in:
commit
ea022d37c8
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useEmployeeAttendacesLog } from "../../hooks/useAttendance";
|
import { useEmployeeAttendacesLog } from "../../hooks/useAttendance";
|
||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { THRESH_HOLD } from "../../utils/constants";
|
import { THRESH_HOLD } from "../../utils/constants";
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ const AttendLogs = ({ Id }) => {
|
|||||||
<p>
|
<p>
|
||||||
Attendance logs for{" "}
|
Attendance logs for{" "}
|
||||||
{logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}{" "}
|
{logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}{" "}
|
||||||
on {logs[0]?.activityTime.slice(0, 10)}{" "}
|
on {formatUTCToLocalTime(logs[0]?.activityTime)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -156,7 +156,7 @@ const AttendLogs = ({ Id }) => {
|
|||||||
.sort((a, b) => b.id - a.id)
|
.sort((a, b) => b.id - a.id)
|
||||||
.map((log, index) => (
|
.map((log, index) => (
|
||||||
<tr key={index}>
|
<tr key={index}>
|
||||||
<td>{log.activityTime.slice(0, 10)}</td>
|
<td>{formatUTCToLocalTime(log.activityTime)}</td>
|
||||||
<td>{convertShortTime(log.activityTime)}</td>
|
<td>{convertShortTime(log.activityTime)}</td>
|
||||||
<td>
|
<td>
|
||||||
{whichActivityPerform(log.activity, log.activityTime)}
|
{whichActivityPerform(log.activity, log.activityTime)}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
import { convertShortTime } from "../../utils/dateUtils";
|
import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -10,7 +10,7 @@ import { useAttendance } from "../../hooks/useAttendance";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -21,7 +21,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
// const selectedProject = useSelector(
|
// const selectedProject = useSelector(
|
||||||
// (store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
// );
|
// );
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const {
|
const {
|
||||||
attendance,
|
attendance,
|
||||||
loading: attLoading,
|
loading: attLoading,
|
||||||
@ -116,7 +116,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
|||||||
<>
|
<>
|
||||||
<div className="table-responsive text-nowrap h-100" >
|
<div className="table-responsive text-nowrap h-100" >
|
||||||
<div className="d-flex text-start align-items-center py-2">
|
<div className="d-flex text-start align-items-center py-2">
|
||||||
<strong>Date : {todayDate.toLocaleDateString("en-GB")}</strong>
|
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
|
||||||
<div className="form-check form-switch text-start m-0 ms-5">
|
<div className="form-check form-switch text-start m-0 ms-5">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -6,11 +6,12 @@ import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
|||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||||
import DateRangePicker from "../common/DateRangePicker";
|
import DateRangePicker from "../common/DateRangePicker";
|
||||||
import { clearCacheKey, getCachedData, useSelectedproject } from "../../slices/apiDataManager";
|
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
import AttendanceRepository from "../../repositories/AttendanceRepository";
|
||||||
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
import { useAttendancesLogs } from "../../hooks/useAttendance";
|
||||||
import { queryClient } from "../../layouts/AuthLayout";
|
import { queryClient } from "../../layouts/AuthLayout";
|
||||||
|
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||||
|
|
||||||
const usePagination = (data, itemsPerPage) => {
|
const usePagination = (data, itemsPerPage) => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
@ -37,7 +38,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
// const selectedProject = useSelector(
|
// const selectedProject = useSelector(
|
||||||
// (store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
// );
|
// );
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -353,7 +354,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
|||||||
<span className="text-secondary">No Pending Record Available !</span>
|
<span className="text-secondary">No Pending Record Available !</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{filteredSearchData.length > 10 && (
|
{filteredSearchData.length > ITEMS_PER_PAGE && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
||||||
|
@ -9,7 +9,7 @@ import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
|
|||||||
import showToast from "../../services/toastService";
|
import showToast from "../../services/toastService";
|
||||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const createSchema = (modeldata) => {
|
const createSchema = (modeldata) => {
|
||||||
return z
|
return z
|
||||||
@ -43,9 +43,8 @@ const createSchema = (modeldata) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const CheckCheckOutmodel = ({ modeldata, closeModal, handleSubmitForm }) => {
|
const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
||||||
// const projectId = useSelector((store) => store.localVariables.projectId);
|
const projectId = useSelectedProject();
|
||||||
const projectId = useSelectedproject();
|
|
||||||
const { mutate: MarkAttendance } = useMarkAttendance();
|
const { mutate: MarkAttendance } = useMarkAttendance();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const coords = usePositionTracker();
|
const coords = usePositionTracker();
|
||||||
@ -174,7 +173,7 @@ const CheckCheckOutmodel = ({ modeldata, closeModal, handleSubmitForm }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CheckCheckOutmodel;
|
export default CheckInCheckOut;
|
||||||
|
|
||||||
const schemaReg = z.object({
|
const schemaReg = z.object({
|
||||||
description: z.string().min(1, { message: "please give reason!" }),
|
description: z.string().min(1, { message: "please give reason!" }),
|
||||||
|
@ -15,7 +15,7 @@ import {useDispatch, useSelector} from "react-redux";
|
|||||||
import {useProfile} from "../../hooks/useProfile";
|
import {useProfile} from "../../hooks/useProfile";
|
||||||
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
|
||||||
import InfraTable from "../Project/Infrastructure/InfraTable";
|
import InfraTable from "../Project/Infrastructure/InfraTable";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import Loader from "../common/Loader";
|
import Loader from "../common/Loader";
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ const InfraPlanning = () =>
|
|||||||
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
const {profile: LoggedUser, refetch : fetchData} = useProfile()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
|
||||||
|
|
||||||
|
|
||||||
@ -35,15 +35,15 @@ const InfraPlanning = () =>
|
|||||||
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
|
||||||
|
|
||||||
|
|
||||||
useEffect( () =>
|
// useEffect( () =>
|
||||||
{
|
// {
|
||||||
if (reloadedData)
|
// if (reloadedData)
|
||||||
{
|
// {
|
||||||
refetch()
|
// refetch()
|
||||||
dispatch( refreshData( false ) )
|
// dispatch( refreshData( false ) )
|
||||||
}
|
// }
|
||||||
|
|
||||||
},[reloadedData])
|
// },[reloadedData])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||||
|
@ -7,13 +7,13 @@ import { useRegularizationRequests } from "../../hooks/useAttendance";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import usePagination from "../../hooks/usePagination";
|
import usePagination from "../../hooks/usePagination";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { cacheData, clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
|
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
const Regularization = ({ handleRequest, searchTerm }) => {
|
const Regularization = ({ handleRequest, searchTerm }) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const [regularizesList, setregularizedList] = useState([]);
|
const [regularizesList, setregularizedList] = useState([]);
|
||||||
const { regularizes, loading, error, refetch } =
|
const { regularizes, loading, error, refetch } =
|
||||||
useRegularizationRequests(selectedProject);
|
useRegularizationRequests(selectedProject);
|
||||||
|
@ -4,7 +4,7 @@ import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
||||||
import {cacheData, getCachedData, useSelectedproject} from '../../slices/apiDataManager';
|
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import { useMarkAttendance } from '../../hooks/useAttendance';
|
import { useMarkAttendance } from '../../hooks/useAttendance';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
@ -18,7 +18,7 @@ const {mutate:MarkAttendance,isPending} = useMarkAttendance()
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||||
const projectId = useSelectedproject();
|
const projectId = useSelectedProject();
|
||||||
const {latitude,longitude} = usePositionTracker();
|
const {latitude,longitude} = usePositionTracker();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
@ -198,23 +198,14 @@ const ManageBucket = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{deleteBucket && (
|
{deleteBucket && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade ${deleteBucket ? "show" : ""}`}
|
isOpen={!!deleteBucket}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header="Delete Bucket"
|
||||||
style={{
|
message="Are you sure you want to delete this bucket?"
|
||||||
display: deleteBucket ? "block" : "none",
|
onSubmit={handleDeleteContact}
|
||||||
backgroundColor: deleteBucket ? "rgba(0,0,0,0.5)" : "transparent",
|
onClose={() => setDeleteBucket(null)}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
<ConfirmModal
|
|
||||||
type={"delete"}
|
|
||||||
header={"Delete Bucket"}
|
|
||||||
message={"Are you sure you want to delete this bucket?"}
|
|
||||||
onSubmit={handleDeleteContact}
|
|
||||||
onClose={() => setDeleteBucket(null)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container m-0 p-0" style={{ minHeight: "00px" }}>
|
<div className="container m-0 p-0" style={{ minHeight: "00px" }}>
|
||||||
@ -237,8 +228,9 @@ const ManageBucket = () => {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
className={`bx bx-refresh cursor-pointer fs-4 ${loading ? "spin" : ""
|
className={`bx bx-refresh cursor-pointer fs-4 ${
|
||||||
}`}
|
loading ? "spin" : ""
|
||||||
|
}`}
|
||||||
title="Refresh"
|
title="Refresh"
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
/>
|
/>
|
||||||
@ -247,8 +239,9 @@ const ManageBucket = () => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn btn-sm btn-primary ms-auto ${action_bucket ? "d-none" : ""
|
className={`btn btn-sm btn-primary ms-auto ${
|
||||||
}`}
|
action_bucket ? "d-none" : ""
|
||||||
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAction_bucket(true);
|
setAction_bucket(true);
|
||||||
select_bucket(null);
|
select_bucket(null);
|
||||||
@ -285,16 +278,18 @@ const ManageBucket = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && buckets.length > 0 && sortedBucktesList.length === 0 && (
|
{!loading &&
|
||||||
<div className="col-12">
|
buckets.length > 0 &&
|
||||||
<div
|
sortedBucktesList.length === 0 && (
|
||||||
className="d-flex justify-content-center align-items-center py-5 w-100"
|
<div className="col-12">
|
||||||
style={{ marginLeft: "250px" }}
|
<div
|
||||||
>
|
className="d-flex justify-content-center align-items-center py-5 w-100"
|
||||||
No matching buckets found.
|
style={{ marginLeft: "250px" }}
|
||||||
|
>
|
||||||
|
No matching buckets found.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
{!loading &&
|
{!loading &&
|
||||||
sortedBucktesList.map((bucket) => (
|
sortedBucktesList.map((bucket) => (
|
||||||
<div className="col" key={bucket.id}>
|
<div className="col" key={bucket.id}>
|
||||||
@ -305,29 +300,29 @@ const ManageBucket = () => {
|
|||||||
{(DirManager ||
|
{(DirManager ||
|
||||||
DirAdmin ||
|
DirAdmin ||
|
||||||
bucket?.createdBy?.id ===
|
bucket?.createdBy?.id ===
|
||||||
profile?.employeeInfo?.id) && (
|
profile?.employeeInfo?.id) && (
|
||||||
<div className="d-flex 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
|
const initialSelectedEmployees = employeesList
|
||||||
.filter((emp) =>
|
.filter((emp) =>
|
||||||
bucket.employeeIds?.includes(
|
bucket.employeeIds?.includes(
|
||||||
emp.employeeId
|
emp.employeeId
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.map((emp) => ({ ...emp, isActive: true }));
|
)
|
||||||
setSelectEmployee(initialSelectedEmployees);
|
.map((emp) => ({ ...emp, isActive: true }));
|
||||||
}}
|
setSelectEmployee(initialSelectedEmployees);
|
||||||
></i>
|
}}
|
||||||
<i
|
></i>
|
||||||
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
|
<i
|
||||||
onClick={() => setDeleteBucket(bucket?.id)}
|
className="bx bx-trash bx-sm text-danger cursor-pointer ms-0"
|
||||||
></i>
|
onClick={() => setDeleteBucket(bucket?.id)}
|
||||||
</div>
|
></i>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
</h6>
|
</h6>
|
||||||
<h6 className="card-subtitle mb-2 text-muted text-start">
|
<h6 className="card-subtitle mb-2 text-muted text-start">
|
||||||
Contacts:{" "}
|
Contacts:{" "}
|
||||||
|
@ -33,7 +33,10 @@ const NoteCardDirectoryEditable = ({
|
|||||||
note: editorValue,
|
note: editorValue,
|
||||||
contactId,
|
contactId,
|
||||||
};
|
};
|
||||||
const response = await DirectoryRepository.UpdateNote(noteItem.id, payload);
|
const response = await DirectoryRepository.UpdateNote(
|
||||||
|
noteItem.id,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
const cachedContactProfile = getCachedData("Contact Profile");
|
const cachedContactProfile = getCachedData("Contact Profile");
|
||||||
if (cachedContactProfile?.contactId === contactId) {
|
if (cachedContactProfile?.contactId === contactId) {
|
||||||
@ -75,9 +78,9 @@ const NoteCardDirectoryEditable = ({
|
|||||||
|
|
||||||
const contactProfile = (contactId) => {
|
const contactProfile = (contactId) => {
|
||||||
DirectoryRepository.GetContactProfile(contactId).then((res) => {
|
DirectoryRepository.GetContactProfile(contactId).then((res) => {
|
||||||
setOpen_contact(res?.data);
|
setOpen_contact(res?.data);
|
||||||
setIsOpenModalNote(true);
|
setIsOpenModalNote(true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestore = async () => {
|
const handleRestore = async () => {
|
||||||
@ -95,7 +98,6 @@ const NoteCardDirectoryEditable = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{isOpenModalNote && (
|
{isOpenModalNote && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={isOpenModalNote}
|
isOpen={isOpenModalNote}
|
||||||
@ -125,7 +127,6 @@ const NoteCardDirectoryEditable = ({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="d-flex justify-content-between align-items-center mb-1">
|
<div className="d-flex justify-content-between align-items-center mb-1">
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
|
|
||||||
<Avatar
|
<Avatar
|
||||||
size="xxs"
|
size="xxs"
|
||||||
firstName={noteItem?.createdBy?.firstName}
|
firstName={noteItem?.createdBy?.firstName}
|
||||||
@ -133,30 +134,36 @@ const NoteCardDirectoryEditable = ({
|
|||||||
className="m-0"
|
className="m-0"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="d-flex ms-0 align-middle cursor-pointer" onClick={() =>contactProfile(noteItem.contactId)}>
|
<div
|
||||||
|
className="d-flex ms-0 align-middle cursor-pointer"
|
||||||
|
onClick={() => contactProfile(noteItem.contactId)}
|
||||||
|
>
|
||||||
<span>
|
<span>
|
||||||
<span className="fw-bold "> {noteItem?.contactName} </span> <span className="text-muted font-weight-normal">
|
<span className="fw-bold "> {noteItem?.contactName} </span>{" "}
|
||||||
|
<span className="text-muted font-weight-normal">
|
||||||
({noteItem?.organizationName})
|
({noteItem?.organizationName})
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="d-flex ms-0 align-middle">
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="d-flex ms-0 align-middle"></div>
|
||||||
<div className="d-flex ms-0 mt-2">
|
<div className="d-flex ms-0 mt-2">
|
||||||
<span className="text-muted">
|
<span className="text-muted">
|
||||||
by <span className="fw-bold "> {noteItem?.createdBy?.firstName} {noteItem?.createdBy?.lastName} </span>
|
by{" "}
|
||||||
<span className="text-muted">
|
<span className="fw-bold ">
|
||||||
on {moment
|
{" "}
|
||||||
|
{noteItem?.createdBy?.firstName}{" "}
|
||||||
|
{noteItem?.createdBy?.lastName}{" "}
|
||||||
|
</span>
|
||||||
|
{" "}
|
||||||
|
<span className="text-muted">
|
||||||
|
on{" "}
|
||||||
|
{moment
|
||||||
.utc(noteItem?.createdAt)
|
.utc(noteItem?.createdAt)
|
||||||
.add(5, "hours")
|
.add(5, "hours")
|
||||||
.add(30, "minutes")
|
.add(30, "minutes")
|
||||||
.format("DD MMMM, YYYY [at] hh:mm A")}
|
.format("DD MMMM, YYYY [at] hh:mm A")}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -228,26 +235,16 @@ const NoteCardDirectoryEditable = ({
|
|||||||
|
|
||||||
{/* Delete Confirm Modal */}
|
{/* Delete Confirm Modal */}
|
||||||
{isDeleteModalOpen && (
|
{isDeleteModalOpen && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade ${isDeleteModalOpen ? "show" : ""}`}
|
isOpen={isDeleteModalOpen}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header="Delete Note"
|
||||||
style={{
|
message="Are you sure you want to delete this note?"
|
||||||
display: isDeleteModalOpen ? "block" : "none",
|
onSubmit={suspendEmployee}
|
||||||
backgroundColor: "rgba(0,0,0,0.5)",
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
}}
|
loading={isDeleting}
|
||||||
aria-hidden="false"
|
paramData={noteItem}
|
||||||
>
|
/>
|
||||||
<ConfirmModal
|
|
||||||
type={"delete"}
|
|
||||||
header={"Delete Note"}
|
|
||||||
message={"Are you sure you want to delete this note?"}
|
|
||||||
onSubmit={suspendEmployee}
|
|
||||||
onClose={() => setIsDeleteModalOpen(false)}
|
|
||||||
loading={isDeleting}
|
|
||||||
paramData={noteItem}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
import React, { useEffect, useState, useMemo } from "react";
|
import React, { useEffect, useState, useMemo } from "react";
|
||||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
||||||
import NoteCardDirectoryEditable from "./NoteCardDirectoryEditable";
|
import NoteCardDirectoryEditable from "./NoteCardDirectoryEditable";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
|
const NotesCardViewDirectory = ({
|
||||||
|
notes,
|
||||||
|
setNotesForFilter,
|
||||||
|
searchText,
|
||||||
|
filterAppliedNotes,
|
||||||
|
}) => {
|
||||||
|
const projectId = useSelectedProject();
|
||||||
|
|
||||||
const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAppliedNotes }) => {
|
|
||||||
const [allNotes, setAllNotes] = useState([]);
|
const [allNotes, setAllNotes] = useState([]);
|
||||||
const [filteredNotes, setFilteredNotes] = useState([]);
|
const [filteredNotes, setFilteredNotes] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@ -13,13 +21,15 @@ const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAp
|
|||||||
const pageSize = 20;
|
const pageSize = 20;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchNotes();
|
if (projectId) {
|
||||||
}, []);
|
fetchNotes(projectId);
|
||||||
|
}
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
const fetchNotes = async () => {
|
const fetchNotes = async (projId) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await DirectoryRepository.GetNotes(1000, 1);
|
const response = await DirectoryRepository.GetNotes(1000, 1, projId); // ✅ pass projectId
|
||||||
const fetchedNotes = response.data?.data || [];
|
const fetchedNotes = response.data?.data || [];
|
||||||
setAllNotes(fetchedNotes);
|
setAllNotes(fetchedNotes);
|
||||||
setNotesForFilter(fetchedNotes)
|
setNotesForFilter(fetchedNotes)
|
||||||
@ -122,7 +132,7 @@ const NotesCardViewDirectory = ({ notes, setNotesForFilter, searchText, filterAp
|
|||||||
prevNotes.map((n) => (n.id === updatedNote.id ? updatedNote : n))
|
prevNotes.map((n) => (n.id === updatedNote.id ? updatedNote : n))
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onNoteDelete={() => fetchNotes()}
|
onNoteDelete={() => fetchNotes(projectId)} // ✅ reload with projectId
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
88
src/components/Documents/DocumentDetailsSkeleton .jsx
Normal file
88
src/components/Documents/DocumentDetailsSkeleton .jsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import React from "react";
|
||||||
|
import VersionListSkeleton from "./VersionListSkeleton";
|
||||||
|
|
||||||
|
const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton mb-2 ${className}`}
|
||||||
|
style={{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const DocumentDetailsSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<div className="p-1">
|
||||||
|
<p className="fw-bold fs-6">Document Details</p>
|
||||||
|
|
||||||
|
{/* Row 1 */}
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="130px" />
|
||||||
|
<SkeletonLine width="60%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="130px" />
|
||||||
|
<SkeletonLine width="50%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 2 */}
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="130px" />
|
||||||
|
<SkeletonLine width="40%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="130px" />
|
||||||
|
<SkeletonLine width="60%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 3 */}
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="130px" />
|
||||||
|
<SkeletonLine width="40%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="130px" />
|
||||||
|
<SkeletonLine width="50%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* Row 6 - Description */}
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="d-flex">
|
||||||
|
|
||||||
|
<SkeletonLine width="100%" height={40} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Version list skeleton */}
|
||||||
|
<div className="row text-start py-2">
|
||||||
|
<VersionListSkeleton items={2} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentDetailsSkeleton;
|
205
src/components/Documents/DocumentFilterPanel.jsx
Normal file
205
src/components/Documents/DocumentFilterPanel.jsx
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { useDocumentFilterEntities } from "../../hooks/useDocument";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import {
|
||||||
|
DocumentFilterDefaultValues,
|
||||||
|
DocumentFilterSchema,
|
||||||
|
} from "./DocumentSchema";
|
||||||
|
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
|
import SelectMultiple from "../common/SelectMultiple";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
const DocumentFilterPanel = ({ entityTypeId, onApply }) => {
|
||||||
|
const [resetKey, setResetKey] = useState(0);
|
||||||
|
|
||||||
|
const { data, isError, isLoading, error } =
|
||||||
|
useDocumentFilterEntities(entityTypeId);
|
||||||
|
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(DocumentFilterSchema),
|
||||||
|
defaultValues: DocumentFilterDefaultValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleSubmit, reset, setValue, watch } = methods;
|
||||||
|
|
||||||
|
// Watch values from form
|
||||||
|
const isUploadedAt = watch("isUploadedAt");
|
||||||
|
const isVerified = watch("isVerified");
|
||||||
|
|
||||||
|
// Close the offcanvas (bootstrap specific)
|
||||||
|
const closePanel = () => {
|
||||||
|
document.querySelector(".offcanvas.show .btn-close")?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (values) => {
|
||||||
|
onApply({
|
||||||
|
...values,
|
||||||
|
startDate: values.startDate
|
||||||
|
? moment.utc(values.startDate, "DD-MM-YYYY").toISOString()
|
||||||
|
: null,
|
||||||
|
endDate: values.endDate
|
||||||
|
? moment.utc(values.endDate, "DD-MM-YYYY").toISOString()
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClear = () => {
|
||||||
|
reset(DocumentFilterDefaultValues);
|
||||||
|
setResetKey((prev) => prev + 1);
|
||||||
|
onApply(DocumentFilterDefaultValues);
|
||||||
|
closePanel();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) return <div>Loading...</div>;
|
||||||
|
if (isError)
|
||||||
|
return <div>Error: {error?.message || "Something went wrong!"}</div>;
|
||||||
|
|
||||||
|
const {
|
||||||
|
uploadedBy = [],
|
||||||
|
documentCategory = [],
|
||||||
|
documentType = [],
|
||||||
|
documentTag = [],
|
||||||
|
} = data?.data || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{/* Date Range Section */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<div className="text-start d-flex align-items-center my-1">
|
||||||
|
<label className="form-label me-2 my-0">Choose Date:</label>
|
||||||
|
<div className="d-inline-flex border rounded-pill overflow-hidden shadow-none">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
|
isUploadedAt ? "active btn-secondary text-white" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => setValue("isUploadedAt", true)}
|
||||||
|
>
|
||||||
|
Uploaded On
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn px-2 py-1 rounded-0 text-tiny ${
|
||||||
|
!isUploadedAt ? "active btn-secondary text-white" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => setValue("isUploadedAt", false)}
|
||||||
|
>
|
||||||
|
Updated On
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DateRangePicker1
|
||||||
|
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||||
|
startField="startDate"
|
||||||
|
endField="endDate"
|
||||||
|
defaultRange={false}
|
||||||
|
resetSignal={resetKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dropdown Filters */}
|
||||||
|
<div className="row g-2 text-start">
|
||||||
|
<SelectMultiple
|
||||||
|
name="uploadedByIds"
|
||||||
|
label="Uploaded By:"
|
||||||
|
options={uploadedBy}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
<SelectMultiple
|
||||||
|
name="documentCategoryIds"
|
||||||
|
label="Document Category:"
|
||||||
|
options={documentCategory}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
<SelectMultiple
|
||||||
|
name="documentTypeIds"
|
||||||
|
label="Document Type:"
|
||||||
|
options={documentType}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
<SelectMultiple
|
||||||
|
name="documentTagIds"
|
||||||
|
label="Tags:"
|
||||||
|
options={documentTag}
|
||||||
|
labelKey="name"
|
||||||
|
valueKey="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status Filter */}
|
||||||
|
<div className="text-start my-2">
|
||||||
|
<label className="form-label d-block mb-2">Choose Status:</label>
|
||||||
|
<div className="d-flex gap-4">
|
||||||
|
<label className="switch switch-sm">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="switch-input"
|
||||||
|
name="isVerified"
|
||||||
|
checked={isVerified === null}
|
||||||
|
onChange={() => setValue("isVerified", null)}
|
||||||
|
/>
|
||||||
|
<span className="switch-toggle-slider">
|
||||||
|
<span className="switch-on"></span>
|
||||||
|
<span className="switch-off"></span>
|
||||||
|
</span>
|
||||||
|
<span className="switch-label">All</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="switch switch-sm">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="switch-input"
|
||||||
|
name="isVerified"
|
||||||
|
checked={isVerified === true}
|
||||||
|
onChange={() => setValue("isVerified", true)}
|
||||||
|
/>
|
||||||
|
<span className="switch-toggle-slider">
|
||||||
|
<span className="switch-on"></span>
|
||||||
|
<span className="switch-off"></span>
|
||||||
|
</span>
|
||||||
|
<span className="switch-label">Verified</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="switch switch-sm">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
className="switch-input"
|
||||||
|
name="isVerified"
|
||||||
|
checked={isVerified === false}
|
||||||
|
onChange={() => setValue("isVerified", false)}
|
||||||
|
/>
|
||||||
|
<span className="switch-toggle-slider">
|
||||||
|
<span className="switch-on"></span>
|
||||||
|
<span className="switch-off"></span>
|
||||||
|
</span>
|
||||||
|
<span className="switch-label">Rejected</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Buttons */}
|
||||||
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary btn-xs"
|
||||||
|
onClick={onClear}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-primary btn-xs">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentFilterPanel;
|
132
src/components/Documents/DocumentSchema.js
Normal file
132
src/components/Documents/DocumentSchema.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { normalizeAllowedContentTypes } from "../../utils/appUtils";
|
||||||
|
|
||||||
|
export const AttachmentSchema = (allowedContentType, maxSizeAllowedInMB) => {
|
||||||
|
const allowedTypes = normalizeAllowedContentTypes(allowedContentType);
|
||||||
|
|
||||||
|
return z.object({
|
||||||
|
fileName: z.string().min(1, { message: "File name is required" }),
|
||||||
|
base64Data: z.string().min(1, { message: "File data is required" }),
|
||||||
|
contentType: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "MIME type is required" })
|
||||||
|
.refine(
|
||||||
|
(val) => (allowedTypes.length ? allowedTypes.includes(val) : true),
|
||||||
|
{
|
||||||
|
message: `File type must be one of: ${allowedTypes.join(", ")}`,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
fileSize: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.nonnegative("fileSize must be ≥ 0")
|
||||||
|
.max(
|
||||||
|
(maxSizeAllowedInMB ?? 25) * 1024 * 1024,
|
||||||
|
`fileSize must be ≤ ${maxSizeAllowedInMB ?? 25}MB`
|
||||||
|
),
|
||||||
|
description: z.string().optional().default(""),
|
||||||
|
isActive: z.boolean(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TagSchema = z.object({
|
||||||
|
name: z.string().min(1, "Tag name is required"),
|
||||||
|
isActive: z.boolean().default(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DocumentPayloadSchema = (docConfig = {}) => {
|
||||||
|
const {
|
||||||
|
isMandatory,
|
||||||
|
regexExpression,
|
||||||
|
allowedContentType,
|
||||||
|
maxSizeAllowedInMB,
|
||||||
|
isUpdateForm,
|
||||||
|
} = docConfig;
|
||||||
|
|
||||||
|
let documentIdSchema = z.string();
|
||||||
|
|
||||||
|
if (isMandatory) {
|
||||||
|
documentIdSchema = documentIdSchema.min(1, {
|
||||||
|
message: "DocumentId is required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regexExpression) {
|
||||||
|
documentIdSchema = documentIdSchema.regex(
|
||||||
|
new RegExp(regexExpression),
|
||||||
|
"Invalid DocumentId format"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base attachment schema
|
||||||
|
let attachmentSchema = AttachmentSchema(
|
||||||
|
allowedContentType,
|
||||||
|
maxSizeAllowedInMB
|
||||||
|
).nullable();
|
||||||
|
|
||||||
|
// If not update form, require attachment
|
||||||
|
if (!isUpdateForm) {
|
||||||
|
attachmentSchema = attachmentSchema.refine((val) => val !== null, {
|
||||||
|
message: "Attachment is required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return z.object({
|
||||||
|
name: z.string().min(1, "Name is required"),
|
||||||
|
documentId: documentIdSchema,
|
||||||
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
|
documentTypeId: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Please Select Document Type" }),
|
||||||
|
documentCategoryId: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: "Please Select Document Category" }),
|
||||||
|
attachment: attachmentSchema,
|
||||||
|
tags: z.array(TagSchema).optional().default([]),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const defaultDocumentValues = {
|
||||||
|
name: "",
|
||||||
|
documentId: "",
|
||||||
|
description: "",
|
||||||
|
// entityId: "",
|
||||||
|
documentTypeId: "",
|
||||||
|
documentCategoryId: "",
|
||||||
|
// attachment: {
|
||||||
|
// fileName: "",
|
||||||
|
// base64Data: "",
|
||||||
|
// contentType: "",
|
||||||
|
// fileSize: 0,
|
||||||
|
// description: "",
|
||||||
|
// isActive: true,
|
||||||
|
// },
|
||||||
|
attachment:null,
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------Filter-------------------------
|
||||||
|
|
||||||
|
export const DocumentFilterSchema = z.object({
|
||||||
|
uploadedByIds: z.array(z.string()).default([]),
|
||||||
|
documentCategoryIds: z.array(z.string()).default([]),
|
||||||
|
documentTypeIds: z.array(z.string()).default([]),
|
||||||
|
documentTagIds: z.array(z.string()).default([]),
|
||||||
|
isUploadedAt: z.boolean().default(true),
|
||||||
|
isVerified: z.boolean().nullable().optional(),
|
||||||
|
startDate: z.string().nullable().optional(),
|
||||||
|
endDate: z.string().nullable().optional(),
|
||||||
|
});
|
||||||
|
export const DocumentFilterDefaultValues = {
|
||||||
|
uploadedByIds: [],
|
||||||
|
documentCategoryIds: [],
|
||||||
|
documentTypeIds: [],
|
||||||
|
documentTagIds: [],
|
||||||
|
isUploadedAt: true,
|
||||||
|
isVerified: null,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
};
|
||||||
|
|
70
src/components/Documents/DocumentSkeleton.jsx
Normal file
70
src/components/Documents/DocumentSkeleton.jsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const SkeletonCell = ({
|
||||||
|
width = "100%",
|
||||||
|
height = 20,
|
||||||
|
className = "",
|
||||||
|
style = {},
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton ${className}`}
|
||||||
|
style={{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
borderRadius: 4,
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DocumentTableSkeleton = ({ rows = 5 }) => {
|
||||||
|
return (
|
||||||
|
|
||||||
|
<table className="card-body table border-top dataTable no-footer dtr-column text-nowrap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="text-start">Name</th>
|
||||||
|
<th className="text-start">Document Type</th>
|
||||||
|
<th className="text-start">Uploaded By</th>
|
||||||
|
<th className="text-center">Uploaded on</th>
|
||||||
|
<th className="text-center">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{[...Array(rows)].map((_, idx) => (
|
||||||
|
<tr key={idx} className={idx % 2 === 0 ? "odd" : "even"}>
|
||||||
|
{/* Name */}
|
||||||
|
<td className="text-start">
|
||||||
|
<SkeletonCell width="120px" height={16} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Document Type */}
|
||||||
|
<td className="text-start">
|
||||||
|
<SkeletonCell width="100px" height={16} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Uploaded By (Avatar + Name) */}
|
||||||
|
<td className="text-start">
|
||||||
|
<div className="d-flex align-items-center gap-2">
|
||||||
|
<SkeletonCell width="30px" height={30} className="rounded-circle" />
|
||||||
|
<SkeletonCell width="80px" height={16} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Uploaded on */}
|
||||||
|
<td className="text-center">
|
||||||
|
<SkeletonCell width="80px" height={16} />
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
<td className="text-center">
|
||||||
|
<SkeletonCell width="70px" height={20} className="rounded" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
170
src/components/Documents/DocumentVersionList.jsx
Normal file
170
src/components/Documents/DocumentVersionList.jsx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import React from "react";
|
||||||
|
import VersionListSkeleton from "./VersionListSkeleton";
|
||||||
|
import { getDocuementsStatus } from "./Documents";
|
||||||
|
import Avatar from "../common/Avatar";
|
||||||
|
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import { DOWNLOAD_DOCUMENT, VERIFY_DOCUMENT } from "../../utils/constants";
|
||||||
|
import { FileIcon } from "../../utils/FileIcon";
|
||||||
|
|
||||||
|
const DocumentVersionList = ({
|
||||||
|
versionLoding,
|
||||||
|
versionList,
|
||||||
|
isPending,
|
||||||
|
setOpenDocument,
|
||||||
|
VerifyDocument,
|
||||||
|
}) => {
|
||||||
|
const canVerifyDocument = useHasUserPermission(VERIFY_DOCUMENT);
|
||||||
|
const canDownloadDocument = useHasUserPermission(DOWNLOAD_DOCUMENT);
|
||||||
|
const handleOpenDocument = () => {
|
||||||
|
if (canDownloadDocument) {
|
||||||
|
setOpenDocument(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const contentTypeIcons = {
|
||||||
|
"application/pdf": "fa-solid fa-file-pdf text-primary",
|
||||||
|
"application/msword": "fa-solid fa-file-word text-primary",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||||
|
"fa-solid fa-file-word text-primary",
|
||||||
|
"application/vnd.ms-excel": "fa-solid fa-file-excel text-success",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
||||||
|
"fa-solid fa-file-excel text-primary",
|
||||||
|
"application/vnd.ms-powerpoint": "fa-solid fa-file-powerpoint text-primary",
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||||
|
"fa-solid fa-file-powerpoint text-primary",
|
||||||
|
"image/jpg": "fa-solid fa-file-image text-primary",
|
||||||
|
"image/jpeg": "fa-solid fa-file-image text-primary",
|
||||||
|
"image/png": "fa-solid fa-file-image text-primary",
|
||||||
|
"image/gif": "fa-solid fa-file-image text-primary",
|
||||||
|
"text/plain": "fa-solid fa-file-lines text-primary",
|
||||||
|
"text/csv": "fa-solid fa-file-csv text-primary",
|
||||||
|
"application/json": "fa-solid fa-file-code text-primary",
|
||||||
|
default: "fa-solid fa-file text-primary",
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIcon = (fileName = "") => {
|
||||||
|
const ext = fileName.split(".").pop().toLowerCase();
|
||||||
|
return contentTypeIcons[ext] || contentTypeIcons.default;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedVersions = versionList?.data
|
||||||
|
? [...versionList.data].sort((a, b) => b.version - a.version)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (versionLoding) {
|
||||||
|
return <VersionListSkeleton items={2} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sortedVersions.length) {
|
||||||
|
return <p className="text-muted">No documents available.</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestDoc = sortedVersions[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="accordion" id="docAccordion">
|
||||||
|
<div className="accordion-item shadow-none">
|
||||||
|
<h2 className="accordion-header" id="headingDoc">
|
||||||
|
<button
|
||||||
|
className="accordion-button"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseDoc"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapseDoc"
|
||||||
|
>
|
||||||
|
<i className="bx bxs-folder me-2 text-warning fs-5"></i>
|
||||||
|
{latestDoc.name} (Latest v{latestDoc.version})
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="collapseDoc"
|
||||||
|
className="accordion-collapse collapse show"
|
||||||
|
aria-labelledby="headingDoc"
|
||||||
|
data-bs-parent="#docAccordion"
|
||||||
|
>
|
||||||
|
<div className="accordion-body p-2">
|
||||||
|
<div className="list-group list-group-flush">
|
||||||
|
{sortedVersions.map((document, index) => (
|
||||||
|
<div
|
||||||
|
key={document.id}
|
||||||
|
className={`list-group-item list-group-item-action d-flex align-items-center cursor-pointer ${
|
||||||
|
index > 0 ? "ms-4" : "" // indent only older versions
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FileIcon
|
||||||
|
type={document.contentType}
|
||||||
|
size="fs-2"
|
||||||
|
className="me-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="w-100">
|
||||||
|
<div className="d-flex justify-content-between align-items-center">
|
||||||
|
<small className=" fw-normal">{document.name}</small>
|
||||||
|
<small className=" fw-normal">
|
||||||
|
Version-{document.version}
|
||||||
|
</small>{" "}
|
||||||
|
<small className=" text-secondary fw-normal">
|
||||||
|
fileSize: {document.fileSize} Kb
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-between m-0">
|
||||||
|
<div
|
||||||
|
className="user-info text-start m-0"
|
||||||
|
onClick={handleOpenDocument}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
{formatUTCToLocalTime(document?.uploadedAt)} |
|
||||||
|
Uploaded by{" "}
|
||||||
|
<div className="d-flex align-items-center ms-1">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={document.uploadedBy?.firstName}
|
||||||
|
lastName={document.uploadedBy?.lastName}
|
||||||
|
/>
|
||||||
|
<span className="text-truncate ms-1">
|
||||||
|
{`${document.uploadedBy?.firstName ?? ""} ${
|
||||||
|
document.uploadedBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{document?.updatedAt && (
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
{formatUTCToLocalTime(document?.updatedAt)} |
|
||||||
|
Updated by{" "}
|
||||||
|
<div className="d-flex align-items-center ms-1">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={document.updatedBy?.firstName}
|
||||||
|
lastName={document.updatedBy?.lastName}
|
||||||
|
/>
|
||||||
|
<span className="text-truncate ms-1">
|
||||||
|
{`${document.updatedBy?.firstName ?? ""} ${
|
||||||
|
document.updatedBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex align-items-end">
|
||||||
|
{getDocuementsStatus(document.isVerified)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentVersionList;
|
27
src/components/Documents/DocumentViewerModal.jsx
Normal file
27
src/components/Documents/DocumentViewerModal.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useDocumentVersion } from "../../hooks/useDocument";
|
||||||
|
import { useDocumentContext } from "./Documents";
|
||||||
|
import { error } from "pdf-lib";
|
||||||
|
|
||||||
|
const DocumentViewerModal = () => {
|
||||||
|
const { viewDoc,setOpenDocument } = useDocumentContext();
|
||||||
|
const { data, isLoading, isError,error } = useDocumentVersion(viewDoc.document);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.data) {
|
||||||
|
const fileUrl = data.data;
|
||||||
|
window.open(fileUrl, "_blank");
|
||||||
|
setOpenDocument(false)
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
if (isLoading) return <p>Loading document...</p>;
|
||||||
|
if (isError) return <div>
|
||||||
|
<p className="danger-text">{error.message}</p>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
// Nothing to render inside modal since we redirect
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentViewerModal;
|
239
src/components/Documents/Documents.jsx
Normal file
239
src/components/Documents/Documents.jsx
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import GlobalModel from "../common/GlobalModel";
|
||||||
|
import NewDocument from "./ManageDocument";
|
||||||
|
import { DOCUMENTS_ENTITIES, UPLOAD_DOCUMENT } from "../../utils/constants";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import DocumentsList from "./DocumentsList";
|
||||||
|
import DocumentFilterPanel from "./DocumentFilterPanel";
|
||||||
|
import { useFab } from "../../Context/FabContext";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
DocumentFilterDefaultValues,
|
||||||
|
DocumentFilterSchema,
|
||||||
|
} from "./DocumentSchema";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import ManageDocument from "./ManageDocument";
|
||||||
|
import ViewDocument from "./ViewDocument";
|
||||||
|
import DocumentViewerModal from "./DocumentViewerModal";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
|
||||||
|
// Context
|
||||||
|
export const DocumentContext = createContext();
|
||||||
|
export const useDocumentContext = () => {
|
||||||
|
const context = useContext(DocumentContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useDocumentContext must be used within an DocumentProvider"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDocuementsStatus = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case true:
|
||||||
|
return (
|
||||||
|
<span className="badge rounded-pill bg-label-success">Verified</span>
|
||||||
|
);
|
||||||
|
case false:
|
||||||
|
return (
|
||||||
|
<span className="badge rounded-pill bg-label-danger">Rejected</span>
|
||||||
|
);
|
||||||
|
case null:
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<span className="badge rounded-pill bg-label-warning"> Pending</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const Documents = ({ Document_Entity, Entity }) => {
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const [isActive, setIsActive] = useState(true);
|
||||||
|
const [filters, setFilter] = useState();
|
||||||
|
const [isRefetching, setIsRefetching] = useState(false);
|
||||||
|
const [refetchFn, setRefetchFn] = useState(null);
|
||||||
|
const [DocumentEntity, setDocumentEntity] = useState(Document_Entity);
|
||||||
|
const { employeeId } = useParams();
|
||||||
|
const [OpenDocument, setOpenDocument] = useState(false);
|
||||||
|
const [ManageDoc, setManageDoc] = useState({
|
||||||
|
document: null,
|
||||||
|
isOpen: false,
|
||||||
|
});
|
||||||
|
const [viewDoc, setViewDoc] = useState({
|
||||||
|
document: null,
|
||||||
|
isOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const canUploadDocument = useHasUserPermission(UPLOAD_DOCUMENT)
|
||||||
|
|
||||||
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
|
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(DocumentFilterSchema),
|
||||||
|
defaultValues: DocumentFilterDefaultValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { reset } = methods;
|
||||||
|
|
||||||
|
const clearFilter = () => {
|
||||||
|
setFilter(DocumentFilterDefaultValues);
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowTrigger(true);
|
||||||
|
setOffcanvasContent(
|
||||||
|
"Document Filters",
|
||||||
|
<DocumentFilterPanel entityTypeId={DocumentEntity} onApply={setFilter} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setShowTrigger(false);
|
||||||
|
setOffcanvasContent("", null);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const contextValues = {
|
||||||
|
ManageDoc,
|
||||||
|
setManageDoc,
|
||||||
|
viewDoc,
|
||||||
|
setViewDoc,
|
||||||
|
setOpenDocument,
|
||||||
|
OpenDocument,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Document_Entity) {
|
||||||
|
setDocumentEntity(Document_Entity);
|
||||||
|
}
|
||||||
|
}, [Document_Entity]);
|
||||||
|
return (
|
||||||
|
<DocumentContext.Provider value={contextValues}>
|
||||||
|
<div className="mt-5">
|
||||||
|
<div className="card d-flex p-2">
|
||||||
|
<div className="row align-items-center">
|
||||||
|
{/* Search */}
|
||||||
|
<div className="d-flex col-8 col-md-8 col-lg-4 mb-md-0 align-items-center">
|
||||||
|
<div className="d-flex"> <input
|
||||||
|
type="search"
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
placeholder="Search Document"
|
||||||
|
/></div>
|
||||||
|
<label className="switch switch-sm mx-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="switch-input"
|
||||||
|
checked={isActive}
|
||||||
|
onChange={(e) => setIsActive(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span className="switch-toggle-slider">
|
||||||
|
<span className="switch-on"></span>
|
||||||
|
<span className="switch-off"></span>
|
||||||
|
</span>
|
||||||
|
<span className="switch-label">
|
||||||
|
{isActive ? "Active" : "In-Active"}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="col-6 col-md-6 col-lg-8 text-end">
|
||||||
|
{/* <span
|
||||||
|
className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer"
|
||||||
|
disabled={isRefetching}
|
||||||
|
onClick={() => {
|
||||||
|
setSearchText("");
|
||||||
|
setFilter(DocumentFilterDefaultValues);
|
||||||
|
refetchFn && refetchFn();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
<i
|
||||||
|
className={`bx bx-refresh ms-1 ${
|
||||||
|
isRefetching ? "bx-spin" : ""
|
||||||
|
}`}
|
||||||
|
></i>
|
||||||
|
</span> */}
|
||||||
|
|
||||||
|
|
||||||
|
{canUploadDocument && (<button
|
||||||
|
type="button"
|
||||||
|
title="Add New Document"
|
||||||
|
className="p-1 bg-primary rounded-circle cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setManageDoc({
|
||||||
|
document: null,
|
||||||
|
isOpen: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus fs-4 text-white"></i>
|
||||||
|
</button>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DocumentsList
|
||||||
|
Document_Entity={DocumentEntity}
|
||||||
|
Entity={Entity}
|
||||||
|
filters={filters}
|
||||||
|
searchText={searchText}
|
||||||
|
setIsRefetching={setIsRefetching}
|
||||||
|
setRefetchFn={setRefetchFn}
|
||||||
|
isActive={isActive}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ManageDoc.isOpen && (
|
||||||
|
<GlobalModel
|
||||||
|
isOpen={ManageDoc.isOpen}
|
||||||
|
closeModal={() =>
|
||||||
|
setManageDoc({
|
||||||
|
document: null,
|
||||||
|
isOpen: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ManageDocument
|
||||||
|
closeModal={() =>
|
||||||
|
setManageDoc({
|
||||||
|
document: null,
|
||||||
|
isOpen: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Document_Entity={DocumentEntity}
|
||||||
|
Entity={Entity}
|
||||||
|
/>
|
||||||
|
</GlobalModel>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{viewDoc.isOpen && (
|
||||||
|
<GlobalModel
|
||||||
|
size="lg"
|
||||||
|
isOpen={viewDoc.isOpen}
|
||||||
|
closeModal={() =>
|
||||||
|
setViewDoc({
|
||||||
|
document: null,
|
||||||
|
isOpen: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ViewDocument />
|
||||||
|
</GlobalModel>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{OpenDocument && (
|
||||||
|
<GlobalModel
|
||||||
|
isOpen={OpenDocument}
|
||||||
|
closeModal={() => setOpenDocument(false)}
|
||||||
|
>
|
||||||
|
<DocumentViewerModal />
|
||||||
|
</GlobalModel>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DocumentContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Documents;
|
251
src/components/Documents/DocumentsList.jsx
Normal file
251
src/components/Documents/DocumentsList.jsx
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
useActiveInActiveDocument,
|
||||||
|
useDocumentListByEntityId,
|
||||||
|
} from "../../hooks/useDocument";
|
||||||
|
import {
|
||||||
|
DELETE_DOCUMENT,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
|
MODIFY_DOCUMENT,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
import Avatar from "../common/Avatar";
|
||||||
|
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
|
import { useDebounce } from "../../utils/appUtils";
|
||||||
|
import { DocumentTableSkeleton } from "./DocumentSkeleton";
|
||||||
|
import { getDocuementsStatus, useDocumentContext } from "./Documents";
|
||||||
|
import Pagination from "../common/Pagination";
|
||||||
|
import ConfirmModal from "../common/ConfirmModal";
|
||||||
|
import { isPending } from "@reduxjs/toolkit";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
|
||||||
|
const DocumentsList = ({
|
||||||
|
Document_Entity,
|
||||||
|
Entity,
|
||||||
|
filters,
|
||||||
|
searchText,
|
||||||
|
setIsRefetching,
|
||||||
|
setRefetchFn,
|
||||||
|
isActive,
|
||||||
|
}) => {
|
||||||
|
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [deletingId, setDeletingId] = useState(null);
|
||||||
|
const [restoringIds, setRestoringIds] = useState([]);
|
||||||
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const canDeleteDocument = useHasUserPermission(DELETE_DOCUMENT);
|
||||||
|
const canModifyDocument = useHasUserPermission(MODIFY_DOCUMENT);
|
||||||
|
const { data, isError, isLoading, error, refetch, isFetching } =
|
||||||
|
useDocumentListByEntityId(
|
||||||
|
Document_Entity,
|
||||||
|
Entity,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
|
currentPage,
|
||||||
|
filters,
|
||||||
|
debouncedSearch,
|
||||||
|
isActive
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRefetchFn(() => refetch);
|
||||||
|
}, [setRefetchFn, refetch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsRefetching(isFetching);
|
||||||
|
}, [isFetching, setIsRefetching]);
|
||||||
|
|
||||||
|
const { setManageDoc, setViewDoc } = useDocumentContext();
|
||||||
|
const { mutate: ActiveInActive, isPending } = useActiveInActiveDocument();
|
||||||
|
|
||||||
|
const paginate = (page) => {
|
||||||
|
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||||
|
setCurrentPage(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const noData = !isLoading && !isError && data?.data.length === 0;
|
||||||
|
const isSearchEmpty = noData && !!debouncedSearch;
|
||||||
|
const isFilterEmpty = noData && !!filters && Object.keys(filters).length > 0;
|
||||||
|
const isInitialEmpty = noData && !debouncedSearch && !isFilterEmpty;
|
||||||
|
|
||||||
|
if (isLoading || isFetching) return <DocumentTableSkeleton />;
|
||||||
|
if (isError)
|
||||||
|
return <div>Error: {error?.message || "Something went wrong"}</div>;
|
||||||
|
if (isInitialEmpty) return <div>No documents found yet.</div>;
|
||||||
|
if (isSearchEmpty) return <div>No results found for "{debouncedSearch}"</div>;
|
||||||
|
if (isFilterEmpty) return <div>No documents match your filter.</div>;
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
ActiveInActive(
|
||||||
|
{ documentId: deletingId, isActive: !isActive },
|
||||||
|
{
|
||||||
|
onSettled: () => {
|
||||||
|
setDeletingId(null);
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRestore = (docId) => {
|
||||||
|
setRestoringIds((prev) => [...prev, docId]);
|
||||||
|
|
||||||
|
ActiveInActive(
|
||||||
|
{ documentId: docId, isActive: true },
|
||||||
|
{
|
||||||
|
onSettled: () => {
|
||||||
|
setRestoringIds((prev) => prev.filter((id) => id !== docId));
|
||||||
|
refetch();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocumentColumns = [
|
||||||
|
{
|
||||||
|
key: "name",
|
||||||
|
label: "Name",
|
||||||
|
getValue: (e) => e.name || "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "documentType",
|
||||||
|
label: "Document Type",
|
||||||
|
getValue: (e) => e.documentType?.name || "N/A",
|
||||||
|
align: "text-start",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "uploadedBy",
|
||||||
|
label: "Uploaded By",
|
||||||
|
align: "text-start",
|
||||||
|
customRender: (e) => (
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={e.uploadedBy?.firstName}
|
||||||
|
lastName={e.uploadedBy?.lastName}
|
||||||
|
/>
|
||||||
|
<span className="text-truncate ms-1">
|
||||||
|
{`${e.uploadedBy?.firstName ?? ""} ${
|
||||||
|
e.uploadedBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
getValue: (e) =>
|
||||||
|
`${e.uploadedBy?.firstName ?? ""} ${
|
||||||
|
e.uploadedBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "uploadedAt",
|
||||||
|
label: "Uploaded on",
|
||||||
|
getValue: (e) => formatUTCToLocalTime(e.uploadedAt),
|
||||||
|
align: "text-center",
|
||||||
|
isAlwaysVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Status",
|
||||||
|
label: "Status",
|
||||||
|
getValue: (e) => getDocuementsStatus(e.isVerified),
|
||||||
|
align: "text-center",
|
||||||
|
isAlwaysVisible: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{IsDeleteModalOpen && (
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={IsDeleteModalOpen}
|
||||||
|
type="delete"
|
||||||
|
header="Delete Document"
|
||||||
|
message="Are you sure you want to delete this document?"
|
||||||
|
onSubmit={handleDelete}
|
||||||
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
|
loading={!!isPending}
|
||||||
|
paramData={deletingId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="table-responsive">
|
||||||
|
<table className="table border-top dataTable text-nowrap">
|
||||||
|
<thead>
|
||||||
|
<tr className="shadow-sm">
|
||||||
|
{DocumentColumns.map((col) => (
|
||||||
|
<th key={col.key} className={`sorting ${col.align}`}>
|
||||||
|
{col.label}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
<th className="sticky-action-column bg-white text-center">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="text-start">
|
||||||
|
{data?.data?.map((doc) => {
|
||||||
|
const isRestoring = restoringIds.includes(doc.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={doc.id}>
|
||||||
|
{DocumentColumns.map((col) => (
|
||||||
|
<td key={col.key} className={`sorting ${col.align}`}>
|
||||||
|
{col.customRender
|
||||||
|
? col.customRender(doc)
|
||||||
|
: col.getValue(doc)}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
<td className="text-center">
|
||||||
|
{doc.isActive ? (
|
||||||
|
<div className="d-flex justify-content-center gap-2">
|
||||||
|
<i
|
||||||
|
className="bx bx-show text-primary cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setViewDoc({ document: doc.id, isOpen: true })
|
||||||
|
}
|
||||||
|
></i>
|
||||||
|
|
||||||
|
{canModifyDocument && (
|
||||||
|
<i
|
||||||
|
className="bx bx-edit text-secondary cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setManageDoc({ document: doc.id, isOpen: true })
|
||||||
|
}
|
||||||
|
></i>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{canDeleteDocument && (
|
||||||
|
<i
|
||||||
|
className="bx bx-trash text-danger cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
setDeletingId(doc.id);
|
||||||
|
}}
|
||||||
|
></i>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : isRestoring ? (
|
||||||
|
<div
|
||||||
|
className="spinner-border spinner-border-sm text-primary"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
<span className="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<i
|
||||||
|
className="bx bx-recycle me-1 text-primary cursor-pointer"
|
||||||
|
onClick={() => handleRestore(doc.id)}
|
||||||
|
></i>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentsList;
|
428
src/components/Documents/ManageDocument.jsx
Normal file
428
src/components/Documents/ManageDocument.jsx
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useForm, FormProvider } from "react-hook-form";
|
||||||
|
import { defaultDocumentValues, DocumentPayloadSchema } from "./DocumentSchema";
|
||||||
|
import Label from "../common/Label";
|
||||||
|
import {
|
||||||
|
useDocumentCategories,
|
||||||
|
useDocumentTypes,
|
||||||
|
} from "../../hooks/masterHook/useMaster";
|
||||||
|
import TagInput from "../common/TagInput";
|
||||||
|
import {
|
||||||
|
useDocumentDetails,
|
||||||
|
useDocumentTags,
|
||||||
|
useUpdateDocument,
|
||||||
|
useUploadDocument,
|
||||||
|
} from "../../hooks/useDocument";
|
||||||
|
import showToast from "../../services/toastService";
|
||||||
|
import { useDocumentContext } from "./Documents";
|
||||||
|
import { isPending } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
const toBase64 = (file) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = (err) => reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
const MergedTagsWithExistenStatus = (formTags = [], originalTags = []) => {
|
||||||
|
const tagMap = new Map();
|
||||||
|
|
||||||
|
const safeFormTags = Array.isArray(formTags) ? formTags : [];
|
||||||
|
const safeOriginalTags = Array.isArray(originalTags) ? originalTags : [];
|
||||||
|
|
||||||
|
safeOriginalTags.forEach(tag => {
|
||||||
|
if (tag?.name) {
|
||||||
|
tagMap.set(tag.name, { ...tag, isActive: tag.isActive ?? true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
safeFormTags.forEach(tag => {
|
||||||
|
if (tag?.name) {
|
||||||
|
tagMap.set(tag.name, { ...tag, isActive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
safeOriginalTags.forEach(tag => {
|
||||||
|
if (tag?.name && !safeFormTags.some(t => t.name === tag.name)) {
|
||||||
|
tagMap.set(tag.name, { ...tag, isActive: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(tagMap.values());
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const ManageDocument = ({ closeModal, Document_Entity, Entity }) => {
|
||||||
|
const { ManageDoc } = useDocumentContext();
|
||||||
|
const isUpdateForm = Boolean(ManageDoc?.document);
|
||||||
|
const [selectedType, setSelectedType] = useState(null);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||||
|
const [schema, setSchema] = useState(() =>
|
||||||
|
DocumentPayloadSchema({ isUpdateForm })
|
||||||
|
);
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: defaultDocumentValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = methods;
|
||||||
|
const { mutate: UploadDocument, isPending: isUploading } = useUploadDocument(
|
||||||
|
() => {
|
||||||
|
showToast("Document Uploaded Successfully", "success");
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const { mutate: UpdateDocument, isPending: isUpdating } = useUpdateDocument(
|
||||||
|
() => {
|
||||||
|
showToast("Document Updated Successfully", "success");
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
const normalizeAttachment = (attachment) => {
|
||||||
|
if (!attachment) return null;
|
||||||
|
return {
|
||||||
|
...attachment,
|
||||||
|
fileSize: Math.ceil(attachment.fileSize / 1024),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...data,
|
||||||
|
attachment: normalizeAttachment(data.attachment),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ManageDoc?.document) {
|
||||||
|
const DocumentPayload = {
|
||||||
|
...payload,
|
||||||
|
id: DocData.id,
|
||||||
|
tags: MergedTagsWithExistenStatus(data?.tags, DocData?.tags),
|
||||||
|
};
|
||||||
|
UpdateDocument({ documentId: DocData?.id, DocumentPayload });
|
||||||
|
} else {
|
||||||
|
const DocumentPayload = { ...payload, entityId: Entity };
|
||||||
|
UploadDocument(DocumentPayload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: DocData,
|
||||||
|
isLoading: isDocLoading,
|
||||||
|
isError: isDocError,
|
||||||
|
DocError,
|
||||||
|
} = useDocumentDetails(ManageDoc?.document);
|
||||||
|
const file = watch("attachment");
|
||||||
|
|
||||||
|
const documentTypeId = watch("documentTypeId");
|
||||||
|
|
||||||
|
// This hooks calling api base Entity(Employee) and Category
|
||||||
|
const { DocumentCategories, isLoading } =
|
||||||
|
useDocumentCategories(Document_Entity);
|
||||||
|
|
||||||
|
const categoryId = watch("documentCategoryId");
|
||||||
|
const { DocumentTypes, isLoading: isTypeLoading } = useDocumentTypes(
|
||||||
|
categoryId || null
|
||||||
|
);
|
||||||
|
const {data:DocumentTags} = useDocumentTags()
|
||||||
|
|
||||||
|
// Update schema whenever document type changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!documentTypeId) return;
|
||||||
|
|
||||||
|
const type = DocumentTypes?.find(
|
||||||
|
(t) => String(t.id) === String(documentTypeId)
|
||||||
|
);
|
||||||
|
if (!type) return;
|
||||||
|
setSelectedType(type)
|
||||||
|
const newSchema = DocumentPayloadSchema({
|
||||||
|
isMandatory: type.isMandatory ?? false,
|
||||||
|
regexExpression: type.regexExpression ?? null,
|
||||||
|
allowedContentType: type.allowedContentType ?? [
|
||||||
|
"application/pdf",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
],
|
||||||
|
maxSizeAllowedInMB: type.maxSizeAllowedInMB ?? 25,
|
||||||
|
isUpdateForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSchema(() => newSchema);
|
||||||
|
|
||||||
|
methods.reset(methods.getValues(), { keepValues: true });
|
||||||
|
methods.formState.errors; // triggers revalidation
|
||||||
|
}, [documentTypeId, DocumentTypes, isUpdateForm]);
|
||||||
|
|
||||||
|
// File Upload
|
||||||
|
const onFileChange = async (e) => {
|
||||||
|
const uploaded = e.target.files[0];
|
||||||
|
if (!uploaded) return;
|
||||||
|
|
||||||
|
const base64Data = await toBase64(uploaded);
|
||||||
|
|
||||||
|
const parsedFile = {
|
||||||
|
fileName: uploaded.name,
|
||||||
|
base64Data,
|
||||||
|
contentType: uploaded.type,
|
||||||
|
fileSize: uploaded.size,
|
||||||
|
description: "",
|
||||||
|
isActive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
setValue("attachment", parsedFile, {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldValidate: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeFile = () => {
|
||||||
|
setValue("attachment", null, {
|
||||||
|
shouldDirty: true,
|
||||||
|
shouldValidate: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// build dynamic file accept string
|
||||||
|
const fileAccept =
|
||||||
|
selectedType?.allowedContentType
|
||||||
|
?.split(",")
|
||||||
|
.map((t) =>
|
||||||
|
t === "application/pdf"
|
||||||
|
? ".pdf"
|
||||||
|
: t === "image/jpeg"
|
||||||
|
? ".jpg,.jpeg"
|
||||||
|
: t === "image/png"
|
||||||
|
? ".png"
|
||||||
|
: ""
|
||||||
|
)
|
||||||
|
.join(",") || "";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (DocData) {
|
||||||
|
reset({
|
||||||
|
...defaultDocumentValues,
|
||||||
|
name: DocData?.name ?? "",
|
||||||
|
documentCategoryId: DocData?.documentType?.documentCategory?.id
|
||||||
|
? String(DocData.documentType.documentCategory.id)
|
||||||
|
: "",
|
||||||
|
documentTypeId: DocData?.documentType?.id
|
||||||
|
? String(DocData.documentType.id)
|
||||||
|
: "",
|
||||||
|
documentId: DocData?.documentId ?? "",
|
||||||
|
description: DocData?.description ?? "",
|
||||||
|
attachment: DocData?.attachment ?? null,
|
||||||
|
tags: DocData?.tags ?? [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [DocData, reset]);
|
||||||
|
|
||||||
|
if (isDocLoading) return <div>Loading...</div>;
|
||||||
|
if (isDocError) return <div>{DocError.message}</div>;
|
||||||
|
|
||||||
|
const isPending = isUploading || isUpdating;
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<p className="fw-bold fs-6">Upload New Document</p>
|
||||||
|
<FormProvider key={documentTypeId} {...methods}>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
|
||||||
|
{/* Document Name */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<Label htmlFor="name" required>
|
||||||
|
Document Name
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("name")}
|
||||||
|
/>
|
||||||
|
{errors.name && (
|
||||||
|
<div className="danger-text">{errors.name.message}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Category */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<Label htmlFor="documentCategoryId">Document Category</Label>
|
||||||
|
<select
|
||||||
|
{...register("documentCategoryId")}
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<option disabled value="">
|
||||||
|
Loading...
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
{!isLoading && <option value="">Select Category</option>}
|
||||||
|
{DocumentCategories?.map((type) => (
|
||||||
|
<option key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.documentCategoryId && (
|
||||||
|
<div className="danger-text">
|
||||||
|
{errors.documentCategoryId.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Type */}
|
||||||
|
{categoryId && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<Label htmlFor="documentTypeId">Document Type</Label>
|
||||||
|
<select
|
||||||
|
{...register("documentTypeId")}
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
>
|
||||||
|
{isTypeLoading && (
|
||||||
|
<option disabled value="">
|
||||||
|
Loading...
|
||||||
|
</option>
|
||||||
|
)}
|
||||||
|
{DocumentTypes?.map((type) => (
|
||||||
|
<option key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.documentTypeId && (
|
||||||
|
<div className="danger-text">
|
||||||
|
{errors.documentTypeId.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Document ID */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="documentId"
|
||||||
|
required={selectedType?.isMandatory ?? false}
|
||||||
|
>
|
||||||
|
Document ID
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
{...register("documentId")}
|
||||||
|
/>
|
||||||
|
{errors.documentId && (
|
||||||
|
<div className="danger-text">{errors.documentId.message}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Upload */}
|
||||||
|
<div className="row my-2">
|
||||||
|
<div className="col-md-12">
|
||||||
|
<Label htmlFor="attachment" required>Upload Document</Label>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => document.getElementById("attachment").click()}
|
||||||
|
>
|
||||||
|
<i className="bx bx-cloud-upload d-block bx-lg"></i>
|
||||||
|
<span className="text-muted d-block">
|
||||||
|
Click to select or click here to browse
|
||||||
|
</span>
|
||||||
|
<small className="text-muted">
|
||||||
|
({selectedType?.allowedContentType || "PDF/JPG/PNG"}, max{" "}
|
||||||
|
{selectedType?.maxSizeAllowedInMB ?? 25}MB)
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="attachment"
|
||||||
|
accept={selectedType?.allowedContentType}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={(e) => {
|
||||||
|
onFileChange(e);
|
||||||
|
e.target.value = ""; // reset input
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{errors.attachment && (
|
||||||
|
<small className="danger-text">
|
||||||
|
{errors.attachment.message
|
||||||
|
? errors.attachment.message
|
||||||
|
: errors.attachment.fileName?.message ||
|
||||||
|
errors.attachment.base64Data?.message ||
|
||||||
|
errors.attachment.contentType?.message ||
|
||||||
|
errors.attachment.fileSize?.message}
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{file?.base64Data && (
|
||||||
|
<div className="d-flex justify-content-between text-start p-1 mt-2">
|
||||||
|
<div>
|
||||||
|
<span className="mb-0 text-secondary small d-block">
|
||||||
|
{file.fileName}
|
||||||
|
</span>
|
||||||
|
<span className="text-body-secondary small d-block">
|
||||||
|
{(file.fileSize / 1024).toFixed(1)} KB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
className="bx bx-trash bx-sm cursor-pointer text-danger"
|
||||||
|
onClick={removeFile}
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<TagInput name="tags" label="Tags" placeholder="Tags.." options={DocumentTags} />
|
||||||
|
{errors.tags && (
|
||||||
|
<small className="danger-text">{errors.tags.message}</small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<Label htmlFor="description" required>Description</Label>
|
||||||
|
<textarea
|
||||||
|
rows="2"
|
||||||
|
className="form-control"
|
||||||
|
{...register("description")}
|
||||||
|
></textarea>
|
||||||
|
{errors.description && (
|
||||||
|
<div className="danger-text">{errors.description.message}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<div className="d-flex justify-content-center gap-3">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary btn-sm"
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? "Please Wait..." : " Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="reset"
|
||||||
|
className="btn btn-secondary btn-sm"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={closeModal}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageDocument;
|
47
src/components/Documents/VersionListSkeleton.jsx
Normal file
47
src/components/Documents/VersionListSkeleton.jsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => (
|
||||||
|
<div
|
||||||
|
className={`skeleton mb-1 ${className}`}
|
||||||
|
style={{
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const VersionListSkeleton = ({ items = 5 }) => {
|
||||||
|
return (
|
||||||
|
<div className="list-group mx-0">
|
||||||
|
{[...Array(items)].map((_, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="list-group-item py-2 border border-bottom border-top-0 border-start-0 border-end-0"
|
||||||
|
>
|
||||||
|
{/* Top row: document name + version/status */}
|
||||||
|
<div className="d-flex w-100 justify-content-between">
|
||||||
|
<SkeletonLine width="40%" height={16} />
|
||||||
|
<div className="d-flex gap-2">
|
||||||
|
<SkeletonLine width="60px" height={14} />
|
||||||
|
<SkeletonLine width="80px" height={14} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Upload by row */}
|
||||||
|
<div className="d-flex align-items-center gap-2 mt-2">
|
||||||
|
<SkeletonLine width="24px" height="24px" className="rounded-circle" />
|
||||||
|
<SkeletonLine width="120px" height={14} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Updated at row */}
|
||||||
|
<div className="d-flex gap-2 mt-2">
|
||||||
|
<SkeletonLine width="150px" height={14} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VersionListSkeleton;
|
234
src/components/Documents/ViewDocument.jsx
Normal file
234
src/components/Documents/ViewDocument.jsx
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
useDocumentDetails,
|
||||||
|
useDocumentVersionList,
|
||||||
|
useVerifyDocument,
|
||||||
|
} from "../../hooks/useDocument";
|
||||||
|
import { getDocuementsStatus, useDocumentContext } from "./Documents";
|
||||||
|
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
|
import Avatar from "../common/Avatar";
|
||||||
|
import {
|
||||||
|
DOWNLOAD_DOCUMENT,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
|
VERIFY_DOCUMENT,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
import Pagination from "../common/Pagination";
|
||||||
|
import VersionListSkeleton from "./VersionListSkeleton";
|
||||||
|
import DocumentDetailsSkeleton from "./DocumentDetailsSkeleton ";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import DocumentVersionList from "./DocumentVersionList";
|
||||||
|
|
||||||
|
const ViewDocument = () => {
|
||||||
|
const { viewDoc, setViewDoc, setOpenDocument } = useDocumentContext();
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const canVerifyDocument = useHasUserPermission(VERIFY_DOCUMENT);
|
||||||
|
const canDownloadDocument = useHasUserPermission(DOWNLOAD_DOCUMENT);
|
||||||
|
const { data, isLoading, isError, error } = useDocumentDetails(
|
||||||
|
viewDoc?.document
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
data: versionList,
|
||||||
|
isError: isVersionError,
|
||||||
|
isLoading: versionLoding,
|
||||||
|
error: versionError,
|
||||||
|
} = useDocumentVersionList(
|
||||||
|
data?.parentAttachmentId,
|
||||||
|
ITEMS_PER_PAGE - 10,
|
||||||
|
currentPage
|
||||||
|
);
|
||||||
|
const paginate = (page) => {
|
||||||
|
if (page >= 1 && page <= (versionList?.totalPages ?? 1)) {
|
||||||
|
setCurrentPage(page);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mutate: VerifyDoc, isPending } = useVerifyDocument();
|
||||||
|
const VerifyDocument = () => {
|
||||||
|
VerifyDoc({ documentId: viewDoc?.document, isVerify: true });
|
||||||
|
};
|
||||||
|
const RejectDocument = () => {
|
||||||
|
VerifyDoc({ documentId: viewDoc?.document, isVerify: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) return <DocumentDetailsSkeleton />;
|
||||||
|
if (isError)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>{error?.response?.data?.message || error?.message}</p>
|
||||||
|
<p className="danger-text">{error?.response?.status}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="p-1">
|
||||||
|
<p className="fw-bold fs-6">Document Details</p>
|
||||||
|
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex text-start">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Document Name:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">{data.name || "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex text-start">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Document ID:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">{data.documentId || "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 2 */}
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-12 col-md-6 text-start">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Version:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">{data.version || "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6 text-start">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Uploaded At:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">
|
||||||
|
{formatUTCToLocalTime(data.uploadedAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 3 */}
|
||||||
|
<div className="row mb-2 text-start">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Uploaded By:
|
||||||
|
</span>
|
||||||
|
<div className="d-flex align-items-center ms-1">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={data.uploadedBy?.firstName}
|
||||||
|
lastName={data.uploadedBy?.lastName}
|
||||||
|
/>
|
||||||
|
<span className="text-truncate ms-1">
|
||||||
|
{`${data.uploadedBy?.firstName ?? ""} ${
|
||||||
|
data.uploadedBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{data.updatedAt && (
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Updated At:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">
|
||||||
|
{formatUTCToLocalTime(data.updatedAt) || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}{" "}
|
||||||
|
<div className="col-12 col-md-6"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 4 */}
|
||||||
|
<div className="row mb-2 text-start">
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Category:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">
|
||||||
|
{data.documentType?.documentCategory?.name || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Type:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">{data.documentType?.name || "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 5 - Tags full width */}
|
||||||
|
<div className="row mb-2 text-start">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Tags:
|
||||||
|
</span>
|
||||||
|
<div className="d-flex flex-wrap gap-2">
|
||||||
|
{data.tags?.length > 0 ? (
|
||||||
|
data.tags.map((t, i) => (
|
||||||
|
<span
|
||||||
|
key={i}
|
||||||
|
className="badge rounded-pill bg-label-secondary"
|
||||||
|
>
|
||||||
|
{t.name}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="text-muted">-</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row mb-2 text-start">
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="d-flex">
|
||||||
|
<span className="fw-semibold me-2" style={{ minWidth: "130px" }}>
|
||||||
|
Description:
|
||||||
|
</span>
|
||||||
|
<span className="text-muted">{data.description || "-"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{data.isVerified === null && (
|
||||||
|
<div className="d-flex justify-content-end">
|
||||||
|
<div className="d-flex text-start text-sm-end">
|
||||||
|
{" "}
|
||||||
|
{isPending ? (
|
||||||
|
"Please Wait..."
|
||||||
|
) : (
|
||||||
|
<div className="mx-2">
|
||||||
|
<a
|
||||||
|
onClick={VerifyDocument}
|
||||||
|
className="cursor-pointer text-primary"
|
||||||
|
>
|
||||||
|
Verify
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
onClick={RejectDocument}
|
||||||
|
className="cursor-pointer text-danger mx-2"
|
||||||
|
>
|
||||||
|
Reject
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<DocumentVersionList
|
||||||
|
versionLoding={versionLoding}
|
||||||
|
versionList={versionList}
|
||||||
|
isPending={isPending}
|
||||||
|
setOpenDocument={setOpenDocument}
|
||||||
|
VerifyDocument={VerifyDocument}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewDocument;
|
@ -126,7 +126,7 @@ const EmpAttendance = ({ employee }) => {
|
|||||||
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
className="dataTables_length text-start py-2 d-flex justify-content-between "
|
||||||
id="DataTables_Table_0_length"
|
id="DataTables_Table_0_length"
|
||||||
>
|
>
|
||||||
<div className="col-md-3 my-0 ">
|
<div className="col-md-4 my-0 ">
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
DateDifference="30"
|
DateDifference="30"
|
||||||
onRangeChange={setDateRange}
|
onRangeChange={setDateRange}
|
||||||
|
@ -59,20 +59,20 @@ const EmpBanner = ({ profile, loggedInUser }) => {
|
|||||||
</h4>
|
</h4>
|
||||||
<ul className="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center gap-4 mt-4">
|
<ul className="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center gap-4 mt-4">
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<i className="icon-base bx bx-crown me-2 align-top"></i>
|
<i className="icon-base bx bx-crown me-1 align-top"></i>
|
||||||
<span className="fw-medium">
|
<span className="fw-medium">
|
||||||
{profile?.jobRole || <em>NA</em>}
|
{profile?.jobRole || <em>NA</em>}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<i className="icon-base bx bx-phone me-2 align-top"></i>
|
<i className="icon-base bx bx-phone me-0 align-top"></i>
|
||||||
<span className="fw-medium">
|
<span className="fw-medium">
|
||||||
{" "}
|
{" "}
|
||||||
{profile?.phoneNumber || <em>NA</em>}
|
{profile?.phoneNumber || <em>NA</em>}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item">
|
<li className="list-inline-item">
|
||||||
<i className="icon-base bx bx-calendar me-2 align-top"></i>
|
<i className="icon-base bx bx-calendar me-0 align-top"></i>
|
||||||
<span className="fw-medium">
|
<span className="fw-medium">
|
||||||
{" "}
|
{" "}
|
||||||
Joined on{" "}
|
Joined on{" "}
|
||||||
@ -85,18 +85,21 @@ const EmpBanner = ({ profile, loggedInUser }) => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center mt-4">
|
<ul className="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center mt-4">
|
||||||
<li className="list-inline-item">
|
{profile?.isActive && ( // ✅ show only if active
|
||||||
<button
|
<li className="list-inline-item">
|
||||||
className="btn btn-sm btn-primary btn-block"
|
|
||||||
onClick={() => setShowModal(true)}
|
|
||||||
>
|
|
||||||
Edit Profile
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li className="list-inline-item">
|
|
||||||
{profile?.id == loggedInUser?.employeeInfo?.id && (
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-outline-primary btn-block"
|
className="btn btn-sm btn-primary btn-block"
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
>
|
||||||
|
Edit Profile
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<li className="list-inline-item">
|
||||||
|
{profile?.id === loggedInUser?.employeeInfo?.id && (
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline-primary btn-block"
|
||||||
onClick={() => openChangePassword()}
|
onClick={() => openChangePassword()}
|
||||||
>
|
>
|
||||||
Change Password
|
Change Password
|
||||||
|
@ -9,7 +9,6 @@ const EmpDashboard = ({ profile }) => {
|
|||||||
refetch,
|
refetch,
|
||||||
} = useProjectsAllocationByEmployee(profile?.id);
|
} = useProjectsAllocationByEmployee(profile?.id);
|
||||||
|
|
||||||
console.log(projectList);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||||
|
import DocumentPage from "../../pages/Documents/DocumentPage";
|
||||||
|
import Documents from "../Documents/Documents";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { DOCUMENTS_ENTITIES } from "../../utils/constants";
|
||||||
|
|
||||||
const EmpDocuments = ({ profile, loggedInUser }) => {
|
const EmpDocuments = ({ profile, loggedInUser }) => {
|
||||||
|
const {employeeId} = useParams()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ComingSoonPage/>
|
<Documents Document_Entity={DOCUMENTS_ENTITIES.EmployeeEntity} Entity={employeeId} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,4 +4,4 @@ const EmployeeList = () => {
|
|||||||
return <div>EmployeeList</div>;
|
return <div>EmployeeList</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmployeeList;
|
export default EmployeeList;
|
@ -1,11 +1,18 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
|
import { VIEW_DOCUMENT } from "../../utils/constants";
|
||||||
const EmployeeNav = ({ onPillClick, activePill }) => {
|
const EmployeeNav = ({ onPillClick, activePill }) => {
|
||||||
const tabs = [
|
const canViewDocuments = useHasUserPermission(VIEW_DOCUMENT)
|
||||||
|
const tabs = [
|
||||||
{ key: "profile", icon: "bx bx-user", label: "Profile" },
|
{ key: "profile", icon: "bx bx-user", label: "Profile" },
|
||||||
{ key: "attendance", icon: "bx bx-group", label: "Attendances" },
|
{ key: "attendance", icon: "bx bx-group", label: "Attendances" },
|
||||||
{ key: "documents", icon: "bx bx-user", label: "Documents" },
|
canViewDocuments && {
|
||||||
|
key: "documents",
|
||||||
|
icon: "bx bx-file",
|
||||||
|
label: "Documents",
|
||||||
|
},
|
||||||
{ key: "activities", icon: "bx bx-grid-alt", label: "Activities" },
|
{ key: "activities", icon: "bx bx-grid-alt", label: "Activities" },
|
||||||
];
|
].filter(Boolean);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
|
@ -71,7 +71,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ Close popup when navigating to another component
|
// Close popup when navigating to another component
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
closePanel();
|
closePanel();
|
||||||
@ -105,6 +105,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
startField="startDate"
|
startField="startDate"
|
||||||
endField="endDate"
|
endField="endDate"
|
||||||
resetSignal={resetKey}
|
resetSignal={resetKey}
|
||||||
|
defaultRange={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
APPROVE_EXPENSE,
|
APPROVE_EXPENSE,
|
||||||
EXPENSE_DRAFT,
|
EXPENSE_DRAFT,
|
||||||
EXPENSE_REJECTEDBY,
|
EXPENSE_REJECTEDBY,
|
||||||
|
ITEMS_PER_PAGE,
|
||||||
} from "../../utils/constants";
|
} from "../../utils/constants";
|
||||||
import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
|
||||||
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
|
||||||
@ -22,12 +23,11 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
const IsExpenseEditable = useHasUserPermission();
|
const IsExpenseEditable = useHasUserPermission();
|
||||||
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const pageSize = 20;
|
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
|
|
||||||
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
||||||
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
||||||
pageSize,
|
ITEMS_PER_PAGE,
|
||||||
currentPage,
|
currentPage,
|
||||||
filters,
|
filters,
|
||||||
debouncedSearch
|
debouncedSearch
|
||||||
@ -110,8 +110,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
label: "Submitted By",
|
label: "Submitted By",
|
||||||
align: "text-start",
|
align: "text-start",
|
||||||
getValue: (e) =>
|
getValue: (e) =>
|
||||||
`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""}`.trim() ||
|
`${e.createdBy?.firstName ?? ""} ${
|
||||||
"N/A",
|
e.createdBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A",
|
||||||
customRender: (e) => (
|
customRender: (e) => (
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -185,26 +186,16 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{IsDeleteModalOpen && (
|
{IsDeleteModalOpen && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade show`}
|
isOpen={IsDeleteModalOpen}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header="Delete Expense"
|
||||||
style={{
|
message="Are you sure you want delete?"
|
||||||
display: "block",
|
onSubmit={handleDelete}
|
||||||
backgroundColor: "rgba(0,0,0,0.5)",
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
}}
|
loading={isPending}
|
||||||
aria-hidden="false"
|
paramData={deletingId}
|
||||||
>
|
/>
|
||||||
<ConfirmModal
|
|
||||||
type="delete"
|
|
||||||
header="Delete Expense"
|
|
||||||
message="Are you sure you want delete?"
|
|
||||||
onSubmit={handleDelete}
|
|
||||||
onClose={() => setIsDeleteModalOpen(false)}
|
|
||||||
loading={isPending}
|
|
||||||
paramData={deletingId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card px-0 px-sm-4">
|
<div className="card px-0 px-sm-4">
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
cacheData,
|
cacheData,
|
||||||
clearAllCache,
|
clearAllCache,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
useSelectedproject,
|
useSelectedProject,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import AuthRepository from "../../repositories/AuthRepository";
|
import AuthRepository from "../../repositories/AuthRepository";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
@ -101,7 +101,7 @@ const Header = () => {
|
|||||||
|
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
|
|
||||||
const projectsForDropdown = isDashboardPath
|
const projectsForDropdown = isDashboardPath
|
||||||
? projectNames
|
? projectNames
|
||||||
|
@ -8,7 +8,7 @@ import { MANAGE_PROJECT } from "../../utils/constants";
|
|||||||
import GlobalModel from "../common/GlobalModel";
|
import GlobalModel from "../common/GlobalModel";
|
||||||
import ManageProjectInfo from "./ManageProjectInfo";
|
import ManageProjectInfo from "./ManageProjectInfo";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const AboutProject = () => {
|
const AboutProject = () => {
|
||||||
const [IsOpenModal, setIsOpenModal] = useState(false);
|
const [IsOpenModal, setIsOpenModal] = useState(false);
|
||||||
@ -21,7 +21,7 @@ const AboutProject = () => {
|
|||||||
|
|
||||||
// *** MODIFIED LINE: Get projectId from Redux store using useSelector ***
|
// *** MODIFIED LINE: Get projectId from Redux store using useSelector ***
|
||||||
// const projectId = useSelector((store) => store.localVariables.projectId);
|
// const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const projectId = useSelectedproject();
|
const projectId = useSelectedProject();
|
||||||
|
|
||||||
const manageProject = useHasUserPermission(MANAGE_PROJECT);
|
const manageProject = useHasUserPermission(MANAGE_PROJECT);
|
||||||
const { projects_Details, isLoading, error, refetch } = useProjectDetails(projectId); // Pass projectId from useSelector
|
const { projects_Details, isLoading, error, refetch } = useProjectDetails(projectId); // Pass projectId from useSelector
|
||||||
|
@ -32,8 +32,8 @@ const WorkItem = ({
|
|||||||
forWorkArea,
|
forWorkArea,
|
||||||
deleteHandleTask,
|
deleteHandleTask,
|
||||||
}) => {
|
}) => {
|
||||||
const projectId = useSelector((store)=>store.localVariables.projectId)
|
const projectId = useSelector((store) => store.localVariables.projectId);
|
||||||
const isTaskPlanning = /^\/activities\/task$/.test(location.pathname);
|
const isTaskPlanning = /^\/activities\/task$/.test(location.pathname);
|
||||||
|
|
||||||
const [itemName, setItemName] = useState("");
|
const [itemName, setItemName] = useState("");
|
||||||
const [NewWorkItem, setNewWorkItem] = useState();
|
const [NewWorkItem, setNewWorkItem] = useState();
|
||||||
@ -135,25 +135,15 @@ const isTaskPlanning = /^\/activities\/task$/.test(location.pathname);
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{showModal2 && (
|
{showModal2 && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade ${showModal2 ? "show" : ""}`}
|
isOpen={showModal2}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header="Delete Activity"
|
||||||
style={{
|
message="Are you sure you want delete?"
|
||||||
display: showModal2 ? "block" : "none",
|
onSubmit={handleSubmit}
|
||||||
backgroundColor: showModal2 ? "rgba(0,0,0,0.5)" : "transparent",
|
onClose={closeModalDelete}
|
||||||
}}
|
loading={isPending}
|
||||||
aria-hidden="false"
|
/>
|
||||||
>
|
|
||||||
<ConfirmModal
|
|
||||||
type={"delete"}
|
|
||||||
header={"Delete Activity"}
|
|
||||||
message={"Are you sure you want delete?"}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onClose={closeModalDelete}
|
|
||||||
loading={loadingDelete}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<tr key={NewWorkItem?.workItemId}>
|
<tr key={NewWorkItem?.workItemId}>
|
||||||
@ -240,9 +230,7 @@ const isTaskPlanning = /^\/activities\/task$/.test(location.pathname);
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{(ManageInfra ||
|
{(ManageInfra ||
|
||||||
(
|
(ManageAndAssignTak && PlannedWork !== CompletedWork)) && (
|
||||||
ManageAndAssignTak &&
|
|
||||||
PlannedWork !== CompletedWork)) && (
|
|
||||||
<td className="text-end align-items-middle border-top">
|
<td className="text-end align-items-middle border-top">
|
||||||
{/* Desktop (md and up): inline icons */}
|
{/* Desktop (md and up): inline icons */}
|
||||||
<div className="d-none d-md-flex justify-content-end gap-1 px-2">
|
<div className="d-none d-md-flex justify-content-end gap-1 px-2">
|
||||||
|
14
src/components/Project/ProjectDocuments.jsx
Normal file
14
src/components/Project/ProjectDocuments.jsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Documents from "../Documents/Documents";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { DOCUMENTS_ENTITIES } from "../../utils/constants";
|
||||||
|
const ProjectDocuments = () => {
|
||||||
|
const selectedProject = useSelectedProject()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Documents Document_Entity={DOCUMENTS_ENTITIES.ProjectEntity} Entity={selectedProject} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectDocuments;
|
@ -15,7 +15,7 @@ import {
|
|||||||
cacheData,
|
cacheData,
|
||||||
clearCacheKey,
|
clearCacheKey,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
useSelectedproject,
|
useSelectedProject,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
|
import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
@ -27,7 +27,7 @@ import GlobalModel from "../common/GlobalModel";
|
|||||||
const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
||||||
{
|
{
|
||||||
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||||
const projectId = useSelectedproject();
|
const projectId = useSelectedProject();
|
||||||
const reloadedData = useSelector((store) => store.localVariables.reload);
|
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||||
const [ expandedBuildings, setExpandedBuildings ] = useState( [] );
|
const [ expandedBuildings, setExpandedBuildings ] = useState( [] );
|
||||||
const {projectInfra,isLoading,error} = useProjectInfra(projectId)
|
const {projectInfra,isLoading,error} = useProjectInfra(projectId)
|
||||||
|
@ -1,84 +1,57 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { hasUserPermission } from "../../utils/authUtils";
|
import { hasUserPermission } from "../../utils/authUtils";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { DIRECTORY_ADMIN, DIRECTORY_MANAGER, DIRECTORY_USER, VIEW_PROJECT_INFRA } from "../../utils/constants";
|
import {
|
||||||
|
DIRECTORY_ADMIN,
|
||||||
|
DIRECTORY_MANAGER,
|
||||||
|
DIRECTORY_USER,
|
||||||
|
VIEW_PROJECT_INFRA,
|
||||||
|
} from "../../utils/constants";
|
||||||
|
|
||||||
const ProjectNav = ({ onPillClick, activePill }) => {
|
const ProjectNav = ({ onPillClick, activePill }) => {
|
||||||
const HasViewInfraStructure = useHasUserPermission( VIEW_PROJECT_INFRA );
|
const HasViewInfraStructure = useHasUserPermission(VIEW_PROJECT_INFRA);
|
||||||
const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
|
const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN);
|
||||||
const DireManager = useHasUserPermission(DIRECTORY_MANAGER)
|
const DireManager = useHasUserPermission(DIRECTORY_MANAGER);
|
||||||
const DirUser = useHasUserPermission(DIRECTORY_USER)
|
const DirUser = useHasUserPermission(DIRECTORY_USER);
|
||||||
|
|
||||||
|
const ProjectTab = [
|
||||||
|
{ key: "profile", icon: "bx bx-user", label: "Profile" },
|
||||||
|
{ key: "teams", icon: "bx bx-group", label: "Teams" },
|
||||||
|
{
|
||||||
|
key: "infra",
|
||||||
|
icon: "bx bx-grid-alt",
|
||||||
|
label: "Infrastructure",
|
||||||
|
hidden: !HasViewInfraStructure,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "directory",
|
||||||
|
icon: "bx bxs-contact",
|
||||||
|
label: "Directory",
|
||||||
|
hidden: !(DirAdmin || DireManager || DirUser),
|
||||||
|
},
|
||||||
|
{ key: "documents", icon: "bx bx-folder-open", label: "Documents" },
|
||||||
|
{ key: "setting", icon: "bx bxs-cog", label: "Setting" },
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<div className="nav-align-top">
|
<div className="nav-align-top">
|
||||||
<ul className="nav nav-tabs ">
|
<ul className="nav nav-tabs">
|
||||||
<li className="nav-item">
|
{ProjectTab?.filter((tab) => !tab.hidden)?.map((tab) => (
|
||||||
<a
|
<li key={tab.key} className="nav-item cursor-pointer">
|
||||||
className={`nav-link ${activePill === "profile" ? "active" : ""} fs-6`}
|
<a
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
className={`nav-link ${
|
||||||
e.preventDefault();
|
activePill === tab.key ? "active cursor-pointer" : ""
|
||||||
onPillClick("profile");
|
} fs-6`}
|
||||||
}}
|
onClick={(e) => {
|
||||||
>
|
e.preventDefault();
|
||||||
<i className="bx bx-user bx-sm me-1_5"></i> <span className="d-none d-md-inline">Profile</span>
|
onPillClick(tab.key);
|
||||||
</a>
|
}}
|
||||||
</li>
|
>
|
||||||
<li className="nav-item">
|
<i className={`${tab.icon} bx-sm me-1_5`}></i>
|
||||||
<a
|
<span className="d-none d-md-inline ">{tab.label}</span>
|
||||||
className={`nav-link ${activePill === "teams" ? "active" : ""} fs-6`}
|
</a>
|
||||||
href="#"
|
</li>
|
||||||
onClick={(e) => {
|
))}
|
||||||
e.preventDefault();
|
|
||||||
onPillClick("teams");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="bx bx-group bx-sm me-1_5"></i><span className="d-none d-md-inline" > Teams</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li className={`nav-item ${!HasViewInfraStructure && "d-none"} `}>
|
|
||||||
<a
|
|
||||||
className={`nav-link ${activePill === "infra" ? "active" : ""} fs-6`}
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
onPillClick("infra");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className="bx bx-grid-alt bx-sm me-1_5"></i> <span className="d-none d-md-inline">Infrastructure</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
|
|
||||||
{(DirAdmin || DireManager || DirUser) && (
|
|
||||||
<li className="nav-item">
|
|
||||||
<a
|
|
||||||
className={`nav-link ${activePill === "directory" ? "active" : ""} fs-6`}
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault(); // Prevent page reload
|
|
||||||
onPillClick("directory");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className='bx bxs-contact bx-sm me-1_5'></i> <span className="d-none d-md-inline">Directory</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
<li className="nav-item">
|
|
||||||
<a
|
|
||||||
className={`nav-link ${
|
|
||||||
activePill === "imagegallary" ? "active" : ""
|
|
||||||
} fs-6`}
|
|
||||||
href="#"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault(); // Prevent page reload
|
|
||||||
onPillClick("imagegallary");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<i className='bx bxs-cog bx-sm me-1_5'></i> <span className="d-none d-md-inline">project Setup</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
181
src/components/Project/ProjectPermission.jsx
Normal file
181
src/components/Project/ProjectPermission.jsx
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import {
|
||||||
|
useProjectLevelEmployeePermission,
|
||||||
|
useProjectLevelModules,
|
||||||
|
useUpdateProjectLevelEmployeePermission,
|
||||||
|
} from "../../hooks/useProjects";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { useEmployeesByProject } from "../../hooks/useEmployees";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import showToast from "../../services/toastService";
|
||||||
|
|
||||||
|
export const ProjectPermissionSchema = z.object({
|
||||||
|
employeeId: z.string().min(1, "Employee is required"),
|
||||||
|
selectedPermissions: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ProjectPermission = () => {
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
|
||||||
|
const { data: ProjectModules = [] } = useProjectLevelModules();
|
||||||
|
const { employees = [], loading } = useEmployeesByProject(selectedProject);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
watch,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(ProjectPermissionSchema),
|
||||||
|
defaultValues: {
|
||||||
|
employeeId: "",
|
||||||
|
selectedPermissions: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedEmployee = watch("employeeId");
|
||||||
|
|
||||||
|
const { data: selectedEmpPermissions } = useProjectLevelEmployeePermission(
|
||||||
|
selectedEmployee || "",
|
||||||
|
selectedProject
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!employees.length) return;
|
||||||
|
|
||||||
|
const enabledPerms =
|
||||||
|
selectedEmpPermissions?.permissions
|
||||||
|
?.filter((perm) => perm.isEnabled)
|
||||||
|
?.map((perm) => perm.id) || [];
|
||||||
|
|
||||||
|
reset({
|
||||||
|
employeeId: selectedEmployee || employees[0]?.id || "",
|
||||||
|
selectedPermissions: enabledPerms,
|
||||||
|
});
|
||||||
|
}, [selectedEmpPermissions, reset, selectedEmployee, employees]);
|
||||||
|
|
||||||
|
const { mutate: updatePermission, isPending } =
|
||||||
|
useUpdateProjectLevelEmployeePermission();
|
||||||
|
|
||||||
|
const onSubmit = (formData) => {
|
||||||
|
if (!formData.employeeId) {
|
||||||
|
showToast("Please select an employee", "warn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingPermissions = selectedEmpPermissions?.permissions || [];
|
||||||
|
|
||||||
|
const payloadPermissions =
|
||||||
|
existingPermissions.length > 0
|
||||||
|
? existingPermissions.map((perm) => ({
|
||||||
|
id: perm.id,
|
||||||
|
isEnabled: formData.selectedPermissions?.includes(perm.id) || false,
|
||||||
|
}))
|
||||||
|
: (formData.selectedPermissions || []).map((id) => ({
|
||||||
|
id,
|
||||||
|
isEnabled: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (payloadPermissions.length === 0) {
|
||||||
|
showToast("No permissions selected", "warn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChanges = existingPermissions.some(
|
||||||
|
(perm) =>
|
||||||
|
perm.isEnabled !==
|
||||||
|
(formData.selectedPermissions?.includes(perm.id) || false)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasChanges && existingPermissions.length > 0) {
|
||||||
|
showToast("No changes detected", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
employeeId: formData.employeeId,
|
||||||
|
projectId: selectedProject,
|
||||||
|
permission: payloadPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
updatePermission(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<form className="row" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
{/* Employee Dropdown */}
|
||||||
|
<div className="d-flex align-items-end gap-2">
|
||||||
|
<div className="text-start">
|
||||||
|
<label className="form-label">Select Employee</label>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("employeeId")}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<option value="">Loading...</option>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<option value="">-- Select --</option>
|
||||||
|
{employees.map((emp) => (
|
||||||
|
<option key={emp.id} value={emp.id}>
|
||||||
|
{emp.firstName} {emp.lastName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
{errors.employeeId && (
|
||||||
|
<div className="text-danger small">
|
||||||
|
{errors.employeeId.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button className="btn btn-sm btn-primary" disabled={isPending || loading}>
|
||||||
|
{isPending ? "Please Wait..." : "Update Permission"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Permissions */}
|
||||||
|
{ProjectModules.map((feature) => (
|
||||||
|
<div key={feature.id} className="row my-2">
|
||||||
|
<div className="col-12 text-start fw-semibold mb-2">
|
||||||
|
{feature.name}
|
||||||
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="row">
|
||||||
|
{feature.featurePermissions?.map((perm) => (
|
||||||
|
<div
|
||||||
|
className="col-12 col-sm-6 col-md-4 mb-2"
|
||||||
|
key={perm.id}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className="form-check-label d-flex align-items-center"
|
||||||
|
htmlFor={perm.id}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input me-2"
|
||||||
|
id={perm.id}
|
||||||
|
value={perm.id}
|
||||||
|
{...register("selectedPermissions")}
|
||||||
|
/>
|
||||||
|
{perm.name}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr className="my-2" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectPermission;
|
74
src/components/Project/ProjectSetting.jsx
Normal file
74
src/components/Project/ProjectSetting.jsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage";
|
||||||
|
import ProjectPermission from "./ProjectPermission";
|
||||||
|
|
||||||
|
const ProjectSetting = () => {
|
||||||
|
const [activePill, setActivePill] = useState(() => {
|
||||||
|
return localStorage.getItem("lastActiveProjectSettingTab") || "Permissions";
|
||||||
|
});
|
||||||
|
const projectSettingTab = [
|
||||||
|
{ key: "Permissions", label: "Permissions" },
|
||||||
|
{ key: "Notification", label: "Notification" },
|
||||||
|
{ key: "SeparatedLink", label: "Separated link", isButton: true },
|
||||||
|
];
|
||||||
|
const handlePillClick = (pillKey) => {
|
||||||
|
setActivePill(pillKey);
|
||||||
|
localStorage.setItem("lastActiveProjectSettingTab", pillKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
switch (activePill) {
|
||||||
|
case "Permissions":
|
||||||
|
return <ProjectPermission />;
|
||||||
|
|
||||||
|
case "Notification":
|
||||||
|
return <ComingSoonPage />;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <ComingSoonPage />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-100">
|
||||||
|
<div className="card p-3">
|
||||||
|
<div className="col-4">
|
||||||
|
<div className="dropdown text-start">
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||||
|
type="button"
|
||||||
|
id="dropdownMenuButton"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
{activePill || "Select Option"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul className="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||||
|
{projectSettingTab.map((item) =>
|
||||||
|
item.isButton ? (
|
||||||
|
<li key={item.key}>
|
||||||
|
<button className="dropdown-item">{item.label}</button>
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li key={item.key}>
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => handlePillClick(item.key)}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3">{renderContent()}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProjectSetting;
|
@ -18,12 +18,12 @@ import {
|
|||||||
useEmployeesByProjectAllocated,
|
useEmployeesByProjectAllocated,
|
||||||
useManageProjectAllocation,
|
useManageProjectAllocation,
|
||||||
} from "../../hooks/useProjects";
|
} from "../../hooks/useProjects";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const Teams = () => {
|
const Teams = () => {
|
||||||
// const {projectId} = useParams()
|
// const {projectId} = useParams()
|
||||||
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||||
const projectId = useSelectedproject();
|
const projectId = useSelectedProject();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { data, loading } = useMaster();
|
const { data, loading } = useMaster();
|
||||||
@ -249,26 +249,15 @@ const Teams = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{IsDeleteModal && (
|
{IsDeleteModal && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade ${IsDeleteModal ? "show" : ""}`}
|
isOpen={IsDeleteModal}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header="Removed Employee"
|
||||||
style={{
|
message="Are you sure you want delete?"
|
||||||
display: IsDeleteModal ? "block" : "none",
|
onSubmit={() => removeAllocation(deleteEmployee)}
|
||||||
backgroundColor: IsDeleteModal ? "rgba(0,0,0,0.5)" : "transparent",
|
onClose={closeDeleteModal}
|
||||||
}}
|
loading={isPending}
|
||||||
aria-hidden="false"
|
/>
|
||||||
>
|
|
||||||
<ConfirmModal
|
|
||||||
type={"delete"}
|
|
||||||
header={"Removed Employee"}
|
|
||||||
message={"Are you sure you want delete?"}
|
|
||||||
onSubmit={removeAllocation}
|
|
||||||
onClose={closeDeleteModal}
|
|
||||||
loading={isPending}
|
|
||||||
paramData={deleteEmployee}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card card-action mb-6">
|
<div className="card card-action mb-6">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
import { useFormContext,useForm,FormProvider } from 'react-hook-form';
|
import { useFormContext, useForm, FormProvider } from 'react-hook-form';
|
||||||
import { useIndustries, useTenantDetails, useUpdateTenantDetails } from '../../hooks/useTenant';
|
import { useIndustries, useTenantDetails, useUpdateTenantDetails } from '../../hooks/useTenant';
|
||||||
import { orgSize, reference } from '../../utils/constants';
|
import { orgSize, reference } from '../../utils/constants';
|
||||||
import { LogoUpload } from './LogoUpload';
|
import { LogoUpload } from './LogoUpload';
|
||||||
@ -8,18 +8,18 @@ import showToast from '../../services/toastService';
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { EditTenant } from './TenantSchema';
|
import { EditTenant } from './TenantSchema';
|
||||||
|
|
||||||
const EditProfile = ({ TenantId,onClose }) => {
|
const EditProfile = ({ TenantId, onClose }) => {
|
||||||
const { data, isLoading, isError, error } = useTenantDetails(TenantId);
|
const { data, isLoading, isError, error } = useTenantDetails(TenantId);
|
||||||
const [logoPreview, setLogoPreview] = useState(null);
|
const [logoPreview, setLogoPreview] = useState(null);
|
||||||
const [logoName, setLogoName] = useState("");
|
const [logoName, setLogoName] = useState("");
|
||||||
const { data: Industries, isLoading: industryLoading, isError: industryError } = useIndustries();
|
const { data: Industries, isLoading: industryLoading, isError: industryError } = useIndustries();
|
||||||
const {mutate:UpdateTenant,isPending,} = useUpdateTenantDetails(()=>{
|
const { mutate: UpdateTenant, isPending, } = useUpdateTenantDetails(() => {
|
||||||
showToast("Tenant Details Updated Successfully","success")
|
showToast("Tenant Details Updated Successfully", "success")
|
||||||
onClose()
|
onClose()
|
||||||
|
|
||||||
})
|
})
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver:zodResolver(EditTenant),
|
resolver: zodResolver(EditTenant),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
firstName: "",
|
firstName: "",
|
||||||
lastName: "",
|
lastName: "",
|
||||||
@ -40,8 +40,8 @@ const EditProfile = ({ TenantId,onClose }) => {
|
|||||||
const { register, reset, handleSubmit, formState: { errors } } = methods;
|
const { register, reset, handleSubmit, formState: { errors } } = methods;
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const tenantPayload = {...formData,contactName:`${formData.firstName} ${formData.lastName}`,id:data.id,}
|
const tenantPayload = { ...formData, contactName: `${formData.firstName} ${formData.lastName}`, id: data.id, }
|
||||||
UpdateTenant({id:data.id,tenantPayload})
|
UpdateTenant({ id: data.id, tenantPayload })
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -70,117 +70,117 @@ const EditProfile = ({ TenantId,onClose }) => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form className="row g-6" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-6" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<h6>Edit Tenant</h6>
|
<h6>Edit Tenant</h6>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="firstName" required>First Name</Label>
|
<Label htmlFor="firstName" required>First Name</Label>
|
||||||
<input id="firstName" type="text" className="form-control form-control-sm" {...register("firstName")} inputMode='text' />
|
<input id="firstName" type="text" className="form-control form-control-sm" {...register("firstName")} inputMode='text' />
|
||||||
{errors.firstName && <div className="danger-text">{errors.firstName.message}</div>}
|
{errors.firstName && <div className="danger-text">{errors.firstName.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="lastName" required>Last Name</Label>
|
<Label htmlFor="lastName" required>Last Name</Label>
|
||||||
<input id="lastName" type="text" className="form-control form-control-sm" {...register("lastName")} />
|
<input id="lastName" type="text" className="form-control form-control-sm" {...register("lastName")} />
|
||||||
{errors.lastName && <div className="danger-text">{errors.lastName.message}</div>}
|
{errors.lastName && <div className="danger-text">{errors.lastName.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="contactNumber" required>Contact Number</Label>
|
<Label htmlFor="contactNumber" required>Contact Number</Label>
|
||||||
<input id="contactNumber" type="text" className="form-control form-control-sm" {...register("contactNumber")} inputMode="tel"
|
<input id="contactNumber" type="text" className="form-control form-control-sm" {...register("contactNumber")} inputMode="tel"
|
||||||
placeholder="+91 9876543210" />
|
placeholder="+91 9876543210" />
|
||||||
{errors.contactNumber && <div className="danger-text">{errors.contactNumber.message}</div>}
|
{errors.contactNumber && <div className="danger-text">{errors.contactNumber.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="domainName" required>Domain Name</Label>
|
<Label htmlFor="domainName" >Domain Name</Label>
|
||||||
<input id="domainName" type="text" className="form-control form-control-sm" {...register("domainName")} />
|
<input id="domainName" type="text" className="form-control form-control-sm" {...register("domainName")} />
|
||||||
{errors.domainName && <div className="danger-text">{errors.domainName.message}</div>}
|
{errors.domainName && <div className="danger-text">{errors.domainName.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="taxId" required>Tax ID</Label>
|
<Label htmlFor="taxId" >Tax ID</Label>
|
||||||
<input id="taxId" type="text" className="form-control form-control-sm" {...register("taxId")} />
|
<input id="taxId" type="text" className="form-control form-control-sm" {...register("taxId")} />
|
||||||
{errors.taxId && <div className="danger-text">{errors.taxId.message}</div>}
|
{errors.taxId && <div className="danger-text">{errors.taxId.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="officeNumber" required>Office Number</Label>
|
<Label htmlFor="officeNumber" >Office Number</Label>
|
||||||
<input id="officeNumber" type="text" className="form-control form-control-sm" {...register("officeNumber")} />
|
<input id="officeNumber" type="text" className="form-control form-control-sm" {...register("officeNumber")} />
|
||||||
{errors.officeNumber && <div className="danger-text">{errors.officeNumber.message}</div>}
|
{errors.officeNumber && <div className="danger-text">{errors.officeNumber.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="industryId" required>Industry</Label>
|
<Label htmlFor="industryId" required>Industry</Label>
|
||||||
<select className="form-select form-select-sm" {...register("industryId")}>
|
<select className="form-select form-select-sm" {...register("industryId")}>
|
||||||
{industryLoading ? <option value="">Loading...</option> :
|
{industryLoading ? <option value="">Loading...</option> :
|
||||||
Industries?.map((indu) => (
|
Industries?.map((indu) => (
|
||||||
<option key={indu.id} value={indu.id}>{indu.name}</option>
|
<option key={indu.id} value={indu.id}>{indu.name}</option>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
{errors.industryId && <div className="danger-text">{errors.industryId.message}</div>}
|
{errors.industryId && <div className="danger-text">{errors.industryId.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6 mt-1">
|
<div className="col-sm-6 mt-1">
|
||||||
<Label htmlFor="reference">Reference</Label>
|
<Label htmlFor="reference">Reference</Label>
|
||||||
<select className="form-select form-select-sm" {...register("reference")}>
|
<select className="form-select form-select-sm" {...register("reference")}>
|
||||||
{reference.map((org) => (
|
{reference.map((org) => (
|
||||||
<option key={org.val} value={org.val}>{org.name}</option>
|
<option key={org.val} value={org.val}>{org.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{errors.reference && <div className="danger-text">{errors.reference.message}</div>}
|
{errors.reference && <div className="danger-text">{errors.reference.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<Label htmlFor="organizationSize" required>
|
<Label htmlFor="organizationSize" required>
|
||||||
Organization Size
|
Organization Size
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm"
|
className="form-select form-select-sm"
|
||||||
{...register("organizationSize")}
|
{...register("organizationSize")}
|
||||||
>
|
>
|
||||||
{orgSize.map((org) => (
|
{orgSize.map((org) => (
|
||||||
<option key={org.val} value={org.val}>
|
<option key={org.val} value={org.val}>
|
||||||
{org.name}
|
{org.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{errors.organizationSize && (
|
{errors.organizationSize && (
|
||||||
<div className="danger-text">{errors.organizationSize.message}</div>
|
<div className="danger-text">{errors.organizationSize.message}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 mt-1">
|
<div className="col-12 mt-1">
|
||||||
<Label htmlFor="billingAddress" required>Billing Address</Label>
|
<Label htmlFor="billingAddress" required>Billing Address</Label>
|
||||||
<textarea id="billingAddress" className="form-control" {...register("billingAddress")} rows={2} />
|
<textarea id="billingAddress" className="form-control" {...register("billingAddress")} rows={2} />
|
||||||
{errors.billingAddress && <div className="danger-text">{errors.billingAddress.message}</div>}
|
{errors.billingAddress && <div className="danger-text">{errors.billingAddress.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 mt-1">
|
<div className="col-12 mt-1">
|
||||||
<Label htmlFor="description">Description</Label>
|
<Label htmlFor="description">Description</Label>
|
||||||
<textarea id="description" className="form-control" {...register("description")} rows={2} />
|
<textarea id="description" className="form-control" {...register("description")} rows={2} />
|
||||||
{errors.description && <div className="danger-text">{errors.description.message}</div>}
|
{errors.description && <div className="danger-text">{errors.description.message}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<Label htmlFor="logImage">Logo Image</Label>
|
<Label htmlFor="logImage">Logo Image</Label>
|
||||||
<LogoUpload
|
<LogoUpload
|
||||||
preview={logoPreview}
|
preview={logoPreview}
|
||||||
setPreview={setLogoPreview}
|
setPreview={setLogoPreview}
|
||||||
fileName={logoName}
|
fileName={logoName}
|
||||||
setFileName={setLogoName}
|
setFileName={setLogoName}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex justify-content-center gap-2 mt-3">
|
<div className="d-flex justify-content-center gap-2 mt-3">
|
||||||
<button type="submit" disabled={isPending} className="btn btn-sm btn-primary">{isPending ? "Please Wait..." : "Submit"}</button>
|
<button type="submit" disabled={isPending} className="btn btn-sm btn-primary">{isPending ? "Please Wait..." : "Submit"}</button>
|
||||||
<button type="button" disabled={isPending} className="btn btn-sm btn-secondary" onClick={onClose}>Cancel</button>
|
<button type="button" disabled={isPending} className="btn btn-sm btn-secondary" onClick={onClose}>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -48,7 +48,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
const data = getValues();
|
const data = getValues();
|
||||||
// onSubmitTenant(data);
|
// onSubmitTenant(data);
|
||||||
// onNext();
|
// onNext();
|
||||||
const tenantPayload = {...data,onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
|
const tenantPayload = { ...data, onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
|
||||||
CreateTenant(tenantPayload);
|
CreateTenant(tenantPayload);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -73,7 +73,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<Label htmlFor="officeNumber" required>
|
<Label htmlFor="officeNumber" >
|
||||||
Office Number
|
Office Number
|
||||||
</Label>
|
</Label>
|
||||||
<input
|
<input
|
||||||
@ -87,7 +87,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<Label htmlFor="domainName" required>
|
<Label htmlFor="domainName" >
|
||||||
Domain Name
|
Domain Name
|
||||||
</Label>
|
</Label>
|
||||||
<input
|
<input
|
||||||
@ -101,7 +101,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<Label htmlFor="taxId" required>
|
<Label htmlFor="taxId" >
|
||||||
Tax ID
|
Tax ID
|
||||||
</Label>
|
</Label>
|
||||||
<input
|
<input
|
||||||
@ -138,8 +138,10 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm"
|
id="organizationSize"
|
||||||
{...register("organizationSize")}
|
className="form-select shadow-none border py-1 px-2"
|
||||||
|
style={{ fontSize: "0.875rem" }} // Bootstrap's small text size
|
||||||
|
{...register("organizationSize", { required: "Organization size is required" })}
|
||||||
>
|
>
|
||||||
{orgSize.map((org) => (
|
{orgSize.map((org) => (
|
||||||
<option key={org.val} value={org.val}>
|
<option key={org.val} value={org.val}>
|
||||||
@ -147,17 +149,20 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
{errors.organizationSize && (
|
{errors.organizationSize && (
|
||||||
<div className="danger-text">{errors.organizationSize.message}</div>
|
<div className="danger-text">{errors.organizationSize.message}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<Label htmlFor="industryId" required>
|
<Label htmlFor="industryId" required>
|
||||||
Industry
|
Industry
|
||||||
</Label>
|
</Label>
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm"
|
id="industryId"
|
||||||
|
className="form-select shadow-none border py-1 px-2 small"
|
||||||
{...register("industryId")}
|
{...register("industryId")}
|
||||||
>
|
>
|
||||||
{industryLoading ? (
|
{industryLoading ? (
|
||||||
@ -177,9 +182,9 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
|
|
||||||
<div className="col-sm-6">
|
<div className="col-sm-6">
|
||||||
<Label htmlFor="reference">Reference</Label>
|
<Label htmlFor="reference">Reference</Label>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
className="form-select form-select-sm"
|
id="reference"
|
||||||
|
className="form-select shadow-none border py-1 px-2 small"
|
||||||
{...register("reference")}
|
{...register("reference")}
|
||||||
>
|
>
|
||||||
{reference.map((org) => (
|
{reference.map((org) => (
|
||||||
@ -193,6 +198,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="col-sm-12">
|
<div className="col-sm-12">
|
||||||
<Label htmlFor="description">Description</Label>
|
<Label htmlFor="description">Description</Label>
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import React, { useState,useCallback } from "react";
|
import React, { useState, useCallback, useEffect } from "react";
|
||||||
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||||
import { defaultFilterValues, filterSchema } from "./TenantSchema";
|
import { defaultFilterValues, filterSchema } from "./TenantSchema";
|
||||||
import Label from "../common/Label";
|
import Label from "../common/Label";
|
||||||
@ -8,15 +8,16 @@ import { useIndustries } from "../../hooks/useTenant";
|
|||||||
import { reference, TENANT_STATUS } from "../../utils/constants";
|
import { reference, TENANT_STATUS } from "../../utils/constants";
|
||||||
import { DateRangePicker1 } from "../common/DateRangePicker";
|
import { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
const TenantFilterPanel = ({ onApply }) => {
|
||||||
|
const [resetKey, setResetKey] = useState(0);
|
||||||
|
|
||||||
const TenantFilterPanel = ({onApply}) => {
|
|
||||||
const [resetKey, setResetKey] = useState(0);
|
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver: zodResolver(filterSchema),
|
resolver: zodResolver(filterSchema),
|
||||||
defaultValues: defaultFilterValues,
|
defaultValues: defaultFilterValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { handleSubmit, reset } = methods;
|
const { handleSubmit, reset } = methods;
|
||||||
const { data: industries = [], isLoading } = useIndustries();
|
const { data: industries = [], isLoading } = useIndustries();
|
||||||
|
|
||||||
@ -36,6 +37,13 @@ const [resetKey, setResetKey] = useState(0);
|
|||||||
[onApply, handleClosePanel]
|
[onApply, handleClosePanel]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// ✅ Close popup when navigating to another component
|
||||||
|
const location = useLocation();
|
||||||
|
useEffect(() => {
|
||||||
|
handleClosePanel();
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
const onClear = useCallback(() => {
|
const onClear = useCallback(() => {
|
||||||
reset(defaultFilterValues);
|
reset(defaultFilterValues);
|
||||||
setResetKey((prev) => prev + 1); // triggers DateRangePicker reset
|
setResetKey((prev) => prev + 1); // triggers DateRangePicker reset
|
||||||
@ -48,44 +56,44 @@ const [resetKey, setResetKey] = useState(0);
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="text-start mb-1">
|
<div className="text-start mb-1">
|
||||||
<div className="text-start my-2">
|
<div className="text-start my-2">
|
||||||
<DateRangePicker1
|
<DateRangePicker1
|
||||||
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||||
startField="startDate"
|
startField="startDate"
|
||||||
endField="endDate"
|
endField="endDate"
|
||||||
resetSignal={resetKey}
|
resetSignal={resetKey}
|
||||||
defaultRange={false}
|
defaultRange={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-strat mb-2">
|
<div className="text-strat mb-2">
|
||||||
<SelectMultiple
|
<SelectMultiple
|
||||||
name="industryIds"
|
name="industryIds"
|
||||||
label="Industries"
|
label="Industries"
|
||||||
options={industries}
|
options={industries}
|
||||||
labelKey="name"
|
labelKey="name"
|
||||||
valueKey="id"
|
valueKey="id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-start mb-2">
|
<div className="text-start mb-2">
|
||||||
<SelectMultiple
|
<SelectMultiple
|
||||||
name="references"
|
name="references"
|
||||||
label="References"
|
label="References"
|
||||||
options={reference}
|
options={reference}
|
||||||
labelKey="name"
|
labelKey="name"
|
||||||
valueKey="val"
|
valueKey="val"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
<SelectMultiple
|
<SelectMultiple
|
||||||
name="tenantStatusIds"
|
name="tenantStatusIds"
|
||||||
label="Tenant Status"
|
label="Tenant Status"
|
||||||
options={TENANT_STATUS}
|
options={TENANT_STATUS}
|
||||||
labelKey="name"
|
labelKey="name"
|
||||||
valueKey="id"
|
valueKey="id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* <SelectMultiple
|
{/* <SelectMultiple
|
||||||
name="references"
|
name="references"
|
||||||
@ -100,7 +108,7 @@ const [resetKey, setResetKey] = useState(0);
|
|||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary btn-xs"
|
className="btn btn-secondary btn-xs"
|
||||||
onClick={onClear}
|
onClick={onClear}
|
||||||
|
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
|
@ -2,34 +2,34 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export const newTenantSchema = z.object({
|
export const newTenantSchema = z.object({
|
||||||
firstName: z
|
firstName: z
|
||||||
.string().trim()
|
.string().trim()
|
||||||
.min(1, { message: "First Name is required!" })
|
.min(1, { message: "First Name is required!" })
|
||||||
.regex(/^[A-Za-z]+$/, { message: "First Name should contain only letters!" }),
|
.regex(/^[A-Za-z]+$/, { message: "First Name should contain only letters!" }),
|
||||||
lastName: z
|
lastName: z
|
||||||
.string().trim()
|
.string().trim()
|
||||||
.min(1, { message: "Last Name is required!" })
|
.min(1, { message: "Last Name is required!" })
|
||||||
.regex(/^[A-Za-z]+$/, { message: "Last Name should contain only letters!" }),
|
.regex(/^[A-Za-z]+$/, { message: "Last Name should contain only letters!" }),
|
||||||
email: z.string().trim().email("Invalid email address"),
|
email: z.string().trim().email("Invalid email address"),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
domainName: z.string().trim().nonempty("Domain name is required"),
|
domainName: z.string().trim().optional(),
|
||||||
billingAddress: z.string().trim().nonempty("Billing address is required"),
|
billingAddress: z.string().trim().nonempty("Billing address is required"),
|
||||||
taxId: z.string().trim().nonempty("Tax ID is required"),
|
taxId: z.string().trim().optional(),
|
||||||
logoImage: z.string().trim().optional(),
|
logoImage: z.string().trim().optional(),
|
||||||
organizationName: z.string().trim().nonempty("Organization name is required"),
|
organizationName: z.string().trim().nonempty("Organization name is required"),
|
||||||
officeNumber: z.string().trim().nonempty("Office number is required"),
|
officeNumber: z.string().trim().optional(),
|
||||||
contactNumber: z.string().trim()
|
contactNumber: z.string().trim()
|
||||||
.nonempty("Contact number is required")
|
.nonempty("Contact number is required")
|
||||||
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
||||||
onBoardingDate: z.preprocess((val) => {
|
onBoardingDate: z.preprocess((val) => {
|
||||||
if (typeof val === "string" && val.includes("-")) {
|
if (typeof val === "string" && val.includes("-")) {
|
||||||
const [day, month, year] = val.split("-");
|
const [day, month, year] = val.split("-");
|
||||||
return new Date(`${year}-${month}-${day}`);
|
return new Date(`${year}-${month}-${day}`);
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}, z.date({
|
}, z.date({
|
||||||
required_error: "Onboarding date is required",
|
required_error: "Onboarding date is required",
|
||||||
invalid_type_error: "Invalid date format",
|
invalid_type_error: "Invalid date format",
|
||||||
})),
|
})),
|
||||||
organizationSize: z.string().nonempty("Organization size is required"),
|
organizationSize: z.string().nonempty("Organization size is required"),
|
||||||
industryId: z.string().uuid("Invalid industry ID"),
|
industryId: z.string().uuid("Invalid industry ID"),
|
||||||
reference: z.string().nonempty("Reference is required"),
|
reference: z.string().nonempty("Reference is required"),
|
||||||
@ -71,8 +71,8 @@ export const getSubscriptionSchema = (minUsers) =>
|
|||||||
|
|
||||||
export const subscriptionDefaultValues = {
|
export const subscriptionDefaultValues = {
|
||||||
// tenantId: "",
|
// tenantId: "",
|
||||||
planId: "",
|
planId: "",
|
||||||
currencyId: "",
|
currencyId: "",
|
||||||
maxUsers: 1,
|
maxUsers: 1,
|
||||||
frequency: 1,
|
frequency: 1,
|
||||||
isTrial: false,
|
isTrial: false,
|
||||||
@ -84,8 +84,8 @@ export const filterSchema = z.object({
|
|||||||
// createdByIds: z.array(z.string()).optional(),
|
// createdByIds: z.array(z.string()).optional(),
|
||||||
tenantStatusIds: z.array(z.string()).optional(),
|
tenantStatusIds: z.array(z.string()).optional(),
|
||||||
references: z.array(z.string()).optional(),
|
references: z.array(z.string()).optional(),
|
||||||
startDate: z.string().optional(),
|
startDate: z.string().optional(),
|
||||||
endDate: z.string().optional(),
|
endDate: z.string().optional(),
|
||||||
});
|
});
|
||||||
export const defaultFilterValues = {
|
export const defaultFilterValues = {
|
||||||
industryIds: [],
|
industryIds: [],
|
||||||
@ -133,23 +133,23 @@ export const getStepFields = (stepIndex) => {
|
|||||||
|
|
||||||
export const EditTenant = z.object({
|
export const EditTenant = z.object({
|
||||||
firstName: z
|
firstName: z
|
||||||
.string().trim()
|
.string().trim()
|
||||||
.min(1, { message: "First Name is required!" })
|
.min(1, { message: "First Name is required!" })
|
||||||
.regex(/^[A-Za-z]+$/, { message: "First Name should contain only letters!" }),
|
.regex(/^[A-Za-z]+$/, { message: "First Name should contain only letters!" }),
|
||||||
lastName: z
|
lastName: z
|
||||||
.string().trim()
|
.string().trim()
|
||||||
.min(1, { message: "Last Name is required!" })
|
.min(1, { message: "Last Name is required!" })
|
||||||
.regex(/^[A-Za-z]+$/, { message: "Last Name should contain only letters!" }),
|
.regex(/^[A-Za-z]+$/, { message: "Last Name should contain only letters!" }),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
domainName: z.string().trim().min(1, { message: "Domain Name is required!" }),
|
domainName: z.string().trim().optional(),
|
||||||
billingAddress: z.string().trim().min(1, { message: "Billing Address is required!" }),
|
billingAddress: z.string().trim().min(1, { message: "Billing Address is required!" }),
|
||||||
taxId: z.string().trim().min(1, { message: "Tax ID is required!" }),
|
taxId: z.string().trim().optional(),
|
||||||
logoImage: z.string().optional(),
|
logoImage: z.string().optional(),
|
||||||
officeNumber: z.string().trim().min(1, { message: "Office Number is required!" }),
|
officeNumber: z.string().trim().optional(),
|
||||||
contactNumber: z.string().trim()
|
contactNumber: z.string().trim()
|
||||||
.nonempty("Contact number is required")
|
.nonempty("Contact number is required")
|
||||||
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
.regex(/^\+?[1-9]\d{7,14}$/, "Enter a valid contact number"),
|
||||||
organizationSize: z.string().min(1, { message: "Organization Size is required!" }),
|
organizationSize: z.string().min(1, { message: "Organization Size is required!" }),
|
||||||
industryId: z.string().min(1,{ message: "Invalid Industry ID!" }),
|
industryId: z.string().min(1, { message: "Invalid Industry ID!" }),
|
||||||
reference: z.string().optional(),
|
reference: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
@ -1,70 +1,71 @@
|
|||||||
import React, { useState } from 'react';
|
import React from "react";
|
||||||
|
|
||||||
const ConfirmModal = ({ type, onSubmit, onClose, message, loading ,header, paramData}) => {
|
const ConfirmModal = ({
|
||||||
|
type,
|
||||||
const TypeofIcon = (type) => {
|
onSubmit,
|
||||||
switch (type) {
|
onClose,
|
||||||
case "delete":
|
message,
|
||||||
return <i className='bx bx-x-circle text-danger ' style={{fontSize:"60px"}} ></i>;
|
loading,
|
||||||
default:
|
header,
|
||||||
return null;
|
paramData,
|
||||||
|
isOpen = false,
|
||||||
|
}) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const TypeofIcon = () => {
|
||||||
|
if (type === "delete") {
|
||||||
|
return (
|
||||||
|
<i
|
||||||
|
className="bx bx-x-circle text-danger"
|
||||||
|
style={{ fontSize: "60px" }}
|
||||||
|
></i>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TypeofModal = (type) => {
|
const modalSize = type === "delete" ? "sm" : "md";
|
||||||
switch (type) {
|
|
||||||
case "delete":
|
|
||||||
return "sm";
|
|
||||||
case "other":
|
|
||||||
return "md";
|
|
||||||
default:
|
|
||||||
return "sm";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`modal-dialog modal-${TypeofModal(type)} modal-simple modal-confirm`}>
|
<div
|
||||||
<div className='modal-dialog modal-dialog-centered'>
|
className="modal fade show"
|
||||||
|
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
>
|
||||||
|
<div className={`modal-dialog modal-${modalSize} modal-dialog-top`}>
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-body py-1 px-2">
|
<div className="modal-body py-1 px-2">
|
||||||
<div className="row">
|
<div className="d-flex justify-content-between mb-4 pt-2">
|
||||||
|
{header && <strong className="mb-0">{header}</strong>}
|
||||||
<div className="text-start mb-1">
|
|
||||||
|
|
||||||
<div className=' d-flex justify-content-between mb-4'>
|
|
||||||
{header && < strong className='mb-0 font-weight-bold'>{header }</strong>}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-close"
|
className="btn-close "
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='row'>
|
<div className="row">
|
||||||
<div className='col-4 col-sm-2'> {TypeofIcon(type)}</div>
|
<div className="col-4 col-sm-2">{TypeofIcon()}</div>
|
||||||
<div className='col-8 col-sm-10 py-sm-2 py-1 text-sm-start'>
|
<div className="col-8 col-sm-10 py-sm-2 py-1 text-sm-start">
|
||||||
<span className='fs-6 text'>{message}</span>
|
<span className="fs-6">{message}</span>
|
||||||
<div className='d-flex justify-content-end mt-4'>
|
<div className="d-flex justify-content-end mt-4">
|
||||||
<button
|
<button
|
||||||
className='btn btn-primary btn-sm'
|
className="btn btn-primary btn-sm"
|
||||||
onClick={()=>onSubmit(paramData)}
|
onClick={() => onSubmit(paramData)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? "Please Wait..." : "Yes"}
|
{loading ? "Please Wait..." : "Yes"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className='btn btn-secondary ms-4 btn-sm'
|
className="btn btn-secondary ms-4 btn-sm"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
No
|
No
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,6 +46,12 @@ const DateRangePicker = ({
|
|||||||
};
|
};
|
||||||
}, [onRangeChange, DateDifference, endDateMode]);
|
}, [onRangeChange, DateDifference, endDateMode]);
|
||||||
|
|
||||||
|
const handleIconClick = () => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current._flatpickr.open(); // directly opens flatpickr
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`col-${sm} col-sm-${md} px-1`}>
|
<div className={`col-${sm} col-sm-${md} px-1`}>
|
||||||
<input
|
<input
|
||||||
@ -57,7 +63,7 @@ const DateRangePicker = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<i
|
<i
|
||||||
className="bx bx-calendar calendar-icon cursor-pointer position-relative top-50 translate-middle-y "
|
className="bx bx-calendar calendar-icon cursor-pointer position-relative top-50 translate-middle-y " onClick={handleIconClick}
|
||||||
style={{ right: "22px", bottom: "-8px" }}
|
style={{ right: "22px", bottom: "-8px" }}
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
@ -77,7 +83,7 @@ export const DateRangePicker1 = ({
|
|||||||
className = "",
|
className = "",
|
||||||
allowText = false,
|
allowText = false,
|
||||||
resetSignal,
|
resetSignal,
|
||||||
defaultRange = true,
|
defaultRange = true,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
const FilterIcon = ({
|
const FilterIcon = ({
|
||||||
taskListData,
|
taskListData,
|
||||||
@ -7,15 +8,26 @@ const FilterIcon = ({
|
|||||||
currentSelectedFloors,
|
currentSelectedFloors,
|
||||||
currentSelectedActivities,
|
currentSelectedActivities,
|
||||||
}) => {
|
}) => {
|
||||||
|
const selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||||
|
|
||||||
const [selectedBuilding, setSelectedBuilding] = useState(currentSelectedBuilding || "");
|
const [selectedBuilding, setSelectedBuilding] = useState(currentSelectedBuilding || "");
|
||||||
const [selectedFloors, setSelectedFloors] = useState(currentSelectedFloors || []);
|
const [selectedFloors, setSelectedFloors] = useState(currentSelectedFloors || []);
|
||||||
const [selectedActivities, setSelectedActivities] = useState(currentSelectedActivities || []);
|
const [selectedActivities, setSelectedActivities] = useState(currentSelectedActivities || []);
|
||||||
|
|
||||||
|
const [appliedBuilding, setAppliedBuilding] = useState(currentSelectedBuilding || "");
|
||||||
|
const [appliedFloors, setAppliedFloors] = useState(currentSelectedFloors || []);
|
||||||
|
const [appliedActivities, setAppliedActivities] = useState(currentSelectedActivities || []);
|
||||||
|
|
||||||
|
// Reset filters whenever inputs OR projectId changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedBuilding(currentSelectedBuilding || "");
|
setSelectedBuilding(currentSelectedBuilding || "");
|
||||||
setSelectedFloors(currentSelectedFloors || []);
|
setSelectedFloors(currentSelectedFloors || []);
|
||||||
setSelectedActivities(currentSelectedActivities || []);
|
setSelectedActivities(currentSelectedActivities || []);
|
||||||
}, [currentSelectedBuilding, currentSelectedFloors, currentSelectedActivities]);
|
|
||||||
|
setAppliedBuilding(currentSelectedBuilding || "");
|
||||||
|
setAppliedFloors(currentSelectedFloors || []);
|
||||||
|
setAppliedActivities(currentSelectedActivities || []);
|
||||||
|
}, [currentSelectedBuilding, currentSelectedFloors, currentSelectedActivities, selectedProject]);
|
||||||
|
|
||||||
const getUniqueFilterValues = (key, overrideBuilding, overrideFloors) => {
|
const getUniqueFilterValues = (key, overrideBuilding, overrideFloors) => {
|
||||||
if (!taskListData) return [];
|
if (!taskListData) return [];
|
||||||
@ -61,12 +73,11 @@ const FilterIcon = ({
|
|||||||
} else if (filterType === "floor") {
|
} else if (filterType === "floor") {
|
||||||
if (updatedFloors.includes(value)) {
|
if (updatedFloors.includes(value)) {
|
||||||
updatedFloors = updatedFloors.filter((floor) => floor !== value);
|
updatedFloors = updatedFloors.filter((floor) => floor !== value);
|
||||||
|
const validActivities = getUniqueFilterValues("activity", updatedBuilding, updatedFloors);
|
||||||
|
updatedActivities = updatedActivities.filter((act) => validActivities.includes(act));
|
||||||
} else {
|
} else {
|
||||||
updatedFloors.push(value);
|
updatedFloors.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validActivities = getUniqueFilterValues("activity", updatedBuilding, updatedFloors);
|
|
||||||
updatedActivities = updatedActivities.filter((act) => validActivities.includes(act));
|
|
||||||
} else if (filterType === "activity") {
|
} else if (filterType === "activity") {
|
||||||
if (updatedActivities.includes(value)) {
|
if (updatedActivities.includes(value)) {
|
||||||
updatedActivities = updatedActivities.filter((act) => act !== value);
|
updatedActivities = updatedActivities.filter((act) => act !== value);
|
||||||
@ -78,12 +89,20 @@ const FilterIcon = ({
|
|||||||
setSelectedBuilding(updatedBuilding);
|
setSelectedBuilding(updatedBuilding);
|
||||||
setSelectedFloors(updatedFloors);
|
setSelectedFloors(updatedFloors);
|
||||||
setSelectedActivities(updatedActivities);
|
setSelectedActivities(updatedActivities);
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyFilters = () => {
|
||||||
|
setAppliedBuilding(selectedBuilding);
|
||||||
|
setAppliedFloors(selectedFloors);
|
||||||
|
setAppliedActivities(selectedActivities);
|
||||||
|
|
||||||
onApplyFilters({
|
onApplyFilters({
|
||||||
selectedBuilding: updatedBuilding,
|
selectedBuilding,
|
||||||
selectedFloors: updatedFloors,
|
selectedFloors,
|
||||||
selectedActivities: updatedActivities,
|
selectedActivities,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("filterDropdown").click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAllFilters = () => {
|
const clearAllFilters = () => {
|
||||||
@ -91,6 +110,10 @@ const FilterIcon = ({
|
|||||||
setSelectedFloors([]);
|
setSelectedFloors([]);
|
||||||
setSelectedActivities([]);
|
setSelectedActivities([]);
|
||||||
|
|
||||||
|
setAppliedBuilding("");
|
||||||
|
setAppliedFloors([]);
|
||||||
|
setAppliedActivities([]);
|
||||||
|
|
||||||
onApplyFilters({
|
onApplyFilters({
|
||||||
selectedBuilding: "",
|
selectedBuilding: "",
|
||||||
selectedFloors: [],
|
selectedFloors: [],
|
||||||
@ -98,21 +121,51 @@ const FilterIcon = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Count applied filters
|
||||||
|
const appliedFilterCount =
|
||||||
|
(appliedBuilding ? 1 : 0) + appliedFloors.length + appliedActivities.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dropdown" style={{marginLeft:"-14px"}}>
|
<div className="dropdown" style={{ marginLeft: "-14px", position: "relative" }}>
|
||||||
<a
|
<a
|
||||||
className="dropdown-toggle hide-arrow cursor-pointer"
|
className="dropdown-toggle hide-arrow cursor-pointer"
|
||||||
id="filterDropdown"
|
id="filterDropdown"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
{/* <i className="bx bx-slider-alt ms-1" /> */}
|
<div style={{ position: "relative", display: "inline-block" }}>
|
||||||
<i
|
<i
|
||||||
className="bx bx-slider-alt"
|
className="bx bx-slider-alt"
|
||||||
style={{ color: selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0 ? "#7161EF" : "gray" }}
|
style={{
|
||||||
></i>
|
color: appliedFilterCount > 0 ? "#7161EF" : "gray",
|
||||||
|
fontSize: "20px",
|
||||||
|
}}
|
||||||
|
></i>
|
||||||
|
{appliedFilterCount > 0 && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-11px",
|
||||||
|
right: "-6px",
|
||||||
|
backgroundColor: "#FFC107", // yellow
|
||||||
|
color: "white",
|
||||||
|
fontSize: "10px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: "18px",
|
||||||
|
height: "18px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
border: "1px solid white",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{appliedFilterCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
className="dropdown-menu p-2 mt-2"
|
className="dropdown-menu p-2 mt-2"
|
||||||
aria-labelledby="filterDropdown"
|
aria-labelledby="filterDropdown"
|
||||||
@ -205,45 +258,30 @@ const FilterIcon = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Buttons */}
|
||||||
<li><hr className="my-1" /></li>
|
<li>
|
||||||
{(selectedBuilding || selectedFloors.length > 0 || selectedActivities.length > 0) && (
|
<hr className="my-1" />
|
||||||
<li className="d-flex justify-content-end gap-2 px-2">
|
</li>
|
||||||
|
{(appliedFilterCount > 0 ||
|
||||||
|
selectedBuilding ||
|
||||||
|
selectedFloors.length > 0 ||
|
||||||
|
selectedActivities.length > 0) && (
|
||||||
|
<li className="d-flex justify-content-end gap-2 px-2 mt-2 mb-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm"
|
className="btn btn-secondary btn-sm py-0 px-2"
|
||||||
style={{
|
|
||||||
backgroundColor: "#7161EF",
|
|
||||||
color: "white",
|
|
||||||
fontSize: "13px",
|
|
||||||
padding: "4px 16px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
boxShadow: "0 1px 4px rgba(0,0,0,0.1)"
|
|
||||||
}}
|
|
||||||
onClick={clearAllFilters}
|
onClick={clearAllFilters}
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm"
|
className="btn btn-primary btn-sm py-0 px-2"
|
||||||
style={{
|
onClick={applyFilters}
|
||||||
backgroundColor: "#7161EF",
|
|
||||||
color: "white",
|
|
||||||
fontSize: "13px",
|
|
||||||
padding: "4px 16px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
boxShadow: "0 1px 4px rgba(0,0,0,0.1)"
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
document.getElementById("filterDropdown").click();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,111 +2,67 @@ import { useFormContext, useWatch } from "react-hook-form";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Label from "./Label";
|
import Label from "./Label";
|
||||||
|
|
||||||
const TagInput = ({
|
const TagInput = ({ label, name, placeholder, color = "#e9ecef", options = [] }) => {
|
||||||
label = "Tags",
|
const { setValue, watch } = useFormContext();
|
||||||
name = "tags",
|
const tags = watch(name) || [];
|
||||||
placeholder = "Start typing to add... like employee, manager",
|
|
||||||
color = "#e9ecef",
|
|
||||||
options = [],
|
|
||||||
}) => {
|
|
||||||
const [tags, setTags] = useState([]);
|
|
||||||
const [input, setInput] = useState("");
|
const [input, setInput] = useState("");
|
||||||
const [suggestions, setSuggestions] = useState([]);
|
const [suggestions, setSuggestions] = useState([]);
|
||||||
const { setValue, trigger, control } = useFormContext();
|
|
||||||
const watchedTags = useWatch({ control, name });
|
|
||||||
|
|
||||||
|
const handleAdd = (newTag) => {
|
||||||
|
const tagObj = typeof newTag === "string" ? { name: newTag, isActive: true } : newTag;
|
||||||
|
|
||||||
useEffect(() => {
|
if (!tags.some((t) => t.name === tagObj.name)) {
|
||||||
if (
|
setValue(name, [...tags, tagObj], { shouldValidate: true });
|
||||||
Array.isArray(watchedTags) &&
|
|
||||||
JSON.stringify(tags) !== JSON.stringify(watchedTags)
|
|
||||||
) {
|
|
||||||
setTags(watchedTags);
|
|
||||||
}
|
|
||||||
}, [JSON.stringify(watchedTags)]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (input.trim() === "") {
|
|
||||||
setSuggestions([]);
|
|
||||||
} else {
|
|
||||||
const filtered = options?.filter(
|
|
||||||
(opt) =>
|
|
||||||
opt?.name?.toLowerCase()?.includes(input.toLowerCase()) &&
|
|
||||||
!tags?.some((tag) => tag.name === opt.name)
|
|
||||||
);
|
|
||||||
setSuggestions(filtered);
|
|
||||||
}
|
}
|
||||||
}, [input, options, tags]);
|
};
|
||||||
|
|
||||||
const addTag = async (tagObj) => {
|
const handleRemove = (tagName) => {
|
||||||
if (!tags.some((tag) => tag.name === tagObj.name)) {
|
setValue(
|
||||||
const cleanedTag = {
|
name,
|
||||||
id: tagObj.id ?? null,
|
tags.filter((t) => t.name !== tagName),
|
||||||
name: tagObj.name,
|
{ shouldValidate: true }
|
||||||
};
|
);
|
||||||
const newTags = [...tags, cleanedTag];
|
};
|
||||||
setTags(newTags);
|
|
||||||
setValue(name, newTags, { shouldValidate: true });
|
const handleKeyDown = (e) => {
|
||||||
await trigger(name);
|
if (e.key === "Enter" && input.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleAdd(input.trim());
|
||||||
setInput("");
|
setInput("");
|
||||||
setSuggestions([]);
|
setSuggestions([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTag = (indexToRemove) => {
|
const handleChange = (e) => {
|
||||||
const newTags = tags.filter((_, i) => i !== indexToRemove);
|
const val = e.target.value;
|
||||||
setTags(newTags);
|
setInput(val);
|
||||||
setValue(name, newTags, { shouldValidate: true });
|
|
||||||
trigger(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputKeyDown = (e) => {
|
if (val) {
|
||||||
if ((e.key === "Enter" || e.key === " ")&& input.trim() !== "") {
|
setSuggestions(
|
||||||
e.preventDefault();
|
options
|
||||||
const existing = options.find(
|
.filter((opt) => {
|
||||||
(opt) => opt.name.toLowerCase() === input.trim().toLowerCase()
|
const label = typeof opt === "string" ? opt : opt.name;
|
||||||
);
|
return (
|
||||||
const newTag = existing
|
label.toLowerCase().includes(val.toLowerCase()) &&
|
||||||
? existing
|
!tags.some((t) => t.name === label)
|
||||||
: {
|
);
|
||||||
id: null,
|
})
|
||||||
name: input.trim(),
|
.map((opt) => ({
|
||||||
description: input.trim(),
|
name: typeof opt === "string" ? opt : opt.name,
|
||||||
};
|
isActive: true,
|
||||||
addTag(newTag);
|
}))
|
||||||
} else if (e.key === "Backspace" && input === "") {
|
|
||||||
setTags((prev) => prev.slice(0, -1));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleInputKey = (e) => {
|
|
||||||
const key = e.key?.toLowerCase();
|
|
||||||
|
|
||||||
if ((key === "enter" || key === " " || e.code === "Space") && input.trim() !== "") {
|
|
||||||
e.preventDefault();
|
|
||||||
const existing = options.find(
|
|
||||||
(opt) => opt.name.toLowerCase() === input.trim().toLowerCase()
|
|
||||||
);
|
);
|
||||||
const newTag = existing
|
} else {
|
||||||
? existing
|
setSuggestions([]);
|
||||||
: {
|
|
||||||
id: null,
|
|
||||||
name: input.trim(),
|
|
||||||
description: input.trim(),
|
|
||||||
};
|
|
||||||
addTag(newTag);
|
|
||||||
} else if ((key === "backspace" || e.code === "Backspace") && input === "") {
|
|
||||||
setTags((prev) => prev.slice(0, -1));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSuggestionClick = (sugg) => {
|
||||||
const handleSuggestionClick = (suggestion) => {
|
handleAdd(sugg);
|
||||||
addTag(suggestion);
|
setInput("");
|
||||||
|
setSuggestions([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const backgroundColor = color || "#f8f9fa";
|
|
||||||
const iconColor = `var(--bs-${color})`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Label htmlFor={name} className="form-label" required>
|
<Label htmlFor={name} className="form-label" required>
|
||||||
@ -123,17 +79,16 @@ useEffect(() => {
|
|||||||
key={index}
|
key={index}
|
||||||
className="d-flex align-items-center"
|
className="d-flex align-items-center"
|
||||||
style={{
|
style={{
|
||||||
color: iconColor,
|
backgroundColor: color,
|
||||||
backgroundColor,
|
|
||||||
padding: "2px 6px",
|
padding: "2px 6px",
|
||||||
borderRadius: "2px",
|
borderRadius: "2px",
|
||||||
fontSize: "0.85rem",
|
fontSize: "0.8rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
<i
|
<i
|
||||||
className="bx bx-x bx-xs ms-1"
|
className="bx bx-x bx-xs ms-1"
|
||||||
onClick={() => removeTag(index)}
|
onClick={() => handleRemove(tag.name)}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -142,9 +97,8 @@ useEffect(() => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={handleChange}
|
||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyUp={handleInputKey}
|
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
style={{
|
style={{
|
||||||
border: "none",
|
border: "none",
|
||||||
@ -157,12 +111,13 @@ useEffect(() => {
|
|||||||
|
|
||||||
{suggestions.length > 0 && (
|
{suggestions.length > 0 && (
|
||||||
<ul
|
<ul
|
||||||
className="list-group position-absolute mt-1 bg-white w-50 shadow-sm "
|
className="list-group position-absolute mt-1 bg-white w-50 shadow-sm"
|
||||||
style={{
|
style={{
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
maxHeight: "150px",
|
maxHeight: "150px",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
boxShadow:"0px 4px 10px rgba(0, 0, 0, 0.2)",borderRadius:"3px",border:"1px solid #ddd"
|
borderRadius: "3px",
|
||||||
|
border: "1px solid #ddd",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{suggestions.map((sugg, i) => (
|
{suggestions.map((sugg, i) => (
|
||||||
@ -170,8 +125,7 @@ useEffect(() => {
|
|||||||
key={i}
|
key={i}
|
||||||
className="dropdown-item p-1 hoverBox"
|
className="dropdown-item p-1 hoverBox"
|
||||||
onClick={() => handleSuggestionClick(sugg)}
|
onClick={() => handleSuggestionClick(sugg)}
|
||||||
style={{cursor: "pointer", fontSize: "0.875rem"}}
|
style={{ cursor: "pointer", fontSize: "0.875rem" }}
|
||||||
|
|
||||||
>
|
>
|
||||||
{sugg.name}
|
{sugg.name}
|
||||||
</li>
|
</li>
|
||||||
@ -182,4 +136,7 @@ useEffect(() => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default TagInput;
|
export default TagInput;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
||||||
import { getCachedData,cacheData } from '../../slices/apiDataManager';
|
import { getCachedData, cacheData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useCreateContactCategory} from '../../hooks/masterHook/useMaster';
|
import {useCreateContactCategory} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,32 +13,32 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: "Category name is required" }),
|
name: z.string().min(1, { message: "Category name is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateContactCategory = ({onClose}) => {
|
const CreateContactCategory = ({ onClose }) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [descriptionLength, setDescriptionLength] = useState(0);
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
const maxDescriptionLength = 255;
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
const { mutate: createContactCategory, isPending: isLoading } = useCreateContactCategory(() => onClose?.());
|
const { mutate: createContactCategory, isPending: isLoading } = useCreateContactCategory(() => onClose?.());
|
||||||
|
|
||||||
const onSubmit = (payload) => {
|
const onSubmit = (payload) => {
|
||||||
createContactCategory(payload);
|
createContactCategory(payload);
|
||||||
};
|
};
|
||||||
// const onSubmit = (data) => {
|
// const onSubmit = (data) => {
|
||||||
// setIsLoading(true)
|
// setIsLoading(true)
|
||||||
// MasterRespository.createContactCategory(data).then((resp)=>{
|
// MasterRespository.createContactCategory(data).then((resp)=>{
|
||||||
@ -55,15 +55,15 @@ const onSubmit = (payload) => {
|
|||||||
// setIsLoading(false)
|
// setIsLoading(false)
|
||||||
// })
|
// })
|
||||||
// };
|
// };
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
reset({ name: "", description: "" });
|
reset({ name: "", description: "" });
|
||||||
setDescriptionLength(0);
|
setDescriptionLength(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => resetForm();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 col-md-12 text-start">
|
<div className="col-12 col-md-12 text-start">
|
||||||
@ -88,10 +88,10 @@ useEffect(() => {
|
|||||||
<div className="text-end small text-muted">
|
<div className="text-end small text-muted">
|
||||||
{maxDescriptionLength - descriptionLength} characters left
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
</div>
|
</div>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -107,10 +107,10 @@ useEffect(() => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
||||||
import { getCachedData,cacheData } from '../../slices/apiDataManager';
|
import { getCachedData, cacheData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useCreateContactTag} from '../../hooks/masterHook/useMaster';
|
import {useCreateContactTag} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,31 +13,31 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: "Tag name is required" }),
|
name: z.string().min(1, { message: "Tag name is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateContactTag = ({onClose}) => {
|
const CreateContactTag = ({ onClose }) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [descriptionLength, setDescriptionLength] = useState(0);
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
const maxDescriptionLength = 255;
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
const { mutate: createContactTag, isPending: isLoading } = useCreateContactTag(() => onClose?.());
|
const { mutate: createContactTag, isPending: isLoading } = useCreateContactTag(() => onClose?.());
|
||||||
|
|
||||||
const onSubmit = (payload) => {
|
const onSubmit = (payload) => {
|
||||||
createContactTag(payload);
|
createContactTag(payload);
|
||||||
};
|
};
|
||||||
// const onSubmit = (data) => {
|
// const onSubmit = (data) => {
|
||||||
// setIsLoading(true)
|
// setIsLoading(true)
|
||||||
// MasterRespository.createContactTag(data).then((resp)=>{
|
// MasterRespository.createContactTag(data).then((resp)=>{
|
||||||
@ -55,15 +55,15 @@ const onSubmit = (payload) => {
|
|||||||
// setIsLoading(false)
|
// setIsLoading(false)
|
||||||
// })
|
// })
|
||||||
// };
|
// };
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
reset({ name: "", description: "" });
|
reset({ name: "", description: "" });
|
||||||
setDescriptionLength(0);
|
setDescriptionLength(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => resetForm();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 col-md-12 text-start">
|
<div className="col-12 col-md-12 text-start">
|
||||||
@ -88,10 +88,10 @@ useEffect(() => {
|
|||||||
<div className="text-end small text-muted">
|
<div className="text-end small text-muted">
|
||||||
{maxDescriptionLength - descriptionLength} characters left
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
</div>
|
</div>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -107,10 +107,10 @@ useEffect(() => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
||||||
import { getCachedData,cacheData } from '../../slices/apiDataManager';
|
import { getCachedData, cacheData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useCreateJobRole} from '../../hooks/masterHook/useMaster';
|
import {useCreateJobRole} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,10 +13,10 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
role: z.string().min(1, { message: "Role is required" }),
|
role: z.string().min(1, { message: "Role is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateJobRole = ({onClose}) => {
|
const CreateJobRole = ({ onClose }) => {
|
||||||
|
|
||||||
const maxDescriptionLength = 255;
|
const maxDescriptionLength = 255;
|
||||||
const [descriptionLength, setDescriptionLength] = useState(0);
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
@ -40,34 +40,34 @@ const CreateJobRole = ({onClose}) => {
|
|||||||
setDescriptionLength(0);
|
setDescriptionLength(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: createJobRole, isPending:isLoading } = useCreateJobRole(() => {
|
const { mutate: createJobRole, isPending: isLoading } = useCreateJobRole(() => {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
} );
|
});
|
||||||
|
|
||||||
|
|
||||||
// const onSubmit = (data) => {
|
// const onSubmit = (data) => {
|
||||||
// setIsLoading(true)
|
// setIsLoading(true)
|
||||||
// const result = {
|
// const result = {
|
||||||
// name: data.role,
|
// name: data.role,
|
||||||
// description: data.description,
|
// description: data.description,
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// MasterRespository.createJobRole(result).then((resp)=>{
|
|
||||||
// setIsLoading(false)
|
|
||||||
// resetForm()
|
|
||||||
// const cachedData = getCachedData("Job Role");
|
|
||||||
// const updatedData = [...cachedData, resp?.data];
|
|
||||||
// cacheData("Job Role", updatedData);
|
|
||||||
// showToast("JobRole Added successfully.", "success");
|
|
||||||
|
|
||||||
// onClose()
|
// MasterRespository.createJobRole(result).then((resp)=>{
|
||||||
// }).catch((error)=>{
|
// setIsLoading(false)
|
||||||
// showToast(error.message, "error");
|
// resetForm()
|
||||||
// setIsLoading(false)
|
// const cachedData = getCachedData("Job Role");
|
||||||
// })
|
// const updatedData = [...cachedData, resp?.data];
|
||||||
|
// cacheData("Job Role", updatedData);
|
||||||
|
// showToast("JobRole Added successfully.", "success");
|
||||||
// };
|
|
||||||
|
// onClose()
|
||||||
|
// }).catch((error)=>{
|
||||||
|
// showToast(error.message, "error");
|
||||||
|
// setIsLoading(false)
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// };
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
name: data.role,
|
name: data.role,
|
||||||
@ -88,7 +88,7 @@ const CreateJobRole = ({onClose}) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
{/* <div className="col-12 col-md-12">
|
{/* <div className="col-12 col-md-12">
|
||||||
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Create Job Role</label>
|
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Create Job Role</label>
|
||||||
</div> */}
|
</div> */}
|
||||||
@ -114,10 +114,10 @@ const CreateJobRole = ({onClose}) => {
|
|||||||
<div className="text-end small text-muted">
|
<div className="text-end small text-muted">
|
||||||
{maxDescriptionLength - descriptionLength} characters left
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
</div>
|
</div>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -133,10 +133,10 @@ const CreateJobRole = ({onClose}) => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
||||||
import { getCachedData,cacheData } from '../../slices/apiDataManager';
|
import { getCachedData, cacheData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useCreateWorkCategory} from '../../hooks/masterHook/useMaster';
|
import {useCreateWorkCategory} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,69 +13,69 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: "Category name is required" }),
|
name: z.string().min(1, { message: "Category name is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateWorkCategory = ({onClose}) => {
|
const CreateWorkCategory = ({ onClose }) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
},
|
},
|
||||||
});
|
|
||||||
|
|
||||||
const [descriptionLength, setDescriptionLength] = useState(0);
|
|
||||||
const maxDescriptionLength = 255;
|
|
||||||
|
|
||||||
const { mutate: createWorkCategory, isPending: isLoading } = useCreateWorkCategory(() => {
|
|
||||||
resetForm();
|
|
||||||
onClose?.();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (payload) => {
|
|
||||||
createWorkCategory(payload)
|
|
||||||
};
|
|
||||||
|
|
||||||
// const onSubmit = (data) => {
|
|
||||||
// setIsLoading(true)
|
|
||||||
|
|
||||||
|
|
||||||
// MasterRespository.createWorkCategory(data).then((resp)=>{
|
|
||||||
// setIsLoading(false)
|
|
||||||
// resetForm()
|
|
||||||
// const cachedData = getCachedData("Work Category");
|
|
||||||
// const updatedData = [...cachedData, resp?.data];
|
|
||||||
// cacheData("Work Category", updatedData);
|
|
||||||
// showToast("Work Category Added successfully.", "success");
|
|
||||||
|
|
||||||
// onClose()
|
|
||||||
// }).catch((error)=>{
|
|
||||||
// showToast(error?.response?.data?.message, "error");
|
|
||||||
// setIsLoading(false)
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
// };
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
reset({
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
});
|
});
|
||||||
setDescriptionLength(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [descriptionLength, setDescriptionLength] = useState(0);
|
||||||
return () => resetForm();
|
const maxDescriptionLength = 255;
|
||||||
}, []);
|
|
||||||
|
const { mutate: createWorkCategory, isPending: isLoading } = useCreateWorkCategory(() => {
|
||||||
|
resetForm();
|
||||||
|
onClose?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (payload) => {
|
||||||
|
createWorkCategory(payload)
|
||||||
|
};
|
||||||
|
|
||||||
|
// const onSubmit = (data) => {
|
||||||
|
// setIsLoading(true)
|
||||||
|
|
||||||
|
|
||||||
|
// MasterRespository.createWorkCategory(data).then((resp)=>{
|
||||||
|
// setIsLoading(false)
|
||||||
|
// resetForm()
|
||||||
|
// const cachedData = getCachedData("Work Category");
|
||||||
|
// const updatedData = [...cachedData, resp?.data];
|
||||||
|
// cacheData("Work Category", updatedData);
|
||||||
|
// showToast("Work Category Added successfully.", "success");
|
||||||
|
|
||||||
|
// onClose()
|
||||||
|
// }).catch((error)=>{
|
||||||
|
// showToast(error?.response?.data?.message, "error");
|
||||||
|
// setIsLoading(false)
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
reset({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
setDescriptionLength(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
{/* <div className="col-12 col-md-12">
|
{/* <div className="col-12 col-md-12">
|
||||||
@ -104,10 +104,10 @@ useEffect(() => {
|
|||||||
<div className="text-end small text-muted">
|
<div className="text-end small text-muted">
|
||||||
{maxDescriptionLength - descriptionLength} characters left
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
</div>
|
</div>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -123,10 +123,10 @@ useEffect(() => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,13 +34,15 @@ const DeleteMaster = ({ master, onClose }) => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button" // ✅ not reset
|
||||||
className="btn btn-label-secondary"
|
className="btn btn-label-secondary"
|
||||||
data-bs-dismiss="modal"
|
onClick={() => {
|
||||||
aria-label="Close"
|
onClose?.(); // properly close modal
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
||||||
import { getCachedData,cacheData } from '../../slices/apiDataManager';
|
import { getCachedData, cacheData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useUpdateContactCategory} from '../../hooks/masterHook/useMaster';
|
import {useUpdateContactCategory} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,38 +13,38 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: "Category name is required" }),
|
name: z.string().min(1, { message: "Category name is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const EditContactCategory= ({data,onClose}) => {
|
const EditContactCategory = ({ data, onClose }) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset,
|
reset,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
description: data?.description || "",
|
description: data?.description || "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
||||||
const maxDescriptionLength = 255;
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
const { mutate: updateContactCategory, isPending: isLoading } = useUpdateContactCategory(() => onClose?.());
|
const { mutate: updateContactCategory, isPending: isLoading } = useUpdateContactCategory(() => onClose?.());
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateContactCategory({ id: data?.id, payload });
|
||||||
};
|
};
|
||||||
|
// const onSubmit = (formdata) => {
|
||||||
updateContactCategory({id:data?.id,payload});
|
|
||||||
};
|
|
||||||
// const onSubmit = (formdata) => {
|
|
||||||
// setIsLoading(true)
|
// setIsLoading(true)
|
||||||
// const result = {
|
// const result = {
|
||||||
// id:data?.id,
|
// id:data?.id,
|
||||||
@ -52,8 +52,8 @@ const onSubmit = (formData) => {
|
|||||||
// description: formdata.description,
|
// description: formdata.description,
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{
|
// MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{
|
||||||
// setIsLoading(false)
|
// setIsLoading(false)
|
||||||
// showToast("Contact Category Updated successfully.", "success");
|
// showToast("Contact Category Updated successfully.", "success");
|
||||||
@ -71,18 +71,18 @@ const onSubmit = (formData) => {
|
|||||||
// showToast(error?.response?.data?.message, "error")
|
// showToast(error?.response?.data?.message, "error")
|
||||||
// setIsLoading(false)
|
// setIsLoading(false)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
reset({ name: "", description: "" });
|
reset({ name: "", description: "" });
|
||||||
setDescriptionLength(0);
|
setDescriptionLength(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => resetForm();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 col-md-12 text-start">
|
<div className="col-12 col-md-12 text-start">
|
||||||
@ -107,10 +107,10 @@ useEffect(() => {
|
|||||||
<div className="text-end small text-muted">
|
<div className="text-end small text-muted">
|
||||||
{maxDescriptionLength - descriptionLength} characters left
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
</div>
|
</div>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -126,10 +126,10 @@ useEffect(() => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React,{useState,useEffect} from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import {useForm} from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
|
||||||
import { getCachedData,cacheData } from '../../slices/apiDataManager';
|
import { getCachedData, cacheData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useUpdateContactTag} from '../../hooks/masterHook/useMaster';
|
import {useUpdateContactTag} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,38 +13,38 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: "Tag name is required" }),
|
name: z.string().min(1, { message: "Tag name is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const EditContactTag= ({data,onClose}) => {
|
const EditContactTag = ({ data, onClose }) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
reset
|
reset
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
description: data?.description || "",
|
description: data?.description || "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
||||||
const maxDescriptionLength = 255;
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
const { mutate: updateContactTag, isPending: isLoading } = useUpdateContactTag(() => onClose?.());
|
const { mutate: updateContactTag, isPending: isLoading } = useUpdateContactTag(() => onClose?.());
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
};
|
};
|
||||||
debugger
|
debugger
|
||||||
updateContactTag({ id: data?.id, payload} );
|
updateContactTag({ id: data?.id, payload });
|
||||||
}
|
}
|
||||||
// const onSubmit = (formdata) => {
|
// const onSubmit = (formdata) => {
|
||||||
// setIsLoading(true)
|
// setIsLoading(true)
|
||||||
// const result = {
|
// const result = {
|
||||||
// id:data?.id,
|
// id:data?.id,
|
||||||
@ -52,8 +52,8 @@ const onSubmit = (formData) => {
|
|||||||
// description: formdata.description,
|
// description: formdata.description,
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MasterRespository.updateContactTag(data?.id,result).then((resp)=>{
|
// MasterRespository.updateContactTag(data?.id,result).then((resp)=>{
|
||||||
// setIsLoading(false)
|
// setIsLoading(false)
|
||||||
// showToast("Contact Tag Updated successfully.", "success");
|
// showToast("Contact Tag Updated successfully.", "success");
|
||||||
@ -71,18 +71,18 @@ const onSubmit = (formData) => {
|
|||||||
// showToast(error?.response?.data?.message, "error")
|
// showToast(error?.response?.data?.message, "error")
|
||||||
// setIsLoading(false)
|
// setIsLoading(false)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
reset({ name: "", description: "" });
|
reset({ name: "", description: "" });
|
||||||
setDescriptionLength(0);
|
setDescriptionLength(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => resetForm();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => resetForm();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 col-md-12 text-start">
|
<div className="col-12 col-md-12 text-start">
|
||||||
@ -107,10 +107,10 @@ useEffect(() => {
|
|||||||
<div className="text-end small text-muted">
|
<div className="text-end small text-muted">
|
||||||
{maxDescriptionLength - descriptionLength} characters left
|
{maxDescriptionLength - descriptionLength} characters left
|
||||||
</div>
|
</div>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -126,10 +126,10 @@ useEffect(() => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm ,Controller} from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { set, z } from 'zod';
|
import { set, z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { cacheData,getCachedData } from '../../slices/apiDataManager';
|
import { cacheData, getCachedData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useUpdateJobRole} from '../../hooks/masterHook/useMaster';
|
import {useUpdateJobRole} from '../../hooks/masterHook/useMaster';
|
||||||
import Label from '../common/Label';
|
import Label from '../common/Label';
|
||||||
@ -13,13 +13,13 @@ import Label from '../common/Label';
|
|||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
role: z.string().min(1, { message: "Role is required" }),
|
role: z.string().min(1, { message: "Role is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const EditJobRole = ({data,onClose}) => {
|
const EditJobRole = ({ data, onClose }) => {
|
||||||
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
||||||
const maxDescriptionLength = 255;
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -36,38 +36,38 @@ const [descriptionLength, setDescriptionLength] = useState(data?.description?.le
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: updateJobRole, isPendin:isLoading } = useUpdateJobRole(() => {
|
const { mutate: updateJobRole, isPendin: isLoading } = useUpdateJobRole(() => {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
});
|
});
|
||||||
// const onSubmit = (formdata) => {
|
// const onSubmit = (formdata) => {
|
||||||
// setIsLoading(true)
|
// setIsLoading(true)
|
||||||
// const result = {
|
// const result = {
|
||||||
// id:data?.id,
|
// id:data?.id,
|
||||||
// name: formdata?.role,
|
// name: formdata?.role,
|
||||||
// description: formdata.description,
|
// description: formdata.description,
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MasterRespository.updateJobRole(data?.id,result).then((resp)=>{
|
|
||||||
// setIsLoading(false)
|
|
||||||
// showToast("JobRole Update successfully.", "success");
|
|
||||||
// const cachedData = getCachedData("Job Role");
|
|
||||||
// if (cachedData) {
|
|
||||||
|
|
||||||
// const updatedData = cachedData.map((role) =>
|
|
||||||
// role.id === data?.id ? { ...role, ...resp.data } : role
|
|
||||||
// );
|
|
||||||
// cacheData("Job Role", updatedData);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// onClose()
|
// MasterRespository.updateJobRole(data?.id,result).then((resp)=>{
|
||||||
// }).catch((error)=>{
|
// setIsLoading(false)
|
||||||
// showToast(error.message, "error")
|
// showToast("JobRole Update successfully.", "success");
|
||||||
// setIsLoading(false)
|
// const cachedData = getCachedData("Job Role");
|
||||||
// })
|
// if (cachedData) {
|
||||||
|
|
||||||
// };
|
// const updatedData = cachedData.map((role) =>
|
||||||
|
// role.id === data?.id ? { ...role, ...resp.data } : role
|
||||||
|
// );
|
||||||
|
// cacheData("Job Role", updatedData);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// onClose()
|
||||||
|
// }).catch((error)=>{
|
||||||
|
// showToast(error.message, "error")
|
||||||
|
// setIsLoading(false)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
updateJobRole({
|
updateJobRole({
|
||||||
@ -94,9 +94,9 @@ const [descriptionLength, setDescriptionLength] = useState(data?.description?.le
|
|||||||
});
|
});
|
||||||
return () => sub.unsubscribe();
|
return () => sub.unsubscribe();
|
||||||
}, [watch]);
|
}, [watch]);
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
{/* <div className="col-12 col-md-12">
|
{/* <div className="col-12 col-md-12">
|
||||||
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Edit Job Role</label>
|
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Edit Job Role</label>
|
||||||
</div> */}
|
</div> */}
|
||||||
@ -126,7 +126,7 @@ const [descriptionLength, setDescriptionLength] = useState(data?.description?.le
|
|||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-end">
|
<div className="col-12 text-end">
|
||||||
<button
|
<button
|
||||||
@ -142,10 +142,11 @@ const [descriptionLength, setDescriptionLength] = useState(data?.description?.le
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,103 +1,103 @@
|
|||||||
import React, { useEffect,useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useForm ,Controller} from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { set, z } from 'zod';
|
import { set, z } from 'zod';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { MasterRespository } from '../../repositories/MastersRepository';
|
import { MasterRespository } from '../../repositories/MastersRepository';
|
||||||
import { cacheData,getCachedData } from '../../slices/apiDataManager';
|
import { cacheData, getCachedData } from '../../slices/apiDataManager';
|
||||||
import showToast from '../../services/toastService';
|
import showToast from '../../services/toastService';
|
||||||
import {useUpdateWorkCategory} from '../../hooks/masterHook/useMaster';
|
import { useUpdateWorkCategory } from '../../hooks/masterHook/useMaster';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().min(1, { message: "Work Category is required" }),
|
name: z.string().min(1, { message: "Work Category is required" }),
|
||||||
description: z.string().min(1, { message: "Description is required" })
|
description: z.string().min(1, { message: "Description is required" })
|
||||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const EditWorkCategory = ({data,onClose}) => {
|
const EditWorkCategory = ({ data, onClose }) => {
|
||||||
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
formState: { errors },
|
|
||||||
reset,
|
|
||||||
} = useForm({
|
|
||||||
resolver: zodResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
name: data?.name || "",
|
|
||||||
description: data?.description || "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
const {
|
||||||
const maxDescriptionLength = 255;
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors },
|
||||||
|
reset,
|
||||||
|
} = useForm({
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: data?.name || "",
|
||||||
|
description: data?.description || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { mutate: updateWorkCategory, isPending: isLoading } = useUpdateWorkCategory(() => onClose?.());
|
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
|
||||||
|
const maxDescriptionLength = 255;
|
||||||
|
|
||||||
const onSubmit = (formdata) => {
|
const { mutate: updateWorkCategory, isPending: isLoading } = useUpdateWorkCategory(() => onClose?.());
|
||||||
const payload = {
|
|
||||||
id: data?.id,
|
const onSubmit = (formdata) => {
|
||||||
name: formdata.name,
|
const payload = {
|
||||||
description: formdata.description,
|
id: data?.id,
|
||||||
|
name: formdata.name,
|
||||||
|
description: formdata.description,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateWorkCategory({ id: data?.id, payload });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateWorkCategory({id:data?.id,payload});
|
// const onSubmit = (formdata) => {
|
||||||
};
|
// setIsLoading(true)
|
||||||
|
// const result = {
|
||||||
// const onSubmit = (formdata) => {
|
// id:data?.id,
|
||||||
// setIsLoading(true)
|
// name: formdata?.name,
|
||||||
// const result = {
|
// description: formdata.description,
|
||||||
// id:data?.id,
|
// };
|
||||||
// name: formdata?.name,
|
|
||||||
// description: formdata.description,
|
|
||||||
// };
|
|
||||||
|
// MasterRespository.updateWorkCategory(data?.id,result).then((resp)=>{
|
||||||
|
// setIsLoading(false)
|
||||||
|
// showToast("Work Category Update successfully.", "success");
|
||||||
// MasterRespository.updateWorkCategory(data?.id,result).then((resp)=>{
|
// const cachedData = getCachedData("Work Category");
|
||||||
// setIsLoading(false)
|
// if (cachedData) {
|
||||||
// showToast("Work Category Update successfully.", "success");
|
|
||||||
// const cachedData = getCachedData("Work Category");
|
// const updatedData = cachedData.map((category) =>
|
||||||
// if (cachedData) {
|
// category.id === data?.id ? { ...category, ...resp.data } : category
|
||||||
|
// );
|
||||||
// const updatedData = cachedData.map((category) =>
|
// cacheData("Work Category", updatedData);
|
||||||
// category.id === data?.id ? { ...category, ...resp.data } : category
|
// }
|
||||||
// );
|
|
||||||
// cacheData("Work Category", updatedData);
|
// onClose()
|
||||||
// }
|
// }).catch((error)=>{
|
||||||
|
// showToast(error?.response?.data?.message, "error")
|
||||||
// onClose()
|
// setIsLoading(false)
|
||||||
// }).catch((error)=>{
|
// })
|
||||||
// showToast(error?.response?.data?.message, "error")
|
|
||||||
// setIsLoading(false)
|
// };
|
||||||
// })
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
// };
|
name: data?.name || "",
|
||||||
useEffect(() => {
|
description: data?.description || "",
|
||||||
reset({
|
});
|
||||||
name: data?.name || "",
|
setDescriptionLength(data?.description?.length || 0);
|
||||||
description: data?.description || "",
|
}, [data, reset]);
|
||||||
});
|
|
||||||
setDescriptionLength(data?.description?.length || 0);
|
|
||||||
}, [data, reset]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 col-md-12">
|
<div className="col-12 col-md-12">
|
||||||
<label className="form-label">Category Name</label>
|
<label className="form-label">Category Name</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
{...register("name")}
|
{...register("name")}
|
||||||
className={`form-control ${errors.name ? 'is-invalids' : ''}`}
|
className={`form-control ${errors.name ? 'is-invalids' : ''}`}
|
||||||
/>
|
/>
|
||||||
{errors.name && <p className="text-danger">{errors.name.message}</p>}
|
{errors.name && <p className="text-danger">{errors.name.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-12">
|
<div className="col-12 col-md-12">
|
||||||
<label className="form-label" htmlFor="description">Description</label>
|
<label className="form-label" htmlFor="description">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
rows="3"
|
rows="3"
|
||||||
{...register("description")}
|
{...register("description")}
|
||||||
@ -114,25 +114,25 @@ useEffect(() => {
|
|||||||
<p className="text-danger">{errors.description.message}</p>
|
<p className="text-danger">{errors.description.message}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 text-center">
|
<div className="col-12 text-center">
|
||||||
<button type="submit" className="btn btn-sm btn-primary me-3">
|
<button type="submit" className="btn btn-sm btn-primary me-3">
|
||||||
{isLoading? "Please Wait...":"Submit"}
|
{isLoading ? "Please Wait..." : "Submit"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="reset"
|
type="button"
|
||||||
className="btn btn-sm btn-label-secondary"
|
className="btn btn-sm btn-label-secondary"
|
||||||
data-bs-dismiss="modal"
|
onClick={onClose}
|
||||||
aria-label="Close"
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
156
src/components/master/ManageDocumentCategory.jsx
Normal file
156
src/components/master/ManageDocumentCategory.jsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useForm, FormProvider } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useFeatures } from "../../hooks/useMasterRole";
|
||||||
|
import { DOCUMENTS_ENTITIES, EXPENSE_MANAGEMENT } from "../../utils/constants";
|
||||||
|
import {
|
||||||
|
useCreateDocumentCatgory,
|
||||||
|
useUpdateDocumentCategory,
|
||||||
|
} from "../../hooks/masterHook/useMaster";
|
||||||
|
|
||||||
|
export const Document_Entity = Object.entries(DOCUMENTS_ENTITIES).map(
|
||||||
|
([key, value]) => ({ key, value })
|
||||||
|
);
|
||||||
|
|
||||||
|
const ExpenseStatusSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
|
entityTypeId: z.string().min(1, { message: "Entity is required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ManageDocumentCategory = ({ data, onClose }) => {
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(ExpenseStatusSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
entityTypeId: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = methods;
|
||||||
|
|
||||||
|
const { masterFeatures, loading } = useFeatures();
|
||||||
|
|
||||||
|
const ExpenseFeature = masterFeatures?.find(
|
||||||
|
(appfeature) => appfeature.id === EXPENSE_MANAGEMENT
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate: CreateDocumentCategory, isPending } =
|
||||||
|
useCreateDocumentCatgory(() => onClose?.());
|
||||||
|
const { mutate: UpdateDocumentCategory, isPending: Updating } =
|
||||||
|
useUpdateDocumentCategory(() => onClose?.());
|
||||||
|
|
||||||
|
const onSubmit = (payload) => {
|
||||||
|
if (data) {
|
||||||
|
UpdateDocumentCategory({
|
||||||
|
id: data.id,
|
||||||
|
payload: { ...payload, id: data.id },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
CreateDocumentCategory(payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
reset({
|
||||||
|
name: data.name ?? "",
|
||||||
|
description: data.description ?? "",
|
||||||
|
entityTypeId: data.entityTypeId ?? "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data, reset]);
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
{loading ? (
|
||||||
|
<div>Loading...</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<p className="fw-semibold">
|
||||||
|
{data ? "Update Document Category" : "Add Document Category"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
className="row g-2 text-start"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Category Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("name")}
|
||||||
|
className={`form-control form-control-sm `}
|
||||||
|
/>
|
||||||
|
{errors.name && (
|
||||||
|
<p className="danger-text">{errors.name.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Select Entity</label>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
{...register("entityTypeId")}
|
||||||
|
>
|
||||||
|
<option value="" disabled>Select entity</option>
|
||||||
|
{Document_Entity.map((entity) => (
|
||||||
|
<option key={entity.key} value={entity.value}>
|
||||||
|
{entity.key}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.entityTypeId && (
|
||||||
|
<p className="danger-text">{errors.entityTypeId.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Description</label>
|
||||||
|
<textarea
|
||||||
|
rows="3"
|
||||||
|
{...register("description")}
|
||||||
|
className={`form-control form-control-sm`}
|
||||||
|
/>
|
||||||
|
{errors.description && (
|
||||||
|
<p className="danger-text">{errors.description.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
disabled={isPending || Updating}
|
||||||
|
>
|
||||||
|
{isPending || Updating
|
||||||
|
? "Please Wait..."
|
||||||
|
: data
|
||||||
|
? "Update"
|
||||||
|
: "Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-secondary"
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={isPending || Updating}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageDocumentCategory;
|
223
src/components/master/ManageDocumentType.jsx
Normal file
223
src/components/master/ManageDocumentType.jsx
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useForm, FormProvider } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useCreateDocumentType, useDocumentCategories, useUpdateDocumentType } from "../../hooks/masterHook/useMaster";
|
||||||
|
import { DOCUMENTS_ENTITIES } from "../../utils/constants";
|
||||||
|
|
||||||
|
|
||||||
|
export const Document_Entity = Object.entries(DOCUMENTS_ENTITIES).map(
|
||||||
|
([key, value]) => ({ key, value })
|
||||||
|
);
|
||||||
|
const DocumentTypeSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: "Name is required" }),
|
||||||
|
regexExpression: z.string().optional(),
|
||||||
|
allowedContentType: z.string().min(1, { message: "Allowed content type is required" }),
|
||||||
|
maxSizeAllowedInMB: z
|
||||||
|
.number({ invalid_type_error: "Must be a number" })
|
||||||
|
.min(1, { message: "Max size must be at least 1MB" }),
|
||||||
|
isValidationRequired: z.boolean().default(true),
|
||||||
|
isMandatory: z.boolean().default(true),
|
||||||
|
documentCategoryId: z.string().min(1, { message: "Document Category is required" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ManageDocumentType = ({ data, onClose, documentCategories = [] }) => {
|
||||||
|
const methods = useForm({
|
||||||
|
resolver: zodResolver(DocumentTypeSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
regexExpression: "",
|
||||||
|
allowedContentType: "",
|
||||||
|
maxSizeAllowedInMB: 25,
|
||||||
|
isValidationRequired: true,
|
||||||
|
isMandatory: true,
|
||||||
|
documentCategoryId: "",
|
||||||
|
entityTypeId:""
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,watch,
|
||||||
|
formState: { errors },
|
||||||
|
} = methods;
|
||||||
|
|
||||||
|
|
||||||
|
const selectedDocumentEntity = watch("entityTypeId")
|
||||||
|
const {DocumentCategories,isLoading} = useDocumentCategories(selectedDocumentEntity)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { mutate: createDocType, isPending: creating } = useCreateDocumentType(() =>
|
||||||
|
onClose?.()
|
||||||
|
);
|
||||||
|
const { mutate: updateDocType, isPending: updating } = useUpdateDocumentType(() =>
|
||||||
|
onClose?.()
|
||||||
|
);
|
||||||
|
const onSubmit = (payload) => {
|
||||||
|
const { entityTypeId, ...rest } = payload;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
updateDocType({ id: data.id, payload: { ...rest, id: data.id } });
|
||||||
|
} else {
|
||||||
|
createDocType(rest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
reset({
|
||||||
|
name: data.name ?? "",
|
||||||
|
regexExpression: data.regexExpression ?? "",
|
||||||
|
allowedContentType: data.allowedContentType ?? "",
|
||||||
|
maxSizeAllowedInMB: data.maxSizeAllowedInMB ?? 25,
|
||||||
|
isValidationRequired: data.isValidationRequired ?? true,
|
||||||
|
isMandatory: data.isMandatory ?? true,
|
||||||
|
documentCategoryId: data.documentCategory?.id ?? "",
|
||||||
|
entityTypeId:data.documentCategory?.entityTypeId ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form className="row g-2 text-start" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="fw-semibold">
|
||||||
|
{data ? "Edit Document Type" : "Add Document Type"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Name */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("name")}
|
||||||
|
className={`form-control form-control-sm `}
|
||||||
|
/>
|
||||||
|
{errors.name && <a className="text-danger">{errors.name.message}</a>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Regex Expression */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Regex Expression</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("regexExpression")}
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Allowed Content Type */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Allowed Content Type</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
{...register("allowedContentType")}
|
||||||
|
className={`form-control form-control form-control-sm`}
|
||||||
|
/>
|
||||||
|
{errors.allowedContentType && (
|
||||||
|
<a className="text-danger">{errors.allowedContentType.message}</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Max Size */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Max Size Allowed (MB)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
{...register("maxSizeAllowedInMB", { valueAsNumber: true })}
|
||||||
|
className={`form-control form-control-sm`}
|
||||||
|
/>
|
||||||
|
{errors.maxSizeAllowedInMB && (
|
||||||
|
<a className="text-danger">{errors.maxSizeAllowedInMB.message}</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Validation Required */}
|
||||||
|
<div className="col-6 form-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
{...register("isValidationRequired")}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label">Validation Required</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mandatory */}
|
||||||
|
<div className="col-6 form-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input "
|
||||||
|
{...register("isMandatory")}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label">Mandatory</label>
|
||||||
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Document Entity</label>
|
||||||
|
<select
|
||||||
|
{...register("entityTypeId")}
|
||||||
|
className={`form-select form-select-sm`}
|
||||||
|
>
|
||||||
|
<option value="">-- Select Category --</option>
|
||||||
|
{Document_Entity.map((entity) => (
|
||||||
|
<option key={entity.key} value={entity.value}>
|
||||||
|
{entity.key}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.entityTypeId && (
|
||||||
|
<a className="text-danger">{errors.entityTypeId.message}</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* Category */}
|
||||||
|
<div className="col-12">
|
||||||
|
<label className="form-label">Document Category</label>
|
||||||
|
<select
|
||||||
|
{...register("documentCategoryId")}
|
||||||
|
className={`form-select form-select-sm`}
|
||||||
|
>
|
||||||
|
|
||||||
|
{isLoading && <option value="" disabled>Loading....</option> }
|
||||||
|
<option value="">-- Select Category --</option>
|
||||||
|
{!isLoading && DocumentCategories?.map((cat) => (
|
||||||
|
<option key={cat.id} value={cat.id}>
|
||||||
|
{cat.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{errors.documentCategoryId && (
|
||||||
|
<a className="text-danger">{errors.documentCategoryId.message}</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Buttons */}
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-sm btn-primary me-3"
|
||||||
|
disabled={creating || updating}
|
||||||
|
>
|
||||||
|
{creating || updating
|
||||||
|
? "Please Wait..."
|
||||||
|
: data
|
||||||
|
? "Update"
|
||||||
|
: "Submit"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-secondary"
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={creating || updating}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageDocumentType;
|
@ -18,31 +18,31 @@ const ManagePaymentMode = ({ data = null, onClose }) => {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(ExpnseSchema),
|
resolver: zodResolver(ExpnseSchema),
|
||||||
defaultValues: { name: "", description: "" },
|
defaultValues: { name: "", description: "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: CreatePaymentMode, isPending } = useCreatePaymentMode(() =>
|
const { mutate: CreatePaymentMode, isPending } = useCreatePaymentMode(() =>
|
||||||
onClose?.()
|
onClose?.()
|
||||||
);
|
);
|
||||||
const {mutate:UpdatePaymentMode,isPending:Updating} = useUpdatePaymentMode(()=>onClose?.())
|
const { mutate: UpdatePaymentMode, isPending: Updating } = useUpdatePaymentMode(() => onClose?.())
|
||||||
|
|
||||||
const onSubmit = (payload) => {
|
const onSubmit = (payload) => {
|
||||||
if(data){
|
if (data) {
|
||||||
UpdatePaymentMode({id:data.id,payload:{...payload,id:data.id}})
|
UpdatePaymentMode({ id: data.id, payload: { ...payload, id: data.id } })
|
||||||
}else(
|
} else (
|
||||||
CreatePaymentMode(payload)
|
CreatePaymentMode(payload)
|
||||||
)
|
)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if(data){
|
if (data) {
|
||||||
reset({
|
reset({
|
||||||
name:data.name ?? "",
|
name: data.name ?? "",
|
||||||
description:data.description ?? ""
|
description: data.description ?? ""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},[data])
|
}, [data])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,140 +1,68 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React from "react";
|
||||||
import CreateRole from "./CreateRole";
|
import CreateRole from "./CreateRole";
|
||||||
import DeleteMaster from "./DeleteMaster";
|
|
||||||
import EditRole from "./EditRole";
|
import EditRole from "./EditRole";
|
||||||
import CreateJobRole from "./CreateJobRole";
|
import CreateJobRole from "./CreateJobRole";
|
||||||
import EditJobRole from "./EditJobRole";
|
import EditJobRole from "./EditJobRole";
|
||||||
import CreateActivity from "./CreateActivity";
|
import CreateActivity from "./CreateActivity";
|
||||||
import EditActivity from "./EditActivity";
|
import EditActivity from "./EditActivity";
|
||||||
import ConfirmModal from "../common/ConfirmModal";
|
|
||||||
import { MasterRespository } from "../../repositories/MastersRepository";
|
|
||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
|
||||||
import showToast from "../../services/toastService";
|
|
||||||
import CreateWorkCategory from "./CreateWorkCategory";
|
import CreateWorkCategory from "./CreateWorkCategory";
|
||||||
import EditWorkCategory from "./EditWorkCategory";
|
import EditWorkCategory from "./EditWorkCategory";
|
||||||
import CreateCategory from "./CreateContactCategory";
|
import CreateCategory from "./CreateContactCategory";
|
||||||
import CreateContactTag from "./CreateContactTag";
|
import CreateContactTag from "./CreateContactTag";
|
||||||
import EditContactCategory from "./EditContactCategory";
|
import EditContactCategory from "./EditContactCategory";
|
||||||
import EditContactTag from "./EditContactTag";
|
import EditContactTag from "./EditContactTag";
|
||||||
import { useDeleteMasterItem } from "../../hooks/masterHook/useMaster";
|
|
||||||
import ManageExpenseType from "./ManageExpenseType";
|
import ManageExpenseType from "./ManageExpenseType";
|
||||||
import ManagePaymentMode from "./ManagePaymentMode";
|
import ManagePaymentMode from "./ManagePaymentMode";
|
||||||
import ManageExpenseStatus from "./ManageExpenseStatus";
|
import ManageExpenseStatus from "./ManageExpenseStatus";
|
||||||
|
import ManageDocumentCategory from "./ManageDocumentCategory";
|
||||||
|
import ManageDocumentType from "./ManageDocumentType";
|
||||||
|
|
||||||
const MasterModal = ({ modaldata, closeModal }) => {
|
const MasterModal = ({ modaldata, closeModal }) => {
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
if (!modaldata?.modalType || modaldata.modalType === "delete") {
|
||||||
const { mutate: deleteMasterItem, isPending } = useDeleteMasterItem();
|
|
||||||
|
|
||||||
const handleSelectedMasterDeleted = () => {
|
|
||||||
const { masterType, item, validateFn } = modaldata || {};
|
|
||||||
if (!masterType || !item?.id) {
|
|
||||||
showToast("Missing master type or item", "error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMasterItem(
|
|
||||||
{ masterType, item, validateFn },
|
|
||||||
{ onSuccess: handleCloseDeleteModal }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloseDeleteModal = () => {
|
|
||||||
setIsDeleteModalOpen(false);
|
|
||||||
closeModal();
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (modaldata?.modalType === "delete") {
|
|
||||||
setIsDeleteModalOpen(true);
|
|
||||||
}
|
|
||||||
}, [modaldata]);
|
|
||||||
|
|
||||||
if (!modaldata?.modalType) {
|
|
||||||
closeModal();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modaldata.modalType === "delete" && isDeleteModalOpen) {
|
const { modalType, item, masterType } = modaldata;
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="modal fade show"
|
|
||||||
tabIndex="-1"
|
|
||||||
role="dialog"
|
|
||||||
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}
|
|
||||||
aria-hidden="false"
|
|
||||||
>
|
|
||||||
<ConfirmModal
|
|
||||||
type="delete"
|
|
||||||
header={`Delete ${modaldata.masterType}`}
|
|
||||||
message="Are you sure you want delete?"
|
|
||||||
onSubmit={handleSelectedMasterDeleted}
|
|
||||||
onClose={handleCloseDeleteModal}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderModalContent = () => {
|
const modalComponents = {
|
||||||
const { modalType, item, masterType } = modaldata;
|
"Application Role": (
|
||||||
|
<CreateRole masmodalType={masterType} onClose={closeModal} />
|
||||||
const modalComponents = {
|
),
|
||||||
"Application Role": <CreateRole masmodalType={masterType} onClose={closeModal} />,
|
"Edit-Application Role": (
|
||||||
"Edit-Application Role": <EditRole master={modaldata} onClose={closeModal} />,
|
<EditRole master={modaldata} onClose={closeModal} />
|
||||||
"Job Role": <CreateJobRole onClose={closeModal} />,
|
),
|
||||||
"Edit-Job Role": <EditJobRole data={item} onClose={closeModal} />,
|
"Job Role": <CreateJobRole onClose={closeModal} />,
|
||||||
"Activity": <CreateActivity onClose={closeModal} />,
|
"Edit-Job Role": <EditJobRole data={item} onClose={closeModal} />,
|
||||||
"Edit-Activity": <EditActivity activityData={item} onClose={closeModal} />,
|
"Activity": <CreateActivity onClose={closeModal} />,
|
||||||
"Work Category": <CreateWorkCategory onClose={closeModal} />,
|
"Edit-Activity": <EditActivity activityData={item} onClose={closeModal} />,
|
||||||
"Edit-Work Category": <EditWorkCategory data={item} onClose={closeModal} />,
|
"Work Category": <CreateWorkCategory onClose={closeModal} />,
|
||||||
"Contact Category": <CreateCategory data={item} onClose={closeModal} />,
|
"Edit-Work Category": <EditWorkCategory data={item} onClose={closeModal} />,
|
||||||
"Edit-Contact Category": <EditContactCategory data={item} onClose={closeModal} />,
|
"Contact Category": <CreateCategory data={item} onClose={closeModal} />,
|
||||||
"Contact Tag": <CreateContactTag data={item} onClose={closeModal} />,
|
"Edit-Contact Category": (
|
||||||
"Edit-Contact Tag": <EditContactTag data={item} onClose={closeModal} />,
|
<EditContactCategory data={item} onClose={closeModal} />
|
||||||
"Expense Type":<ManageExpenseType onClose={closeModal} />,
|
),
|
||||||
"Edit-Expense Type":<ManageExpenseType data={item} onClose={closeModal} />,
|
"Contact Tag": <CreateContactTag data={item} onClose={closeModal} />,
|
||||||
"Payment Mode":<ManagePaymentMode onClose={closeModal}/>,
|
"Edit-Contact Tag": <EditContactTag data={item} onClose={closeModal} />,
|
||||||
"Edit-Payment Mode":<ManagePaymentMode data={item} onClose={closeModal}/>,
|
"Expense Type": <ManageExpenseType onClose={closeModal} />,
|
||||||
"Expense Status":<ManageExpenseStatus onClose={closeModal}/>,
|
"Edit-Expense Type": <ManageExpenseType data={item} onClose={closeModal} />,
|
||||||
"Edit-Expense Status":<ManageExpenseStatus data={item} onClose={closeModal}/>
|
"Payment Mode": <ManagePaymentMode onClose={closeModal} />,
|
||||||
};
|
"Edit-Payment Mode": <ManagePaymentMode data={item} onClose={closeModal} />,
|
||||||
|
"Expense Status": <ManageExpenseStatus onClose={closeModal} />,
|
||||||
return modalComponents[modalType] || null;
|
"Edit-Expense Status": (
|
||||||
|
<ManageExpenseStatus data={item} onClose={closeModal} />
|
||||||
|
),
|
||||||
|
"Document Category": <ManageDocumentCategory onClose={closeModal} />,
|
||||||
|
"Edit-Document Category": (
|
||||||
|
<ManageDocumentCategory data={item} onClose={closeModal} />
|
||||||
|
),
|
||||||
|
"Document Type": <ManageDocumentType onClose={closeModal} />,
|
||||||
|
"Edit-Document Type": (
|
||||||
|
<ManageDocumentType data={item} onClose={closeModal} />
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLargeModal = ["Application Role", "Edit-Application Role"].includes(
|
return modalComponents[modalType] || null;
|
||||||
modaldata.modalType
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="modal fade show"
|
|
||||||
id="master-modal"
|
|
||||||
tabIndex="-1"
|
|
||||||
role="dialog"
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-labelledby="modalToggleLabel"
|
|
||||||
style={{ display: "block" }}
|
|
||||||
>
|
|
||||||
<div className={`modal-dialog mx-sm-auto mx-1 ${isLargeModal ? "modal-lg" : "modal-md"} modal-simple`}>
|
|
||||||
<div className="modal-content">
|
|
||||||
<div className="modal-body p-sm-4 p-0">
|
|
||||||
<div className="d-flex justify-content-between">
|
|
||||||
<h6>{modaldata?.modalType}</h6>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn-close"
|
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
onClick={closeModal}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{renderModalContent()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MasterModal;
|
export default MasterModal;
|
||||||
|
@ -171,6 +171,56 @@ const {
|
|||||||
|
|
||||||
return { ExpenseStatus, loading, error };
|
return { ExpenseStatus, loading, error };
|
||||||
}
|
}
|
||||||
|
export const useDocumentTypes =(category)=>{
|
||||||
|
const {
|
||||||
|
data: DocumentTypes = [],
|
||||||
|
error,
|
||||||
|
isError,
|
||||||
|
isLoading
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["Document Type",category],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await MasterRespository.getDocumentTypes(category)
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
enabled:!!category,
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Failed to fetch Expense Status",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { DocumentTypes, isError, isLoading, error };
|
||||||
|
}
|
||||||
|
export const useDocumentCategories =(EntityType)=>{
|
||||||
|
const {
|
||||||
|
data: DocumentCategories = [],
|
||||||
|
error,
|
||||||
|
isError,
|
||||||
|
isLoading
|
||||||
|
} = useQuery({
|
||||||
|
queryKey: ["Document Category",EntityType],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await MasterRespository.getDocumentCategories(EntityType)
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
enabled:!!EntityType,
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Failed to fetch Expense Status",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { DocumentCategories, isError, isLoading, error };
|
||||||
|
}
|
||||||
// ===Application Masters Query=================================================
|
// ===Application Masters Query=================================================
|
||||||
|
|
||||||
const fetchMasterData = async (masterType) => {
|
const fetchMasterData = async (masterType) => {
|
||||||
@ -193,6 +243,10 @@ const fetchMasterData = async (masterType) => {
|
|||||||
return (await MasterRespository.getPaymentMode()).data;
|
return (await MasterRespository.getPaymentMode()).data;
|
||||||
case "Expense Status":
|
case "Expense Status":
|
||||||
return (await MasterRespository.getExpenseStatus()).data;
|
return (await MasterRespository.getExpenseStatus()).data;
|
||||||
|
case "Document Type":
|
||||||
|
return (await MasterRespository.getDocumentTypes()).data;
|
||||||
|
case "Document Category":
|
||||||
|
return (await MasterRespository.getDocumentCategories()).data;
|
||||||
case "Status":
|
case "Status":
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -634,6 +688,7 @@ export const useUpdatePaymentMode = (onSuccessCallback)=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------Expense Status----------------------------------
|
// -------------------Expense Status----------------------------------
|
||||||
export const useCreateExpenseStatus =(onSuccessCallback)=>{
|
export const useCreateExpenseStatus =(onSuccessCallback)=>{
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -677,6 +732,100 @@ export const useUpdateExpenseStatus = (onSuccessCallback)=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------Document-Category--------------------------------
|
||||||
|
export const useCreateDocumentCatgory =(onSuccessCallback)=>{
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation( {
|
||||||
|
mutationFn: async ( payload ) =>
|
||||||
|
{
|
||||||
|
const resp = await MasterRespository.createDocumenyCategory(payload);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
onSuccess: ( data ) =>
|
||||||
|
{
|
||||||
|
queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Category" ]} )
|
||||||
|
queryClient.invalidateQueries( {queryKey:[ "Document Category" ]} )
|
||||||
|
showToast( "Document Category added successfully", "success" );
|
||||||
|
if(onSuccessCallback) onSuccessCallback(data)
|
||||||
|
},
|
||||||
|
onError: ( error ) =>
|
||||||
|
{
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateDocumentCategory =(onSuccessCallback)=>{
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation( {
|
||||||
|
mutationFn: async ( {id,payload} ) =>
|
||||||
|
{
|
||||||
|
const resp = await MasterRespository.updateDocumentCategory(id,payload);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
onSuccess: ( data ) =>
|
||||||
|
{
|
||||||
|
queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Category" ]} )
|
||||||
|
queryClient.invalidateQueries( {queryKey:[ "Document Category" ]} )
|
||||||
|
showToast( "Document Category Updated successfully", "success" );
|
||||||
|
if(onSuccessCallback) onSuccessCallback(data)
|
||||||
|
},
|
||||||
|
onError: ( error ) =>
|
||||||
|
{
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------Document-Type-----------------------------------
|
||||||
|
export const useCreateDocumentType =(onSuccessCallback)=>{
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation( {
|
||||||
|
mutationFn: async ( payload ) =>
|
||||||
|
{
|
||||||
|
const resp = await MasterRespository.createDocumentType(payload);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
onSuccess: ( data ) =>
|
||||||
|
{
|
||||||
|
queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Type" ]} )
|
||||||
|
showToast( "Document Type added successfully", "success" );
|
||||||
|
if(onSuccessCallback) onSuccessCallback(data)
|
||||||
|
},
|
||||||
|
onError: ( error ) =>
|
||||||
|
{
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateDocumentType =(onSuccessCallback)=>{
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation( {
|
||||||
|
mutationFn: async ( {id,payload} ) =>
|
||||||
|
{
|
||||||
|
const resp = await MasterRespository.updateDocumentType(id,payload);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
onSuccess: ( data ) =>
|
||||||
|
{
|
||||||
|
queryClient.invalidateQueries( {queryKey:[ "masterData", "Document Type" ]} )
|
||||||
|
showToast( "Document Type Updated successfully", "success" );
|
||||||
|
if(onSuccessCallback) onSuccessCallback(data)
|
||||||
|
},
|
||||||
|
onError: ( error ) =>
|
||||||
|
{
|
||||||
|
showToast(error.message || "Something went wrong", "error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
// -Delete Master --------
|
// -Delete Master --------
|
||||||
export const useDeleteMasterItem = () => {
|
export const useDeleteMasterItem = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { cacheData, getCachedData, useSelectedproject } from "../slices/apiDataManager";
|
import { cacheData, getCachedData, useSelectedProject } from "../slices/apiDataManager";
|
||||||
import AttendanceRepository from "../repositories/AttendanceRepository";
|
import AttendanceRepository from "../repositories/AttendanceRepository";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import showToast from "../services/toastService";
|
import showToast from "../services/toastService";
|
||||||
@ -143,8 +143,7 @@ export const useRegularizationRequests = (projectId) => {
|
|||||||
|
|
||||||
export const useMarkAttendance = () => {
|
export const useMarkAttendance = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
const selectedProject = useSelectedProject();
|
||||||
const selectedProject = useSelectedproject();
|
|
||||||
const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange)
|
const selectedDateRange = useSelector((store)=>store.localVariables.defaultDateRange)
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
204
src/hooks/useDocument.js
Normal file
204
src/hooks/useDocument.js
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import showToast from "../services/toastService";
|
||||||
|
import { DocumentRepository } from "../repositories/DocumentRepository";
|
||||||
|
|
||||||
|
// ----------------------Query-------------------------------
|
||||||
|
const cleanFilter = (filter) => {
|
||||||
|
const cleaned = { ...filter };
|
||||||
|
|
||||||
|
[
|
||||||
|
"uploadedByIds",
|
||||||
|
"documentCategoryIds",
|
||||||
|
"documentTypeIds",
|
||||||
|
"documentTagIds",
|
||||||
|
].forEach((key) => {
|
||||||
|
if (Array.isArray(cleaned[key]) && cleaned[key].length === 0) {
|
||||||
|
delete cleaned[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
["startDate", "endDate", "isVerified"].forEach((key) => {
|
||||||
|
if (cleaned[key] === null) {
|
||||||
|
delete cleaned[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
};
|
||||||
|
export const useDocumentListByEntityId = (
|
||||||
|
entityTypeId,
|
||||||
|
entityId,
|
||||||
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
filter,
|
||||||
|
searchString = "",
|
||||||
|
isActive
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [
|
||||||
|
"DocumentList",
|
||||||
|
entityTypeId,
|
||||||
|
entityId,
|
||||||
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
filter,
|
||||||
|
searchString,
|
||||||
|
isActive
|
||||||
|
],
|
||||||
|
queryFn: async () => {
|
||||||
|
const cleanedFilter = cleanFilter(filter);
|
||||||
|
const resp = await DocumentRepository.getDocumentList(
|
||||||
|
entityTypeId,
|
||||||
|
entityId,
|
||||||
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
cleanedFilter,
|
||||||
|
searchString,
|
||||||
|
isActive
|
||||||
|
);
|
||||||
|
return resp.data
|
||||||
|
|
||||||
|
},
|
||||||
|
enabled: !!entityTypeId && !!entityId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDocumentFilterEntities = (entityTypeId) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["DFilter", entityTypeId],
|
||||||
|
queryFn: async () =>
|
||||||
|
await DocumentRepository.getFilterEntities(entityTypeId),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDocumentDetails = (documentId) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["Document", documentId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await DocumentRepository.getDocumentById(documentId);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
enabled: !!documentId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDocumentVersionList = (parentAttachmentId,pageSize,pageNumber) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["DocumentVersionList", parentAttachmentId,pageSize,pageNumber],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await DocumentRepository.getDocumentVersionList(parentAttachmentId,pageSize,pageNumber);
|
||||||
|
return resp.data
|
||||||
|
},
|
||||||
|
|
||||||
|
enabled: !!parentAttachmentId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const useDocumentVersion = (id)=>{
|
||||||
|
return useQuery({
|
||||||
|
queryKey:["DocumentVersion",id],
|
||||||
|
queryFn:async()=> await DocumentRepository.getDocumentVersion(id),
|
||||||
|
enabled:!!id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDocumentTags =()=>{
|
||||||
|
return useQuery({
|
||||||
|
queryKey:["DocumentTag"],
|
||||||
|
queryFn:async()=> {const resp = await DocumentRepository.getDocumentTags()
|
||||||
|
return resp.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//----------------------- MUTATION -------------------------
|
||||||
|
|
||||||
|
export const useUploadDocument = (onSuccessCallBack) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (DocumentPayload) =>
|
||||||
|
DocumentRepository.uploadDocument(DocumentPayload),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
|
|
||||||
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error);
|
||||||
|
showToast(
|
||||||
|
error.response.data.message ||
|
||||||
|
"Something went wrong please try again !",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const useUpdateDocument = (onSuccessCallBack) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({ documentId, DocumentPayload }) =>
|
||||||
|
DocumentRepository.UpdateDocument(documentId, DocumentPayload),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
const { documentId } = variables;
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["Document", documentId] });
|
||||||
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error);
|
||||||
|
showToast(
|
||||||
|
error.response.data.message ||
|
||||||
|
"Something went wrong please try again !",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useVerifyDocument = ()=>{
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn:async({documentId,isVerify}) => await DocumentRepository.verifyDocument(documentId,isVerify),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["DocumentVersionList"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["Document"] });
|
||||||
|
showToast(
|
||||||
|
data.response.data.message ||
|
||||||
|
"Document Successfully Verified !",
|
||||||
|
"success"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(
|
||||||
|
error.response.data.message ||
|
||||||
|
"Something went wrong please try again !",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useActiveInActiveDocument = ()=>{
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn:async({documentId,isActive}) => await DocumentRepository.deleteDocument(documentId,isActive),
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["DocumentList"] });
|
||||||
|
showToast(
|
||||||
|
data.response.data.message ||
|
||||||
|
"Document Successfully Verified !",
|
||||||
|
"success"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(
|
||||||
|
error.response.data.message ||
|
||||||
|
"Something went wrong please try again !",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -17,13 +17,13 @@ const cleanFilter = (filter) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// moment.utc() to get consistent UTC ISO strings
|
// moment.utc() to get consistent UTC ISO strings
|
||||||
if (!cleaned.startDate) {
|
// if (!cleaned.startDate) {
|
||||||
cleaned.startDate = moment.utc().subtract(7, "days").startOf("day").toISOString();
|
// cleaned.startDate = moment.utc().subtract(7, "days").startOf("day").toISOString();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!cleaned.endDate) {
|
// if (!cleaned.endDate) {
|
||||||
cleaned.endDate = moment.utc().startOf("day").toISOString();
|
// cleaned.endDate = moment.utc().startOf("day").toISOString();
|
||||||
}
|
// }
|
||||||
|
|
||||||
return cleaned;
|
return cleaned;
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import showToast from "../services/toastService";
|
import showToast from "../services/toastService";
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------Query-------------------
|
// ------------------------------Query-------------------
|
||||||
|
|
||||||
export const useProjects = () => {
|
export const useProjects = () => {
|
||||||
@ -153,7 +152,7 @@ export const useProjectName = () => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
refetch,
|
refetch,
|
||||||
isError
|
isError,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["basicProjectNameList"],
|
queryKey: ["basicProjectNameList"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -164,7 +163,13 @@ export const useProjectName = () => {
|
|||||||
showToast(error.message || "Error while Fetching project Name", "error");
|
showToast(error.message || "Error while Fetching project Name", "error");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return { projectNames: data, loading: isLoading, Error: error, refetch,isError };
|
return {
|
||||||
|
projectNames: data,
|
||||||
|
loading: isLoading,
|
||||||
|
Error: error,
|
||||||
|
refetch,
|
||||||
|
isError,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProjectInfra = (projectId) => {
|
export const useProjectInfra = (projectId) => {
|
||||||
@ -175,7 +180,7 @@ export const useProjectInfra = (projectId) => {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["ProjectInfra", projectId],
|
queryKey: ["ProjectInfra", projectId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if(!projectId) return null;
|
if (!projectId) return null;
|
||||||
const res = await ProjectRepository.getProjectInfraByproject(projectId);
|
const res = await ProjectRepository.getProjectInfraByproject(projectId);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
@ -207,12 +212,7 @@ export const useProjectTasks = (workAreaId, IsExpandedArea = false) => {
|
|||||||
return { ProjectTaskList, isLoading, error };
|
return { ProjectTaskList, isLoading, error };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProjectTasksByEmployee = (
|
export const useProjectTasksByEmployee = (employeeId, fromDate, toDate) => {
|
||||||
employeeId,
|
|
||||||
fromDate,
|
|
||||||
toDate,
|
|
||||||
) => {
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["TasksByEmployee", employeeId],
|
queryKey: ["TasksByEmployee", employeeId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -230,6 +230,43 @@ export const useProjectTasksByEmployee = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useProjectLevelEmployeeList = (ProjectId) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["ProjectLevelEmployeeList", ProjectId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await ProjectRepository.getProjectLevelEmployeeList(
|
||||||
|
ProjectId
|
||||||
|
);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
enabled: !!ProjectId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProjectLevelModules = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["ProjectLevelModules"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await ProjectRepository.getProjectLevelModules();
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useProjectLevelEmployeePermission = (employeeId, projectId) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["ProjectLevelEmployeePermission", employeeId, projectId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await ProjectRepository.getProjectLevelEmployeePermissions(
|
||||||
|
employeeId,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
return resp.data;
|
||||||
|
},
|
||||||
|
enabled: !!employeeId && !!projectId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// -- -------------Mutation-------------------------------
|
// -- -------------Mutation-------------------------------
|
||||||
|
|
||||||
export const useCreateProject = ({ onSuccessCallback }) => {
|
export const useCreateProject = ({ onSuccessCallback }) => {
|
||||||
@ -558,3 +595,26 @@ export const useDeleteProjectTask = (onSuccessCallback) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdateProjectLevelEmployeePermission = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload) =>
|
||||||
|
await ProjectRepository.updateProjectLevelEmployeePermission(payload),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["ProjectLevelEmployeePermission"],
|
||||||
|
});
|
||||||
|
showToast("Permission Updated successfully", "success");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showToast(
|
||||||
|
error?.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Failed to delete task",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
|
if (onSuccessCallback) onSuccessCallback();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
clearCacheKey,
|
clearCacheKey,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
getCachedProfileData,
|
getCachedProfileData,
|
||||||
useSelectedproject,
|
useSelectedProject,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
import AttendanceLog from "../../components/Activities/AttendcesLogs";
|
||||||
@ -26,11 +26,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||||||
const AttendancePage = () => {
|
const AttendancePage = () => {
|
||||||
const [activeTab, setActiveTab] = useState("all");
|
const [activeTab, setActiveTab] = useState("all");
|
||||||
const [ShowPending, setShowPending] = useState(false);
|
const [ShowPending, setShowPending] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState(""); // 🔹 New state for search
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const loginUser = getCachedProfileData();
|
const loginUser = getCachedProfileData();
|
||||||
// const selectedProject = useSelector((store) => store.localVariables.projectId);
|
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [attendances, setAttendances] = useState();
|
const [attendances, setAttendances] = useState();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { useTaskList } from "../../hooks/useTasks";
|
import { useTaskList } from "../../hooks/useTasks";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
@ -13,13 +13,13 @@ import SubTask from "../../components/Activities/SubTask";
|
|||||||
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
|
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
|
import { APPROVE_TASK, ASSIGN_REPORT_TASK } from "../../utils/constants";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Loader from "../../components/common/Loader";
|
import Loader from "../../components/common/Loader";
|
||||||
|
|
||||||
const DailyTask = () => {
|
const DailyTask = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const { projectNames } = useProjectName();
|
const { projectNames } = useProjectName();
|
||||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
||||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||||
@ -41,10 +41,20 @@ const DailyTask = () => {
|
|||||||
// Ensure project is set
|
// Ensure project is set
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedProject && projectNames.length > 0) {
|
if (!selectedProject && projectNames.length > 0) {
|
||||||
|
debugger
|
||||||
dispatch(setProjectId(projectNames[0].id));
|
dispatch(setProjectId(projectNames[0].id));
|
||||||
}
|
}
|
||||||
}, [selectedProject, projectNames, dispatch]);
|
}, [selectedProject, projectNames, dispatch]);
|
||||||
|
|
||||||
|
// 🔹 Reset filters when project changes
|
||||||
|
useEffect(() => {
|
||||||
|
setFilters({
|
||||||
|
selectedBuilding: "",
|
||||||
|
selectedFloors: [],
|
||||||
|
selectedActivities: [],
|
||||||
|
});
|
||||||
|
}, [selectedProject]);
|
||||||
|
|
||||||
// Memoized filtering
|
// Memoized filtering
|
||||||
const filteredTasks = useMemo(() => {
|
const filteredTasks = useMemo(() => {
|
||||||
if (!TaskList) return [];
|
if (!TaskList) return [];
|
||||||
@ -91,8 +101,8 @@ const DailyTask = () => {
|
|||||||
data-bs-content={`
|
data-bs-content={`
|
||||||
<div class="border border-secondary rounded custom-popover p-2 px-3">
|
<div class="border border-secondary rounded custom-popover p-2 px-3">
|
||||||
${task.teamMembers
|
${task.teamMembers
|
||||||
.map(
|
.map(
|
||||||
(m) => `
|
(m) => `
|
||||||
<div class="d-flex align-items-center gap-2 mb-2">
|
<div class="d-flex align-items-center gap-2 mb-2">
|
||||||
<div class="avatar avatar-xs">
|
<div class="avatar avatar-xs">
|
||||||
<span class="avatar-initial rounded-circle bg-label-primary">
|
<span class="avatar-initial rounded-circle bg-label-primary">
|
||||||
@ -101,8 +111,8 @@ const DailyTask = () => {
|
|||||||
</div>
|
</div>
|
||||||
<span>${m.firstName} ${m.lastName}</span>
|
<span>${m.firstName} ${m.lastName}</span>
|
||||||
</div>`
|
</div>`
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
@ -163,6 +173,7 @@ const DailyTask = () => {
|
|||||||
currentSelectedBuilding={filters.selectedBuilding}
|
currentSelectedBuilding={filters.selectedBuilding}
|
||||||
currentSelectedFloors={filters.selectedFloors}
|
currentSelectedFloors={filters.selectedFloors}
|
||||||
currentSelectedActivities={filters.selectedActivities}
|
currentSelectedActivities={filters.selectedActivities}
|
||||||
|
selectedProject={selectedProject}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,21 +4,19 @@ import InfraPlanning from "../../components/Activities/InfraPlanning";
|
|||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
import { useSelectedproject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
|
||||||
const TaskPlannng = () => {
|
const TaskPlannng = () => {
|
||||||
const selectedProject = useSelectedproject();
|
const selectedProject = useSelectedProject();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { projectNames, loading: projectLoading } = useProjectName();
|
const { projectNames = [], loading: projectLoading } = useProjectName();
|
||||||
|
|
||||||
const initialized = useRef(false);
|
useEffect(() => {
|
||||||
|
if (!selectedProject) {
|
||||||
|
dispatch(setProjectId(projectNames[0].id));
|
||||||
|
}
|
||||||
|
}, [projectNames, selectedProject?.id, dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!initialized.current && projectNames.length > 0 && !selectedProject?.id) {
|
|
||||||
dispatch(setProjectId(projectNames[0].id));
|
|
||||||
initialized.current = true;
|
|
||||||
}
|
|
||||||
}, [projectNames, selectedProject, dispatch]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
|
@ -301,25 +301,15 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
{deleteContact && (
|
{deleteContact && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade ${deleteContact ? "show" : ""}`}
|
isOpen={!!deleteContact}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header="Delete Contact"
|
||||||
style={{
|
message="Are you sure you want delete?"
|
||||||
display: deleteContact ? "block" : "none",
|
onSubmit={handleDeleteContact}
|
||||||
backgroundColor: deleteContact ? "rgba(0,0,0,0.5)" : "transparent",
|
onClose={() => setDeleteContact(null)}
|
||||||
}}
|
loading={IsDeleting}
|
||||||
aria-hidden="false"
|
/>
|
||||||
>
|
|
||||||
<ConfirmModal
|
|
||||||
type={"delete"}
|
|
||||||
header={"Delete Contact"}
|
|
||||||
message={"Are you sure you want delete?"}
|
|
||||||
onSubmit={handleDeleteContact}
|
|
||||||
onClose={() => setDeleteContact(null)}
|
|
||||||
loading={IsDeleting}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{openBucketModal && (
|
{openBucketModal && (
|
||||||
@ -383,12 +373,14 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
|
|
||||||
{/* Empty state AFTER list */}
|
{/* Empty state AFTER list */}
|
||||||
{!loading && contacts?.length === 0 && (
|
{!loading && contacts?.length === 0 && (
|
||||||
<p className="mt-3 ms-3 text-muted" >No contact found</p>
|
<p className="mt-3 ms-3 text-muted">No contact found</p>
|
||||||
)}
|
)}
|
||||||
{!loading &&
|
{!loading &&
|
||||||
contacts?.length > 0 &&
|
contacts?.length > 0 &&
|
||||||
currentItems.length === 0 && (
|
currentItems.length === 0 && (
|
||||||
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
|
<p className="mt-3 ms-3 text-muted">
|
||||||
|
No matching contact found
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -419,11 +411,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
{!loading && contacts?.length === 0 && (
|
{!loading && contacts?.length === 0 && (
|
||||||
<p className="mt-3 ms-3 text-muted">No contact found</p>
|
<p className="mt-3 ms-3 text-muted">No contact found</p>
|
||||||
)}
|
)}
|
||||||
{!loading &&
|
{!loading && contacts?.length > 0 && currentItems.length === 0 && (
|
||||||
contacts?.length > 0 &&
|
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
|
||||||
currentItems.length === 0 && (
|
)}
|
||||||
<p className="mt-3 ms-3 text-muted">No matching contact found</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -446,7 +436,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
currentItems.length > ITEMS_PER_PAGE && (
|
currentItems.length > ITEMS_PER_PAGE && (
|
||||||
<nav aria-label="Page navigation">
|
<nav aria-label="Page navigation">
|
||||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||||
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
|
<li
|
||||||
|
className={`page-item ${currentPage === 1 ? "disabled" : ""}`}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link btn-xs"
|
className="page-link btn-xs"
|
||||||
onClick={() => paginate(currentPage - 1)}
|
onClick={() => paginate(currentPage - 1)}
|
||||||
@ -458,8 +450,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
{[...Array(totalPages)].map((_, index) => (
|
{[...Array(totalPages)].map((_, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
className={`page-item ${
|
||||||
}`}
|
currentPage === index + 1 ? "active" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
@ -471,8 +464,9 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<li
|
<li
|
||||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
className={`page-item ${
|
||||||
}`}
|
currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
@ -489,4 +483,4 @@ const Directory = ({ IsPage = true, prefernceContacts }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Directory;
|
export default Directory;
|
||||||
|
41
src/pages/Documents/DocumentPage.jsx
Normal file
41
src/pages/Documents/DocumentPage.jsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Breadcrumb from '../../components/common/Breadcrumb'
|
||||||
|
|
||||||
|
const DocumentPage = () => {
|
||||||
|
return (
|
||||||
|
<div className=''>
|
||||||
|
|
||||||
|
<div className="card d-flex p-2">
|
||||||
|
<div className="row align-items-center">
|
||||||
|
{/* Search */}
|
||||||
|
<div className="col-6 col-md-6 col-lg-3 mb-md-0">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
placeholder="Search Document"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="col-6 col-md-6 col-lg-9 text-end">
|
||||||
|
<span className="text-tiny text-muted p-1 border-0 bg-none lead mx-3 cursor-pointer">
|
||||||
|
Refresh
|
||||||
|
< i className={`bx bx-refresh ms-1 `}></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Add New Document"
|
||||||
|
className="p-1 bg-primary rounded-circle cursor-pointer"
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus fs-4 text-white"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DocumentPage
|
@ -85,6 +85,14 @@ const LoginPage = () => {
|
|||||||
navigate("/auth/login-otp");
|
navigate("/auth/login-otp");
|
||||||
}
|
}
|
||||||
}, [IsLoginWithOTP]);
|
}, [IsLoginWithOTP]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const token = localStorage.getItem("jwtToken");
|
||||||
|
// if (token) {
|
||||||
|
// navigate("/dashboard");
|
||||||
|
// }
|
||||||
|
// }, [navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
<h4 className="mb-2">Welcome to PMS!</h4>
|
<h4 className="mb-2">Welcome to PMS!</h4>
|
||||||
@ -210,7 +218,7 @@ const LoginPage = () => {
|
|||||||
Login With Password
|
Login With Password
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<Link to="/auth/reqest/demo" className="registration-link">
|
<Link to="/market/enquire" className="registration-link">
|
||||||
Request a Demo
|
Request a Demo
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
VIEW_ALL_EMPLOYEES,
|
VIEW_ALL_EMPLOYEES,
|
||||||
VIEW_TEAM_MEMBERS,
|
VIEW_TEAM_MEMBERS,
|
||||||
} from "../../utils/constants";
|
} from "../../utils/constants";
|
||||||
import { clearCacheKey, useSelectedproject } from "../../slices/apiDataManager";
|
import { clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
|
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
|
||||||
import {
|
import {
|
||||||
@ -41,7 +41,7 @@ const EmployeeList = () => {
|
|||||||
// const selectedProjectId = useSelector(
|
// const selectedProjectId = useSelector(
|
||||||
// (store) => store.localVariables.projectId
|
// (store) => store.localVariables.projectId
|
||||||
// );
|
// );
|
||||||
const selectedProjectId = useSelectedproject();
|
const selectedProjectId = useSelectedProject();
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -177,10 +177,12 @@ const EmployeeList = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && Array.isArray(employees)) {
|
if (!loading && Array.isArray(employees)) {
|
||||||
const sorted = [...employees].sort((a, b) => {
|
const sorted = [...employees].sort((a, b) => {
|
||||||
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""
|
const nameA = `${a.firstName || ""}${a.middleName || ""}${
|
||||||
}`.toLowerCase();
|
a.lastName || ""
|
||||||
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""
|
}`.toLowerCase();
|
||||||
}`.toLowerCase();
|
const nameB = `${b.firstName || ""}${b.middleName || ""}${
|
||||||
|
b.lastName || ""
|
||||||
|
}`.toLowerCase();
|
||||||
return nameA?.localeCompare(nameB);
|
return nameA?.localeCompare(nameB);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -259,37 +261,26 @@ const EmployeeList = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{IsDeleteModalOpen && (
|
{IsDeleteModalOpen && (
|
||||||
<div
|
<ConfirmModal
|
||||||
className={`modal fade ${IsDeleteModalOpen ? "show" : ""}`}
|
isOpen={IsDeleteModalOpen}
|
||||||
tabIndex="-1"
|
type="delete"
|
||||||
role="dialog"
|
header={
|
||||||
style={{
|
selectedEmpFordelete?.isActive
|
||||||
display: IsDeleteModalOpen ? "block" : "none",
|
? "Suspend Employee"
|
||||||
backgroundColor: IsDeleteModalOpen
|
: "Reactivate Employee"
|
||||||
? "rgba(0,0,0,0.5)"
|
}
|
||||||
: "transparent",
|
message={`Are you sure you want to ${
|
||||||
}}
|
selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
||||||
aria-hidden="false"
|
} this employee?`}
|
||||||
>
|
onSubmit={() =>
|
||||||
<ConfirmModal
|
suspendEmployee({
|
||||||
type={"delete"}
|
employeeId: selectedEmpFordelete.id,
|
||||||
header={
|
active: !selectedEmpFordelete.isActive,
|
||||||
selectedEmpFordelete?.isActive
|
})
|
||||||
? "Suspend Employee"
|
}
|
||||||
: "Reactivate Employee"
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
}
|
loading={employeeLodaing}
|
||||||
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
|
/>
|
||||||
} this employee?`}
|
|
||||||
onSubmit={() =>
|
|
||||||
suspendEmployee({
|
|
||||||
employeeId: selectedEmpFordelete.id,
|
|
||||||
active: !selectedEmpFordelete.isActive,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onClose={() => setIsDeleteModalOpen(false)}
|
|
||||||
loading={employeeLodaing}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -511,8 +502,9 @@ const EmployeeList = () => {
|
|||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className={`sorting_disabled ${!Manage_Employee && "d-none"
|
className={`sorting_disabled ${
|
||||||
}`}
|
!Manage_Employee && "d-none"
|
||||||
|
}`}
|
||||||
rowSpan="1"
|
rowSpan="1"
|
||||||
colSpan="1"
|
colSpan="1"
|
||||||
style={{ width: "50px" }}
|
style={{ width: "50px" }}
|
||||||
@ -532,9 +524,9 @@ const EmployeeList = () => {
|
|||||||
)}
|
)}
|
||||||
{/* Conditional messages for no data or no search results */}
|
{/* Conditional messages for no data or no search results */}
|
||||||
{!loading &&
|
{!loading &&
|
||||||
displayData?.length === 0 &&
|
displayData?.length === 0 &&
|
||||||
searchText &&
|
searchText &&
|
||||||
!showAllEmployees ? (
|
!showAllEmployees ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8}>
|
<td colSpan={8}>
|
||||||
<small className="muted">
|
<small className="muted">
|
||||||
@ -544,8 +536,8 @@ const EmployeeList = () => {
|
|||||||
</tr>
|
</tr>
|
||||||
) : null}
|
) : null}
|
||||||
{!loading &&
|
{!loading &&
|
||||||
displayData?.length === 0 &&
|
displayData?.length === 0 &&
|
||||||
(!searchText || showAllEmployees) ? (
|
(!searchText || showAllEmployees) ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td
|
<td
|
||||||
colSpan={8}
|
colSpan={8}
|
||||||
@ -639,7 +631,9 @@ const EmployeeList = () => {
|
|||||||
<div className="dropdown-menu dropdown-menu-end">
|
<div className="dropdown-menu dropdown-menu-end">
|
||||||
{/* View always visible */}
|
{/* View always visible */}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/employee/${item.id}`)}
|
onClick={() =>
|
||||||
|
navigate(`/employee/${item.id}`)
|
||||||
|
}
|
||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
>
|
>
|
||||||
<i className="bx bx-detail bx-sm"></i> View
|
<i className="bx bx-detail bx-sm"></i> View
|
||||||
@ -650,9 +644,12 @@ const EmployeeList = () => {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
onClick={() => handleEmployeeModel(item.id)}
|
onClick={() =>
|
||||||
|
handleEmployeeModel(item.id)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-edit bx-sm"></i> Edit
|
<i className="bx bx-edit bx-sm"></i>{" "}
|
||||||
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Suspend only when active */}
|
{/* Suspend only when active */}
|
||||||
@ -661,7 +658,8 @@ const EmployeeList = () => {
|
|||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
onClick={() => handleOpenDelete(item)}
|
onClick={() => handleOpenDelete(item)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-task-x bx-sm"></i> Suspend
|
<i className="bx bx-task-x bx-sm"></i>{" "}
|
||||||
|
Suspend
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -670,11 +668,13 @@ const EmployeeList = () => {
|
|||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#managerole-modal"
|
data-bs-target="#managerole-modal"
|
||||||
onClick={() => setEmpForManageRole(item.id)}
|
onClick={() =>
|
||||||
|
setEmpForManageRole(item.id)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bx-cog bx-sm"></i> Manage Role
|
<i className="bx bx-cog bx-sm"></i>{" "}
|
||||||
|
Manage Role
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -684,7 +684,8 @@ const EmployeeList = () => {
|
|||||||
className="dropdown-item py-1"
|
className="dropdown-item py-1"
|
||||||
onClick={() => handleOpenDelete(item)}
|
onClick={() => handleOpenDelete(item)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-refresh bx-sm me-1"></i> Re-activate
|
<i className="bx bx-refresh bx-sm me-1"></i>{" "}
|
||||||
|
Re-activate
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -703,8 +704,9 @@ const EmployeeList = () => {
|
|||||||
<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 ${currentPage === 1 ? "disabled" : ""
|
className={`page-item ${
|
||||||
}`}
|
currentPage === 1 ? "disabled" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link btn-xs"
|
className="page-link btn-xs"
|
||||||
@ -717,8 +719,9 @@ const EmployeeList = () => {
|
|||||||
{[...Array(totalPages)]?.map((_, index) => (
|
{[...Array(totalPages)]?.map((_, index) => (
|
||||||
<li
|
<li
|
||||||
key={index}
|
key={index}
|
||||||
className={`page-item ${currentPage === index + 1 ? "active" : ""
|
className={`page-item ${
|
||||||
}`}
|
currentPage === index + 1 ? "active" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
@ -730,8 +733,9 @@ const EmployeeList = () => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<li
|
<li
|
||||||
className={`page-item ${currentPage === totalPages ? "disabled" : ""
|
className={`page-item ${
|
||||||
}`}
|
currentPage === totalPages ? "disabled" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className="page-link"
|
className="page-link"
|
||||||
|
@ -4,43 +4,45 @@ import MasterModal from "../../components/master/MasterModal";
|
|||||||
import { mastersList } from "../../data/masters";
|
import { mastersList } from "../../data/masters";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||||
import useMaster, { useMasterMenu } from "../../hooks/masterHook/useMaster"
|
import useMaster, {
|
||||||
|
useDeleteMasterItem,
|
||||||
|
useMasterMenu,
|
||||||
|
} from "../../hooks/masterHook/useMaster";
|
||||||
import MasterTable from "./MasterTable";
|
import MasterTable from "./MasterTable";
|
||||||
import { getCachedData } from "../../slices/apiDataManager";
|
import { getCachedData } from "../../slices/apiDataManager";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { MANAGE_MASTER } from "../../utils/constants";
|
import { MANAGE_MASTER } from "../../utils/constants";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import GlobalModel from "../../components/common/GlobalModel";
|
||||||
|
import ConfirmModal from "../../components/common/ConfirmModal";
|
||||||
|
|
||||||
const MasterPage = () => {
|
const MasterPage = () => {
|
||||||
const {data,isLoading,isError,error:menuError} = useMasterMenu()
|
const { data, isLoading, isError, error: menuError } = useMasterMenu();
|
||||||
const [modalConfig, setModalConfig] = useState({ modalType: "", item: null, masterType: null });
|
const [modalConfig, setModalConfig] = useState(null);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [filteredResults, setFilteredResults] = useState([]);
|
const [filteredResults, setFilteredResults] = useState([]);
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
|
|
||||||
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER);
|
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
|
const selectedMaster = useSelector(
|
||||||
|
(store) => store.localVariables.selectedMaster
|
||||||
|
);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: masterData = [], loading, error, RecallApi,isError:isMasterError } = useMaster();
|
const {
|
||||||
|
data: masterData = [],
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
RecallApi,
|
||||||
|
isError: isMasterError,
|
||||||
|
} = useMaster();
|
||||||
|
const { mutate: deleteMasterItem, isPending } = useDeleteMasterItem();
|
||||||
const openModal = () => setIsCreateModalOpen(true);
|
const openModal = () => setIsCreateModalOpen(true);
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsCreateModalOpen(false);
|
setIsCreateModalOpen(false);
|
||||||
setModalConfig(null);
|
setModalConfig(null);
|
||||||
|
|
||||||
// Clean up Bootstrap modal manually
|
|
||||||
const modalEl = document.getElementById('master-modal');
|
|
||||||
modalEl?.classList.remove('show');
|
|
||||||
if (modalEl) modalEl.style.display = 'none';
|
|
||||||
|
|
||||||
document.body.classList.remove('modal-open');
|
|
||||||
document.body.style.overflow = 'auto';
|
|
||||||
|
|
||||||
document.querySelectorAll('.modal-backdrop').forEach((el) => el.remove());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleModalData = (modalType, item, masterType = selectedMaster) => {
|
const handleModalData = (modalType, item, masterType = selectedMaster) => {
|
||||||
@ -54,15 +56,17 @@ const MasterPage = () => {
|
|||||||
if (!masterData?.length) return;
|
if (!masterData?.length) return;
|
||||||
|
|
||||||
const results = masterData.filter((item) =>
|
const results = masterData.filter((item) =>
|
||||||
Object.values(item).some(
|
Object.values(item).some((field) =>
|
||||||
(field) => field?.toString().toLowerCase().includes(value)
|
field?.toString().toLowerCase().includes(value)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
setFilteredResults(results);
|
setFilteredResults(results);
|
||||||
};
|
};
|
||||||
const displayData = useMemo(() => {
|
const displayData = useMemo(() => {
|
||||||
if (searchTerm) return filteredResults;
|
if (searchTerm) return filteredResults;
|
||||||
return queryClient.getQueryData(["masterData", selectedMaster]) || masterData;
|
return (
|
||||||
|
queryClient.getQueryData(["masterData", selectedMaster]) || masterData
|
||||||
|
);
|
||||||
}, [searchTerm, filteredResults, selectedMaster, masterData]);
|
}, [searchTerm, filteredResults, selectedMaster, masterData]);
|
||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
@ -73,9 +77,12 @@ const MasterPage = () => {
|
|||||||
}));
|
}));
|
||||||
}, [displayData]);
|
}, [displayData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalConfig) openModal();
|
if (modalConfig?.modalType && modalConfig?.modalType !== "delete") {
|
||||||
}, [modalConfig]);
|
openModal();
|
||||||
|
}
|
||||||
|
}, [modalConfig]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -84,15 +91,46 @@ const MasterPage = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if(isError || isMasterError) return <div className="d-flex flex-column align-items-center justify-content-center py-5">
|
if (isError || isMasterError)
|
||||||
<h4 className=" mb-3"><i className="fa-solid fa-triangle-exclamation fs-5" /> Oops, an error occurred</h4>
|
return (
|
||||||
<p className="text-muted">{error?.message || menuError?.message}</p>
|
<div className="d-flex flex-column align-items-center justify-content-center py-5">
|
||||||
</div>
|
<h4 className=" mb-3">
|
||||||
|
<i className="fa-solid fa-triangle-exclamation fs-5" /> Oops, an error
|
||||||
|
occurred
|
||||||
|
</h4>
|
||||||
|
<p className="text-muted">{error?.message || menuError?.message}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isCreateModalOpen && (
|
{modalConfig?.modalType === "delete" && (
|
||||||
<MasterModal modaldata={modalConfig} closeModal={closeModal} />
|
<ConfirmModal
|
||||||
|
type="delete"
|
||||||
|
header={`Delete ${modalConfig.masterType}`}
|
||||||
|
message="Are you sure you want to delete?"
|
||||||
|
onSubmit={() => {
|
||||||
|
deleteMasterItem(
|
||||||
|
{
|
||||||
|
masterType: modalConfig.masterType,
|
||||||
|
item: modalConfig.item,
|
||||||
|
validateFn: modalConfig.validateFn,
|
||||||
|
},
|
||||||
|
{ onSuccess: closeModal }
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onClose={closeModal}
|
||||||
|
loading={isPending}
|
||||||
|
isOpen={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isCreateModalOpen && modalConfig?.modalType !== "delete" && (
|
||||||
|
<GlobalModel
|
||||||
|
isOpen={isCreateModalOpen}
|
||||||
|
closeModal={closeModal}
|
||||||
|
size={modalConfig?.masterType === "Application Role" ? "lg" : "md"}
|
||||||
|
>
|
||||||
|
<MasterModal modaldata={modalConfig} closeModal={closeModal} />
|
||||||
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -119,17 +157,25 @@ const MasterPage = () => {
|
|||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
<select
|
<select
|
||||||
onChange={(e) => dispatch(changeMaster(e.target.value))}
|
onChange={(e) =>
|
||||||
|
dispatch(changeMaster(e.target.value))
|
||||||
|
}
|
||||||
name="DataTables_Table_0_length"
|
name="DataTables_Table_0_length"
|
||||||
aria-controls="DataTables_Table_0"
|
aria-controls="DataTables_Table_0"
|
||||||
className="form-select form-select-sm"
|
className="form-select py-1 px-2"
|
||||||
|
style={{ fontSize: "0.875rem", height: "32px", width: "150px" }}
|
||||||
value={selectedMaster}
|
value={selectedMaster}
|
||||||
>
|
>
|
||||||
{isLoading && (<option value={null}>Loading...</option>)}
|
{isLoading && (
|
||||||
{(!isLoading && data) && data?.map((item) => (
|
<option value={null}>Loading...</option>
|
||||||
|
)}
|
||||||
<option key={item.id} value={item.name}>{item.name}</option>
|
{!isLoading &&
|
||||||
))}
|
data &&
|
||||||
|
data?.map((item) => (
|
||||||
|
<option key={item.id} value={item.name}>
|
||||||
|
{item.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -152,10 +198,13 @@ const MasterPage = () => {
|
|||||||
></input>
|
></input>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={`dt-buttons btn-group flex-wrap ${!hasMasterPermission && 'd-none'}`}>
|
<div
|
||||||
|
className={`dt-buttons btn-group flex-wrap ${
|
||||||
|
!hasMasterPermission && "d-none"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{" "}
|
{" "}
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`btn btn-sm add-new btn-primary `}
|
className={`btn btn-sm add-new btn-primary `}
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
@ -164,13 +213,17 @@ const MasterPage = () => {
|
|||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#master-modal"
|
data-bs-target="#master-modal"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleModalData(selectedMaster, "null", selectedMaster)
|
handleModalData(
|
||||||
|
selectedMaster,
|
||||||
|
null,
|
||||||
|
selectedMaster
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
<span className=" d-sm-inline-block">
|
<span className=" d-sm-inline-block">
|
||||||
Add {selectedMaster}
|
Add {selectedMaster}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>{" "}
|
</button>{" "}
|
||||||
@ -180,13 +233,17 @@ const MasterPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MasterTable data={displayData} columns={columns} loading={loading} handleModalData={handleModalData} />
|
<MasterTable
|
||||||
|
data={displayData}
|
||||||
|
columns={columns}
|
||||||
|
loading={loading}
|
||||||
|
handleModalData={handleModalData}
|
||||||
|
/>
|
||||||
<div style={{ width: "1%" }}></div>
|
<div style={{ width: "1%" }}></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,14 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
|
|||||||
"noOfPersonsRequired",
|
"noOfPersonsRequired",
|
||||||
"color",
|
"color",
|
||||||
"displayName",
|
"displayName",
|
||||||
"permissionIds"
|
"permissionIds",
|
||||||
|
"entityTypeId",
|
||||||
|
"regexExpression",
|
||||||
|
"isMandatory",
|
||||||
|
"maxFilesAllowed",
|
||||||
|
"maxSizeAllowedInMB",
|
||||||
|
"isValidationRequired",
|
||||||
|
"documentCategory"
|
||||||
];
|
];
|
||||||
|
|
||||||
const safeData = Array.isArray(data) ? data : [];
|
const safeData = Array.isArray(data) ? data : [];
|
||||||
@ -75,17 +82,16 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
) : (
|
) : (
|
||||||
<table
|
<table className="datatables-users table border-top dataTable no-footer dtr-column w-100">
|
||||||
className="datatables-users table border-top dataTable no-footer dtr-column"
|
<thead className="shadow-sm">
|
||||||
id="DataTables_Table_0"
|
|
||||||
aria-describedby="DataTables_Table_0_info"
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th className="text-start"> {selectedMaster === "Activity" ? "Activity" : "Name"}</th>
|
<th className="text-start"> {selectedMaster === "Activity" ? "Activity" : "Name"}</th>
|
||||||
<th className="text-start"> {selectedMaster === "Activity" ? "Unit" : "Description"}</th>
|
<th className="text-start"> {selectedMaster === "Activity"
|
||||||
|
? "Unit"
|
||||||
|
: selectedMaster === "Document Type"
|
||||||
|
? "Content Type"
|
||||||
|
: "Description"}</th>
|
||||||
<th className={` ${!hasMasterPermission && "d-none"}`}>
|
<th className={` ${!hasMasterPermission && "d-none"}`}>
|
||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
@ -151,19 +157,17 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => {
|
|||||||
data-bs-target="#master-modal"
|
data-bs-target="#master-modal"
|
||||||
className="btn p-0 dropdown-toggle hide-arrow"
|
className="btn p-0 dropdown-toggle hide-arrow"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleModalData(`Edit-${selectedMaster}`, item)
|
handleModalData(`Edit-${selectedMaster}`, item, selectedMaster)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<i className="bx bxs-edit me-2 text-primary"></i>
|
<i className="bx bxs-edit me-2 text-primary"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
aria-label="Delete"
|
aria-label="Delete"
|
||||||
type="button"
|
type="button"
|
||||||
className="btn p-0 dropdown-toggle hide-arrow"
|
className="btn p-0 dropdown-toggle hide-arrow"
|
||||||
data-bs-toggle="modal"
|
onClick={() => handleModalData("delete", item, selectedMaster)}
|
||||||
data-bs-target="#master-modal"
|
|
||||||
onClick={() => handleModalData("delete", item)}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-trash me-1 text-danger"></i>
|
<i className="bx bx-trash me-1 text-danger"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@ -13,12 +13,10 @@ import {
|
|||||||
cacheData,
|
cacheData,
|
||||||
clearCacheKey,
|
clearCacheKey,
|
||||||
getCachedData,
|
getCachedData,
|
||||||
useSelectedproject,
|
useSelectedProject,
|
||||||
} from "../../slices/apiDataManager";
|
} from "../../slices/apiDataManager";
|
||||||
import "./ProjectDetails.css";
|
import "./ProjectDetails.css";
|
||||||
import {
|
import { useProjectDetails } from "../../hooks/useProjects";
|
||||||
useProjectDetails,
|
|
||||||
} from "../../hooks/useProjects";
|
|
||||||
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
import { ComingSoonPage } from "../Misc/ComingSoonPage";
|
||||||
import Directory from "../Directory/Directory";
|
import Directory from "../Directory/Directory";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
@ -26,19 +24,22 @@ import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChar
|
|||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import AttendanceOverview from "../../components/Dashboard/AttendanceChart";
|
import AttendanceOverview from "../../components/Dashboard/AttendanceChart";
|
||||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||||
|
import ProjectDocument from "../../components/Project/ProjectDocuments";
|
||||||
|
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||||
|
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||||
|
|
||||||
const ProjectDetails = () => {
|
const ProjectDetails = () => {
|
||||||
|
|
||||||
const projectId = useSelectedproject()
|
const projectId = useSelectedProject()
|
||||||
|
|
||||||
const { projectNames, fetchData } = useProjectName();
|
const { projectNames, fetchData } = useProjectName();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectId == null) {
|
if (projectId == null) {
|
||||||
dispatch(setProjectId(projectNames[0]?.id));
|
dispatch(setProjectId(projectNames[0]?.id));
|
||||||
}
|
}
|
||||||
}, [projectNames])
|
}, [projectNames]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projects_Details,
|
projects_Details,
|
||||||
@ -49,12 +50,15 @@ const ProjectDetails = () => {
|
|||||||
|
|
||||||
// const [activePill, setActivePill] = useState("profile");
|
// const [activePill, setActivePill] = useState("profile");
|
||||||
const [activePill, setActivePill] = useState(() => {
|
const [activePill, setActivePill] = useState(() => {
|
||||||
return localStorage.getItem("lastActiveProjectTab") || "profile";
|
return localStorage.getItem("lastActiveProjectTab") || "profile";
|
||||||
});
|
});
|
||||||
|
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
(msg) => {
|
(msg) => {
|
||||||
if (msg.keyword === "Update_Project" && projects_Details?.id === msg.response.id) {
|
if (
|
||||||
|
msg.keyword === "Update_Project" &&
|
||||||
|
projects_Details?.id === msg.response.id
|
||||||
|
) {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -66,11 +70,10 @@ const ProjectDetails = () => {
|
|||||||
return () => eventBus.off("project", handler);
|
return () => eventBus.off("project", handler);
|
||||||
}, [handler]);
|
}, [handler]);
|
||||||
|
|
||||||
const handlePillClick = (pillKey) => {
|
const handlePillClick = (pillKey) => {
|
||||||
setActivePill(pillKey);
|
setActivePill(pillKey);
|
||||||
localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage
|
localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (projectLoading || !projects_Details) return <Loader />;
|
if (projectLoading || !projects_Details) return <Loader />;
|
||||||
@ -85,9 +88,14 @@ const ProjectDetails = () => {
|
|||||||
<ProjectOverview project={projectId} />
|
<ProjectOverview project={projectId} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-8 col-md-7 mt-5">
|
<div className="col-lg-8 col-md-7 mt-5">
|
||||||
<ProjectProgressChart ShowAllProject="false" DefaultRange="1M" />
|
<ProjectProgressChart
|
||||||
<div className="mt-5"> <AttendanceOverview /></div>
|
ShowAllProject="false"
|
||||||
|
DefaultRange="1M"
|
||||||
|
/>
|
||||||
|
<div className="mt-5">
|
||||||
|
{" "}
|
||||||
|
<AttendanceOverview />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -103,14 +111,10 @@ const ProjectDetails = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case "infra":
|
case "infra":
|
||||||
return (
|
return <ProjectInfra data={projects_Details} onDataChange={refetch} />;
|
||||||
<ProjectInfra data={projects_Details} onDataChange={refetch} />
|
|
||||||
);
|
|
||||||
|
|
||||||
case "workplan":
|
case "workplan":
|
||||||
return (
|
return <WorkPlan data={projects_Details} onDataChange={refetch} />;
|
||||||
<WorkPlan data={projects_Details} onDataChange={refetch} />
|
|
||||||
);
|
|
||||||
|
|
||||||
case "directory":
|
case "directory":
|
||||||
return (
|
return (
|
||||||
@ -118,6 +122,18 @@ const ProjectDetails = () => {
|
|||||||
<Directory IsPage={false} prefernceContacts={projects_Details.id} />
|
<Directory IsPage={false} prefernceContacts={projects_Details.id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
case "documents":
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<ProjectDocuments />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "setting":
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<ProjectSetting />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <ComingSoonPage />;
|
return <ComingSoonPage />;
|
||||||
@ -142,4 +158,4 @@ const ProjectDetails = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectDetails;
|
export default ProjectDetails;
|
||||||
|
@ -34,6 +34,8 @@ export const DirectoryRepository = {
|
|||||||
DeleteNote: (id, isActive) =>
|
DeleteNote: (id, isActive) =>
|
||||||
api.delete(`/api/directory/note/${id}?active=${isActive}`),
|
api.delete(`/api/directory/note/${id}?active=${isActive}`),
|
||||||
|
|
||||||
GetNotes: (pageSize, pageNumber) =>
|
GetNotes: (pageSize, pageNumber, projectId) =>
|
||||||
api.get(`/api/directory/notes?pageSize=${pageSize}&pageNumber=${pageNumber}`),
|
api.get(
|
||||||
|
`/api/directory/notes?pageSize=${pageSize}&pageNumber=${pageNumber}&projectId=${projectId}`
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
26
src/repositories/DocumentRepository.jsx
Normal file
26
src/repositories/DocumentRepository.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { api } from "../utils/axiosClient";
|
||||||
|
|
||||||
|
export const DocumentRepository = {
|
||||||
|
uploadDocument:(data)=> api.post(`/api/Document/upload`,data),
|
||||||
|
getDocumentList:(entityTypeId,entityId,pageSize, pageNumber, filter,searchString,isActive)=>{
|
||||||
|
const payloadJsonString = JSON.stringify(filter);
|
||||||
|
return api.get(`/api/Document/list/${entityTypeId}/entity/${entityId}/?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}&isActive=${isActive}`)
|
||||||
|
},
|
||||||
|
getDocumentById:(id)=>api.get(`/api/Document/get/details/${id}`),
|
||||||
|
|
||||||
|
getFilterEntities:(entityTypeId)=>api.get(`/api/Document/get/filter/${entityTypeId}`),
|
||||||
|
|
||||||
|
UpdateDocument:(documentId,data)=>api.put(`/api/Document/edit/${documentId}`,data),
|
||||||
|
|
||||||
|
getDocumentVersionList:(parentAttachmentId,pageSize,pageNumber)=>api.get(`/api/Document/list/versions/${parentAttachmentId}/?pageSize=${pageSize}&pageNumber=${pageNumber}`),
|
||||||
|
|
||||||
|
getDocumentVersion:(id)=>api.get(`/api/Document/get/version/${id}`),
|
||||||
|
|
||||||
|
verifyDocument:(id,isVerify)=>api.post(`/api/Document/verify/${id}/?isVerify=${isVerify}`),
|
||||||
|
|
||||||
|
deleteDocument:(id,isActive)=>api.delete(`/api/Document/delete/${id}/?isActive=${isActive}`),
|
||||||
|
|
||||||
|
getDocumentTags:()=>api.get('/api/Document/get/tags')
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { api } from "../utils/axiosClient";
|
import { api } from "../utils/axiosClient";
|
||||||
|
|
||||||
export const MarketRepository = {
|
export const MarketRepository = {
|
||||||
requestDemo: (data) => api.post("/api/market/inquiry", data),
|
requestDemo: (data) => api.post("/api/market/enquire", data),
|
||||||
getIndustries: () => api.get("api/market/industries"),
|
getIndustries: () => api.get("api/market/industries"),
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ export const RolesRepository = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MasterRespository = {
|
export const MasterRespository = {
|
||||||
getMasterMenus:()=>api.get("/api/AppMenu/get/master-list"),
|
getMasterMenus: () => api.get("/api/AppMenu/get/master-list"),
|
||||||
|
|
||||||
getRoles: () => api.get("/api/roles"),
|
getRoles: () => api.get("/api/roles"),
|
||||||
createRole: (data) => api.post("/api/roles", data),
|
createRole: (data) => api.post("/api/roles", data),
|
||||||
@ -48,6 +48,9 @@ export const MasterRespository = {
|
|||||||
api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)),
|
api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)),
|
||||||
"Expense Status": (id, isActive) =>
|
"Expense Status": (id, isActive) =>
|
||||||
api.delete(`/api/Master/expenses-status/delete/${id}`, (isActive = false)),
|
api.delete(`/api/Master/expenses-status/delete/${id}`, (isActive = false)),
|
||||||
|
"Document Type": (id) => api.delete(`/api/Master/document-type/delete/${id}`),
|
||||||
|
"Document Category": (id) =>
|
||||||
|
api.delete(`/api/Master/document-category/delete/${id}`),
|
||||||
|
|
||||||
getWorkCategory: () => api.get(`/api/master/work-categories`),
|
getWorkCategory: () => api.get(`/api/master/work-categories`),
|
||||||
createWorkCategory: (data) => api.post(`/api/master/work-category`, data),
|
createWorkCategory: (data) => api.post(`/api/master/work-category`, data),
|
||||||
@ -58,7 +61,7 @@ export const MasterRespository = {
|
|||||||
createContactCategory: (data) =>
|
createContactCategory: (data) =>
|
||||||
api.post(`/api/master/contact-category`, data),
|
api.post(`/api/master/contact-category`, data),
|
||||||
updateContactCategory: (id, data) =>
|
updateContactCategory: (id, data) =>
|
||||||
api.post(`/api/master/contact-category/edit/${id}`, data),
|
api.put(`/api/master/contact-category/edit/${id}`, data),
|
||||||
|
|
||||||
getContactTag: () => api.get(`/api/master/contact-tags`),
|
getContactTag: () => api.get(`/api/master/contact-tags`),
|
||||||
createContactTag: (data) => api.post(`/api/master/contact-tag`, data),
|
createContactTag: (data) => api.post(`/api/master/contact-tag`, data),
|
||||||
@ -81,4 +84,26 @@ export const MasterRespository = {
|
|||||||
createExpenseStatus: (data) => api.post("/api/Master/expenses-status", data),
|
createExpenseStatus: (data) => api.post("/api/Master/expenses-status", data),
|
||||||
updateExepnseStatus: (id, data) =>
|
updateExepnseStatus: (id, data) =>
|
||||||
api.put(`/api/Master/expenses-status/edit/${id}`, data),
|
api.put(`/api/Master/expenses-status/edit/${id}`, data),
|
||||||
|
|
||||||
|
getDocumentCategories: (entityType) =>
|
||||||
|
api.get(
|
||||||
|
`/api/Master/document-category/list${
|
||||||
|
entityType ? `?entityTypeId=${entityType}` : ""
|
||||||
|
}`
|
||||||
|
),
|
||||||
|
createDocumenyCategory: (data) =>
|
||||||
|
api.post(`/api/Master/document-category`, data),
|
||||||
|
updateDocumentCategory: (id, data) =>
|
||||||
|
api.put(`/api/Master/document-category/edit/${id}`, data),
|
||||||
|
|
||||||
|
getDocumentTypes: (category) =>
|
||||||
|
api.get(
|
||||||
|
`/api/Master/document-type/list${
|
||||||
|
category ? `?documentCategoryId=${category}` : ""
|
||||||
|
}`
|
||||||
|
),
|
||||||
|
|
||||||
|
createDocumentType: (data) => api.post(`/api/Master/document-type`, data),
|
||||||
|
updateDocumentType: (id, data) =>
|
||||||
|
api.put(`/api/Master/document-type/edit/${id}`, data),
|
||||||
};
|
};
|
||||||
|
@ -37,6 +37,14 @@ const ProjectRepository = {
|
|||||||
api.get(
|
api.get(
|
||||||
`/api/project/tasks-employee/${id}?fromDate=${fromDate}&toDate=${toDate}`
|
`/api/project/tasks-employee/${id}?fromDate=${fromDate}&toDate=${toDate}`
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
// Permission Managment for Employee at Project Level
|
||||||
|
|
||||||
|
getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`),
|
||||||
|
getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`),
|
||||||
|
getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`),
|
||||||
|
updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data)
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TasksRepository = {
|
export const TasksRepository = {
|
||||||
|
@ -44,6 +44,7 @@ import ExpensePage from "../pages/Expense/ExpensePage";
|
|||||||
import TenantDetails from "../pages/Tenant/TenantDetails";
|
import TenantDetails from "../pages/Tenant/TenantDetails";
|
||||||
import SelfTenantDetails from "../pages/Tenant/SelfTenantDetails";
|
import SelfTenantDetails from "../pages/Tenant/SelfTenantDetails";
|
||||||
import SuperTenantDetails from "../pages/Tenant/SuperTenantDetails";
|
import SuperTenantDetails from "../pages/Tenant/SuperTenantDetails";
|
||||||
|
import DocumentPage from "../pages/Documents/DocumentPage";
|
||||||
|
|
||||||
const router = createBrowserRouter(
|
const router = createBrowserRouter(
|
||||||
[
|
[
|
||||||
@ -52,7 +53,7 @@ const router = createBrowserRouter(
|
|||||||
children: [
|
children: [
|
||||||
{ path: "/auth/login", element: <LoginPage /> },
|
{ path: "/auth/login", element: <LoginPage /> },
|
||||||
{ path: "/auth/login-otp", element: <LoginWithOtp /> },
|
{ path: "/auth/login-otp", element: <LoginWithOtp /> },
|
||||||
{ path: "/auth/reqest/demo", element: <RegisterPage /> },
|
{ path: "/market/enquire", element: <RegisterPage /> },
|
||||||
{ path: "/auth/forgot-password", element: <ForgotPasswordPage /> },
|
{ path: "/auth/forgot-password", element: <ForgotPasswordPage /> },
|
||||||
{ path: "/reset-password", element: <ResetPasswordPage /> },
|
{ path: "/reset-password", element: <ResetPasswordPage /> },
|
||||||
{ path: "/legal-info", element: <LegalInfoCard /> },
|
{ path: "/legal-info", element: <LegalInfoCard /> },
|
||||||
@ -69,6 +70,7 @@ const router = createBrowserRouter(
|
|||||||
{ path: "/", element: <Dashboard /> },
|
{ path: "/", element: <Dashboard /> },
|
||||||
{ path: "/dashboard", element: <Dashboard /> },
|
{ path: "/dashboard", element: <Dashboard /> },
|
||||||
{ path: "/projects", element: <ProjectList /> },
|
{ path: "/projects", element: <ProjectList /> },
|
||||||
|
{ path: "/document", element: <DocumentPage /> },
|
||||||
{ path: "/projects/details", element: <ProjectDetails /> },
|
{ path: "/projects/details", element: <ProjectDetails /> },
|
||||||
{ path: "/project/manage/:projectId", element: <ManageProject /> },
|
{ path: "/project/manage/:projectId", element: <ManageProject /> },
|
||||||
{ path: "/employees", element: <EmployeeList /> },
|
{ path: "/employees", element: <EmployeeList /> },
|
||||||
|
@ -33,13 +33,12 @@ export const clearAllCache = () => {
|
|||||||
export const cacheProfileData = ( data) => {
|
export const cacheProfileData = ( data) => {
|
||||||
store.dispatch(setLoginUserPermmisions(data));
|
store.dispatch(setLoginUserPermmisions(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get cached data
|
// Get cached data
|
||||||
export const getCachedProfileData = () => {
|
export const getCachedProfileData = () => {
|
||||||
return store.getState().globalVariables.loginUser;
|
return store.getState().globalVariables.loginUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSelectedproject = () => {
|
export const useSelectedProject = () => {
|
||||||
const selectedProject = useSelector((store)=> store.localVariables.projectId);
|
const selectedProject = useSelector((store)=> store.localVariables.projectId);
|
||||||
var project = localStorage.getItem("project");
|
var project = localStorage.getItem("project");
|
||||||
if(project){
|
if(project){
|
||||||
@ -47,7 +46,7 @@ export const useSelectedproject = () => {
|
|||||||
} else{
|
} else{
|
||||||
return selectedProject
|
return selectedProject
|
||||||
}
|
}
|
||||||
// return project ? selectedProject
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
26
src/utils/FileIcon.jsx
Normal file
26
src/utils/FileIcon.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const contentTypeIcons = {
|
||||||
|
"application/pdf": "fa-solid fa-file-pdf text-danger",
|
||||||
|
"application/msword": "fa-solid fa-file-word text-primary",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||||
|
"fa-solid fa-file-word text-primary",
|
||||||
|
"application/vnd.ms-excel": "fa-solid fa-file-excel text-success",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
||||||
|
"fa-solid fa-file-excel text-success",
|
||||||
|
"application/vnd.ms-powerpoint": "fa-solid fa-file-powerpoint text-warning",
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||||
|
"fa-solid fa-file-powerpoint text-warning",
|
||||||
|
"image/jpg": "fa-solid fa-file-image text-info",
|
||||||
|
"image/jpeg": "fa-solid fa-file-image text-info",
|
||||||
|
"image/png": "fa-solid fa-file-image text-info",
|
||||||
|
"image/gif": "fa-solid fa-file-image text-info",
|
||||||
|
"text/plain": "fa-solid fa-file-lines text-secondary",
|
||||||
|
"text/csv": "fa-solid fa-file-csv text-success",
|
||||||
|
"application/json": "fa-solid fa-file-code text-dark",
|
||||||
|
folder: "fa-solid fa-folder text-warning", // special for folders
|
||||||
|
default: "fa-solid fa-file text-muted",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FileIcon = ({ type, size = "fs-4", className = "" }) => {
|
||||||
|
const iconClass = contentTypeIcons[type] || contentTypeIcons.default;
|
||||||
|
return <i className={`${iconClass} ${size} ${className}`}></i>;
|
||||||
|
};
|
@ -61,3 +61,11 @@ export const getIconByFileType = (type = "") => {
|
|||||||
|
|
||||||
return "bx bx-file";
|
return "bx bx-file";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const normalizeAllowedContentTypes = (allowedContentType) => {
|
||||||
|
if (!allowedContentType) return [];
|
||||||
|
if (Array.isArray(allowedContentType)) return allowedContentType;
|
||||||
|
if (typeof allowedContentType === "string") return allowedContentType.split(",");
|
||||||
|
return [];
|
||||||
|
};
|
@ -39,12 +39,14 @@ export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c"
|
|||||||
|
|
||||||
export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"
|
export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2"
|
||||||
|
|
||||||
|
// ------------------------Directory-------------------------------------
|
||||||
export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda"
|
export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda"
|
||||||
|
|
||||||
export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5"
|
export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5"
|
||||||
|
|
||||||
export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb"
|
export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb"
|
||||||
|
|
||||||
|
// -----------------------Expense----------------------------------------
|
||||||
export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116"
|
export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116"
|
||||||
|
|
||||||
export const VIEW_ALL_EXPNESE = "01e06444-9ca7-4df4-b900-8c3fa051b92f";
|
export const VIEW_ALL_EXPNESE = "01e06444-9ca7-4df4-b900-8c3fa051b92f";
|
||||||
@ -55,7 +57,6 @@ export const REVIEW_EXPENSE = "1f4bda08-1873-449a-bb66-3e8222bd871b";
|
|||||||
|
|
||||||
export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca";
|
export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca";
|
||||||
|
|
||||||
|
|
||||||
export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"
|
export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"
|
||||||
|
|
||||||
export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"
|
export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11"
|
||||||
@ -64,10 +65,19 @@ export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965ed
|
|||||||
|
|
||||||
export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
||||||
|
|
||||||
|
// ----------------------------Tenant-------------------------
|
||||||
export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b"
|
export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b"
|
||||||
export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092"
|
export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092"
|
||||||
export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55"
|
export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55"
|
||||||
export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8"
|
||||||
|
|
||||||
|
// ---------------------Documents---------------------------------
|
||||||
|
export const VIEW_DOCUMENT = "71189504-f1c8-4ca5-8db6-810497be2854";
|
||||||
|
export const UPLOAD_DOCUMENT = "3f6d1f67-6fa5-4b7c-b17b-018d4fe4aab8";
|
||||||
|
export const MODIFY_DOCUMENT = "c423fd81-6273-4b9d-bb5e-76a0fb343833";
|
||||||
|
export const DELETE_DOCUMENT = "40863a13-5a66-469d-9b48-135bc5dbf486";
|
||||||
|
export const DOWNLOAD_DOCUMENT = "404373d0-860f-490e-a575-1c086ffbce1d";
|
||||||
|
export const VERIFY_DOCUMENT = "13a1f30f-38d1-41bf-8e7a-b75189aab8e0";
|
||||||
// -------------------Application Role------------------------------
|
// -------------------Application Role------------------------------
|
||||||
|
|
||||||
// 1 - Expense Manage
|
// 1 - Expense Manage
|
||||||
@ -79,6 +89,12 @@ export const TENANT_STATUS = [
|
|||||||
{id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"}
|
{id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const DOCUMENTS_ENTITIES = {
|
||||||
|
ProjectEntity : "c8fe7115-aa27-43bc-99f4-7b05fabe436e",
|
||||||
|
EmployeeEntity:"dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const CONSTANT_TEXT = {
|
export const CONSTANT_TEXT = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user