Merge branch 'Organization_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Image_Gallery_filter
This commit is contained in:
commit
98acafb9ff
119
package-lock.json
generated
119
package-lock.json
generated
@ -809,9 +809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -1552,13 +1552,13 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.13.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
|
||||
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
|
||||
"version": "24.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
||||
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
"undici-types": "~7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@ -1835,9 +1835,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -1845,6 +1846,19 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-import-phases": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
|
||||
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
@ -2625,9 +2639,9 @@
|
||||
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
||||
"version": "5.18.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -2741,9 +2755,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
||||
"integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@ -3138,9 +3152,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -5163,9 +5177,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -5567,24 +5581,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
|
||||
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.39.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
|
||||
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
|
||||
"version": "5.44.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
|
||||
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
@ -5777,9 +5795,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@ -5907,9 +5925,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -5927,21 +5945,23 @@
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.98.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
|
||||
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
|
||||
"version": "5.101.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
|
||||
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
"acorn": "^8.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-import-phases": "^1.0.3",
|
||||
"browserslist": "^4.24.0",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"enhanced-resolve": "^5.17.3",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
@ -5951,11 +5971,11 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.0",
|
||||
"schema-utils": "^4.3.2",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"watchpack": "^2.4.1",
|
||||
"webpack-sources": "^3.2.3"
|
||||
"webpack-sources": "^3.3.3"
|
||||
},
|
||||
"bin": {
|
||||
"webpack": "bin/webpack.js"
|
||||
@ -5974,15 +5994,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
|
||||
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/webpack/node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
|
@ -1,8 +1,12 @@
|
||||
:root,
|
||||
[data-bs-theme="light"] {
|
||||
--bs-nav-link-font-size: 0.7375rem;
|
||||
--bg-border-color :#f8f6f6
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 0.5rem var(--bs-card-cap-padding-x);
|
||||
}
|
||||
.table_header_border {
|
||||
border-bottom:2px solid var(--bs-table-border-color) ;
|
||||
}
|
5
public/assets/img/SP-Placeholdeer.svg
Normal file
5
public/assets/img/SP-Placeholdeer.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="120" height="120" fill="#EFF1F3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2503 38.4816C33.2603 37.0472 34.4199 35.8864 35.8543 35.875H83.1463C84.5848 35.875 85.7503 37.0431 85.7503 38.4816V80.5184C85.7403 81.9528 84.5807 83.1136 83.1463 83.125H35.8543C34.4158 83.1236 33.2503 81.957 33.2503 80.5184V38.4816ZM80.5006 41.1251H38.5006V77.8751L62.8921 53.4783C63.9172 52.4536 65.5788 52.4536 66.6039 53.4783L80.5006 67.4013V41.1251ZM43.75 51.6249C43.75 54.5244 46.1005 56.8749 49 56.8749C51.8995 56.8749 54.25 54.5244 54.25 51.6249C54.25 48.7254 51.8995 46.3749 49 46.3749C46.1005 46.3749 43.75 48.7254 43.75 51.6249Z" fill="#687787"/>
|
||||
</svg>
|
After Width: | Height: | Size: 888 B |
@ -4,6 +4,7 @@ import { ToastContainer } from "react-toastify";
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { queryClient } from "./layouts/AuthLayout";
|
||||
import ModalProvider from "./ModalProvider";
|
||||
|
||||
|
||||
|
||||
@ -11,6 +12,7 @@ const App = () => {
|
||||
return (
|
||||
<div className="app">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ModalProvider/>
|
||||
<DireProvider>
|
||||
<AppRoutes />
|
||||
</DireProvider>
|
||||
|
@ -1,112 +0,0 @@
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
const ModalContext = createContext();
|
||||
|
||||
export const useModal = () => useContext(ModalContext);
|
||||
|
||||
// ModalProvider to manage modal state and expose functionality to the rest of the app
|
||||
export const ModalProvider = ({ children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [modalContent, setModalContent] = useState(null);
|
||||
const [onSubmit, setOnSubmit] = useState(null);
|
||||
const [modalSize, setModalSize] = useState("lg");
|
||||
|
||||
// Ref to track the modal content element
|
||||
const modalRef = useRef(null);
|
||||
|
||||
const openModal = (content, onSubmitCallback, size = "lg") => {
|
||||
setModalContent(content); // Set modal content dynamically
|
||||
setOnSubmit(() => onSubmitCallback); // Set the submit handler dynamically
|
||||
setIsOpen(true); // Open the modal
|
||||
setModalSize(size); // Set the modal size
|
||||
};
|
||||
|
||||
// Function to close the modal
|
||||
const closeModal = () => {
|
||||
setIsOpen(false); // Close the modal
|
||||
setModalContent(null); // Clear modal content
|
||||
setOnSubmit(null); // Clear the submit callback
|
||||
setModalSize("lg"); // Reset modal size
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
isOpen,
|
||||
openModal,
|
||||
closeModal,
|
||||
modalContent,
|
||||
modalSize,
|
||||
onSubmit,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
{isOpen && (
|
||||
<div style={overlayStyles}>
|
||||
<div
|
||||
ref={modalRef}
|
||||
style={{
|
||||
...modalStyles,
|
||||
maxWidth: modalSize === "sm" ? "400px" : "800px",
|
||||
}}
|
||||
>
|
||||
<div>{modalContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const overlayStyles = {
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 1050,
|
||||
};
|
||||
|
||||
const modalStyles = {
|
||||
backgroundColor: "white",
|
||||
padding: "20px",
|
||||
borderRadius: "5px",
|
||||
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
|
||||
width: "90%",
|
||||
maxWidth: "800px",
|
||||
};
|
19
src/ModalProvider.jsx
Normal file
19
src/ModalProvider.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useOrganizationModal } from "./hooks/useOrganization";
|
||||
import OrganizationModal from "./components/Organization/OrganizationModal";
|
||||
import { useAuthModal } from "./hooks/useAuth";
|
||||
import SwitchTenant from "./pages/authentication/SwitchTenant";
|
||||
|
||||
const ModalProvider = () => {
|
||||
const { isOpen, onClose } = useOrganizationModal();
|
||||
const { isOpen: isAuthOpen } = useAuthModal();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && <OrganizationModal />}
|
||||
{isAuthOpen && <SwitchTenant />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalProvider;
|
@ -12,22 +12,19 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, includeInactive, date }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [todayDate, setTodayDate] = useState(new Date());
|
||||
const [ShowPending, setShowPending] = useState(false);
|
||||
// const selectedProject = useSelector(
|
||||
// (store) => store.localVariables.projectId
|
||||
// );
|
||||
const selectedProject = useSelectedProject();
|
||||
const {
|
||||
attendance,
|
||||
loading: attLoading,
|
||||
recall: attrecall,
|
||||
isFetching
|
||||
} = useAttendance(selectedProject);
|
||||
} = useAttendance(selectedProject, organizationId, includeInactive, date);
|
||||
const filteredAttendance = ShowPending
|
||||
? attendance?.filter(
|
||||
(att) => att?.checkInTime !== null && att?.checkOutTime === null
|
||||
@ -62,12 +59,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
const role = item.jobRoleName?.toLowerCase() || "";
|
||||
return (
|
||||
fullName.includes(lowercasedSearchTerm) ||
|
||||
role.includes(lowercasedSearchTerm) // ✅ also search by role
|
||||
role.includes(lowercasedSearchTerm) // also search by role
|
||||
);
|
||||
});
|
||||
}, [group1, group2, searchTerm]);
|
||||
|
||||
|
||||
const { currentPage, totalPages, currentItems, paginate } = usePagination(
|
||||
finalFilteredData,
|
||||
ITEMS_PER_PAGE
|
||||
@ -116,7 +112,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
<>
|
||||
<div
|
||||
className="table-responsive text-nowrap h-100"
|
||||
style={{ minHeight: "200px" }} // 🔹 Ensures fixed height
|
||||
style={{ minHeight: "200px" }} // Ensures fixed height
|
||||
>
|
||||
<div className="d-flex text-start align-items-center py-2">
|
||||
<strong>Date : {formatUTCToLocalTime(todayDate)}</strong>
|
||||
@ -142,6 +138,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
<tr className="border-top-1">
|
||||
<th colSpan={2}>Name</th>
|
||||
<th>Role</th>
|
||||
<th>Organization</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i>
|
||||
Check-In
|
||||
@ -190,6 +187,8 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
</td>
|
||||
|
||||
<td>{item.jobRoleName}</td>
|
||||
<td>{item.organizationName || "--"}</td>
|
||||
|
||||
<td>
|
||||
{item.checkInTime
|
||||
? convertShortTime(item.checkInTime)
|
||||
@ -213,7 +212,11 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
))}
|
||||
{!attendance && (
|
||||
<tr>
|
||||
<td colSpan={6} className="text-center text-secondary" style={{ height: "200px" }}>
|
||||
<td
|
||||
colSpan={7}
|
||||
className="text-center text-secondary"
|
||||
style={{ height: "200px" }}
|
||||
>
|
||||
No employees assigned to the project!
|
||||
</td>
|
||||
</tr>
|
||||
@ -221,6 +224,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => {
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
|
||||
<nav aria-label="Page ">
|
||||
<ul className="pagination pagination-sm justify-content-end py-1">
|
||||
|
@ -4,7 +4,6 @@ import Avatar from "../common/Avatar";
|
||||
import { convertShortTime } from "../../utils/dateUtils";
|
||||
import RenderAttendanceStatus from "./RenderAttendanceStatus";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { fetchAttendanceData } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||
import DateRangePicker from "../common/DateRangePicker";
|
||||
import { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
|
||||
import eventBus from "../../services/eventBus";
|
||||
@ -34,7 +33,7 @@ const usePagination = (data, itemsPerPage) => {
|
||||
};
|
||||
};
|
||||
|
||||
const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
const AttendanceLog = ({ handleModalData, searchTerm ,organizationId}) => {
|
||||
// const selectedProject = useSelector(
|
||||
// (store) => store.localVariables.projectId
|
||||
// );
|
||||
@ -82,7 +81,8 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
} = useAttendancesLogs(
|
||||
selectedProject,
|
||||
dateRange.startDate,
|
||||
dateRange.endDate
|
||||
dateRange.endDate,
|
||||
organizationId
|
||||
);
|
||||
const filtering = (data) => {
|
||||
const filteredData = showPending
|
||||
@ -151,6 +151,33 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
});
|
||||
}, [processedData, searchTerm]);
|
||||
|
||||
// const filteredSearchData = useMemo(() => {
|
||||
// let tempData = processedData;
|
||||
|
||||
// if (searchTerm) {
|
||||
// const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||
// tempData = tempData.filter((item) => {
|
||||
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||
// return fullName.includes(lowercasedSearchTerm);
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (filters?.selectedOrganization) {
|
||||
// tempData = tempData.filter(
|
||||
// (item) => item.organization?.name === filters.selectedOrganization
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (filters?.selectedServices?.length > 0) {
|
||||
// tempData = tempData.filter((item) =>
|
||||
// filters.selectedServices.includes(item.service?.name)
|
||||
// );
|
||||
// }
|
||||
|
||||
// return tempData;
|
||||
// }, [processedData, searchTerm, filters]);
|
||||
|
||||
|
||||
const {
|
||||
currentPage,
|
||||
totalPages,
|
||||
@ -243,14 +270,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
<label className="form-check-label ms-0">Show Pending</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-2 m-0 text-end">
|
||||
<i
|
||||
className={`bx bx-refresh cursor-pointer fs-4 ${isFetching ? "spin" : ""
|
||||
}`}
|
||||
title="Refresh"
|
||||
onClick={() => refetch()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}>
|
||||
{isLoading ? (
|
||||
@ -265,9 +285,9 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
Name
|
||||
</th>
|
||||
<th className="border-top-1">Date</th>
|
||||
<th>Organization</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i>{" "}
|
||||
Check-In
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i> Check-In
|
||||
</th>
|
||||
<th>
|
||||
<i className="bx bxs-up-arrow-alt text-danger"></i> Check-Out
|
||||
@ -294,7 +314,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
key={`header-${currentDate}`}
|
||||
className="table-row-header"
|
||||
>
|
||||
<td colSpan={6} className="text-start">
|
||||
<td colSpan={8} className="text-start">
|
||||
<strong>
|
||||
{moment(currentDate).format("DD-MM-YYYY")}
|
||||
</strong>
|
||||
@ -324,6 +344,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
attendance.checkInTime || attendance.checkOutTime
|
||||
).format("DD-MMM-YYYY")}
|
||||
</td>
|
||||
<td>{attendance.organizationName || "--"}</td>
|
||||
<td>{convertShortTime(attendance.checkInTime)}</td>
|
||||
<td>
|
||||
{attendance.checkOutTime
|
||||
@ -345,7 +366,7 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
|
||||
<div className="my-12"><span className="text-secondary">No data available for the selected date range. Please Select another date.</span></div>
|
||||
)}
|
||||
</div>
|
||||
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
|
||||
|
@ -5,7 +5,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import TimePicker from "../common/TimePicker";
|
||||
import { usePositionTracker } from "../../hooks/usePositionTracker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { markAttendance } from "../../slices/apiSlice/attedanceLogsSlice";
|
||||
import showToast from "../../services/toastService";
|
||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||
|
@ -55,7 +55,7 @@ const InfraPlanning = () => {
|
||||
|
||||
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
|
||||
return (
|
||||
<div className="card text-center">
|
||||
<div className="text-center">
|
||||
<p className="my-3">No Result Found</p>
|
||||
</div>
|
||||
);
|
||||
@ -63,11 +63,9 @@ const InfraPlanning = () => {
|
||||
|
||||
return (
|
||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||
<div className="card">
|
||||
<div className="card-body" style={{ padding: "0.5rem" }}>
|
||||
<div className="row">
|
||||
<InfraTable buildings={projectInfra} projectId={selectedProject} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,13 +10,13 @@ import eventBus from "../../services/eventBus";
|
||||
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
const Regularization = ({ handleRequest, searchTerm,projectId, organizationId, IncludeInActive }) => {
|
||||
const queryClient = useQueryClient();
|
||||
// var selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
const selectedProject = useSelectedProject();
|
||||
const [regularizesList, setregularizedList] = useState([]);
|
||||
const { regularizes, loading, error, refetch } =
|
||||
useRegularizationRequests(selectedProject);
|
||||
useRegularizationRequests(selectedProject, organizationId, IncludeInActive);
|
||||
|
||||
useEffect(() => {
|
||||
setregularizedList(regularizes);
|
||||
@ -59,6 +59,36 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
});
|
||||
}, [regularizesList, searchTerm]);
|
||||
|
||||
// const filteredSearchData = useMemo(() => {
|
||||
// let sortedList = [...regularizesList].sort(sortByName);
|
||||
|
||||
// // Search filter
|
||||
// if (searchTerm) {
|
||||
// const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||
// sortedList = sortedList.filter((item) => {
|
||||
// const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
|
||||
// return fullName.includes(lowercasedSearchTerm);
|
||||
// });
|
||||
// }
|
||||
|
||||
// // Organization filter
|
||||
// if (filters?.selectedOrganization) {
|
||||
// sortedList = sortedList.filter(
|
||||
// (item) => item.organization?.name === filters.selectedOrganization
|
||||
// );
|
||||
// }
|
||||
|
||||
// // Services filter
|
||||
// if (filters?.selectedServices?.length > 0) {
|
||||
// sortedList = sortedList.filter((item) =>
|
||||
// filters.selectedServices.includes(item.service?.name)
|
||||
// );
|
||||
// }
|
||||
|
||||
// return sortedList;
|
||||
// }, [regularizesList, searchTerm, filters]);
|
||||
|
||||
|
||||
const { currentPage, totalPages, currentItems, paginate } =
|
||||
usePagination(filteredSearchData, 20);
|
||||
|
||||
@ -98,6 +128,7 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
<tr>
|
||||
<th colSpan={2}>Name</th>
|
||||
<th>Date</th>
|
||||
<th>Organization</th>
|
||||
<th>
|
||||
<i className="bx bxs-down-arrow-alt text-success"></i>Check-In
|
||||
</th>
|
||||
@ -115,7 +146,7 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
<Avatar
|
||||
firstName={att.firstName}
|
||||
lastName={att.lastName}
|
||||
></Avatar>
|
||||
/>
|
||||
<div className="d-flex flex-column">
|
||||
<a href="#" className="text-heading text-truncate">
|
||||
<span className="fw-normal">
|
||||
@ -126,6 +157,9 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
</div>
|
||||
</td>
|
||||
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
|
||||
|
||||
<td>{att.organizationName || "--"}</td>
|
||||
|
||||
<td>{convertShortTime(att.checkInTime)}</td>
|
||||
<td>
|
||||
{att.checkOutTime ? convertShortTime(att.checkOutTime) : "--"}
|
||||
@ -136,12 +170,12 @@ const Regularization = ({ handleRequest, searchTerm }) => {
|
||||
handleRequest={handleRequest}
|
||||
refresh={refetch}
|
||||
/>
|
||||
{/* </div> */}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
) : (
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center"
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React, { act, useEffect, useState } from 'react'
|
||||
import useAttendanceStatus, { ACTIONS } from '../../hooks/useAttendanceStatus';
|
||||
// import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { usePositionTracker } from '../../hooks/usePositionTracker';
|
||||
import {markCurrentAttendance} from '../../slices/apiSlice/attendanceAllSlice';
|
||||
import {cacheData, getCachedData, useSelectedProject} from '../../slices/apiDataManager';
|
||||
import showToast from '../../services/toastService';
|
||||
import { useMarkAttendance } from '../../hooks/useAttendance';
|
||||
|
@ -1,21 +1,16 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import LineChart from "../Charts/LineChart";
|
||||
import React, { useState, useMemo } from "react";
|
||||
import ApexChart from "../Charts/Circle";
|
||||
import { useProjects } from "../../hooks/useProjects";
|
||||
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
|
||||
import ApexChart from "../Charts/Circle";
|
||||
|
||||
const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId";
|
||||
import { useSelectedProject } from "../../hooks/useSelectedProject"; // ✅ your custom hook
|
||||
|
||||
const Attendance = () => {
|
||||
const { projects } = useProjects();
|
||||
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
|
||||
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
const [selectedDate, setSelectedDate] = useState(today);
|
||||
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
|
||||
const initialProjectId = storedProjectId || "all";
|
||||
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId);
|
||||
const [displayedProjectName, setDisplayedProjectName] =
|
||||
useState("Select Project");
|
||||
const [activeTab, setActiveTab] = useState("Summary");
|
||||
|
||||
// central project selection hook
|
||||
const selectedProjectId = useSelectedProject()
|
||||
|
||||
const {
|
||||
dashboard_Attendancedata: AttendanceData,
|
||||
@ -23,38 +18,24 @@ const Attendance = () => {
|
||||
error: isError,
|
||||
} = useDashboard_AttendanceData(selectedDate, selectedProjectId);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProjectId === "all") {
|
||||
setDisplayedProjectName("All Projects");
|
||||
} else if (projects) {
|
||||
const foundProject = projects.find((p) => p.id === selectedProjectId);
|
||||
setDisplayedProjectName(
|
||||
foundProject ? foundProject.name : "Select Project"
|
||||
);
|
||||
} else {
|
||||
setDisplayedProjectName("Select Project");
|
||||
}
|
||||
|
||||
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
|
||||
// project name derived once
|
||||
const displayedProjectName = useMemo(() => {
|
||||
if (selectedProjectId === "all") return "All Projects";
|
||||
const found = projects?.find((p) => p.id === selectedProjectId);
|
||||
return found?.name || "Select Project";
|
||||
}, [selectedProjectId, projects]);
|
||||
|
||||
const handleProjectSelect = (projectId) => {
|
||||
setSelectedProjectId(projectId);
|
||||
};
|
||||
|
||||
const handleDateChange = (e) => {
|
||||
setSelectedDate(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card-header mb-1 pb-0 ">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 pb-0 ">
|
||||
{/* Header */}
|
||||
<div className="card-header mb-1 pb-0">
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center">
|
||||
<div className="card-title mb-0 text-start">
|
||||
<h5 className="mb-1">Attendance</h5>
|
||||
<p className="card-subtitle">Daily Attendance Data</p>
|
||||
</div>
|
||||
|
||||
{/* Project Dropdown */}
|
||||
<div className="btn-group">
|
||||
<button
|
||||
className="btn btn-outline-primary btn-sm dropdown-toggle"
|
||||
@ -68,7 +49,7 @@ const Attendance = () => {
|
||||
<li>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleProjectSelect("all")}
|
||||
onClick={() => setSelectedProjectId("all")}
|
||||
>
|
||||
All Projects
|
||||
</button>
|
||||
@ -77,7 +58,7 @@ const Attendance = () => {
|
||||
<li key={project.id}>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => handleProjectSelect(project.id)}
|
||||
onClick={() => setSelectedProjectId(project.id)}
|
||||
>
|
||||
{project.name}
|
||||
</button>
|
||||
@ -88,52 +69,43 @@ const Attendance = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 mt-0 me-5 ms-5">
|
||||
{/* Tabs */}
|
||||
<div>
|
||||
<ul className="nav nav-tabs " role="tablist">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "Summary" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("Summary")}
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
Summary
|
||||
</button>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "Details" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveTab("Details")}
|
||||
data-bs-toggle="tab"
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/* ✅ Date Picker Aligned Left with Padding */}
|
||||
<div className="ps-6 mb-3 mt-0">
|
||||
<div style={{ width: "120px" }}>
|
||||
<input
|
||||
type="date"
|
||||
className="form-control p-1"
|
||||
// style={{ fontSize: "1rem" }}
|
||||
value={selectedDate}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</div>
|
||||
{/* Tabs + Date Picker */}
|
||||
<div className="d-flex flex-wrap justify-content-between align-items-center me-5 ms-5">
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${AttendanceData?.activeTab === "Summary" ? "active" : ""}`}
|
||||
onClick={() => (AttendanceData.activeTab = "Summary")}
|
||||
>
|
||||
Summary
|
||||
</button>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${AttendanceData?.activeTab === "Details" ? "active" : ""}`}
|
||||
onClick={() => (AttendanceData.activeTab = "Details")}
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div className="ps-6 mb-3">
|
||||
<input
|
||||
type="date"
|
||||
className="form-control p-1"
|
||||
style={{ width: "120px" }}
|
||||
value={selectedDate}
|
||||
onChange={(e) => setSelectedDate(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="card-body">
|
||||
{activeTab === "Summary" && (
|
||||
{/* Summary */}
|
||||
{AttendanceData?.activeTab === "Summary" && (
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4">
|
||||
{isLoading ? (
|
||||
@ -143,7 +115,7 @@ const Attendance = () => {
|
||||
) : (
|
||||
AttendanceData && (
|
||||
<>
|
||||
<h5 className="fw-bold mb-0 text-center w-100">
|
||||
<h5 className="fw-bold mb-0">
|
||||
<i className="bx bx-task text-info"></i> Attendance
|
||||
</h5>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
@ -164,11 +136,9 @@ const Attendance = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "Details" && (
|
||||
<div
|
||||
className="table-responsive"
|
||||
style={{ maxHeight: "300px", overflowY: "auto" }}
|
||||
>
|
||||
{/* Details */}
|
||||
{AttendanceData?.activeTab === "Details" && (
|
||||
<div className="table-responsive" style={{ maxHeight: "300px" }}>
|
||||
<table className="table table-hover mb-0 text-start">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -178,32 +148,17 @@ const Attendance = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{AttendanceData?.attendanceTable &&
|
||||
AttendanceData.attendanceTable.length > 0 ? (
|
||||
AttendanceData.attendanceTable.map((record, index) => (
|
||||
<tr key={index}>
|
||||
<td>
|
||||
{record.firstName} {record.lastName}
|
||||
</td>
|
||||
<td>
|
||||
{new Date(record.inTime).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</td>
|
||||
<td>
|
||||
{new Date(record.outTime).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</td>
|
||||
{AttendanceData?.attendanceTable?.length ? (
|
||||
AttendanceData.attendanceTable.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td>{r.firstName} {r.lastName}</td>
|
||||
<td>{r.inTime ? new Date(r.inTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
|
||||
<td>{r.outTime ? new Date(r.outTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="3" className="text-center">
|
||||
No attendance data available
|
||||
</td>
|
||||
<td colSpan="3" className="text-center">No attendance data available</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
|
@ -132,7 +132,7 @@ const AttendanceOverview = () => {
|
||||
onClick={() => setView("table")}
|
||||
title="Table View"
|
||||
>
|
||||
<i class="bx bx-list-ul fs-5"></i>
|
||||
<i className="bx bx-list-ul fs-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,33 +1,28 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import GlobalRepository from "../../repositories/GlobalRepository";
|
||||
|
||||
const Projects = () => {
|
||||
const { projectsCardData } = useDashboardProjectsCardData();
|
||||
const [projectData, setProjectsData] = useState(projectsCardData);
|
||||
const {
|
||||
data: projectsCardData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
refetch,
|
||||
} = useDashboardProjectsCardData();
|
||||
|
||||
useEffect(() => {
|
||||
setProjectsData(projectsCardData);
|
||||
}, [projectsCardData]);
|
||||
// When "project" event happens, just refetch
|
||||
const handler = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handler = useCallback(
|
||||
async (msg) => {
|
||||
try {
|
||||
const response =
|
||||
await GlobalRepository.getDashboardProjectsCardData();
|
||||
setProjectsData(response.data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
[GlobalRepository]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("project", handler);
|
||||
return () => eventBus.off("project", handler);
|
||||
}, [handler]);
|
||||
}, [refetch]);
|
||||
|
||||
const totalProjects = projectsCardData?.totalProjects ?? 0;
|
||||
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
@ -37,20 +32,29 @@ const Projects = () => {
|
||||
Projects
|
||||
</h5>
|
||||
</div>
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.totalProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{projectData.ongoingProjects?.toLocaleString()}
|
||||
</h4>
|
||||
<small className="text-muted">Ongoing</small>
|
||||
) : isError ? (
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
||||
{error?.message || "Error loading data"}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">{totalProjects.toLocaleString()}</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">{ongoingProjects.toLocaleString()}</h4>
|
||||
<small className="text-muted">Ongoing</small>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,16 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
|
||||
|
||||
const TasksCard = () => {
|
||||
const projectId = useSelector((store) => store.localVariables?.projectId);
|
||||
const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId);
|
||||
const projectId = useSelectedProject();
|
||||
|
||||
const {
|
||||
data: tasksCardData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useDashboardTasksCardData(projectId);
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
@ -14,28 +20,30 @@ const TasksCard = () => {
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
// Loader will be displayed when loading is true
|
||||
{isLoading ? (
|
||||
// Loader while fetching
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
// Error message if there's an error
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div>
|
||||
) : isError ? (
|
||||
// Show error
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
||||
{error?.message || "Error loading data"}
|
||||
</div>
|
||||
) : (
|
||||
// Actual data when loaded successfully
|
||||
// Show data
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{tasksCardData?.totalTasks?.toLocaleString()}
|
||||
{tasksCardData?.totalTasks?.toLocaleString() ?? 0}
|
||||
</h4>
|
||||
<small className="text-muted">Total</small>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">
|
||||
{tasksCardData?.completedTasks?.toLocaleString()}
|
||||
{tasksCardData?.completedTasks?.toLocaleString() ?? 0}
|
||||
</h4>
|
||||
<small className="text-muted">Completed</small>
|
||||
</div>
|
||||
@ -45,4 +53,4 @@ const TasksCard = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TasksCard;
|
||||
export default TasksCard;
|
||||
|
@ -1,33 +1,45 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const Teams = () => {
|
||||
const projectId = useSelector((store) => store.localVariables?.projectId);
|
||||
const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId);
|
||||
const queryClient = useQueryClient();
|
||||
const projectId = useSelectedProject()
|
||||
|
||||
const [totalEmployees, setTotalEmployee] = useState(0);
|
||||
const [inToday, setInToday] = useState(0);
|
||||
|
||||
// Update state when API data arrives
|
||||
useEffect(() => {
|
||||
setTotalEmployee(teamsCardData?.totalEmployees || 0);
|
||||
setInToday(teamsCardData?.inToday || 0);
|
||||
}, [teamsCardData]);
|
||||
const {
|
||||
data: teamsCardData,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useDashboardTeamsCardData(projectId);
|
||||
|
||||
// Handle real-time updates via eventBus
|
||||
const handler = useCallback((msg) => {
|
||||
if (msg.activity === 1) {
|
||||
setInToday((prev) => prev + 1);
|
||||
}
|
||||
}, []);
|
||||
const handler = useCallback(
|
||||
(msg) => {
|
||||
if (msg.activity === 1) {
|
||||
queryClient.setQueryData(["dashboardTeams", projectId], (old) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
inToday: (old.inToday || 0) + 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
[queryClient, projectId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on("attendance", handler);
|
||||
return () => eventBus.off("attendance", handler);
|
||||
}, [handler]);
|
||||
|
||||
const inToday = teamsCardData?.inToday ?? 0;
|
||||
const totalEmployees = teamsCardData?.totalEmployees ?? 0;
|
||||
|
||||
return (
|
||||
<div className="card p-3 h-100 text-center d-flex justify-content-between">
|
||||
<div className="d-flex justify-content-start align-items-center mb-3">
|
||||
@ -36,18 +48,17 @@ const Teams = () => {
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
// Blue spinner loader
|
||||
{isLoading ? (
|
||||
<div className="d-flex justify-content-center align-items-center flex-grow-1">
|
||||
<div className="spinner-border text-primary" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
// Error message if data fetching fails
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div>
|
||||
) : isError ? (
|
||||
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
|
||||
{error?.message || "Error loading data"}
|
||||
</div>
|
||||
) : (
|
||||
// Display data once loaded
|
||||
<div className="d-flex justify-content-around align-items-start mt-n2">
|
||||
<div>
|
||||
<h4 className="mb-0 fw-bold">{totalEmployees.toLocaleString()}</h4>
|
||||
@ -63,4 +74,4 @@ const Teams = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Teams;
|
||||
export default Teams;
|
||||
|
@ -105,7 +105,7 @@ const ListViewContact = ({ data, Pagination }) => {
|
||||
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
||||
<table className="table dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr style={{ borderBottom: "2px solid var(--bs-table-border-color)"}}>
|
||||
<tr className="table_header_border">
|
||||
{contactList?.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
|
@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react";
|
||||
import moment from "moment";
|
||||
import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice";
|
||||
import usePagination from "../../hooks/usePagination";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { convertShortTime } from "../../utils/dateUtils";
|
||||
|
@ -19,6 +19,7 @@ import { useProjectName } from "../../hooks/useProjects";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { MANAGE_PROJECT } from "../../utils/constants";
|
||||
import { useAuthModal, useLogout } from "../../hooks/useAuth";
|
||||
|
||||
const Header = () => {
|
||||
const { profile } = useProfile();
|
||||
@ -26,10 +27,9 @@ const Header = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { data, loading } = useMaster();
|
||||
const navigate = useNavigate();
|
||||
const {onOpen} = useAuthModal()
|
||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||
// {
|
||||
// console.log(location.pathname);
|
||||
// }
|
||||
const { mutate : logout,isPending:logouting} = useLogout()
|
||||
|
||||
const isDashboardPath =
|
||||
/^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname);
|
||||
@ -59,41 +59,9 @@ const Header = () => {
|
||||
return role ? role.name : "User";
|
||||
};
|
||||
|
||||
const handleLogout = (e) => {
|
||||
e.preventDefault();
|
||||
logout();
|
||||
};
|
||||
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
let data = {
|
||||
refreshToken: localStorage.getItem("refreshToken"),
|
||||
};
|
||||
|
||||
AuthRepository.logout(data)
|
||||
.then(() => {
|
||||
localStorage.removeItem("jwtToken");
|
||||
localStorage.removeItem("refreshToken");
|
||||
localStorage.removeItem("user");
|
||||
localStorage.clear();
|
||||
clearAllCache();
|
||||
window.location.href = "/auth/login";
|
||||
})
|
||||
.catch(() => {
|
||||
localStorage.removeItem("jwtToken");
|
||||
localStorage.removeItem("refreshToken");
|
||||
localStorage.removeItem("user");
|
||||
localStorage.clear();
|
||||
clearAllCache();
|
||||
window.location.href = "/auth/login";
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error during logout:",
|
||||
error?.response?.data || error.message
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleProfilePage = () => {
|
||||
navigate(`/employee/${profile?.employeeInfo?.id}`);
|
||||
@ -455,6 +423,16 @@ const Header = () => {
|
||||
<span className="align-middle">Change Password</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li onClick={()=>onOpen()}>
|
||||
{" "}
|
||||
<a
|
||||
className="dropdown-item cusor-pointer"
|
||||
>
|
||||
<i className="bx bx-transfer-alt me-2"></i>
|
||||
<span className="align-middle">Switch Tenant</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div className="dropdown-divider"></div>
|
||||
</li>
|
||||
@ -462,11 +440,10 @@ const Header = () => {
|
||||
<a
|
||||
aria-label="click to log out"
|
||||
className="dropdown-item cusor-pointer"
|
||||
href="/logout"
|
||||
onClick={handleLogout}
|
||||
onClick={()=>handleLogout()}
|
||||
>
|
||||
<i className="bx bx-power-off me-2"></i>
|
||||
<span className="align-middle">Log Out</span>
|
||||
{logouting ? "Please Wait":<> <i className="bx bx-log-out me-2"></i>
|
||||
<span className="align-middle">SignOut</span></>}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
260
src/components/Organization/AssignOrg.jsx
Normal file
260
src/components/Organization/AssignOrg.jsx
Normal file
@ -0,0 +1,260 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useProjectAssignedServices } from "../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import {
|
||||
useOrganizationType,
|
||||
useServices,
|
||||
} from "../../hooks/masterHook/useMaster";
|
||||
import Label from "../common/Label";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { assignedOrgToProject } from "./OrganizationSchema";
|
||||
import {
|
||||
useAssignOrgToProject,
|
||||
useAssignOrgToTenant,
|
||||
useOrganizationModal,
|
||||
} from "../../hooks/useOrganization";
|
||||
|
||||
const AssignOrg = ({ setStep }) => {
|
||||
const { isOpen, orgData, startStep, onOpen, flowType, prevStep, onClose } =
|
||||
useOrganizationModal();
|
||||
const selectedProject = useSelectedProject();
|
||||
const { data: masterService, isLoading: isMasterserviceLoading } =
|
||||
useServices();
|
||||
const { data: projectServices, isLoading } =
|
||||
useProjectAssignedServices(selectedProject);
|
||||
const { data: orgType, isLoading: orgLoading } = useOrganizationType();
|
||||
|
||||
const { mutate: AssignToProject, isPending: isPendingProject } =
|
||||
useAssignOrgToProject(() => onClose());
|
||||
const { mutate: AssignToTenant, isPending: isPendingTenat } =
|
||||
useAssignOrgToTenant(() => {
|
||||
onClose();
|
||||
});
|
||||
const isPending = isPendingProject || isPendingTenat;
|
||||
const mergedServices = useMemo(() => {
|
||||
if (!masterService || !projectServices) return [];
|
||||
const combined = [...masterService?.data, ...projectServices];
|
||||
return combined.filter(
|
||||
(item, index, self) => index === self.findIndex((s) => s.id === item.id)
|
||||
);
|
||||
}, [masterService, projectServices]);
|
||||
|
||||
const resolver =
|
||||
flowType === "default" ? undefined : zodResolver(assignedOrgToProject);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver,
|
||||
defaultValues: {
|
||||
organizationTypeId: "",
|
||||
serviceIds: [],
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (formData) => {
|
||||
if (flowType === "default") {
|
||||
const payload = orgData.id;
|
||||
|
||||
AssignToTenant(payload);
|
||||
} else {
|
||||
const payload = {
|
||||
...formData,
|
||||
projectId: selectedProject,
|
||||
organizationId: orgData.id,
|
||||
parentOrganizationId: null,
|
||||
};
|
||||
AssignToProject(payload);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
onOpen({ startStep: 4, orgData });
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (prevStep === 1 && flowType === "assign") {
|
||||
onOpen({ startStep: prevStep });
|
||||
} else if (prevStep === 1 && flowType !== "assign") {
|
||||
onOpen({ startStep: 1 });
|
||||
} else {
|
||||
onOpen({ startStep: 2 });
|
||||
}
|
||||
};
|
||||
|
||||
if (isMasterserviceLoading || isLoading)
|
||||
return <div className="text-center">Loading....</div>;
|
||||
|
||||
return (
|
||||
<div className="row text-black text-start mb-3">
|
||||
{/* Organization Info Display */}
|
||||
<div className="col-12 mb-3">
|
||||
<div className="d-flex justify-content-between align-items-center text-start mb-2">
|
||||
<div className="fw-semibold text-wrap">{orgData.name}</div>
|
||||
<div className="text-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleEdit}
|
||||
className="btn btn-link p-0"
|
||||
>
|
||||
<i className="bx bx-edit text-secondary"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="col-md-6 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Contact Person :
|
||||
</label>
|
||||
<div className="text-muted">{orgData.contactPerson}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Contact Number :
|
||||
</label>
|
||||
<div className="text-muted">{orgData.contactNumber}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Email Address :
|
||||
</label>
|
||||
<div className="text-muted">{orgData.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-2 mb-0 fw-semibold"
|
||||
style={{ maxWidth: "130px" }}
|
||||
>
|
||||
Service provider Id (SPRID) :
|
||||
</label>
|
||||
<div className="text-muted">{orgData.sprid}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 mb-3">
|
||||
<div className="d-flex">
|
||||
<label
|
||||
className="form-label me-1 mb-0 fw-semibold"
|
||||
style={{ minWidth: "130px" }}
|
||||
>
|
||||
Address :
|
||||
</label>
|
||||
<div className="text-muted text-start">{orgData.address}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="text-black text-start">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Show fields only if flowType is NOT default */}
|
||||
{flowType !== "default" && (
|
||||
<>
|
||||
{/* Organization Type */}
|
||||
<div className="mb-3 text-start">
|
||||
<Label htmlFor="organizationTypeId" className="mb-3 fw-semibold" required>
|
||||
Organization Type
|
||||
</Label>
|
||||
<div className="d-flex flex-wrap gap-3 mt-1">
|
||||
{orgType?.data.map((type) => (
|
||||
<div
|
||||
key={type.id}
|
||||
className="form-check d-flex align-items-center gap-2 p-0 m-0"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id={`organizationType-${type.id}`}
|
||||
value={type.id}
|
||||
{...register("organizationTypeId")}
|
||||
className="form-check-input m-0"
|
||||
/>
|
||||
<label
|
||||
className="form-check-label m-0"
|
||||
htmlFor={`organizationType-${type.id}`}
|
||||
>
|
||||
{type.name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{errors.organizationTypeId && (
|
||||
<span className="text-danger">
|
||||
{errors.organizationTypeId.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Services */}
|
||||
<div className="mb-3">
|
||||
<Label htmlFor="serviceIds" className="mb-3 fw-semibold" required>
|
||||
Select Services
|
||||
</Label>
|
||||
{mergedServices?.map((service) => (
|
||||
<div key={service.id} className="form-check mb-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
value={service.id}
|
||||
{...register("serviceIds")}
|
||||
className="form-check-input"
|
||||
/>
|
||||
<label className="form-check-label">{service.name}</label>
|
||||
</div>
|
||||
))}
|
||||
{errors.serviceIds && (
|
||||
<div className="text-danger small">
|
||||
{errors.serviceIds.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Buttons: Always visible */}
|
||||
<div className="d-flex justify-content-between mt-5">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-outline-secondary"
|
||||
onClick={handleBack}
|
||||
disabled={isPending}
|
||||
>
|
||||
<i className="bx bx-left-arrow-alt"></i> Back
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isPending}
|
||||
>
|
||||
{isPending
|
||||
? "Please wait..."
|
||||
: flowType === "default"
|
||||
? "Assign Organization"
|
||||
: "Add"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssignOrg;
|
203
src/components/Organization/ManagOrg.jsx
Normal file
203
src/components/Organization/ManagOrg.jsx
Normal file
@ -0,0 +1,203 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
useCreateOrganization,
|
||||
useOrganizationModal,
|
||||
useUpdateOrganization,
|
||||
} from "../../hooks/useOrganization";
|
||||
import {
|
||||
defaultOrganizationValues,
|
||||
organizationSchema,
|
||||
} from "./OrganizationSchema";
|
||||
import Label from "../common/Label";
|
||||
import { useGlobalServices } from "../../hooks/masterHook/useMaster";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import SelectMultiple from "../common/SelectMultiple";
|
||||
|
||||
const ManagOrg = () => {
|
||||
const { data: service, isLoading } = useGlobalServices();
|
||||
const { flowType, orgData, startStep, onOpen, onClose, prevStep } =
|
||||
useOrganizationModal();
|
||||
|
||||
const method = useForm({
|
||||
resolver: zodResolver(organizationSchema),
|
||||
defaultValues: defaultOrganizationValues,
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = method;
|
||||
|
||||
// Create & Update mutations
|
||||
const { mutate: createOrganization, isPending: isCreating } =
|
||||
useCreateOrganization(() => {
|
||||
reset(defaultOrganizationValues);
|
||||
onOpen({ startStep: 1 });
|
||||
onClose();
|
||||
});
|
||||
|
||||
const { mutate: updateOrganization, isPending: isUpdating } =
|
||||
useUpdateOrganization(() => {
|
||||
reset(defaultOrganizationValues);
|
||||
onOpen({ startStep: 1 });
|
||||
onClose();
|
||||
});
|
||||
|
||||
// Prefill form if editing
|
||||
useEffect(() => {
|
||||
if (orgData) {
|
||||
console.log(orgData);
|
||||
reset({
|
||||
name: orgData.name || "",
|
||||
contactPerson: orgData.contactPerson || "",
|
||||
contactNumber: orgData.contactNumber || "",
|
||||
email: orgData.email || "",
|
||||
serviceIds: orgData.services?.map((s) => s.id) || [],
|
||||
address: orgData.address || "",
|
||||
});
|
||||
}
|
||||
}, [orgData, reset]);
|
||||
|
||||
const onSubmit = (payload) => {
|
||||
if (orgData?.id) {
|
||||
updateOrganization({ id: orgData.id, ...payload });
|
||||
} else {
|
||||
createOrganization(payload);
|
||||
}
|
||||
};
|
||||
const handleBack = () => {
|
||||
if (flowType === "edit") {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (flowType === "assign") {
|
||||
if (prevStep === 1) {
|
||||
onOpen({ startStep: 1 });
|
||||
} else {
|
||||
onOpen({ startStep: prevStep ?? 2 });
|
||||
}
|
||||
return;
|
||||
}
|
||||
onOpen({ startStep: 2 });
|
||||
};
|
||||
return (
|
||||
<FormProvider {...method}>
|
||||
<form className="form" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="name" required>
|
||||
Organization Name
|
||||
</Label>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
{...register("name")}
|
||||
/>
|
||||
{errors.name && (
|
||||
<span className="danger-text">{errors.name.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="contactPerson" required>
|
||||
Contact Person
|
||||
</Label>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
{...register("contactPerson")}
|
||||
/>
|
||||
{errors.contactPerson && (
|
||||
<span className="danger-text">{errors.contactPerson.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="contactNumber" required>
|
||||
Contact Number
|
||||
</Label>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
{...register("contactNumber")}
|
||||
/>
|
||||
{errors.contactNumber && (
|
||||
<span className="danger-text">{errors.contactNumber.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="email" required>
|
||||
Email Address
|
||||
</Label>
|
||||
<input
|
||||
className="form-control form-control-sm"
|
||||
{...register("email")}
|
||||
/>
|
||||
{errors.email && (
|
||||
<span className="danger-text">{errors.email.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-1 text-start">
|
||||
<SelectMultiple
|
||||
name="serviceIds"
|
||||
label="Services"
|
||||
required
|
||||
valueKey="id"
|
||||
options={service?.data || []}
|
||||
required = {true}
|
||||
/>
|
||||
{errors.serviceIds && (
|
||||
<span className="danger-text">{errors.serviceIds.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-1 text-start">
|
||||
<Label htmlFor="address" required>
|
||||
Address
|
||||
</Label>
|
||||
<textarea
|
||||
className="form-control form-control-sm"
|
||||
{...register("address")}
|
||||
rows={2}
|
||||
/>
|
||||
{errors.address && (
|
||||
<span className="danger-text">{errors.address.message}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-between gap-2 my-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={handleBack}
|
||||
>
|
||||
{flowType === "edit" ? (
|
||||
"Close"
|
||||
) : (
|
||||
<>
|
||||
<i className="bx bx-chevron-left"></i>Back
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isCreating || isUpdating || isLoading}
|
||||
>
|
||||
{isCreating || isUpdating
|
||||
? "Please Wait..."
|
||||
: orgData
|
||||
? "Update"
|
||||
: "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagOrg;
|
145
src/components/Organization/OrgPickerFromSPId.jsx
Normal file
145
src/components/Organization/OrgPickerFromSPId.jsx
Normal file
@ -0,0 +1,145 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
useAssignOrgToTenant,
|
||||
useOrganizationBySPRID,
|
||||
useOrganizationModal,
|
||||
} from "../../hooks/useOrganization";
|
||||
import Label from "../common/Label";
|
||||
import { useDebounce } from "../../utils/appUtils";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { spridSchema } from "./OrganizationSchema";
|
||||
import { OrgCardSkeleton } from "./OrganizationSkeleton";
|
||||
|
||||
// Zod schema: only allow exactly 4 digits
|
||||
|
||||
const OrgPickerFromSPId = ({ title, placeholder }) => {
|
||||
const { onClose, startStep, flowType, onOpen, prevStep } =
|
||||
useOrganizationModal();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
watch,
|
||||
} = useForm({
|
||||
resolver: zodResolver(spridSchema),
|
||||
defaultValues: { spridSearchText: "" },
|
||||
});
|
||||
|
||||
const [SPRID, setSPRID] = useState("");
|
||||
|
||||
const { data, isLoading, isError, error, refetch } =
|
||||
useOrganizationBySPRID(SPRID);
|
||||
|
||||
const onSubmit = (formdata) => {
|
||||
setSPRID(formdata.spridSearchText);
|
||||
};
|
||||
|
||||
const handleOrg = (orgId) => {};
|
||||
const SP = watch("spridSearchText");
|
||||
return (
|
||||
<div className="d-block">
|
||||
<form
|
||||
className="d-flex flex-row gap-6 text-start align-items-center"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="d-flex flex-row align-items-center gap-2">
|
||||
<Label className="text-secondary">Search by SPRID</Label>
|
||||
<input
|
||||
type="search"
|
||||
{...register("spridSearchText")}
|
||||
className="form-control form-control-sm w-auto"
|
||||
placeholder="Enter SPRID"
|
||||
maxLength={4}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="btn btn-sm btn-primary">
|
||||
<i className="bx bx-sm bx-search-alt-2"></i> Search
|
||||
</button>
|
||||
</form>
|
||||
<div className="text-start danger-text">
|
||||
{" "}
|
||||
{errors.spridSearchText && (
|
||||
<p className="text-danger small mt-1">
|
||||
{errors.spridSearchText.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ---- Organization list ---- */}
|
||||
{isLoading ? (
|
||||
<OrgCardSkeleton />
|
||||
) : data && data?.data.length > 0 ? (
|
||||
<div className="py-2 text-tiny text-center">
|
||||
<div className="d-flex flex-column gap-2 border-0 bg-none">
|
||||
{data.data.map((org) => (
|
||||
<div className="d-flex flex-row gap-2 text-start text-black ">
|
||||
<div className="mt-1">
|
||||
<img
|
||||
src="/public/assets/img/SP-Placeholdeer.svg"
|
||||
alt="logo"
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex flex-column p-0 m-0 cursor-pointer"
|
||||
onClick={() => onOpen({ startStep: 3, orgData: org })}
|
||||
>
|
||||
<span className="fs-6 fw-semibold">{org.name}</span>
|
||||
<div className="d-flex gap-2">
|
||||
<small
|
||||
className=" fw-semibold text-uppercase"
|
||||
style={{ letterSpacing: "1px" }}
|
||||
>
|
||||
SPRID :{" "}
|
||||
</small>
|
||||
<small className="fs-6">{org.sprid}</small>
|
||||
</div>
|
||||
<div className="d-flex flex-row gap-2">
|
||||
<small className="text-small fw-semibold">Address:</small>
|
||||
<div className="d-flex text-wrap">{org.address}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : SPRID ? (
|
||||
<div className="py-3 text-center text-secondary">
|
||||
No organization found for "{SPRID}"
|
||||
</div>
|
||||
) : null}
|
||||
<div className="py-12 text-center text-tiny text-black">
|
||||
<small className="d-block text-secondary">
|
||||
Do not have SPRID or could not find organization ?
|
||||
</small>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary mt-3"
|
||||
onClick={() => onOpen({ startStep: 4 })}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Create New Organization
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* ---- Footer buttons ---- */}
|
||||
<div className={`d-flex text-secondary mt-3`}>
|
||||
{flowType !== "default" && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-xs btn-outline-secondary"
|
||||
onClick={() => onOpen({ startStep: prevStep })}
|
||||
>
|
||||
<i className="bx bx-chevron-left"></i> Back
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgPickerFromSPId;
|
167
src/components/Organization/OrgPickerfromTenant.jsx
Normal file
167
src/components/Organization/OrgPickerfromTenant.jsx
Normal file
@ -0,0 +1,167 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
useOrganizationModal,
|
||||
useOrganizationsList,
|
||||
} from "../../hooks/useOrganization";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import Label from "../common/Label";
|
||||
import Pagination from "../common/Pagination";
|
||||
|
||||
const OrgPickerfromTenant = ({ title }) => {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data, isLoading } = useOrganizationsList(
|
||||
ITEMS_PER_PAGE - 10,
|
||||
1,
|
||||
true,
|
||||
null,
|
||||
searchText
|
||||
);
|
||||
|
||||
const paginate = (page) => {
|
||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
};
|
||||
const { isOpen, orgData, startStep, onOpen, flowType, prevStep } =
|
||||
useOrganizationModal();
|
||||
|
||||
const handleBack = () => {
|
||||
if (prevStep == 1 && flowType == "assign") {
|
||||
onOpen({ startStep: prevStep });
|
||||
} else if (prevStep == 1 && flowType == "assign") {
|
||||
onOpen({ startStep: 1 });
|
||||
} else {
|
||||
onOpen({ startStep: 2 });
|
||||
}
|
||||
};
|
||||
|
||||
const contactList = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
getValue: (org) => (
|
||||
<div className="d-flex gap-2 py-1 ">
|
||||
<i class="bx bx-buildings"></i>
|
||||
<span
|
||||
className="text-truncate d-inline-block "
|
||||
style={{ maxWidth: "150px" }}
|
||||
>
|
||||
{org?.name || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "sprid",
|
||||
label: "SPRID",
|
||||
getValue: (org) => (
|
||||
<span
|
||||
className="text-truncate d-inline-block"
|
||||
style={{ maxWidth: "200px" }}
|
||||
>
|
||||
{org?.sprid || "N/A"}
|
||||
</span>
|
||||
),
|
||||
align: "text-center",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="d-block">
|
||||
<div className="text-start mb-1">
|
||||
<Label className="text-secondary">{title}</Label>
|
||||
<input
|
||||
type="text"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText?.(e.target.value)}
|
||||
className="form-control form-control-sm w-auto"
|
||||
placeholder="Enter Organization Name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ---- Organization list ---- */}
|
||||
{isLoading ? (
|
||||
<div>Loading....</div>
|
||||
) : data && data?.data?.length > 0 ? (
|
||||
<div className="dataTables_wrapper no-footer pb-2">
|
||||
<table className="table dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr className="table_header_border">
|
||||
{contactList.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
</th>
|
||||
))}
|
||||
<th className="sticky-action-column bg-white text-center">
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.isArray(data.data) && data.data.length > 0
|
||||
? data.data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{contactList.map((col) => (
|
||||
<td key={col.key} className={col.align}>
|
||||
{col.getValue(row)}
|
||||
</td>
|
||||
))}
|
||||
<td className="sticky-action-column bg-white">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => onOpen({ startStep: 3, orgData: row })}
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="d-flex flex-column align-items-center text-center text-wrap text-black gap-2">
|
||||
<small className="mb-1">
|
||||
Could not find organization in your database? Please search within the
|
||||
global database.
|
||||
</small>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-primary w-auto"
|
||||
onClick={() => onOpen({ startStep: 2 })}
|
||||
>
|
||||
Search Using SPRID
|
||||
</button>
|
||||
</div>
|
||||
{/* ---- Footer buttons ---- */}
|
||||
<div
|
||||
className={`d-flex justify-content-end
|
||||
text-secondary mt-3`}
|
||||
>
|
||||
{flowType == "default" && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={handleBack}
|
||||
>
|
||||
<i className="bx bx-left-arrow-alt"></i> Back
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* <button
|
||||
type="button"
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={() => onOpen({ startStep: 4 })}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Add New Organization
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgPickerfromTenant;
|
110
src/components/Organization/OrganizationModal.jsx
Normal file
110
src/components/Organization/OrganizationModal.jsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
defaultOrganizationValues,
|
||||
organizationSchema,
|
||||
} from "./OrganizationSchema";
|
||||
import Modal from "../common/Modal";
|
||||
import {
|
||||
useCreateOrganization,
|
||||
useOrganizationBySPRID,
|
||||
useOrganizationModal,
|
||||
useOrganizationsList,
|
||||
} from "../../hooks/useOrganization";
|
||||
import Label from "../common/Label";
|
||||
import SelectMultiple from "../common/SelectMultiple";
|
||||
import { useServices } from "../../hooks/masterHook/useMaster";
|
||||
import AssignOrg from "./AssignOrg";
|
||||
import ManagOrg from "./ManagOrg";
|
||||
import OrgPickerFromSPId from "./OrgPickerFromSPId";
|
||||
import OrgPickerfromTenant from "./OrgPickerfromTenant";
|
||||
|
||||
const OrganizationModal = () => {
|
||||
const { isOpen, orgData, startStep, onOpen, onClose, onToggle } =
|
||||
useOrganizationModal();
|
||||
const { data: masterService, isLoading } = useServices();
|
||||
const [searchText, setSearchText] = useState();
|
||||
const [SPRID, setSPRID] = useState("");
|
||||
|
||||
const [Organization, setOrganization] = useState({});
|
||||
|
||||
const method = useForm({
|
||||
resolver: zodResolver(organizationSchema),
|
||||
defaultValues: defaultOrganizationValues,
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = method;
|
||||
|
||||
const { mutate: CreateOrganization, isPending } = useCreateOrganization(
|
||||
() => {
|
||||
reset(defaultOrganizationValues);
|
||||
onClose();
|
||||
}
|
||||
);
|
||||
|
||||
const onSubmit = (OrgPayload) => {
|
||||
CreateOrganization(OrgPayload);
|
||||
};
|
||||
|
||||
const RenderTitle = useMemo(() => {
|
||||
if (orgData) {
|
||||
return "Assign Organization";
|
||||
}
|
||||
|
||||
if (startStep === 1) {
|
||||
return orgData && orgData !== null
|
||||
? "Add Organization"
|
||||
: "Choose Organization1";
|
||||
}
|
||||
|
||||
if (startStep === 2) {
|
||||
return "Add Organization";
|
||||
}
|
||||
|
||||
if (startStep === 3) {
|
||||
return "Assign Organization";
|
||||
}
|
||||
|
||||
return "Manage Organization";
|
||||
}, [startStep, orgData]);
|
||||
|
||||
const contentBody = (
|
||||
<div>
|
||||
{/* ---------- STEP 1: Service Provider- Form Own Tenant list ---------- */}
|
||||
{startStep === 1 && <OrgPickerfromTenant title="Find Organization" />}
|
||||
|
||||
{startStep === 2 && (
|
||||
<OrgPickerFromSPId
|
||||
title="Find Organization"
|
||||
placeholder="Enter Service Provider Id"
|
||||
projectOrganizations={orgData}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ---------- STEP 2: Existing Organization Details ---------- */}
|
||||
{startStep === 3 && Organization && (
|
||||
<AssignOrg Organization={Organization} />
|
||||
)}
|
||||
|
||||
{/* ---------- STEP 3: Add New Organization ---------- */}
|
||||
{startStep === 4 && <ManagOrg />}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={RenderTitle}
|
||||
body={contentBody}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationModal;
|
57
src/components/Organization/OrganizationSchema.js
Normal file
57
src/components/Organization/OrganizationSchema.js
Normal file
@ -0,0 +1,57 @@
|
||||
import { array, z } from "zod";
|
||||
|
||||
const phoneRegex = /^\+?[1-9]\d{6,14}$/;
|
||||
|
||||
export const organizationSchema = z.object({
|
||||
name: z.string().min(1, { message: "Name is required" }),
|
||||
contactNumber: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(7, { message: "Contact number must be at least 7 digits" })
|
||||
.max(20, { message: "Contact number cannot exceed 12 digits" })
|
||||
|
||||
.regex(phoneRegex, { message: "Invalid phone number" }),
|
||||
contactPerson: z.string().min(1, { message: "Person name required" }),
|
||||
address: z.string().min(1, { message: "Address is required!" }),
|
||||
email: z
|
||||
.string()
|
||||
.min(1, { message: "Email is required" })
|
||||
.email("Invalid email address"),
|
||||
serviceIds: z
|
||||
.array(z.string())
|
||||
.min(1, { message: "Please insert service id" }),
|
||||
});
|
||||
|
||||
export const defaultOrganizationValues = {
|
||||
name: "",
|
||||
contactNumber: "",
|
||||
contactPerson: "",
|
||||
address: "",
|
||||
email: "",
|
||||
serviceIds: [],
|
||||
};
|
||||
|
||||
export const assignedOrgToProject = z.object({
|
||||
// projectId: z.string().uuid({ message: "Invalid projectId format" }),
|
||||
// organizationId: z.string().string({ message: "Invalid organizationId format" }),
|
||||
// parentOrganizationId: z
|
||||
// .string()
|
||||
// .nullable()
|
||||
// .optional(),
|
||||
serviceIds: z.preprocess(
|
||||
(val) => (Array.isArray(val) ? val : []),
|
||||
z
|
||||
.array(z.string().uuid({ message: "Invalid serviceId format" }))
|
||||
.nonempty({ message: "At least one service must be selected" })
|
||||
),
|
||||
|
||||
organizationTypeId: z
|
||||
.string()
|
||||
.min(1, { message: "Organization is required" }),
|
||||
});
|
||||
|
||||
export const spridSchema = z.object({
|
||||
spridSearchText: z
|
||||
.string()
|
||||
.regex(/^\d{4}$/, { message: "SPRID must be exactly 4 digits" }),
|
||||
});
|
44
src/components/Organization/OrganizationSkeleton.jsx
Normal file
44
src/components/Organization/OrganizationSkeleton.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
|
||||
<div
|
||||
className={`skeleton mb-2 ${className}`}
|
||||
style={{
|
||||
height,
|
||||
width,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
export const OrgCardSkeleton = () => {
|
||||
return (
|
||||
<div className="row p-3">
|
||||
{[...Array(1)].map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="list-group-item d-flex flex-row gap-2 align-items-start border-0 shadow-sm rounded mb-3 p-3"
|
||||
>
|
||||
{/* Left: Logo/avatar placeholder */}
|
||||
<div className="mt-1">
|
||||
<SkeletonLine height={50} width={50} className="rounded-circle" />
|
||||
</div>
|
||||
|
||||
{/* Right: Info section */}
|
||||
<div className="d-flex flex-column flex-grow-1 text-start">
|
||||
{/* Org name */}
|
||||
<SkeletonLine height={18} width="160px" className="mb-2" />
|
||||
|
||||
{/* SPRID */}
|
||||
<div className="d-flex gap-2 mb-2">
|
||||
<SkeletonLine height={14} width="60px" />
|
||||
<SkeletonLine height={14} width="100px" />
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
<div className="d-flex gap-2">
|
||||
<SkeletonLine height={14} width="70px" />
|
||||
<SkeletonLine height={14} width="100%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
164
src/components/Organization/OrganizationsList.jsx
Normal file
164
src/components/Organization/OrganizationsList.jsx
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { useState } from "react";
|
||||
import { useOrganizationModal, useOrganizationsList } from "../../hooks/useOrganization";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { useDebounce } from "../../utils/appUtils";
|
||||
import Pagination from "../common/Pagination";
|
||||
|
||||
const OrganizationsList = ({searchText}) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const searchString = useDebounce(searchText,500)
|
||||
const {
|
||||
data = [],
|
||||
isLoading,
|
||||
isFetching,
|
||||
isError,
|
||||
error,
|
||||
} = useOrganizationsList(ITEMS_PER_PAGE, 1, true,null,searchString);
|
||||
const {onClose, startStep, flowType, onOpen,orgData } = useOrganizationModal();
|
||||
|
||||
const organizationsColumns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Organization Name",
|
||||
getValue: (org) => (
|
||||
<div className="d-flex gap-2 py-1 ">
|
||||
<i class="bx bx-buildings"></i>
|
||||
<span
|
||||
className="text-truncate d-inline-block "
|
||||
style={{ maxWidth: "150px" }}
|
||||
>
|
||||
{org?.name || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "contactPerson",
|
||||
label: "Contact Person",
|
||||
getValue: (org) => (
|
||||
(
|
||||
<div className="d-flex align-items-center ps-1">
|
||||
<Avatar
|
||||
size="xs"
|
||||
classAvatar="m-0"
|
||||
firstName={(org?.name || "").trim().split(" ")[0] || ""}
|
||||
lastName={(org?.name || "").trim().split(" ")[1] || ""}
|
||||
/>
|
||||
<span
|
||||
className="text-truncate d-inline-block "
|
||||
style={{ maxWidth: "150px" }}
|
||||
>
|
||||
{org?.name || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "contactNumber",
|
||||
label: "Phone Number",
|
||||
getValue: (org) => org.contactNumber || "N/A",
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "email",
|
||||
label: "Email",
|
||||
getValue: (org) => (
|
||||
<span
|
||||
className="text-truncate d-inline-block"
|
||||
style={{ maxWidth: "200px" }}
|
||||
>
|
||||
{org?.email || "N/A"}
|
||||
</span>
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "sprid",
|
||||
label: "SPRID Id",
|
||||
getValue: (org) => org.sprid || "N/A",
|
||||
align: "text-center",
|
||||
},
|
||||
];
|
||||
|
||||
const paginate = (page) => {
|
||||
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
|
||||
setCurrentPage(page);
|
||||
}
|
||||
};
|
||||
if (isFetching && !isFetching) return <div>Loading...</div>;
|
||||
if (isError) return <div>{error?.message || "Something went wrong"}</div>;
|
||||
|
||||
return (
|
||||
<div className="card px-0 px-sm-4">
|
||||
<div className="card-datatable table-responsive" id="horizontal-example">
|
||||
<div className="dataTables_wrapper no-footer px-2">
|
||||
<table className="table border-top dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr className="table_header_border">
|
||||
{organizationsColumns.map((col) => (
|
||||
<th
|
||||
key={col.key}
|
||||
className="sorting d-table-cell"
|
||||
aria-sort="descending"
|
||||
>
|
||||
<div className={`${col.align}`}>{col.label}</div>
|
||||
</th>
|
||||
))}
|
||||
<th className="sticky-action-column bg-white text-center">
|
||||
Action
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data?.data?.length > 0 ? (
|
||||
data?.data?.map((org) => (
|
||||
<tr key={org.id}>
|
||||
{organizationsColumns.map((col) => (
|
||||
<td
|
||||
key={col.key}
|
||||
className={`d-table-cell ${col.align ?? ""}`}
|
||||
>
|
||||
{col.customRender
|
||||
? col.customRender(org)
|
||||
: col.getValue(org)}
|
||||
</td>
|
||||
))}
|
||||
<td className="sticky-action-column ">
|
||||
<div className="d-flex justify-content-center gap-2">
|
||||
<i className="bx bx-show text-primary cursor-pointer" ></i>
|
||||
<i className="bx bx-edit text-secondary cursor-pointer" onClick={()=>onOpen({startStep:4,orgData:org,flowType:"edit"})}></i>
|
||||
<i className="bx bx-trash text-danger cursor-pointer"></i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={organizationsColumns.length + 1}
|
||||
className="text-center"
|
||||
>
|
||||
<p className="fw-semibold">Not Found</p>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
{data?.data?.length > 0 && (
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data.totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationsList;
|
1
src/components/Organization/tempCodeRunnerFile.jsx
Normal file
1
src/components/Organization/tempCodeRunnerFile.jsx
Normal file
@ -0,0 +1 @@
|
||||
useAssignOrgToTenant
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import WorkArea from "./WorkArea";
|
||||
const Floor = ({ floor, workAreas, forBuilding }) => {
|
||||
const Floor = ({ floor, workAreas, forBuilding,serviceId }) => {
|
||||
return (
|
||||
<React.Fragment key={floor.id}>
|
||||
{workAreas && workAreas.length > 0 ? (
|
||||
@ -10,6 +10,7 @@ const Floor = ({ floor, workAreas, forBuilding }) => {
|
||||
key={workArea.id}
|
||||
workArea={workArea}
|
||||
floor={floor}
|
||||
serviceId={serviceId}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
getCachedData,
|
||||
} from "../../../slices/apiDataManager";
|
||||
|
||||
const InfraTable = ({ buildings, projectId}) => {
|
||||
const InfraTable = ({ buildings, projectId, serviceId }) => {
|
||||
const [projectBuilding, setProjectBuilding] = useState([]);
|
||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||
const [showFloorModal, setShowFloorModal] = useState(false);
|
||||
@ -90,6 +90,7 @@ const InfraTable = ({ buildings, projectId}) => {
|
||||
forBuilding={building}
|
||||
floor={floor}
|
||||
workAreas={floor.workAreas}
|
||||
serviceId={serviceId}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
@ -100,7 +101,7 @@ const InfraTable = ({ buildings, projectId}) => {
|
||||
No floors have been added yet. Start by adding floors to manage
|
||||
this building.
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -143,7 +144,7 @@ const InfraTable = ({ buildings, projectId}) => {
|
||||
// }, [handler]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="px-6">
|
||||
{projectBuilding && projectBuilding.length > 0 && (
|
||||
<table className="table table-bordered">
|
||||
<tbody>
|
||||
|
@ -6,9 +6,10 @@ import {
|
||||
useActivitiesMaster,
|
||||
useWorkCategoriesMaster,
|
||||
} from "../../../hooks/masterHook/useMaster";
|
||||
import { useManageTask } from "../../../hooks/useProjects";
|
||||
import { useManageTask, useProjectAssignedServices } from "../../../hooks/useProjects";
|
||||
import showToast from "../../../services/toastService";
|
||||
import Label from "../../common/Label";
|
||||
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||
|
||||
const taskSchema = z.object({
|
||||
buildingID: z.string().min(1, "Building is required"),
|
||||
@ -37,6 +38,15 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
const { activities, loading: activityLoading } = useActivitiesMaster();
|
||||
const { categories, categoryLoading } = useWorkCategoriesMaster();
|
||||
|
||||
const projectId = useSelectedProject();
|
||||
|
||||
const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId);
|
||||
|
||||
const [selectedService, setSelectedService] = useState("");
|
||||
|
||||
const handleServiceChange = (e) => {
|
||||
setSelectedService(e.target.value);
|
||||
};
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -96,10 +106,12 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
}, [categories]);
|
||||
|
||||
const onSubmitForm = async (data) => {
|
||||
const payload = [data];
|
||||
CreateTask({payload:payload,buildingId: data.buildingID,
|
||||
const payload = [data];
|
||||
CreateTask({
|
||||
payload: payload, buildingId: data.buildingID,
|
||||
floorId: data.floorId,
|
||||
workAreaId: data.workAreaId, PreviousPlannedWork:0,previousCompletedWork:0});
|
||||
workAreaId: data.workAreaId, PreviousPlannedWork: 0, previousCompletedWork: 0
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -150,6 +162,7 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Work Area Selection */}
|
||||
{selectedFloor && (
|
||||
<div className="col-12 text-start">
|
||||
<Label className="form-label" required>Select Work Area</Label>
|
||||
@ -172,6 +185,32 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Services Selection */}
|
||||
{selectedWorkArea && (
|
||||
<div className="col-12 text-start">
|
||||
<Label className="form-label">Select Services</Label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
aria-label="Select Service"
|
||||
value={selectedService}
|
||||
onChange={handleServiceChange}
|
||||
>
|
||||
{servicesLoading && <option>Loading...</option>}
|
||||
{assignedServices?.map((service) => (
|
||||
<option key={service.id} value={service.id}>
|
||||
{service.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.buildingID && (
|
||||
<p className="danger-text">{errors.buildingID.message}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Activity Selection */}
|
||||
{selectedWorkArea && (
|
||||
<div className="col-12 text-start">
|
||||
<Label className="form-label" required>Select Activity</Label>
|
||||
@ -192,6 +231,7 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{selectedWorkArea && (
|
||||
<div className="col-12 text-start">
|
||||
<label className="form-label">Select Work Category</label>
|
||||
@ -261,7 +301,7 @@ const TaskModel = ({ project, onSubmit, onClose }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="col-12 text-end mt-5">
|
||||
<div className="col-12 text-end mt-5">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-label-secondary me-3"
|
||||
|
@ -16,18 +16,17 @@ import { useParams } from "react-router-dom";
|
||||
import ProgressBar from "../../common/ProgressBar";
|
||||
import {formatNumber} from "../../../utils/dateUtils";
|
||||
|
||||
const WorkArea = ({ workArea, floor, forBuilding }) => {
|
||||
const WorkArea = ({ workArea, floor, forBuilding,serviceId = null }) => {
|
||||
const selectedProject = useSelector((store) => store.localVariables.projectId);
|
||||
const { projects_Details, loading } = useProjectDetails(selectedProject);
|
||||
const [IsExpandedArea, setIsExpandedArea] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const [Project, setProject] = useState();
|
||||
// const { projectId } = useParams();
|
||||
|
||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||
|
||||
const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id, IsExpandedArea);
|
||||
const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id,serviceId, IsExpandedArea);
|
||||
|
||||
const [workAreaStatus, setWorkAreaStatus] = useState({
|
||||
completed: 0,
|
||||
|
@ -17,22 +17,20 @@ import {
|
||||
getCachedData,
|
||||
useSelectedProject,
|
||||
} from "../../slices/apiDataManager";
|
||||
import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
|
||||
import { useProjectAssignedServices, useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { refreshData } from "../../slices/localVariablesSlice";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import {useParams} from "react-router-dom";
|
||||
import { useParams } from "react-router-dom";
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
|
||||
const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
||||
{
|
||||
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
|
||||
const projectId = useSelectedProject();
|
||||
const reloadedData = useSelector((store) => store.localVariables.reload);
|
||||
const [ expandedBuildings, setExpandedBuildings ] = useState( [] );
|
||||
const {projectInfra,isLoading,error} = useProjectInfra(projectId)
|
||||
const [expandedBuildings, setExpandedBuildings] = useState([]);
|
||||
const { projectInfra, isLoading, error } = useProjectInfra(projectId)
|
||||
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
|
||||
const [ project, setProject ] = useState( projects_Details );
|
||||
const [project, setProject] = useState(projects_Details);
|
||||
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
|
||||
const ManageTask = useHasUserPermission(MANAGE_TASK)
|
||||
const [showModalFloor, setshowModalFloor] = useState(false);
|
||||
@ -40,47 +38,46 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
||||
const [showModalTask, setshowModalTask] = useState(false);
|
||||
const [showModalBuilding, setshowModalBuilding] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId);
|
||||
|
||||
const [selectedService, setSelectedService] = useState("");
|
||||
const handleServiceChange = (e) => {
|
||||
setSelectedService(e.target.value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setProject(projectInfra);
|
||||
}, [data, projects_Details]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (reloadedData) {
|
||||
// refetch();
|
||||
// dispatch(refreshData(false));
|
||||
// }
|
||||
// }, [reloadedData]);
|
||||
|
||||
const signalRHandler = (response) => {
|
||||
setProject(response);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModalBuilding && <GlobalModel isOpen={showModalBuilding} size="md" closeModal={() => setshowModalBuilding( false )}>
|
||||
{showModalBuilding && <GlobalModel isOpen={showModalBuilding} size="md" closeModal={() => setshowModalBuilding(false)}>
|
||||
<BuildingModel
|
||||
project={projectInfra}
|
||||
onClose={() => setshowModalBuilding( false )}
|
||||
onClose={() => setshowModalBuilding(false)}
|
||||
/>
|
||||
</GlobalModel>}
|
||||
{showModalFloor && <GlobalModel isOpen={showModalFloor} size="md" closeModal={()=>setshowModalFloor(false)}>
|
||||
</GlobalModel>}
|
||||
{showModalFloor && <GlobalModel isOpen={showModalFloor} size="md" closeModal={() => setshowModalFloor(false)}>
|
||||
<FloorModel
|
||||
project={projectInfra}
|
||||
onClose={()=>setshowModalFloor(false)}
|
||||
/>
|
||||
project={projectInfra}
|
||||
onClose={() => setshowModalFloor(false)}
|
||||
/>
|
||||
</GlobalModel>}
|
||||
{showModalWorkArea && <GlobalModel isOpen={showModalWorkArea} size="lg" closeModal={()=>setshowModalWorkArea(false)} >
|
||||
<WorkAreaModel
|
||||
project={projectInfra}
|
||||
onClose={()=>setshowModalWorkArea(false)}
|
||||
/>
|
||||
{showModalWorkArea && <GlobalModel isOpen={showModalWorkArea} size="lg" closeModal={() => setshowModalWorkArea(false)} >
|
||||
<WorkAreaModel
|
||||
project={projectInfra}
|
||||
onClose={() => setshowModalWorkArea(false)}
|
||||
/>
|
||||
</GlobalModel>}
|
||||
{showModalTask && ( <GlobalModel isOpen={showModalTask} size="lg" closeModal={()=>setshowModalTask(false)}>
|
||||
{showModalTask && (<GlobalModel isOpen={showModalTask} size="lg" closeModal={() => setshowModalTask(false)}>
|
||||
<TaskModel
|
||||
project={projectInfra}
|
||||
onClose={()=>setshowModalTask(false)}
|
||||
/>
|
||||
project={projectInfra}
|
||||
onClose={() => setshowModalTask(false)}
|
||||
/>
|
||||
</GlobalModel>)}
|
||||
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
|
||||
<div className="card">
|
||||
@ -88,38 +85,67 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
||||
<div className="align-items-center">
|
||||
<div className="row ">
|
||||
<div
|
||||
className={`col-12 text-end mb-1 `}
|
||||
className="dataTables_length text-start py-2 px-6 col-md-4 col-12"
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
{ManageInfra && (<>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
|
||||
onClick={()=>setshowModalBuilding(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Building
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md m-1 btn-primary"
|
||||
onClick={()=>setshowModalFloor(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Floors
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md m-1 btn-primary"
|
||||
onClick={() => setshowModalWorkArea(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Work Areas
|
||||
</button></>)}
|
||||
|
||||
{!servicesLoading && assignedServices?.length > 0 && (
|
||||
assignedServices.length > 1 ? (
|
||||
<label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
aria-label="Select Service"
|
||||
value={selectedService}
|
||||
onChange={handleServiceChange}
|
||||
>
|
||||
<option value="">All Services</option>
|
||||
{assignedServices.map((service) => (
|
||||
<option key={service.id} value={service.id}>
|
||||
{service.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<h5>{assignedServices[0].name}</h5>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Buttons Section (aligned to right) */}
|
||||
<div className="col-md-8 col-12 text-end mb-1">
|
||||
{ManageInfra && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
|
||||
onClick={() => setshowModalBuilding(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Building
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md m-1 btn-primary"
|
||||
onClick={() => setshowModalFloor(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Floors
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md m-1 btn-primary"
|
||||
onClick={() => setshowModalWorkArea(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Manage Work Areas
|
||||
</button></>)}
|
||||
|
||||
{(ManageTask || ManageInfra) && (<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md m-1 btn-primary"
|
||||
onClick={()=>setshowModalTask(true)}
|
||||
onClick={() => setshowModalTask(true)}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Create Tasks
|
||||
@ -132,8 +158,7 @@ const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
|
||||
<InfraTable
|
||||
buildings={projectInfra}
|
||||
projectId={projectId}
|
||||
// handleFloor={submitData}
|
||||
// signalRHandler ={signalRHandler}
|
||||
serviceId={selectedService}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && projectInfra?.length == 0 && <div className="mt-5"><p>No Infra Avaiable</p></div>}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import React from "react";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import {
|
||||
DIRECTORY_ADMIN,
|
||||
DIRECTORY_MANAGER,
|
||||
@ -13,6 +11,7 @@ import {
|
||||
VIEW_DOCUMENT,
|
||||
VIEW_PROJECT_INFRA,
|
||||
} from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
const ProjectNav = ({ onPillClick, activePill }) => {
|
||||
const HasViewInfraStructure = useHasUserPermission(VIEW_PROJECT_INFRA);
|
||||
@ -22,7 +21,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
||||
const DireManager = useHasUserPermission(DIRECTORY_MANAGER);
|
||||
const DirUser = useHasUserPermission(DIRECTORY_USER);
|
||||
const isManageTeam = useHasUserPermission(MANAGE_TEAM)
|
||||
const isViewDocuments = hasUserPermission(VIEW_DOCUMENT);
|
||||
const isViewDocuments = useHasUserPermission(VIEW_DOCUMENT);
|
||||
const isUploadDocument = useHasUserPermission(UPLOAD_DOCUMENT)
|
||||
const isModifyDocument = useHasUserPermission(MODIFY_DOCUMENT)
|
||||
|
||||
@ -43,6 +42,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
|
||||
},
|
||||
{ key: "documents", icon: "bx bx-folder-open", label: "Documents",hidden:!(isViewDocuments || isModifyDocument || isUploadDocument) },
|
||||
{ key: "setting", icon: "bx bxs-cog", label: "Setting",hidden:!isManageTeam },
|
||||
{ key: "organization", icon: "bx bx-buildings", label: "Organization"},
|
||||
];
|
||||
return (
|
||||
<div className="nav-align-top">
|
||||
|
@ -0,0 +1,99 @@
|
||||
import React from "react";
|
||||
import { useProjectAssignedOrganizations } from "../../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||
|
||||
const ProjectAssignedOrgs = () => {
|
||||
const selectedProject = useSelectedProject();
|
||||
const { data, isLoading, isError, error } =
|
||||
useProjectAssignedOrganizations(selectedProject);
|
||||
|
||||
const contactList = [
|
||||
{
|
||||
key: "name",
|
||||
label: "Organization Name",
|
||||
getValue: (org) => (
|
||||
<div className="d-flex gap-2 py-1 ">
|
||||
<i class="bx bx-buildings"></i>
|
||||
<span
|
||||
className="text-truncate d-inline-block "
|
||||
style={{ maxWidth: "150px" }}
|
||||
>
|
||||
{org?.name || "N/A"}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
{
|
||||
key: "sprid",
|
||||
label: "Service Provider Id",
|
||||
getValue: (org) => (
|
||||
<span
|
||||
className="text-truncate d-inline-block"
|
||||
style={{ maxWidth: "200px" }}
|
||||
>
|
||||
{org?.sprid || "N/A"}
|
||||
</span>
|
||||
),
|
||||
align: "text-center",
|
||||
},
|
||||
{
|
||||
key: "isActive",
|
||||
label: "Status",
|
||||
getValue: (org) => (
|
||||
<span
|
||||
className={`text-truncate d-inline-block badge bg-label-${org.isActive ? "primary" :"secondary"}`}
|
||||
style={{ maxWidth: "80px" }}
|
||||
>
|
||||
{org?.isActive ? "Active" : "Inactive"}
|
||||
</span>
|
||||
),
|
||||
align: "text-start",
|
||||
},
|
||||
];
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="dataTables_wrapper no-footer mx-5 pb-2">
|
||||
<table className="table dataTable text-nowrap">
|
||||
<thead>
|
||||
<tr className="table_header_border">
|
||||
{contactList.map((col) => (
|
||||
<th key={col.key} className={col.align}>
|
||||
{col.label}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.isArray(data) && data.length > 0 ? (
|
||||
data.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{contactList.map((col) => (
|
||||
<td key={col.key} className={col.align}>
|
||||
{col.getValue(row)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr style={{ height: "200px" }}>
|
||||
<td
|
||||
colSpan={contactList.length}
|
||||
className="text-center align-middle"
|
||||
>
|
||||
Not Assigned yet
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectAssignedOrgs;
|
31
src/components/Project/ProjectOrganizations.jsx
Normal file
31
src/components/Project/ProjectOrganizations.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import ProjectAssignedOrgs from "./ProjectOrganization/ProjectAssignedOrgs";
|
||||
|
||||
const ProjectOrganizations = () => {
|
||||
const { onOpen, startStep, flowType } = useOrganizationModal();
|
||||
const selectedProject = useSelectedProject();
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="d-flex justify-content-end px-2">
|
||||
<button
|
||||
type="button"
|
||||
className="link-button btn btn-xs rounded-md link-button-sm m-1 btn-primary"
|
||||
onClick={() => onOpen({ startStep: 1, flowType: "assign" })}
|
||||
>
|
||||
<i className="bx bx-plus-circle me-2"></i>
|
||||
Add Organization
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<ProjectAssignedOrgs />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectOrganizations;
|
@ -17,12 +17,11 @@ import eventBus from "../../services/eventBus";
|
||||
import {
|
||||
useEmployeesByProjectAllocated,
|
||||
useManageProjectAllocation,
|
||||
useProjectAssignedServices,
|
||||
} from "../../hooks/useProjects";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
|
||||
const Teams = () => {
|
||||
// const {projectId} = useParams()
|
||||
// const projectId = useSelector((store)=>store.localVariables.projectId)
|
||||
const projectId = useSelectedProject();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@ -37,6 +36,15 @@ const Teams = () => {
|
||||
const [activeEmployee, setActiveEmployee] = useState(true);
|
||||
const [deleteEmployee, setDeleteEmplyee] = useState(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State for search term
|
||||
const [selectedService, setSelectedService] = useState(null);
|
||||
|
||||
const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId);
|
||||
const handleToggleActive = e => setActiveEmployee(e.target.checked);
|
||||
|
||||
|
||||
const handleServiceChange = (e) => {
|
||||
setSelectedService(e.target.value);
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -46,7 +54,7 @@ const Teams = () => {
|
||||
projectEmployees,
|
||||
loading: employeeLodaing,
|
||||
refetch,
|
||||
} = useEmployeesByProjectAllocated(projectId);
|
||||
} = useEmployeesByProjectAllocated(projectId, selectedService);
|
||||
const {
|
||||
mutate: submitAllocations,
|
||||
isPending,
|
||||
@ -136,7 +144,6 @@ const Teams = () => {
|
||||
useEffect(() => {
|
||||
if (projectEmployees) {
|
||||
setEmployees(projectEmployees);
|
||||
//setFilteredEmployees(projectEmployees?.filter((emp) => emp.isActive));
|
||||
const filtered = projectEmployees.filter((emp) => emp.isActive);
|
||||
setFilteredEmployees(filtered);
|
||||
}
|
||||
@ -147,7 +154,6 @@ const Teams = () => {
|
||||
setEmpJobRoles(data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const filterAndSearchEmployees = useCallback(() => {
|
||||
const statusFiltered = employees.filter((emp) =>
|
||||
activeEmployee ? emp.isActive : !emp.isActive
|
||||
@ -159,33 +165,31 @@ const Teams = () => {
|
||||
}
|
||||
|
||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
||||
|
||||
const searchedAndFiltered = statusFiltered.filter((item) => {
|
||||
const fullName =
|
||||
`${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase();
|
||||
const fullName = `${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase();
|
||||
const roleName = getRole(item.jobRoleId).toLowerCase();
|
||||
const orgName = (item.organizationName || "").toLowerCase();
|
||||
const serviceName = (item.serviceName || "").toLowerCase();
|
||||
|
||||
return (
|
||||
fullName.includes(lowercasedSearchTerm) ||
|
||||
roleName.includes(lowercasedSearchTerm)
|
||||
roleName.includes(lowercasedSearchTerm) ||
|
||||
orgName.includes(lowercasedSearchTerm) ||
|
||||
serviceName.includes(lowercasedSearchTerm)
|
||||
);
|
||||
});
|
||||
|
||||
setFilteredEmployees(searchedAndFiltered);
|
||||
}, [employees, activeEmployee, searchTerm, getRole]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
filterAndSearchEmployees();
|
||||
}, [employees, activeEmployee, searchTerm, filterAndSearchEmployees]);
|
||||
|
||||
const handleFilterEmployee = (e) => {
|
||||
const filterValue = e.target.value;
|
||||
// if (filterValue === "true") {
|
||||
// setActiveEmployee(true);
|
||||
// setFilteredEmployees(employees.filter((emp) => emp.isActive));
|
||||
// } else {
|
||||
// setFilteredEmployees(employees.filter((emp) => !emp.isActive));
|
||||
// setActiveEmployee(false);
|
||||
// }
|
||||
setActiveEmployee(filterValue === "true");
|
||||
setSearchTerm("");
|
||||
};
|
||||
@ -264,6 +268,46 @@ const Teams = () => {
|
||||
<div className="card-body">
|
||||
<div className="row d-flex justify-content-between mb-4">
|
||||
<div className="col-md-6 col-12 d-flex align-items-center">
|
||||
<div className="dataTables_length text-start py-1 px-0 col-md-4 col-12">
|
||||
{!servicesLoading && assignedServices?.length > 0 && (
|
||||
assignedServices.length > 1 ? (
|
||||
<label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
aria-label="Select Service"
|
||||
value={selectedService}
|
||||
onChange={handleServiceChange}
|
||||
style={{ fontSize: "0.875rem", height: "35px", width: "190px" }}
|
||||
>
|
||||
<option value="">All Services</option>
|
||||
{assignedServices.map((service) => (
|
||||
<option key={service.id} value={service.id}>
|
||||
{service.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<h5>{assignedServices[0].name}</h5>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6 col-12 d-flex justify-content-end align-items-center">
|
||||
<div className="form-check form-switch me-2 mt-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={activeEmployee}
|
||||
onChange={handleToggleActive}
|
||||
id="activeEmployeeSwitch"
|
||||
/>
|
||||
<label className="form-check-label ms-0 " htmlFor="activeEmployeeSwitch">
|
||||
{activeEmployee ? "Active Employees" : "Inactive Employees"}
|
||||
</label>
|
||||
</div>
|
||||
<div className="dataTables_filter d-inline-flex align-items-center ms-2">
|
||||
<input
|
||||
type="search"
|
||||
@ -274,32 +318,11 @@ const Teams = () => {
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6 col-12 d-flex justify-content-end align-items-center">
|
||||
<div
|
||||
className="dataTables_length text-start py-2 px-2"
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
<label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
onChange={handleFilterEmployee}
|
||||
// value={false}
|
||||
aria-label=""
|
||||
defaultValue="true"
|
||||
>
|
||||
<option value="true">Active Employee</option>
|
||||
<option value="false">In-Active Employee</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`link-button btn-primary btn-sm ${
|
||||
HasAssignUserPermission ? "" : "d-none"
|
||||
}`}
|
||||
className={`link-button btn-primary btn-sm ${HasAssignUserPermission ? "" : "d-none"
|
||||
}`}
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#user-model"
|
||||
>
|
||||
@ -319,6 +342,8 @@ const Teams = () => {
|
||||
<th>
|
||||
<div className="text-start ms-5">Name</div>
|
||||
</th>
|
||||
<th>Services</th>
|
||||
<th>Organization</th>
|
||||
<th>Assigned Date</th>
|
||||
{!activeEmployee && <th>Release Date</th>}
|
||||
<th>Project Role</th>
|
||||
@ -334,7 +359,7 @@ const Teams = () => {
|
||||
<Avatar
|
||||
firstName={item.firstName}
|
||||
lastName={item.lastName}
|
||||
></Avatar>
|
||||
/>
|
||||
<div className="d-flex flex-column">
|
||||
<a
|
||||
onClick={() =>
|
||||
@ -352,18 +377,17 @@ const Teams = () => {
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>{item.serviceName || "N/A"}</td>
|
||||
<td>{item.organizationName || "N/A"}</td>
|
||||
|
||||
<td>
|
||||
{" "}
|
||||
{moment(item.allocationDate).format(
|
||||
"DD-MMM-YYYY"
|
||||
)}{" "}
|
||||
{moment(item.allocationDate).format("DD-MMM-YYYY")}
|
||||
</td>
|
||||
{!activeEmployee && (
|
||||
<td>
|
||||
{item.reAllocationDate
|
||||
? moment(item.reAllocationDate).format(
|
||||
"DD-MMM-YYYY"
|
||||
)
|
||||
? moment(item.reAllocationDate).format("DD-MMM-YYYY")
|
||||
: "Present"}
|
||||
</td>
|
||||
)}
|
||||
@ -373,7 +397,7 @@ const Teams = () => {
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{item.isActive && (
|
||||
{item.isActive ? (
|
||||
<button
|
||||
aria-label="Delete"
|
||||
type="button"
|
||||
@ -381,27 +405,26 @@ const Teams = () => {
|
||||
className="btn p-0 dropdown-toggle hide-arrow"
|
||||
onClick={() => deleteModalOpen(item)}
|
||||
>
|
||||
{" "}
|
||||
{removingEmployeeId === item.id ? (
|
||||
<div
|
||||
className="spinner-border spinner-border-sm text-primary"
|
||||
role="status"
|
||||
>
|
||||
<span className="visually-hidden">
|
||||
Loading...
|
||||
</span>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<i className="bx bx-trash me-1 text-danger"></i>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span>Not in project</span>
|
||||
)}
|
||||
{!item.isActive && <span>Not in project</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
)}
|
||||
{!employeeLodaing && filteredEmployees.length === 0 && (
|
||||
<div className="text-center text-muted py-3">
|
||||
|
@ -5,12 +5,12 @@ import GlobalModel from "../common/GlobalModel";
|
||||
import { useTenantContext } from "../../pages/Tenant/TenantPage";
|
||||
import { useTenantDetailsContext } from "../../pages/Tenant/TenantDetails";
|
||||
import IconButton from "../common/IconButton";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { MANAGE_TENANTS } from "../../utils/constants";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
const Profile = ({ data }) => {
|
||||
const {setEditTenant} = useTenantDetailsContext()
|
||||
const canUpdateTenant = hasUserPermission(MANAGE_TENANTS)
|
||||
const canUpdateTenant = useHasUserPermission(MANAGE_TENANTS)
|
||||
return (
|
||||
<>
|
||||
<div className="container-fuid">
|
||||
|
@ -203,7 +203,7 @@ const FilterIcon = ({
|
||||
<>
|
||||
<li><hr className="my-1" /></li>
|
||||
<li>
|
||||
<div className="fw-bold text-dark mb-1">Floors</div>
|
||||
<div className="fw-bold text-dark mb-2 mt-2">Floors</div>
|
||||
<div className="row">
|
||||
{uniqueFloors.length > 0 ? (
|
||||
uniqueFloors.map((floor, idx) => (
|
||||
@ -235,7 +235,7 @@ const FilterIcon = ({
|
||||
<>
|
||||
<li><hr className="my-1" /></li>
|
||||
<li>
|
||||
<div className="fw-bold text-dark mb-1">Activities</div>
|
||||
<div className="fw-bold text-dark mb-2 mt-2">Activities</div>
|
||||
<div className="row">
|
||||
{uniqueActivities.length > 0 ? (
|
||||
uniqueActivities.map((activity, idx) => (
|
||||
|
50
src/components/common/Modal.jsx
Normal file
50
src/components/common/Modal.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
const Modal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
body,
|
||||
disabled,
|
||||
size = "md",
|
||||
position = "top",
|
||||
}) => {
|
||||
const handleClose = useCallback(() => {
|
||||
if (disabled) return;
|
||||
onClose();
|
||||
}, [disabled, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="modal fade show"
|
||||
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.6)" }}
|
||||
tabIndex="-1"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
className={`modal-dialog modal-${size} modal-dialog-${position}`}
|
||||
role="document"
|
||||
>
|
||||
<div className="modal-content text-white shadow-lg">
|
||||
{/* Header */}
|
||||
<div className="modal-header justify-content-center pb-2 border-0">
|
||||
<h5 className="modal-title">{title}</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close btn-close-white"
|
||||
onClick={handleClose}
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="modal-body pt-0">{body}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
@ -2,15 +2,17 @@ import React, { useState, useEffect, useRef } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { createPortal } from "react-dom";
|
||||
import "./MultiSelectDropdown.css";
|
||||
import Label from "./Label";
|
||||
|
||||
const SelectMultiple = ({
|
||||
name,
|
||||
options = [],
|
||||
label = "Select options",
|
||||
labelKey = "name", // Can now be a function or a string
|
||||
labelKey = "name",
|
||||
valueKey = "id",
|
||||
placeholder = "Please select...",
|
||||
IsLoading = false,
|
||||
required = false
|
||||
}) => {
|
||||
const { setValue, watch } = useFormContext();
|
||||
const selectedValues = watch(name) || [];
|
||||
@ -20,7 +22,11 @@ const SelectMultiple = ({
|
||||
const containerRef = useRef(null);
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
const [dropdownStyles, setDropdownStyles] = useState({ top: 0, left: 0, width: 0 });
|
||||
const [dropdownStyles, setDropdownStyles] = useState({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e) => {
|
||||
@ -100,8 +106,14 @@ const SelectMultiple = ({
|
||||
return (
|
||||
<div
|
||||
key={valueVal}
|
||||
className={`multi-select-dropdown-option ${isChecked ? "selected" : ""}`}
|
||||
style={{ display: "flex", alignItems: "center", padding: "4px 8px" }}
|
||||
className={`multi-select-dropdown-option ${
|
||||
isChecked ? "selected" : ""
|
||||
}`}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: "4px 8px",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -130,8 +142,13 @@ const SelectMultiple = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={containerRef} className="multi-select-dropdown-container" style={{ position: "relative" }}>
|
||||
<label className="form-label mb-1">{label}</label>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="multi-select-dropdown-container"
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
<Label required={required}>{label}</Label>
|
||||
|
||||
|
||||
<div
|
||||
className="multi-select-dropdown-header"
|
||||
@ -140,7 +157,9 @@ const SelectMultiple = ({
|
||||
>
|
||||
<span
|
||||
className={
|
||||
selectedValues.length > 0 ? "placeholder-style-selected" : "placeholder-style"
|
||||
selectedValues.length > 0
|
||||
? "placeholder-style-selected"
|
||||
: "placeholder-style"
|
||||
}
|
||||
>
|
||||
<div className="selected-badges-container">
|
||||
@ -149,7 +168,10 @@ const SelectMultiple = ({
|
||||
const found = options.find((opt) => opt[valueKey] === val);
|
||||
const label = found ? getLabel(found) : "";
|
||||
return (
|
||||
<span key={val} className="badge badge-selected-item mx-1 mb-1">
|
||||
<span
|
||||
key={val}
|
||||
className="badge badge-selected-item mx-1 mb-1"
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
|
@ -16,6 +16,7 @@ import ManagePaymentMode from "./ManagePaymentMode";
|
||||
import ManageExpenseStatus from "./ManageExpenseStatus";
|
||||
import ManageDocumentCategory from "./ManageDocumentCategory";
|
||||
import ManageDocumentType from "./ManageDocumentType";
|
||||
import ManageServices from "./Services/ManageServices";
|
||||
|
||||
const MasterModal = ({ modaldata, closeModal }) => {
|
||||
if (!modaldata?.modalType || modaldata.modalType === "delete") {
|
||||
@ -60,6 +61,12 @@ const MasterModal = ({ modaldata, closeModal }) => {
|
||||
"Edit-Document Type": (
|
||||
<ManageDocumentType data={item} onClose={closeModal} />
|
||||
),
|
||||
"Services": (
|
||||
<ManageServices onClose={closeModal} />
|
||||
),
|
||||
"Edit-Services": (
|
||||
<ManageServices data={item} onClose={closeModal} />
|
||||
),
|
||||
};
|
||||
|
||||
return modalComponents[modalType] || null;
|
||||
|
107
src/components/master/Services/ManageServices.jsx
Normal file
107
src/components/master/Services/ManageServices.jsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Label from "../../common/Label";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useCreateService, useUpdateService } from "../../../hooks/masterHook/useMaster";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, { message: "Service Name is required" }),
|
||||
description: z
|
||||
.string()
|
||||
.min(1, { message: "Description is required" })
|
||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||
});
|
||||
|
||||
const ManageServices = ({ data , onClose }) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: { name: "", description: "" },
|
||||
});
|
||||
|
||||
const { mutate: CreateServices, isPending: Creating } = useCreateService(() =>
|
||||
onClose?.()
|
||||
);
|
||||
const { mutate: UpdateServices, isPending: Updating } = useUpdateService(() =>
|
||||
onClose?.()
|
||||
);
|
||||
|
||||
const onSubmit = (payload) => {
|
||||
if (data && data.id) {
|
||||
UpdateServices({ id: data.id, payload: { ...payload, id: data.id } });
|
||||
} else {
|
||||
CreateServices(payload);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset({
|
||||
name: data.name ?? "",
|
||||
description: data.description ?? "",
|
||||
});
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
return (
|
||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="col-12 col-md-12 text-start">
|
||||
<Label className="form-label" required>
|
||||
Service Name
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
{...register("name")}
|
||||
className={`form-control ${errors.name ? "is-invalid" : ""}`}
|
||||
/>
|
||||
{errors.name && <p className="danger-text">{errors.name.message}</p>}
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-12 text-start">
|
||||
<Label className="form-label" htmlFor="description" required>
|
||||
Description
|
||||
</Label>
|
||||
<textarea
|
||||
rows="3"
|
||||
{...register("description")}
|
||||
className={`form-control ${errors.description ? "is-invalid" : ""}`}
|
||||
></textarea>
|
||||
{errors.description && (
|
||||
<p className="danger-text">{errors.description.message}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-12 text-end">
|
||||
<button
|
||||
type="reset"
|
||||
className="btn btn-sm btn-label-secondary me-3"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
disabled={Creating || Updating}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={Creating || Updating}
|
||||
>
|
||||
{Creating
|
||||
? "Please Wait..."
|
||||
: Updating
|
||||
? "Please Wait..."
|
||||
: data
|
||||
? "Update"
|
||||
: "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManageServices;
|
9
src/components/master/Services/ServicesSchema.js
Normal file
9
src/components/master/Services/ServicesSchema.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, { message: "Service Name is required" }),
|
||||
description: z
|
||||
.string()
|
||||
.min(1, { message: "Description is required" })
|
||||
.max(255, { message: "Description cannot exceed 255 characters" }),
|
||||
});
|
@ -9,7 +9,18 @@ import showToast from "../../services/toastService";
|
||||
|
||||
|
||||
|
||||
|
||||
export const useServices = ()=>{
|
||||
return useQuery({
|
||||
queryKey:["services"],
|
||||
queryFn:async()=> await MasterRespository.getMasterServices()
|
||||
})
|
||||
}
|
||||
export const useGlobalServices = ()=>{
|
||||
return useQuery({
|
||||
queryKey:["globalServices"],
|
||||
queryFn:async()=> await MasterRespository.getGlobalServices()
|
||||
})
|
||||
}
|
||||
|
||||
export const useMasterMenu = ()=>{
|
||||
return useQuery({
|
||||
@ -221,6 +232,12 @@ const {
|
||||
|
||||
return { DocumentCategories, isError, isLoading, error };
|
||||
}
|
||||
export const useOrganizationType =()=>{
|
||||
return useQuery({
|
||||
queryKey:["orgType"],
|
||||
queryFn:async()=>await MasterRespository.getOrganizationType()
|
||||
})
|
||||
}
|
||||
// ===Application Masters Query=================================================
|
||||
|
||||
const fetchMasterData = async (masterType) => {
|
||||
@ -231,6 +248,8 @@ const fetchMasterData = async (masterType) => {
|
||||
return (await MasterRespository.getJobRole()).data;
|
||||
case "Activity":
|
||||
return (await MasterRespository.getActivites()).data;
|
||||
case "Services":
|
||||
return (await MasterRespository.getService()).data;
|
||||
case "Work Category":
|
||||
return (await MasterRespository.getWorkCategory()).data;
|
||||
case "Contact Category":
|
||||
@ -667,6 +686,7 @@ export const useCreatePaymentMode = (onSuccessCallback)=>{
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdatePaymentMode = (onSuccessCallback)=>{
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -689,6 +709,81 @@ export const useUpdatePaymentMode = (onSuccessCallback)=>{
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Services-------------------------------
|
||||
|
||||
// export const useCreateService = (onSuccessCallback) => {
|
||||
// const queryClient = useQueryClient();
|
||||
|
||||
// return useMutation({
|
||||
// mutationFn: async (payload) => {
|
||||
// const resp = await MasterRespository.createService(payload);
|
||||
// return resp.data; // full API response
|
||||
// },
|
||||
// onSuccess: (data) => {
|
||||
// // Invalidate & refetch service list
|
||||
// queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] });
|
||||
|
||||
// showToast(data?.message || "Service added successfully", "success");
|
||||
|
||||
// if (onSuccessCallback) onSuccessCallback(data?.data); // pass back new service object
|
||||
// },
|
||||
// onError: (error) => {
|
||||
// showToast(error.message || "Something went wrong", "error");
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
|
||||
export const useCreateService = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (payload) => {
|
||||
debugger;
|
||||
const resp = await MasterRespository.createService(payload);
|
||||
debugger;
|
||||
return resp.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
debugger;
|
||||
queryClient.invalidateQueries({ queryKey: ["masterData", "Services"] });
|
||||
|
||||
showToast(data?.message || "Service added successfully", "success");
|
||||
|
||||
if (onSuccessCallback) onSuccessCallback(data?.data);
|
||||
},
|
||||
onError: (error) => {
|
||||
debugger;
|
||||
showToast(error.message || "Something went wrong", "error");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const useUpdateService = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ id, payload }) => {
|
||||
const response = await MasterRespository.updateService(id, payload);
|
||||
return response; // full response since it already has { success, message, data }
|
||||
},
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["masterData", "Services"],
|
||||
});
|
||||
|
||||
showToast(data.message || "Service updated successfully.", "success");
|
||||
|
||||
if (onSuccessCallback) onSuccessCallback(data);
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(error?.message || "Something went wrong", "error");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// -------------------Expense Status----------------------------------
|
||||
export const useCreateExpenseStatus =(onSuccessCallback)=>{
|
||||
const queryClient = useQueryClient();
|
||||
|
@ -12,46 +12,76 @@ import { setDefaultDateRange } from "../slices/localVariablesSlice";
|
||||
// ----------------------------Query-----------------------------
|
||||
|
||||
|
||||
export const useAttendance = (projectId) => {
|
||||
const dispatch = useDispatch()
|
||||
// export const useAttendance = (projectId) => {
|
||||
// const dispatch = useDispatch()
|
||||
// const {
|
||||
// data: attendance = [],
|
||||
// isLoading: loading,
|
||||
// error,
|
||||
// refetch: recall,
|
||||
// isFetching
|
||||
// } = useQuery({
|
||||
// queryKey: ["attendance", projectId],
|
||||
// queryFn: async () => {
|
||||
// const response = await AttendanceRepository.getAttendance(projectId);
|
||||
// return response.data;
|
||||
// },
|
||||
// enabled: !!projectId,
|
||||
// onError: (error) => {
|
||||
// showToast(error.message || "Error while fetching Attendance", "error");
|
||||
// },
|
||||
// });
|
||||
|
||||
// return {
|
||||
// attendance,
|
||||
// loading,
|
||||
// error,
|
||||
// recall,
|
||||
// isFetching
|
||||
// };
|
||||
// };
|
||||
|
||||
export const useAttendance = (projectId, organizationId, includeInactive = false, date = null) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
data: attendance = [],
|
||||
isLoading: loading,
|
||||
error,
|
||||
refetch: recall,
|
||||
isFetching
|
||||
isFetching,
|
||||
} = useQuery({
|
||||
queryKey: ["attendance", projectId],
|
||||
queryKey: ["attendance", projectId, organizationId, includeInactive, date], // include filters in cache key
|
||||
queryFn: async () => {
|
||||
const response = await AttendanceRepository.getAttendance(projectId);
|
||||
const response = await AttendanceRepository.getAttendance(
|
||||
projectId,
|
||||
organizationId,
|
||||
includeInactive,
|
||||
date
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
enabled: !!projectId, // only run if projectId exists
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while fetching Attendance", "error");
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
attendance,
|
||||
loading,
|
||||
error,
|
||||
recall,
|
||||
isFetching
|
||||
};
|
||||
return { attendance, loading, error, recall, isFetching };
|
||||
};
|
||||
|
||||
export const useAttendancesLogs = (projectId, fromDate, toDate) => {
|
||||
export const useAttendancesLogs = (projectId, fromDate, toDate,organizationId) => {
|
||||
const dispatch = useDispatch();
|
||||
const enabled = !!projectId && !!fromDate && !!toDate;
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['attendanceLogs', projectId, fromDate, toDate],
|
||||
queryKey: ['attendanceLogs', projectId, fromDate, toDate,organizationId],
|
||||
queryFn: async () => {
|
||||
const res = await AttendanceRepository.getAttendanceFilteredByDate(
|
||||
projectId,
|
||||
fromDate,
|
||||
toDate
|
||||
toDate,
|
||||
organizationId
|
||||
);
|
||||
return res.data;
|
||||
},
|
||||
@ -112,30 +142,58 @@ export const useAttendanceByEmployee = (employeeId, fromDate, toDate) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useRegularizationRequests = (projectId) => {
|
||||
// export const useRegularizationRequests = (projectId) => {
|
||||
// const {
|
||||
// data: regularizes = [],
|
||||
// isLoading: loading,
|
||||
// error,
|
||||
// refetch,
|
||||
// } = useQuery({
|
||||
// queryKey: ["regularizedList", projectId],
|
||||
// queryFn: async () => {
|
||||
// const response = await AttendanceRepository.getRegularizeList(projectId);
|
||||
// return response.data;
|
||||
// },
|
||||
// enabled: !!projectId,
|
||||
// onError: (error) => {
|
||||
// showToast(error.message || "Error while fetching Regularization Requests", "error");
|
||||
// },
|
||||
// });
|
||||
|
||||
// return {
|
||||
// regularizes,
|
||||
// loading,
|
||||
// error,
|
||||
// refetch,
|
||||
// };
|
||||
// };
|
||||
|
||||
export const useRegularizationRequests = (projectId, organizationId, IncludeInActive = false) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {
|
||||
data: regularizes = [],
|
||||
isLoading: loading,
|
||||
error,
|
||||
refetch,
|
||||
refetch: recall,
|
||||
isFetching,
|
||||
} = useQuery({
|
||||
queryKey: ["regularizedList", projectId],
|
||||
queryKey: ["regularizedList", projectId, organizationId, IncludeInActive], // include filters in cache key
|
||||
queryFn: async () => {
|
||||
const response = await AttendanceRepository.getRegularizeList(projectId);
|
||||
const response = await AttendanceRepository.getRegularizeList(
|
||||
projectId,
|
||||
organizationId,
|
||||
IncludeInActive,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
enabled: !!projectId, // only run if projectId exists
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while fetching Regularization Requests", "error");
|
||||
showToast(error.message || "Error while fetching regularizes", "error");
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
regularizes,
|
||||
loading,
|
||||
error,
|
||||
refetch,
|
||||
};
|
||||
return { regularizes, loading, error, recall, isFetching };
|
||||
};
|
||||
|
||||
|
||||
|
83
src/hooks/useAuth.jsx
Normal file
83
src/hooks/useAuth.jsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
Mutation,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import AuthRepository from "../repositories/AuthRepository.jsx";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
closeAuthModal,
|
||||
openAuthModal,
|
||||
} from "../slices/localVariablesSlice.jsx";
|
||||
|
||||
export const useTenants = () => {
|
||||
return useQuery({
|
||||
queryKey: ["tenantlist"],
|
||||
queryFn: async () => await AuthRepository.getTenantList(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSelectTenant = (onSuccessCallBack) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (tenantId) => {
|
||||
const res = await AuthRepository.selectTenant(tenantId);
|
||||
return res.data;
|
||||
},
|
||||
|
||||
onSuccess: (data) => {
|
||||
localStorage.setItem("jwtToken", data.token);
|
||||
localStorage.setItem("refreshToken", data.refreshToken);
|
||||
if (onSuccessCallBack) onSuccessCallBack();
|
||||
},
|
||||
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while creating project", "error");
|
||||
localStorage.removeItem("jwtToken");
|
||||
localStorage.removeItem("refreshToken")
|
||||
localStorage.removeItem("ctnt")
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useAuthModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { isOpen } = useSelector((state) => state.localVariables.AuthModal);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
onOpen: () => dispatch(openAuthModal()),
|
||||
onClose: () => dispatch(closeAuthModal()),
|
||||
};
|
||||
};
|
||||
|
||||
export const useLogout = ()=>{
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
let payload = {refreshToken: localStorage.getItem("refreshToken")}
|
||||
return await AuthRepository.logout(payload);
|
||||
},
|
||||
|
||||
onSuccess: (data) => {
|
||||
localStorage.removeItem("jwtToken");
|
||||
localStorage.removeItem("refreshToken");
|
||||
localStorage.removeItem("ctnt");
|
||||
localStorage.clear();
|
||||
window.location.href = "/auth/login";
|
||||
if (onSuccessCallBack) onSuccessCallBack();
|
||||
},
|
||||
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while creating project", "error");
|
||||
localStorage.removeItem("jwtToken");
|
||||
localStorage.removeItem("refreshToken")
|
||||
localStorage.removeItem("ctnt")
|
||||
},
|
||||
});
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import GlobalRepository from "../repositories/GlobalRepository";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
|
||||
// 🔹 Dashboard Progression Data Hook
|
||||
export const useDashboard_Data = ({ days, FromDate, projectId }) => {
|
||||
const [dashboard_data, setDashboard_Data] = useState([]);
|
||||
const [isLineChartLoading, setLoading] = useState(false);
|
||||
@ -38,120 +39,120 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
|
||||
};
|
||||
|
||||
|
||||
export const useDashboard_AttendanceData = (date, projectId) => {
|
||||
const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]);
|
||||
const [isLineChartLoading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
// export const useDashboard_AttendanceData = (date, projectId) => {
|
||||
// const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]);
|
||||
// const [isLineChartLoading, setLoading] = useState(false);
|
||||
// const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
// useEffect(() => {
|
||||
// const fetchData = async () => {
|
||||
// setLoading(true);
|
||||
// setError("");
|
||||
|
||||
try {
|
||||
const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param
|
||||
setDashboard_AttendanceData(response.data);
|
||||
} catch (err) {
|
||||
setError("Failed to fetch dashboard data.");
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// try {
|
||||
// const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param
|
||||
// setDashboard_AttendanceData(response.data);
|
||||
// } catch (err) {
|
||||
// setError("Failed to fetch dashboard data.");
|
||||
// console.error(err);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
if (date && projectId !== null) {
|
||||
fetchData();
|
||||
}
|
||||
}, [date, projectId]);
|
||||
// if (date && projectId !== null) {
|
||||
// fetchData();
|
||||
// }
|
||||
// }, [date, projectId]);
|
||||
|
||||
return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error };
|
||||
};
|
||||
// return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error };
|
||||
// };
|
||||
|
||||
|
||||
// 🔹 Dashboard Projects Card Data Hook
|
||||
export const useDashboardProjectsCardData = () => {
|
||||
const [projectsCardData, setProjectsData] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
// export const useDashboardProjectsCardData = () => {
|
||||
// const [projectsCardData, setProjectsData] = useState([]);
|
||||
// const [loading, setLoading] = useState(false);
|
||||
// const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProjectsData = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
// useEffect(() => {
|
||||
// const fetchProjectsData = async () => {
|
||||
// setLoading(true);
|
||||
// setError("");
|
||||
|
||||
try {
|
||||
const response = await GlobalRepository.getDashboardProjectsCardData();
|
||||
setProjectsData(response.data);
|
||||
} catch (err) {
|
||||
setError("Failed to fetch projects card data.");
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// try {
|
||||
// const response = await GlobalRepository.getDashboardProjectsCardData();
|
||||
// setProjectsData(response.data);
|
||||
// } catch (err) {
|
||||
// setError("Failed to fetch projects card data.");
|
||||
// console.error(err);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
fetchProjectsData();
|
||||
}, []);
|
||||
// fetchProjectsData();
|
||||
// }, []);
|
||||
|
||||
return { projectsCardData, loading, error };
|
||||
};
|
||||
// return { projectsCardData, loading, error };
|
||||
// };
|
||||
|
||||
// 🔹 Dashboard Teams Card Data Hook
|
||||
export const useDashboardTeamsCardData = (projectId) => {
|
||||
const [teamsCardData, setTeamsData] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
// export const useDashboardTeamsCardData = (projectId) => {
|
||||
// const [teamsCardData, setTeamsData] = useState({});
|
||||
// const [loading, setLoading] = useState(false);
|
||||
// const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTeamsData = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
// useEffect(() => {
|
||||
// const fetchTeamsData = async () => {
|
||||
// setLoading(true);
|
||||
// setError("");
|
||||
|
||||
try {
|
||||
const response = await GlobalRepository.getDashboardTeamsCardData(projectId);
|
||||
setTeamsData(response.data || {});
|
||||
} catch (err) {
|
||||
setError("Failed to fetch teams card data.");
|
||||
console.error("Error fetching teams card data:", err);
|
||||
setTeamsData({});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// try {
|
||||
// const response = await GlobalRepository.getDashboardTeamsCardData(projectId);
|
||||
// setTeamsData(response.data || {});
|
||||
// } catch (err) {
|
||||
// setError("Failed to fetch teams card data.");
|
||||
// console.error("Error fetching teams card data:", err);
|
||||
// setTeamsData({});
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
fetchTeamsData();
|
||||
}, [projectId]);
|
||||
// fetchTeamsData();
|
||||
// }, [projectId]);
|
||||
|
||||
return { teamsCardData, loading, error };
|
||||
};
|
||||
// return { teamsCardData, loading, error };
|
||||
// };
|
||||
|
||||
export const useDashboardTasksCardData = (projectId) => {
|
||||
const [tasksCardData, setTasksData] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
// export const useDashboardTasksCardData = (projectId) => {
|
||||
// const [tasksCardData, setTasksData] = useState({});
|
||||
// const [loading, setLoading] = useState(false);
|
||||
// const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTasksData = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
// useEffect(() => {
|
||||
// const fetchTasksData = async () => {
|
||||
// setLoading(true);
|
||||
// setError("");
|
||||
|
||||
try {
|
||||
const response = await GlobalRepository.getDashboardTasksCardData(projectId);
|
||||
setTasksData(response.data);
|
||||
} catch (err) {
|
||||
setError("Failed to fetch tasks card data.");
|
||||
console.error(err);
|
||||
setTasksData({});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
// try {
|
||||
// const response = await GlobalRepository.getDashboardTasksCardData(projectId);
|
||||
// setTasksData(response.data);
|
||||
// } catch (err) {
|
||||
// setError("Failed to fetch tasks card data.");
|
||||
// console.error(err);
|
||||
// setTasksData({});
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
fetchTasksData();
|
||||
}, [projectId]);
|
||||
// fetchTasksData();
|
||||
// }, [projectId]);
|
||||
|
||||
return { tasksCardData, loading, error };
|
||||
};
|
||||
// return { tasksCardData, loading, error };
|
||||
// };
|
||||
|
||||
|
||||
export const useAttendanceOverviewData = (projectId, days) => {
|
||||
@ -180,3 +181,75 @@ export const useAttendanceOverviewData = (projectId, days) => {
|
||||
|
||||
return { attendanceOverviewData, loading, error };
|
||||
};
|
||||
|
||||
|
||||
// -------------------Query----------------------------
|
||||
|
||||
// export const useDashboard_Data = (days, FromDate, projectId)=>{
|
||||
// return useQuery({
|
||||
// queryKey:["dashboardProjectProgress"],
|
||||
// queryFn:async()=> {
|
||||
// const payload = {
|
||||
// days,
|
||||
// FromDate: FromDate || '',
|
||||
// projectId: projectId || null,
|
||||
// };
|
||||
// const resp = await GlobalRepository.getDashboardProgressionData(payload);
|
||||
// return resp.data;
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
export const useDashboard_AttendanceData = (date,projectId)=>{
|
||||
return useQuery({
|
||||
queryKey:["dashboardAttendances",date,projectId],
|
||||
queryFn:async()=> {
|
||||
|
||||
const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId)
|
||||
return resp.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const useDashboardTeamsCardData =(projectId)=>{
|
||||
return useQuery({
|
||||
queryKey:["dashboardTeams",projectId],
|
||||
queryFn:async()=> {
|
||||
|
||||
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId)
|
||||
return resp.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const useDashboardTasksCardData = (projectId) => {
|
||||
return useQuery({
|
||||
queryKey:["dashboardTasks",projectId],
|
||||
queryFn:async()=> {
|
||||
|
||||
const resp = await GlobalRepository.getDashboardTasksCardData(projectId)
|
||||
return resp.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
// export const useAttendanceOverviewData = (projectId, days) => {
|
||||
// return useQuery({
|
||||
// queryKey:["dashboardAttendanceOverView",projectId],
|
||||
// queryFn:async()=> {
|
||||
|
||||
// const resp = await GlobalRepository.getAttendanceOverview(projectId, days);
|
||||
// return resp.data;
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
export const useDashboardProjectsCardData = () => {
|
||||
return useQuery({
|
||||
queryKey:["dashboardProjects"],
|
||||
queryFn:async()=> {
|
||||
|
||||
const resp = await GlobalRepository.getDashboardProjectsCardData();
|
||||
return resp.data;
|
||||
}
|
||||
})
|
||||
}
|
161
src/hooks/useOrganization.js
Normal file
161
src/hooks/useOrganization.js
Normal file
@ -0,0 +1,161 @@
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
toggleOrgModal,
|
||||
openOrgModal,
|
||||
closeOrgModal,
|
||||
} from "../slices/localVariablesSlice";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import OrganizationRepository from "../repositories/OrganizationRespository";
|
||||
import showToast from "../services/toastService";
|
||||
|
||||
export const useOrganizationModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { isOpen, orgData, startStep, prevStep, flowType } = useSelector(
|
||||
(state) => state.localVariables.OrganizationModal
|
||||
);
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
orgData,
|
||||
startStep,
|
||||
prevStep,
|
||||
flowType,
|
||||
onOpen: (options = {}) =>
|
||||
dispatch(
|
||||
openOrgModal({
|
||||
isOpen: true,
|
||||
orgData: options.orgData ?? orgData ?? null,
|
||||
startStep: options.startStep ?? startStep ?? 1,
|
||||
prevStep: options.prevStep ?? prevStep ?? 1,
|
||||
flowType: options.flowType ?? flowType ?? "default",
|
||||
})
|
||||
),
|
||||
onClose: () => dispatch(closeOrgModal()),
|
||||
onToggle: () => dispatch(toggleOrgModal()),
|
||||
};
|
||||
};
|
||||
|
||||
export const useOrganizationBySPRID = (sprid) => {
|
||||
return useQuery({
|
||||
queryKey: ["organization by", sprid],
|
||||
queryFn: async () => {
|
||||
const resp = await OrganizationRepository.getOrganizationBySPRID(sprid);
|
||||
return resp.data;
|
||||
},
|
||||
enabled: !!sprid,
|
||||
});
|
||||
};
|
||||
|
||||
export const useOrganizationsList = (
|
||||
pageSize,
|
||||
pageNumber,
|
||||
active,
|
||||
sprid,
|
||||
searchString = ""
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: [
|
||||
"organizationList",
|
||||
pageSize,
|
||||
pageNumber,
|
||||
active,
|
||||
sprid,
|
||||
searchString,
|
||||
],
|
||||
queryFn: async () => {
|
||||
const resp = await OrganizationRepository.getOrganizationList(
|
||||
pageSize,
|
||||
pageNumber,
|
||||
active,
|
||||
sprid,
|
||||
searchString
|
||||
);
|
||||
return resp.data;
|
||||
},
|
||||
keepPreviousData: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateOrganization = (onSuccessCallback) => {
|
||||
const useClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (OrgPayload) =>
|
||||
await OrganizationRepository.createOrganization(OrgPayload),
|
||||
onSuccess: (_, variables) => {
|
||||
useClient.invalidateQueries({ queryKey: ["organizationList"] });
|
||||
showToast("Organization created successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useAssignOrgToProject = (onSuccessCallback) => {
|
||||
const useClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (payload) =>
|
||||
await OrganizationRepository.assignOrganizationToProject(payload),
|
||||
onSuccess: (_, variables) => {
|
||||
useClient.invalidateQueries({
|
||||
queryKey: ["projectAssignedOrganiztions"],
|
||||
});
|
||||
showToast("Organization successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
export const useAssignOrgToTenant = (onSuccessCallback) => {
|
||||
const useClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (payload) =>
|
||||
await OrganizationRepository.assignOrganizationToTenanat(payload),
|
||||
onSuccess: (_, variables) => {
|
||||
useClient.invalidateQueries({ queryKey: ["organizationList"] });
|
||||
showToast("Organization added successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
export const useUpdateOrganization = () => {
|
||||
const useClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (payload) =>
|
||||
await OrganizationRepository.assignOrganizationToProject(payload),
|
||||
onSuccess: (_, variables) => {
|
||||
// useClient.invalidateQueries({ queryKey: ["organizationList"] });
|
||||
showToast("Organization successfully", "success");
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.response.data.message ||
|
||||
error.message ||
|
||||
"Something went wrong please try again !",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
@ -41,22 +41,30 @@ export const useProjects = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useEmployeesByProjectAllocated = (selectedProject) => {
|
||||
export const useEmployeesByProjectAllocated = (
|
||||
projectId,
|
||||
serviceId,
|
||||
organizationId,
|
||||
) => {
|
||||
const {
|
||||
data = [],
|
||||
isLoading,
|
||||
refetch,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["empListByProjectAllocated", selectedProject],
|
||||
queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId],
|
||||
queryFn: async () => {
|
||||
const res = await ProjectRepository.getProjectAllocation(selectedProject);
|
||||
return res.data || res;
|
||||
const res = await ProjectRepository.getProjectAllocation(
|
||||
projectId,
|
||||
organizationId,
|
||||
serviceId
|
||||
);
|
||||
return res?.data || res;
|
||||
},
|
||||
enabled: !!selectedProject,
|
||||
enabled: !!projectId,
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error.message || "Error while Fetching project Allocated Employees",
|
||||
error.message || "Error while fetching project allocated employees",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
@ -177,7 +185,7 @@ export const useProjectInfra = (projectId) => {
|
||||
data: projectInfra,
|
||||
isLoading,
|
||||
error,
|
||||
isFetched
|
||||
isFetched,
|
||||
} = useQuery({
|
||||
queryKey: ["ProjectInfra", projectId],
|
||||
queryFn: async () => {
|
||||
@ -191,26 +199,23 @@ export const useProjectInfra = (projectId) => {
|
||||
},
|
||||
});
|
||||
|
||||
return { projectInfra, isLoading, error,isFetched };
|
||||
return { projectInfra, isLoading, error, isFetched };
|
||||
};
|
||||
|
||||
export const useProjectTasks = (workAreaId, IsExpandedArea = false) => {
|
||||
const {
|
||||
data: ProjectTaskList,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery({
|
||||
queryKey: ["WorkItems", workAreaId],
|
||||
export const useProjectTasks = (workAreaId, serviceId = null, isExpandedArea = false) => {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ["WorkItems", workAreaId, serviceId],
|
||||
queryFn: async () => {
|
||||
const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId);
|
||||
return res.data;
|
||||
const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId, serviceId);
|
||||
return res.data; // return actual task list
|
||||
},
|
||||
enabled: !!workAreaId && !!IsExpandedArea,
|
||||
onError: (error) => {
|
||||
showToast(error.message || "Error while Fetching project Tasks", "error");
|
||||
enabled: !!workAreaId && isExpandedArea, // only fetch if workAreaId exists and area is expanded
|
||||
onError: (err) => {
|
||||
showToast(err.message || "Error while fetching project tasks", "error");
|
||||
},
|
||||
});
|
||||
return { ProjectTaskList, isLoading, error };
|
||||
|
||||
return { ProjectTaskList: data, isLoading, error };
|
||||
};
|
||||
|
||||
export const useProjectTasksByEmployee = (employeeId, fromDate, toDate) => {
|
||||
@ -268,6 +273,31 @@ export const useProjectLevelEmployeePermission = (employeeId, projectId) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useProjectAssignedOrganizations = (projectId) => {
|
||||
return useQuery({
|
||||
queryKey: ["projectAssignedOrganiztions", projectId],
|
||||
queryFn: async () => {
|
||||
const resp = await ProjectRepository.getProjectAssignedOrganizations(
|
||||
projectId
|
||||
);
|
||||
return resp.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
});
|
||||
};
|
||||
export const useProjectAssignedServices = (projectId) => {
|
||||
return useQuery({
|
||||
queryKey: ["projectAssignedOrganization", projectId],
|
||||
queryFn: async () => {
|
||||
const resp = await ProjectRepository.getProjectAssignedServices(
|
||||
projectId
|
||||
);
|
||||
return resp.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
});
|
||||
};
|
||||
|
||||
// -- -------------Mutation-------------------------------
|
||||
|
||||
export const useCreateProject = ({ onSuccessCallback }) => {
|
||||
|
@ -2,11 +2,9 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
// import { MasterDataProvider } from "./provider/MasterDataContext";
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from './store/store';
|
||||
import { ModalProvider } from './ModalContext.jsx';
|
||||
import { ChangePasswordProvider } from './components/Context/ChangePasswordContext.jsx';
|
||||
import { ModalProvider1 } from './components/ImageGallery/ModalContext.jsx';
|
||||
|
||||
@ -15,15 +13,12 @@ createRoot(document.getElementById('root')!).render(
|
||||
// <StrictMode>
|
||||
// <MasterDataProvider>
|
||||
<Provider store={ store }>
|
||||
<ModalProvider>
|
||||
<ChangePasswordProvider >
|
||||
<ModalProvider1>
|
||||
<App />
|
||||
</ModalProvider1>
|
||||
</ChangePasswordProvider>
|
||||
</ModalProvider>
|
||||
</Provider>
|
||||
// </MasterDataProvider>
|
||||
|
||||
// </StrictMode>,
|
||||
)
|
||||
|
@ -13,11 +13,10 @@ import Regularization from "../../components/Activities/Regularization";
|
||||
import { useAttendance } from "../../hooks/useAttendance";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
|
||||
import eventBus from "../../services/eventBus";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { useProjectAssignedOrganizations, useProjectName } from "../../hooks/useProjects";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import CheckCheckOutmodel from "../../components/Activities/CheckCheckOutForm";
|
||||
import AttendLogs from "../../components/Activities/AttendLogs";
|
||||
@ -39,6 +38,13 @@ const AttendancePage = () => {
|
||||
const [modelConfig, setModelConfig] = useState();
|
||||
const DoRegularized = useHasUserPermission(REGULARIZE_ATTENDANCE);
|
||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||
const [appliedFilters, setAppliedFilters] = useState({
|
||||
selectedOrganization: "",
|
||||
selectedServices: [],
|
||||
});
|
||||
|
||||
const { data: organizations = [], isLoading: orgLoading } =
|
||||
useProjectAssignedOrganizations(selectedProject);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
markTime: "",
|
||||
@ -95,11 +101,11 @@ const AttendancePage = () => {
|
||||
{(modelConfig?.action === 0 ||
|
||||
modelConfig?.action === 1 ||
|
||||
modelConfig?.action === 2) && (
|
||||
<CheckCheckOutmodel
|
||||
modeldata={modelConfig}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
<CheckCheckOutmodel
|
||||
modeldata={modelConfig}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
)}
|
||||
{/* For view logs */}
|
||||
{modelConfig?.action === 6 && (
|
||||
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
|
||||
@ -128,9 +134,8 @@ const AttendancePage = () => {
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "all" ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${activeTab === "all" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange("all")}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-home"
|
||||
@ -141,9 +146,8 @@ const AttendancePage = () => {
|
||||
<li className="nav-item">
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "logs" ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${activeTab === "logs" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange("logs")}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-profile"
|
||||
@ -155,9 +159,8 @@ const AttendancePage = () => {
|
||||
<li className={`nav-item ${!DoRegularized ? "d-none" : ""}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${
|
||||
activeTab === "regularization" ? "active" : ""
|
||||
} fs-6`}
|
||||
className={`nav-link ${activeTab === "regularization" ? "active" : ""
|
||||
} fs-6`}
|
||||
onClick={() => handleTabChange("regularization")}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#navs-top-messages"
|
||||
@ -168,8 +171,30 @@ const AttendancePage = () => {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Single search input that moves */}
|
||||
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto px-2">
|
||||
{/* Search + Organization filter */}
|
||||
<div className="col-12 col-md-auto mt-2 mt-md-0 ms-md-auto d-flex gap-2 align-items-center">
|
||||
{/* Organization Dropdown */}
|
||||
<select
|
||||
className="form-select form-select-sm"
|
||||
style={{ minWidth: "180px" }}
|
||||
value={appliedFilters.selectedOrganization}
|
||||
onChange={(e) =>
|
||||
setAppliedFilters((prev) => ({
|
||||
...prev,
|
||||
selectedOrganization: e.target.value,
|
||||
}))
|
||||
}
|
||||
disabled={orgLoading}
|
||||
>
|
||||
<option value="">All Organizations</option>
|
||||
{organizations?.map((org) => (
|
||||
<option key={org.id} value={org.id}>
|
||||
{org.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* Search Input */}
|
||||
<input
|
||||
type="text"
|
||||
className="form-control form-control-sm"
|
||||
@ -179,6 +204,8 @@ const AttendancePage = () => {
|
||||
style={{ minWidth: "200px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -191,6 +218,7 @@ const AttendancePage = () => {
|
||||
handleModalData={handleModalData}
|
||||
getRole={getRole}
|
||||
searchTerm={searchTerm}
|
||||
organizationId={appliedFilters.selectedOrganization}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -199,12 +227,16 @@ const AttendancePage = () => {
|
||||
<AttendanceLog
|
||||
handleModalData={handleModalData}
|
||||
searchTerm={searchTerm}
|
||||
organizationId={appliedFilters.selectedOrganization}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "regularization" && DoRegularized && (
|
||||
<div className="tab-pane fade show active py-0">
|
||||
<Regularization searchTerm={searchTerm} />
|
||||
<Regularization
|
||||
searchTerm={searchTerm}
|
||||
organizationId={appliedFilters.selectedOrganization}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useTaskList } from "../../hooks/useTasks";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { useProjectAssignedServices, useProjectName } from "../../hooks/useProjects";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import DateRangePicker from "../../components/common/DateRangePicker";
|
||||
@ -24,6 +24,14 @@ const DailyTask = () => {
|
||||
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
|
||||
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
|
||||
|
||||
const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(selectedProject);
|
||||
|
||||
const [selectedService, setSelectedService] = useState("");
|
||||
|
||||
const handleServiceChange = (e) => {
|
||||
setSelectedService(e.target.value);
|
||||
};
|
||||
|
||||
const [filters, setFilters] = useState({
|
||||
selectedBuilding: "",
|
||||
selectedFloors: [],
|
||||
@ -38,7 +46,6 @@ const DailyTask = () => {
|
||||
dateRange?.endDate || null
|
||||
);
|
||||
|
||||
// Ensure project is set
|
||||
useEffect(() => {
|
||||
if (!selectedProject && projectNames.length > 0) {
|
||||
debugger
|
||||
@ -46,7 +53,6 @@ const DailyTask = () => {
|
||||
}
|
||||
}, [selectedProject, projectNames, dispatch]);
|
||||
|
||||
// 🔹 Reset filters when project changes
|
||||
useEffect(() => {
|
||||
setFilters({
|
||||
selectedBuilding: "",
|
||||
@ -55,7 +61,6 @@ const DailyTask = () => {
|
||||
});
|
||||
}, [selectedProject]);
|
||||
|
||||
// Memoized filtering
|
||||
const filteredTasks = useMemo(() => {
|
||||
if (!TaskList) return [];
|
||||
return TaskList.filter((task) => {
|
||||
@ -69,7 +74,6 @@ const DailyTask = () => {
|
||||
});
|
||||
}, [TaskList, filters]);
|
||||
|
||||
// Memoized dates
|
||||
const groupedTasks = useMemo(() => {
|
||||
const groups = {};
|
||||
filteredTasks.forEach((task) => {
|
||||
@ -82,13 +86,11 @@ const DailyTask = () => {
|
||||
.map((date) => ({ date, tasks: groups[date] }));
|
||||
}, [filteredTasks]);
|
||||
|
||||
// --- Modal State
|
||||
const [modal, setModal] = useState({ type: null, data: null });
|
||||
|
||||
const openModal = (type, data = null) => setModal({ type, data });
|
||||
const closeModal = () => setModal({ type: null, data: null });
|
||||
|
||||
// --- Render helpers
|
||||
const renderTeamMembers = (task, refIndex) => (
|
||||
<div
|
||||
key={refIndex}
|
||||
@ -133,7 +135,6 @@ const DailyTask = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* --- Modals --- */}
|
||||
{modal.type === "report" && (
|
||||
<GlobalModel isOpen size="md" closeModal={closeModal}>
|
||||
<ReportTask report={modal.data} closeModal={closeModal} />
|
||||
@ -161,22 +162,60 @@ const DailyTask = () => {
|
||||
<div className="container-fluid">
|
||||
<Breadcrumb data={[{ label: "Home", link: "/dashboard" }, { label: "Daily Progress Report" }]} />
|
||||
|
||||
<div className="card card-action mb-6">
|
||||
<div className="card card-action mb-6 p-5">
|
||||
<div className="card-body p-1 p-sm-2">
|
||||
{!selectedProject && (<div className="text-center text-muted">Please Select Project</div>)}
|
||||
{/* --- Filters --- */}
|
||||
<div className="d-flex align-items-center mb-2">
|
||||
<DateRangePicker onRangeChange={setDateRange} endDateMode="today" DateDifference="6" dateFormat="DD-MM-YYYY" />
|
||||
<FilterIcon
|
||||
taskListData={TaskList}
|
||||
onApplyFilters={setFilters}
|
||||
currentSelectedBuilding={filters.selectedBuilding}
|
||||
currentSelectedFloors={filters.selectedFloors}
|
||||
currentSelectedActivities={filters.selectedActivities}
|
||||
selectedProject={selectedProject}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-flex align-items-center justify-content-between mb-2">
|
||||
{/* --- Left: Service Dropdown + Filter Icon --- */}
|
||||
<div className="d-flex align-items-center gap-6">
|
||||
<div className="me-3">
|
||||
{!servicesLoading && assignedServices?.length > 0 && (
|
||||
assignedServices.length > 1 ? (
|
||||
<label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
aria-label="Select Service"
|
||||
value={selectedService}
|
||||
onChange={handleServiceChange}
|
||||
style={{ fontSize: "0.875rem", height: "35px", width: "190px" }}
|
||||
>
|
||||
<option value="">All Services</option>
|
||||
{assignedServices.map((service) => (
|
||||
<option key={service.id} value={service.id}>
|
||||
{service.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<h5>{assignedServices[0].name}</h5>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* --- Right: DateRangePicker --- */}
|
||||
<div className="d-flex justify-content-end align-items-center gap-3 me-3">
|
||||
<FilterIcon
|
||||
taskListData={TaskList}
|
||||
onApplyFilters={setFilters}
|
||||
currentSelectedBuilding={filters.selectedBuilding}
|
||||
currentSelectedFloors={filters.selectedFloors}
|
||||
currentSelectedActivities={filters.selectedActivities}
|
||||
selectedProject={selectedProject}
|
||||
/>
|
||||
<DateRangePicker
|
||||
onRangeChange={setDateRange}
|
||||
endDateMode="today"
|
||||
DateDifference="6"
|
||||
dateFormat="DD-MM-YYYY"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* --- Table --- */}
|
||||
<div className="table-responsive text-nowrap mt-3" style={{ minHeight: "200px" }}>
|
||||
<table className="table">
|
||||
@ -241,11 +280,10 @@ const DailyTask = () => {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DailyTask;
|
||||
export default DailyTask;
|
@ -1,22 +1,31 @@
|
||||
import React,{useEffect,useRef} from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import InfraPlanning from "../../components/Activities/InfraPlanning";
|
||||
import { useProjectName } from "../../hooks/useProjects";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||
import { useProjectAssignedServices } from "../../hooks/useProjects";
|
||||
|
||||
const TaskPlannng = () => {
|
||||
const selectedProject = useSelectedProject();
|
||||
const dispatch = useDispatch();
|
||||
const { projectNames = [], loading: projectLoading } = useProjectName();
|
||||
const selectedProject = useSelectedProject();
|
||||
const dispatch = useDispatch();
|
||||
const { projectNames = [], loading: projectLoading } = useProjectName();
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedProject) {
|
||||
dispatch(setProjectId(projectNames[0]?.id));
|
||||
}
|
||||
}, [projectNames, selectedProject?.id, dispatch]);
|
||||
// Service dropdown state
|
||||
const { data: assignedServices, isLoading: servicesLoading } =
|
||||
useProjectAssignedServices(selectedProject);
|
||||
const [selectedService, setSelectedService] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedProject && projectNames?.length > 0) {
|
||||
dispatch(setProjectId(projectNames[0]?.id));
|
||||
}
|
||||
}, [projectNames, selectedProject, dispatch]);
|
||||
|
||||
const handleServiceChange = (e) => {
|
||||
setSelectedService(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
@ -26,11 +35,50 @@ useEffect(() => {
|
||||
{ label: "Daily Task Planning" },
|
||||
]}
|
||||
/>
|
||||
{selectedProject ? (
|
||||
<InfraPlanning />
|
||||
) : (
|
||||
<div className="text-center">Please Select Project</div>
|
||||
)}
|
||||
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
{/* Service Dropdown */}
|
||||
|
||||
<div
|
||||
className="dataTables_length text-start py-2 px-5 col-md-4 col-12"
|
||||
id="DataTables_Table_0_length"
|
||||
>
|
||||
{!servicesLoading && assignedServices?.length > 0 && (
|
||||
assignedServices.length > 1 ? (
|
||||
<label>
|
||||
<select
|
||||
name="DataTables_Table_0_length"
|
||||
aria-controls="DataTables_Table_0"
|
||||
className="form-select form-select-sm"
|
||||
aria-label="Select Service"
|
||||
value={selectedService}
|
||||
onChange={handleServiceChange}
|
||||
style={{ fontSize: "0.875rem", height: "35px", width: "190px" }}
|
||||
>
|
||||
<option value="">All Services</option>
|
||||
{assignedServices.map((service) => (
|
||||
<option key={service.id} value={service.id}>
|
||||
{service.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
<h5>{assignedServices[0].name}</h5>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Infra Planning Component */}
|
||||
{selectedProject ? (
|
||||
<InfraPlanning selectedService={selectedService} />
|
||||
) : (
|
||||
<div className="text-center">Please Select Project</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
||||
};
|
||||
|
||||
if (isError) return <div>{error.message}</div>;
|
||||
if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
|
||||
// if (isLoading) return gridView ? <CardViewContactSkeleton /> : <ListViewContactSkeleton />;
|
||||
|
||||
return (
|
||||
<div className="row mt-5">
|
||||
@ -94,11 +94,11 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
||||
) : (
|
||||
<div className="col-12">
|
||||
<ListViewContact
|
||||
data={data.data}
|
||||
data={data?.data}
|
||||
Pagination={
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={data.totalPages}
|
||||
totalPages={data?.totalPages}
|
||||
onPageChange={paginate}
|
||||
/>
|
||||
}
|
||||
|
51
src/pages/Organization/OrganizationPage.jsx
Normal file
51
src/pages/Organization/OrganizationPage.jsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { useState } from "react";
|
||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||
import { useOrganizationModal } from "../../hooks/useOrganization";
|
||||
import OrganizationsList from "../../components/Organization/OrganizationsList";
|
||||
|
||||
const OrganizationPage = () => {
|
||||
const { isOpen, orgData, startStep, onOpen, flowType } =
|
||||
useOrganizationModal();
|
||||
const [searchText,setSearchText] = useState("")
|
||||
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
<Breadcrumb
|
||||
data={[{ label: "Home", link: "/" }, { label: "Organizations" }]}
|
||||
/>
|
||||
<div className="card my-3 px-sm-2 px-0">
|
||||
<div className="card-body py-2 px-3">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-6 d-flex ">
|
||||
<div className="d-flex align-items-center">
|
||||
<input
|
||||
type="search"
|
||||
value={searchText}
|
||||
onChange={(e)=>setSearchText(e.target.value)}
|
||||
className="form-control form-control-sm w-auto"
|
||||
placeholder="Search Organization"
|
||||
aria-describedby="search-label"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-6 text-end mt-2 mt-sm-0">
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 me-1 m-sm-0 bg-primary rounded-circle"
|
||||
title="Add New Organization"
|
||||
onClick={()=>onOpen({ startStep: 2,flowType:"default" })}
|
||||
>
|
||||
<i className="bx bx-plus fs-4 text-white"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OrganizationsList searchText={searchText}/>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationPage;
|
@ -1,16 +1,16 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import TenantDetails from "./TenantDetails";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
import { VIEW_TENANTS } from "../../utils/constants";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Loader from "../../components/common/Loader";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
const SelfTenantDetails = () => {
|
||||
const { profile, loading } = useProfile();
|
||||
const tenantId = profile?.employeeInfo?.tenantId;
|
||||
const navigate = useNavigate();
|
||||
const isSelfTenantView = hasUserPermission(VIEW_TENANTS);
|
||||
const isSelfTenantView = useHasUserPermission(VIEW_TENANTS);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSelfTenantView) {
|
||||
|
@ -20,7 +20,6 @@ import TenantFilterPanel from "../../components/Tenant/TenantFilterPanel";
|
||||
import { useDebounce } from "../../utils/appUtils";
|
||||
import { useFab } from "../../Context/FabContext";
|
||||
import { setCurrentTenant } from "../../slices/globalVariablesSlice";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
|
||||
// ------ Schema -------
|
||||
import {
|
||||
@ -35,6 +34,7 @@ import {
|
||||
VIEW_TENANTS,
|
||||
} from "../../utils/constants";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
// ---------- Context ----------
|
||||
export const TenantContext = createContext();
|
||||
@ -63,9 +63,9 @@ const TenantPage = () => {
|
||||
const debouncedSearch = useDebounce(searchText, 500);
|
||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||
|
||||
const isSuperTenant = hasUserPermission(SUPPER_TENANT);
|
||||
const canManageTenants = hasUserPermission(MANAGE_TENANTS);
|
||||
const isSelfTenant = hasUserPermission(VIEW_TENANTS);
|
||||
const isSuperTenant = useHasUserPermission(SUPPER_TENANT);
|
||||
const canManageTenants = useHasUserPermission(MANAGE_TENANTS);
|
||||
const isSelfTenant = useHasUserPermission(VIEW_TENANTS);
|
||||
|
||||
const methods = useForm({
|
||||
resolver: zodResolver(filterSchema),
|
||||
|
@ -44,7 +44,7 @@ const LoginPage = () => {
|
||||
localStorage.setItem("jwtToken", response.data.token);
|
||||
localStorage.setItem("refreshToken", response.data.refreshToken);
|
||||
setLoading(false);
|
||||
navigate("/dashboard");
|
||||
navigate("/auth/switch/org");
|
||||
} else {
|
||||
await AuthRepository.sendOTP({ email: data.username });
|
||||
showToast("OTP has been sent to your email.", "success");
|
||||
|
105
src/pages/authentication/SwitchTenant.jsx
Normal file
105
src/pages/authentication/SwitchTenant.jsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React, { useState } from "react";
|
||||
import Modal from "../../components/common/Modal";
|
||||
import { useAuthModal, useSelectTenant, useTenants } from "../../hooks/useAuth";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import AuthRepository from "../../repositories/AuthRepository";
|
||||
import Loader from "../../components/common/Loader";
|
||||
|
||||
const SwitchTenant = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { profile } = useProfile();
|
||||
const [pendingTenant, setPendingTenant] = useState(null);
|
||||
const { isOpen, onClose, onOpen } = useAuthModal();
|
||||
const { data, isLoading, isError, error } = useTenants();
|
||||
const { mutate: chooseTenant, isPending } = useSelectTenant(() => {
|
||||
onClose();
|
||||
queryClient.clear();
|
||||
|
||||
// 2. Force fetch profile fresh for the new tenant
|
||||
queryClient.fetchQuery({
|
||||
queryKey: ["profile"],
|
||||
queryFn: () => AuthRepository.profile(),
|
||||
});
|
||||
window.location.reload();
|
||||
});
|
||||
const currentTenant = localStorage.getItem("ctnt");
|
||||
const handleTenantselection = (tenantId) => {
|
||||
setPendingTenant(tenantId);
|
||||
localStorage.setItem("ctnt", tenantId);
|
||||
chooseTenant(tenantId);
|
||||
};
|
||||
|
||||
|
||||
const contentBody = (
|
||||
<div className="container text-black">
|
||||
<p className=" fs-5">Switch Workplace</p>
|
||||
<div className="row justify-content-center g-4">
|
||||
{data?.data.map((tenant) => (
|
||||
<div key={tenant.id} className="col-12 ">
|
||||
<div
|
||||
className={`d-flex flex-column flex-md-row gap-3 align-items-center align-items-md-start p-1 border ${
|
||||
currentTenant === tenant.id ? "border-primary" : ""
|
||||
} `}
|
||||
>
|
||||
<div
|
||||
className="flex-shrink-0 text-center"
|
||||
style={{
|
||||
width: "80px",
|
||||
aspectRatio: "1 / 1",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={tenant?.logoImage || "/assets/img/SP-Placeholdeer.svg"}
|
||||
alt={tenant.name}
|
||||
className="img-fluid"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-column text-start gap-2">
|
||||
<p className="fs-5 text-muted fw-semibold mb-1">
|
||||
{tenant?.name}
|
||||
</p>
|
||||
|
||||
<div className="d-flex flex-wrap gap-2 align-items-center">
|
||||
<p className="fw-semibold m-0">Industry:</p>
|
||||
<p className="m-0">
|
||||
{tenant?.industry?.name || "Not Available"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{tenant?.description && (
|
||||
<p className="text-start text-wrap m-0">
|
||||
{tenant?.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
className={` ${currentTenant === tenant.id ? "badge bg-label-primary w-50" :"btn btn-primary btn-sm mt-2 align-self-start" }`}
|
||||
onClick={() => handleTenantselection(tenant?.id)}
|
||||
disabled={isPending && pendingTenant === tenant.id || currentTenant === tenant.id }
|
||||
>
|
||||
{currentTenant === tenant.id ? "Active Tenant" :isPending && pendingTenant === tenant.id
|
||||
? "Please Wait.."
|
||||
: "Go To Dashboard"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <Modal isOpen={isOpen} onClose={onClose} body={isLoading ? <Loader/>:contentBody} />;
|
||||
};
|
||||
|
||||
export default SwitchTenant;
|
138
src/pages/authentication/TenantSelectionPage.jsx
Normal file
138
src/pages/authentication/TenantSelectionPage.jsx
Normal file
@ -0,0 +1,138 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTenants, useSelectTenant, useLogout } from "../../hooks/useAuth.jsx";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Dashboard from "../../components/Dashboard/Dashboard.jsx";
|
||||
import Loader from "../../components/common/Loader.jsx";
|
||||
|
||||
const TenantSelectionPage = () => {
|
||||
const [pendingTenant, setPendingTenant] = useState(null);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data, isLoading, isError, error } = useTenants();
|
||||
const { mutate: chooseTenant, isPending } = useSelectTenant(() => {
|
||||
navigate("/dashboard");
|
||||
});
|
||||
const handleTenantselection = (tenantId) => {
|
||||
setPendingTenant(tenantId);
|
||||
localStorage.setItem("ctnt", tenantId);
|
||||
chooseTenant(tenantId);
|
||||
};
|
||||
|
||||
const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{})
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if (localStorage.getItem("ctnt")) {
|
||||
// navigate("/dashboard");
|
||||
// }
|
||||
// }, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && data?.data?.length === 1) {
|
||||
const tenant = data.data[0];
|
||||
handleTenantselection(tenant.id);
|
||||
}
|
||||
}, [isLoading, data]);
|
||||
|
||||
if (isLoading) return <Loader />;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (!data?.data?.length) {
|
||||
return (
|
||||
<div className="text-center py-5">
|
||||
<p>No tenant assigned to your account.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="container-fluid">
|
||||
{/* Logo */}
|
||||
<div className="text-center">
|
||||
<img
|
||||
src="/img/brand/marco.png"
|
||||
alt="marco-logo"
|
||||
className="app-brand-logo-login img-fluid"
|
||||
style={{ maxHeight: "80px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Heading */}
|
||||
<div className="text-center mb-4">
|
||||
<p className="fs-4 fw-bold mb-1">Welcome</p>
|
||||
<p className="fs-6 fs-md-5">
|
||||
Please select which dashboard you want to explore!!!
|
||||
</p>
|
||||
<div onClick={()=>handleLogout()}>
|
||||
{isLogouting ? "Please Wait...":<span className="fs-6 fw-semibold cursor-pointer text-decoration-underline"><i className='bx bx-log-out'></i>SignOut</span>}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Card Section */}
|
||||
<div className="row justify-content-center g-4 ">
|
||||
{data?.data.map((tenant) => (
|
||||
<div key={tenant.id} className="col-12 col-md-10 col-lg-8">
|
||||
<div className="d-flex flex-column flex-md-row gap-4 align-items-center align-items-md-start p-3 border rounded shadow-sm bg-white h-100">
|
||||
|
||||
{/* Image */}
|
||||
<div className="flex-shrink-0 text-center">
|
||||
<img
|
||||
src={tenant?.logoImage || "/assets/img/SP-Placeholdeer.svg"}
|
||||
alt={tenant.name}
|
||||
className="img-fluid rounded"
|
||||
style={{
|
||||
maxWidth: "140px",
|
||||
aspectRatio: "3 / 2",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="d-flex flex-column text-start gap-2 w-100">
|
||||
{/* Title */}
|
||||
<p className="fs-5 fs-md-4 text-dark fw-semibold mb-1">
|
||||
{tenant?.name}
|
||||
</p>
|
||||
|
||||
{/* Industry */}
|
||||
<div className="d-flex flex-wrap gap-2 align-items-center">
|
||||
<p className="fw-semibold m-0">Industry:</p>
|
||||
<p className="m-0 text-muted">
|
||||
{tenant?.industry?.name || "Not Available"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{tenant?.description && (
|
||||
<p className="text-start text-wrap text-muted small m-0">
|
||||
{tenant?.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Button */}
|
||||
<button
|
||||
className="btn btn-primary btn-sm mt-2 align-self-start"
|
||||
onClick={() => handleTenantselection(tenant?.id)}
|
||||
disabled={pendingTenant === tenant.id && isPending}
|
||||
>
|
||||
{isPending && pendingTenant === tenant.id
|
||||
? "Please Wait.."
|
||||
: "Go To Dashboard"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default TenantSelectionPage;
|
1
src/pages/authentication/tempCodeRunnerFile.jsx
Normal file
1
src/pages/authentication/tempCodeRunnerFile.jsx
Normal file
@ -0,0 +1 @@
|
||||
login
|
@ -11,7 +11,7 @@ import {
|
||||
} from "../../hooks/useEmployees";
|
||||
import { useProjectName, useProjects } from "../../hooks/useProjects";
|
||||
import { useProfile } from "../../hooks/useProfile";
|
||||
import { hasUserPermission } from "../../utils/authUtils";
|
||||
|
||||
import {
|
||||
ITEMS_PER_PAGE,
|
||||
MANAGE_EMPLOYEES,
|
||||
@ -19,7 +19,6 @@ import {
|
||||
VIEW_TEAM_MEMBERS,
|
||||
} from "../../utils/constants";
|
||||
import { clearCacheKey } from "../../slices/apiDataManager";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
|
||||
import {
|
||||
exportToCSV,
|
||||
@ -36,6 +35,7 @@ import { newlineChars } from "pdf-lib";
|
||||
import GlobalModel from "../../components/common/GlobalModel";
|
||||
import usePagination from "../../hooks/usePagination";
|
||||
import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||
|
||||
const EmployeeList = () => {
|
||||
const selectedProjectId = useSelector(
|
||||
|
@ -91,7 +91,7 @@ const MasterPage = () => {
|
||||
{modalConfig && (
|
||||
<GlobalModel
|
||||
size={
|
||||
["Application Role", "Edit-Application Role"].includes(
|
||||
["Application Role", "Edit-Application Role", "Services"].includes(
|
||||
modalConfig.masterType
|
||||
)
|
||||
? "lg"
|
||||
|
@ -20,9 +20,10 @@ import { setProjectId } from "../../slices/localVariablesSlice";
|
||||
import ProjectDocuments from "../../components/Project/ProjectDocuments";
|
||||
import ProjectSetting from "../../components/Project/ProjectSetting";
|
||||
import DirectoryPage from "../Directory/DirectoryPage";
|
||||
import { useProjectAccess } from "../../hooks/useProjectAccess"; // ✅ new
|
||||
import { useProjectAccess } from "../../hooks/useProjectAccess";
|
||||
|
||||
import "./ProjectDetails.css";
|
||||
import ProjectOrganizations from "../../components/Project/ProjectOrganizations";
|
||||
|
||||
const ProjectDetails = () => {
|
||||
const projectId = useSelectedProject();
|
||||
@ -96,6 +97,8 @@ const ProjectDetails = () => {
|
||||
return <ProjectDocuments />;
|
||||
case "setting":
|
||||
return <ProjectSetting />;
|
||||
case "organization":
|
||||
return <ProjectOrganizations />;
|
||||
default:
|
||||
return <ComingSoonPage />;
|
||||
}
|
||||
|
@ -1,13 +1,24 @@
|
||||
import { api } from "../utils/axiosClient";
|
||||
|
||||
const AttendanceRepository = {
|
||||
markAttendance: (data) => api.post("/api/attendance/record", data),
|
||||
|
||||
getAttendance: (projectId, organizationId, includeInactive,date) => {
|
||||
let url = `/api/attendance/project/team?projectId=${projectId}`;
|
||||
|
||||
const AttendanceRepository = {
|
||||
markAttendance:(data)=>api.post("/api/attendance/record",data),
|
||||
getAttendance:(id)=>api.get(`api/attendance/project/team?projectId=${id}`),
|
||||
getAttendanceFilteredByDate: ( projectId, fromDate, toDate ) =>
|
||||
{
|
||||
|
||||
let url = `api/Attendance/project/log?projectId=${ projectId }`
|
||||
const params = [];
|
||||
if (organizationId) params.push(`organizationId=${organizationId}`);
|
||||
if (includeInactive) params.push(`includeInactive=${includeInactive}`);
|
||||
if (date) params.push(`date=${date}`);
|
||||
|
||||
if (params.length > 0) {
|
||||
url += `&${params.join("&")}`; }
|
||||
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
getAttendanceFilteredByDate: (projectId, fromDate, toDate,organizationId) => {
|
||||
let url = `api/Attendance/project/log?projectId=${projectId}`;
|
||||
if (fromDate) {
|
||||
url += `&dateFrom=${fromDate}`;
|
||||
}
|
||||
@ -15,27 +26,38 @@ const AttendanceRepository = {
|
||||
if (toDate) {
|
||||
url += `&dateTo=${toDate}`;
|
||||
}
|
||||
return api.get(url)
|
||||
if (organizationId) {
|
||||
url += `&organizationId=${organizationId}`;
|
||||
}
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
getAttendanceLogs: ( id ) => api.get( `api/attendance/log/attendance/${ id }` ),
|
||||
getRegularizeList: ( id ) => api.get( `api/attendance/regularize?projectId=${ id }` ),
|
||||
|
||||
getAttendanceByEmployee: ( employeeId, fromDate, toDate ) =>
|
||||
{
|
||||
|
||||
let url = `api/Attendance/log/employee/${ employeeId }?`
|
||||
if (fromDate) {
|
||||
url += `&dateFrom=${fromDate}`;
|
||||
}
|
||||
|
||||
if (toDate) {
|
||||
url += `&dateTo=${toDate}`;
|
||||
}
|
||||
return api.get(url)
|
||||
},
|
||||
|
||||
}
|
||||
getAttendanceLogs: (id) => api.get(`api/attendance/log/attendance/${id}`),
|
||||
|
||||
getRegularizeList: (projectId, organizationId, IncludeInActive) => {
|
||||
let url = `/api/attendance/regularize?projectId=${projectId}`;
|
||||
|
||||
const params = [];
|
||||
if (organizationId) params.push(`organizationId=${organizationId}`);
|
||||
if (IncludeInActive) params.push(`IncludeInActive=${IncludeInActive}`);
|
||||
|
||||
if (params.length > 0) {
|
||||
url += `&${params.join("&")}`; }
|
||||
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
getAttendanceByEmployee: (employeeId, fromDate, toDate) => {
|
||||
let url = `api/Attendance/log/employee/${employeeId}?`;
|
||||
if (fromDate) {
|
||||
url += `&dateFrom=${fromDate}`;
|
||||
}
|
||||
|
||||
if (toDate) {
|
||||
url += `&dateTo=${toDate}`;
|
||||
}
|
||||
return api.get(url);
|
||||
},
|
||||
};
|
||||
|
||||
export default AttendanceRepository;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { api } from "../utils/axiosClient";
|
||||
|
||||
const AuthRepository = {
|
||||
// Public routes (no auth token required)
|
||||
login: (data) => api.postPublic("/api/auth/login", data),
|
||||
login: (data) => api.postPublic("/api/auth/login/v1", data),
|
||||
refreshToken: (data) => api.postPublic("/api/auth/refresh-token", data),
|
||||
forgotPassword: (data) => api.postPublic("/api/auth/forgot-password", data),
|
||||
resetPassword: (data) => api.postPublic("/api/auth/reset-password", data),
|
||||
@ -15,7 +15,9 @@ const AuthRepository = {
|
||||
logout: (data) => api.post("/api/auth/logout", data),
|
||||
profile: () => api.get("/api/user/profile"),
|
||||
changepassword: (data) => api.post("/api/auth/change-password", data),
|
||||
appmenu:()=>api.get('/api/appmenu/get/menu')
|
||||
appmenu: () => api.get('/api/appmenu/get/menu'),
|
||||
selectTenant: (tenantId) => api.post(`/api/Auth/select-tenant/${tenantId}`),
|
||||
getTenantList: () => api.get("/api/Auth/get/user/tenants"),
|
||||
|
||||
};
|
||||
|
||||
|
@ -31,6 +31,13 @@ export const MasterRespository = {
|
||||
|
||||
getActivites: () => api.get("api/master/activities"),
|
||||
createActivity: (data) => api.post("api/master/activity", data),
|
||||
|
||||
//Services
|
||||
getService: () => api.get("api/master/service/list"),
|
||||
createService: (data) => api.post("api/master/service/create", data),
|
||||
updateService: (id, data) => api.put(`api/master/service/edit/${id}`, data),
|
||||
"Services": (id) => api.delete(`/api/master/service/delete/${id}`),
|
||||
|
||||
updateActivity: (id, data) =>
|
||||
api.post(`api/master/activity/edit/${id}`, data),
|
||||
getIndustries: () => api.get("api/master/industries"),
|
||||
@ -106,4 +113,11 @@ export const MasterRespository = {
|
||||
createDocumentType: (data) => api.post(`/api/Master/document-type`, data),
|
||||
updateDocumentType: (id, data) =>
|
||||
api.put(`/api/Master/document-type/edit/${id}`, data),
|
||||
|
||||
|
||||
|
||||
getGlobalServices:()=>api.get("/api/Master/global-service/list"),
|
||||
getMasterServices:()=>api.get("/api/Master/service/list"),
|
||||
|
||||
getOrganizationType:()=>api.get('/api/Master/organization-type/list')
|
||||
};
|
||||
|
20
src/repositories/OrganizationRespository.jsx
Normal file
20
src/repositories/OrganizationRespository.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { api } from "../utils/axiosClient";
|
||||
|
||||
const OrganizationRepository = {
|
||||
createOrganization: (data) => api.post("/api/Organization/create", data),
|
||||
getOrganizationList: (pageSize, pageNumber, active, sprid, searchString) => {
|
||||
return api.get(
|
||||
`/api/Organization/list?pageSize=${pageSize}&pageNumber=${pageNumber}&active=${active}&${
|
||||
sprid ? `sprid=${sprid}&` : ""
|
||||
}searchString=${searchString}`
|
||||
);
|
||||
},
|
||||
|
||||
getOrganizationBySPRID :(sprid)=>api.get(`/api/Organization/list?sprid=${sprid}`),
|
||||
|
||||
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data),
|
||||
|
||||
assignOrganizationToTenanat:(organizationId)=>api.post(`/api/Organization/assign/tenant/${organizationId}`)
|
||||
};
|
||||
|
||||
export default OrganizationRepository;
|
@ -5,14 +5,25 @@ const ProjectRepository = {
|
||||
getProjectByprojectId: (projetid) =>
|
||||
api.get(`/api/project/details/${projetid}`),
|
||||
|
||||
getProjectAllocation: (projetid) =>
|
||||
api.get(`api/project/allocation/${projetid}`),
|
||||
getProjectAllocation: (projectId, organizationId, serviceId) => {
|
||||
let url = `/api/project/allocation/${projectId}`;
|
||||
|
||||
const params = [];
|
||||
if (organizationId) params.push(`organizationId=${organizationId}`);
|
||||
if (serviceId) params.push(`serviceId=${serviceId}`);
|
||||
|
||||
if (params.length > 0) {
|
||||
url += `?${params.join("&")}`;
|
||||
}
|
||||
|
||||
return api.get(url);
|
||||
},
|
||||
|
||||
|
||||
getEmployeesByProject: (projectId) =>
|
||||
api.get(`/api/Project/employees/get/${projectId}`),
|
||||
|
||||
manageProject: (data) => api.post("/api/project", data),
|
||||
// updateProject: (data) => api.post("/api/project/update", data),
|
||||
|
||||
manageProjectAllocation: (data) => api.post("/api/project/allocation", data),
|
||||
|
||||
@ -32,20 +43,31 @@ const ProjectRepository = {
|
||||
|
||||
getProjectDetails: (id) => api.get(`/api/project/details/${id}`),
|
||||
getProjectInfraByproject: (id) => api.get(`/api/project/infra-details/${id}`),
|
||||
getProjectTasksByWorkArea: (id) => api.get(`/api/project/tasks/${id}`),
|
||||
getProjectTasksByWorkArea: (workAreaId, serviceId) => {
|
||||
let url = `/api/project/tasks/${workAreaId}`;
|
||||
if (serviceId) {
|
||||
url += `?serviceId=${serviceId}`;
|
||||
}
|
||||
|
||||
return api.get(url);
|
||||
},
|
||||
getProjectTasksByEmployee: (id, fromDate, toDate) =>
|
||||
api.get(
|
||||
`/api/project/tasks-employee/${id}?fromDate=${fromDate}&toDate=${toDate}`
|
||||
),
|
||||
|
||||
// Permission Managment for Employee at Project Level
|
||||
|
||||
// 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),
|
||||
getAllProjectLevelPermission: (projectId) => api.get(`/api/Project/get/all/project-level-permission/${projectId}`),
|
||||
|
||||
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),
|
||||
getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`)
|
||||
|
||||
// Services
|
||||
getProjectAssignedServices: (projectId) => api.get(`/api/Project/get/assigned/services/${projectId}`),
|
||||
getProjectAssignedOrganizations: (projectId) => api.get(`/api/Project/get/assigned/organization/${projectId}`)
|
||||
};
|
||||
|
||||
export const TasksRepository = {
|
||||
|
@ -50,7 +50,9 @@ import MainResetPasswordPage from "../pages/authentication/MainResetPasswordPage
|
||||
import TenantPage from "../pages/Tenant/TenantPage";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import CreateTenant from "../pages/Tenant/CreateTenant";
|
||||
import OrganizationPage from "../pages/Organization/OrganizationPage";
|
||||
import LandingPage from "../pages/Home/LandingPage";
|
||||
import TenantSelectionPage from "../pages/authentication/TenantSelectionPage";
|
||||
const router = createBrowserRouter(
|
||||
[
|
||||
{
|
||||
@ -69,6 +71,7 @@ const router = createBrowserRouter(
|
||||
{ path: "/auth/changepassword", element: <ChangePasswordPage /> },
|
||||
],
|
||||
},
|
||||
{ path: "/auth/switch/org", element: <TenantSelectionPage /> },
|
||||
{
|
||||
element: <ProtectedRoute />,
|
||||
errorElement: <ErrorPage />,
|
||||
@ -97,6 +100,7 @@ const router = createBrowserRouter(
|
||||
{ path: "/tenants/new-tenant", element: <CreateTenant /> },
|
||||
{ path: "/tenant/:tenantId", element: <SuperTenantDetails /> },
|
||||
{ path: "/tenant/self", element: <SelfTenantDetails /> },
|
||||
{ path: "/organizations", element: <OrganizationPage /> },
|
||||
{ path: "/help/support", element: <Support /> },
|
||||
{ path: "/help/docs", element: <Documentation /> },
|
||||
{ path: "/help/connect", element: <Connect /> },
|
||||
|
@ -8,7 +8,7 @@ const ProtectedRoute = () => {
|
||||
// // const isAuthenticated = true;
|
||||
// isTokenValid();
|
||||
// return isAuthenticated ? <Outlet /> : <Navigate to="/auth/login" />
|
||||
|
||||
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -66,7 +66,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => {
|
||||
localStorage.setItem("jwtToken", response.data.token);
|
||||
localStorage.setItem("refreshToken", response.data.refreshToken);
|
||||
return true;
|
||||
// api
|
||||
// api
|
||||
// .post("/api/auth/refresh-token", {
|
||||
// token: localStorage.getItem("jwtToken"),
|
||||
// refreshToken: refreshToken,
|
||||
|
@ -1,93 +0,0 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import {clearCacheKey} from '../apiDataManager';
|
||||
|
||||
// Fetch attendance data
|
||||
export const fetchAttendanceData = createAsyncThunk(
|
||||
'attendanceLogs/fetchAttendanceData',
|
||||
async ( {projectId, fromDate, toDate}, thunkAPI ) =>
|
||||
{
|
||||
try {
|
||||
const response = await AttendanceRepository.getAttendanceFilteredByDate(projectId, fromDate, toDate);
|
||||
return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0);
|
||||
} catch (error) {
|
||||
return thunkAPI.rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
export const markAttendance = createAsyncThunk(
|
||||
'attendanceLogs/markAttendance', // Updated action type prefix
|
||||
async ( formData, thunkAPI ) =>
|
||||
|
||||
{
|
||||
try {
|
||||
let newRecordAttendance = {
|
||||
Id: formData.id || null,
|
||||
comment: formData.description,
|
||||
employeeID: formData.employeeId,
|
||||
projectID: formData.projectId,
|
||||
date: new Date().toISOString(),
|
||||
markTime: formData.markTime,
|
||||
latitude: formData.latitude.toString(),
|
||||
longitude: formData.longitude.toString(),
|
||||
action: formData.action,
|
||||
image: null,
|
||||
};
|
||||
|
||||
const response = await AttendanceRepository.markAttendance( newRecordAttendance );
|
||||
return response.data;
|
||||
} catch ( error )
|
||||
{
|
||||
const message = error?.response?.data?.message || error.message || "Error Occured During Api Call";
|
||||
return thunkAPI.rejectWithValue(message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Attendance Logs Slice
|
||||
const attendanceLogsSlice = createSlice({
|
||||
name: 'attendanceLogs', // Updated slice name
|
||||
initialState: {
|
||||
data: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
},
|
||||
reducers: {
|
||||
setAttendanceData: (state, action) => {
|
||||
state.data = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
// Fetch attendance data
|
||||
.addCase(fetchAttendanceData.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(fetchAttendanceData.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.data = action.payload;
|
||||
})
|
||||
.addCase(fetchAttendanceData.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
|
||||
// Mark attendance - log attenace data
|
||||
.addCase(markAttendance.fulfilled, (state, action) => {
|
||||
const updatedRecord = action.payload;
|
||||
const index = state.data.findIndex(item => item.id === updatedRecord.id);
|
||||
|
||||
if (index !== -1) {
|
||||
state.data[index] = { ...state.data[index], ...updatedRecord };
|
||||
} else {
|
||||
state.data.push(updatedRecord);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
export const { setAttendanceData } = attendanceLogsSlice.actions;
|
||||
export default attendanceLogsSlice.reducer;
|
@ -1,38 +0,0 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import {clearCacheKey} from '../apiDataManager';
|
||||
|
||||
export const markCurrentAttendance = createAsyncThunk(
|
||||
'attendanceCurrentDate/markAttendance',
|
||||
async ( formData, {getState, dispatch, rejectWithValue} ) =>
|
||||
{
|
||||
|
||||
const { projectId } = getState().localVariables
|
||||
try
|
||||
{
|
||||
|
||||
// Create the new attendance record
|
||||
const newRecordAttendance = {
|
||||
Id: formData.id || null,
|
||||
comment: formData.description,
|
||||
employeeID: formData.employeeId,
|
||||
projectId: projectId,
|
||||
date: new Date().toISOString(),
|
||||
markTime: formData.markTime,
|
||||
latitude: formData.latitude.toString(),
|
||||
longitude: formData.longitude.toString(),
|
||||
action: formData.action,
|
||||
image: null,
|
||||
};
|
||||
|
||||
const response = await AttendanceRepository.markAttendance(newRecordAttendance);
|
||||
const markedAttendance = response.data
|
||||
clearCacheKey("AttendanceLogs")
|
||||
return markedAttendance;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error marking attendance:', error);
|
||||
return rejectWithValue(error.message); // Reject with error message
|
||||
}
|
||||
}
|
||||
);
|
@ -1,56 +0,0 @@
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import AttendanceRepository from '../../repositories/AttendanceRepository';
|
||||
import { markAttendance } from './attedanceLogsSlice';
|
||||
|
||||
export const fetchEmployeeAttendanceData = createAsyncThunk(
|
||||
'employeeAttendance/fetchEmployeeAttendanceData',
|
||||
async ( {employeeId, fromDate, toDate}, thunkAPI ) =>
|
||||
{
|
||||
try {
|
||||
const response = await AttendanceRepository.getAttendanceByEmployee( employeeId, fromDate, toDate );
|
||||
// return response?.data?.filter((log) => log.checkInTime !== null && log.activity !== 0);
|
||||
return response.data
|
||||
} catch (error) {
|
||||
return thunkAPI.rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const employeeAttendancesSlice = createSlice({
|
||||
name: 'employeeAttendance', // Updated slice name
|
||||
initialState: {
|
||||
data: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
},
|
||||
reducers: {
|
||||
setEmployeeAttendanceData: (state, action) => {
|
||||
state.data = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
// Fetch attendance data
|
||||
.addCase(fetchEmployeeAttendanceData.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(fetchEmployeeAttendanceData.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.data = action.payload;
|
||||
})
|
||||
|
||||
|
||||
.addCase(fetchEmployeeAttendanceData.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
export const { setEmployeeAttendanceData } = employeeAttendancesSlice.actions;
|
||||
export default employeeAttendancesSlice.reducer;
|
@ -3,23 +3,23 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
const globalVariablesSlice = createSlice({
|
||||
name: "globalVariables",
|
||||
initialState: {
|
||||
loginUser:null,
|
||||
currentTenant:null
|
||||
loginUser: null,
|
||||
currentTenant: null,
|
||||
},
|
||||
reducers: {
|
||||
setGlobalVariable: (state, action) => {
|
||||
const { key, value } = action.payload;
|
||||
state[key] = value;
|
||||
},
|
||||
setLoginUserPermmisions: ( state, action ) =>
|
||||
{
|
||||
state.loginUser = action.payload
|
||||
setLoginUserPermmisions: (state, action) => {
|
||||
state.loginUser = action.payload;
|
||||
},
|
||||
setCurrentTenant: (state, action) => {
|
||||
state.currentTenant = action.payload;
|
||||
},
|
||||
setCurrentTenant:(state,action)=>{
|
||||
state.currentTenant = action.payload
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const { setGlobalVariable,setLoginUserPermmisions,setCurrentTenant } = globalVariablesSlice.actions;
|
||||
export const { setGlobalVariable, setLoginUserPermmisions, setCurrentTenant } =
|
||||
globalVariablesSlice.actions;
|
||||
export default globalVariablesSlice.reducer;
|
||||
|
@ -3,37 +3,86 @@ import { createSlice } from "@reduxjs/toolkit";
|
||||
const localVariablesSlice = createSlice({
|
||||
name: "localVariables",
|
||||
initialState: {
|
||||
selectedMaster:"Application Role",
|
||||
regularizationCount:0,
|
||||
defaultDateRange: {
|
||||
selectedMaster: "Application Role",
|
||||
regularizationCount: 0,
|
||||
defaultDateRange: {
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
},
|
||||
projectId: null,
|
||||
reload:false
|
||||
|
||||
reload: false,
|
||||
|
||||
OrganizationModal: {
|
||||
isOpen: false,
|
||||
orgData: null,
|
||||
prevStep: null,
|
||||
startStep: 1,
|
||||
flowType: "default",
|
||||
},
|
||||
|
||||
AuthModal: {
|
||||
isOpen: false,
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
changeMaster: (state, action) => {
|
||||
state.selectedMaster = action.payload;
|
||||
state.selectedMaster = action.payload;
|
||||
},
|
||||
updateRegularizationCount: (state, action) => {
|
||||
state.regularizationCount = action.payload;
|
||||
},
|
||||
setProjectId: (state, action) => {
|
||||
localStorage.setItem("project",null)
|
||||
setProjectId: (state, action) => {
|
||||
localStorage.setItem("project", null);
|
||||
state.projectId = action.payload;
|
||||
localStorage.setItem("project",state.projectId || null)
|
||||
localStorage.setItem("project", state.projectId || null);
|
||||
},
|
||||
refreshData: ( state, action ) =>
|
||||
{
|
||||
state.reload = action.payload
|
||||
refreshData: (state, action) => {
|
||||
state.reload = action.payload;
|
||||
},
|
||||
setDefaultDateRange: (state, action) => {
|
||||
state.defaultDateRange = action.payload;
|
||||
},
|
||||
|
||||
openOrgModal: (state, action) => {
|
||||
state.OrganizationModal.isOpen = true;
|
||||
state.OrganizationModal.orgData = action.payload?.orgData || null;
|
||||
|
||||
if (state.OrganizationModal.startStep) {
|
||||
state.OrganizationModal.prevStep = state.OrganizationModal.startStep;
|
||||
}
|
||||
|
||||
state.OrganizationModal.startStep = action.payload?.startStep || 1;
|
||||
state.OrganizationModal.flowType = action.payload?.flowType || "default";
|
||||
},
|
||||
closeOrgModal: (state) => {
|
||||
state.OrganizationModal.isOpen = false;
|
||||
state.OrganizationModal.orgData = null;
|
||||
state.OrganizationModal.startStep = 1;
|
||||
state.OrganizationModal.prevStep = null;
|
||||
},
|
||||
toggleOrgModal: (state) => {
|
||||
state.OrganizationModal.isOpen = !state.OrganizationModal.isOpen;
|
||||
},
|
||||
|
||||
openAuthModal: (state, action) => {
|
||||
state.AuthModal.isOpen = true;
|
||||
},
|
||||
closeAuthModal: (state, action) => {
|
||||
state.AuthModal.isOpen = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeMaster ,updateRegularizationCount,setProjectId,refreshData,setDefaultDateRange} = localVariablesSlice.actions;
|
||||
export default localVariablesSlice.reducer;
|
||||
export const {
|
||||
changeMaster,
|
||||
updateRegularizationCount,
|
||||
setProjectId,
|
||||
refreshData,
|
||||
setDefaultDateRange,
|
||||
openOrgModal,
|
||||
closeOrgModal,
|
||||
toggleOrgModal,
|
||||
openAuthModal,
|
||||
closeAuthModal,
|
||||
} = localVariablesSlice.actions;
|
||||
export default localVariablesSlice.reducer;
|
@ -2,15 +2,12 @@ import { configureStore } from "@reduxjs/toolkit";
|
||||
import apiCacheReducer from "../slices/apiCacheSlice";
|
||||
import globalVariablesReducer from "../slices/globalVariablesSlice";
|
||||
import localVariableRducer from "../slices/localVariablesSlice"
|
||||
import attendanceReducer from "../slices/apiSlice/attedanceLogsSlice"
|
||||
import employeeAttendanceReducer from "../slices/apiSlice/employeeAttendanceSlice"
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
apiCache: apiCacheReducer,
|
||||
globalVariables: globalVariablesReducer,
|
||||
localVariables:localVariableRducer,
|
||||
attendanceLogs: attendanceReducer,
|
||||
employeeAttendance: employeeAttendanceReducer,
|
||||
|
||||
},
|
||||
});
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { useProfile } from "../hooks/useProfile";
|
||||
|
||||
export const hasUserPermission = (permission) => {
|
||||
const { profile } = useProfile();
|
||||
if (profile) {
|
||||
if (!permission || typeof permission !== "string") {
|
||||
return false;
|
||||
}
|
||||
return profile?.featurePermissions.includes(permission);
|
||||
}
|
||||
return false;
|
||||
};
|
@ -4,7 +4,7 @@ import axiosRetry from "axios-retry";
|
||||
import showToast from "../services/toastService";
|
||||
import { startSignalR, stopSignalR } from "../services/signalRService";
|
||||
import { BASE_URL } from "./constants";
|
||||
const base_Url = BASE_URL
|
||||
const base_Url = BASE_URL;
|
||||
|
||||
export const axiosClient = axios.create({
|
||||
baseURL: base_Url,
|
||||
@ -44,7 +44,10 @@ axiosClient.interceptors.response.use(
|
||||
const originalRequest = error.config;
|
||||
|
||||
// Skip retry for public requests or already retried ones
|
||||
if (!originalRequest && originalRequest._retry || originalRequest.authRequired === false) {
|
||||
if (
|
||||
(!originalRequest && originalRequest._retry) ||
|
||||
originalRequest.authRequired === false
|
||||
) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
@ -53,7 +56,10 @@ axiosClient.interceptors.response.use(
|
||||
originalRequest._toastShown = true;
|
||||
|
||||
if (error.code === "ERR_CONNECTION_REFUSED") {
|
||||
showToast("Unable to connect to the server. Please try again later.", "error");
|
||||
showToast(
|
||||
"Unable to connect to the server. Please try again later.",
|
||||
"error"
|
||||
);
|
||||
} else if (error.code === "ERR_NETWORK") {
|
||||
showToast("Network error. Please check your connection.", "error");
|
||||
redirectToLogin();
|
||||
@ -68,7 +74,10 @@ axiosClient.interceptors.response.use(
|
||||
|
||||
const refreshToken = localStorage.getItem("refreshToken");
|
||||
|
||||
if (!refreshToken || error.response.data?.errors === "Invalid or expired refresh token.") {
|
||||
if (
|
||||
!refreshToken ||
|
||||
error.response.data?.errors === "Invalid or expired refresh token."
|
||||
) {
|
||||
redirectToLogin();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -88,7 +97,7 @@ axiosClient.interceptors.response.use(
|
||||
localStorage.setItem("jwtToken", token);
|
||||
localStorage.setItem("refreshToken", newRefreshToken);
|
||||
|
||||
startSignalR()
|
||||
startSignalR();
|
||||
// Set Authorization header
|
||||
originalRequest.headers["Authorization"] = `Bearer ${token}`;
|
||||
return axiosClient(originalRequest);
|
||||
@ -160,4 +169,4 @@ export const api = {
|
||||
// Redirect helper
|
||||
function redirectToLogin() {
|
||||
window.location.href = "/auth/login";
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user