Compare commits

..

No commits in common. "main" and "Document_Manag" have entirely different histories.

287 changed files with 10765 additions and 14254 deletions

119
package-lock.json generated
View File

@ -809,9 +809,9 @@
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"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==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -1552,13 +1552,13 @@
"peer": true
},
"node_modules/@types/node": {
"version": "24.5.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
"version": "22.13.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.13.tgz",
"integrity": "sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.12.0"
"undici-types": "~6.20.0"
}
},
"node_modules/@types/prop-types": {
@ -1835,10 +1835,9 @@
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"bin": {
"acorn": "bin/acorn"
},
@ -1846,19 +1845,6 @@
"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",
@ -2639,9 +2625,9 @@
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
},
"node_modules/enhanced-resolve": {
"version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -2755,9 +2741,9 @@
}
},
"node_modules/es-module-lexer": {
"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==",
"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==",
"license": "MIT",
"peer": true
},
@ -3152,9 +3138,9 @@
"dev": true
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
"funding": [
{
"type": "github",
@ -5177,9 +5163,9 @@
}
},
"node_modules/schema-utils": {
"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==",
"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==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -5581,28 +5567,24 @@
}
},
"node_modules/tapable": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser": {
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
"version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
@ -5795,9 +5777,9 @@
}
},
"node_modules/undici-types": {
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"license": "MIT",
"peer": true
},
@ -5925,9 +5907,9 @@
}
},
"node_modules/watchpack": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"license": "MIT",
"peer": true,
"dependencies": {
@ -5945,23 +5927,21 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.101.3",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
"version": "5.98.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz",
"integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
"@types/json-schema": "^7.0.15",
"@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.15.0",
"acorn-import-phases": "^1.0.3",
"acorn": "^8.14.0",
"browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.3",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
@ -5971,11 +5951,11 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^4.3.2",
"schema-utils": "^4.3.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.11",
"watchpack": "^2.4.1",
"webpack-sources": "^3.3.3"
"webpack-sources": "^3.2.3"
},
"bin": {
"webpack": "bin/webpack.js"
@ -5994,22 +5974,15 @@
}
},
"node_modules/webpack-sources": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
"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==",
"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",

View File

@ -1,282 +1,8 @@
: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) ;
}
.text-xxs { font-size: 0.55rem; } /* 8px */
.text-xs { font-size: 0.75rem; } /* 12px */
.text-sm { font-size: 0.875rem; } /* 14px */
.text-base { font-size: 1rem; } /* 16px */
.text-lg { font-size: 1.125rem; } /* 18px */
.text-xl { font-size: 1.25rem; } /* 20px */
.text-2xl { font-size: 1.5rem; } /* 24px */
.text-3xl { font-size: 1.875rem; } /* 30px */
.text-4xl { font-size: 2.25rem; } /* 36px */
.text-5xl { font-size: 3rem; } /* 48px */
.text-6xl { font-size: 3.75rem; } /* 60px */
.text-7xl { font-size: 4.5rem; } /* 72px */
.text-8xl { font-size: 6rem; } /* 96px */
.text-9xl { font-size: 8rem; } /* 128px */
/* */
.w-0 { width: 0px; }
.w-px { width: 1px; }
.w-1 { width: 0.25rem; } /* 4px */
.w-2 { width: 0.5rem; } /* 8px */
.w-3 { width: 0.75rem; } /* 12px */
.w-4 { width: 1rem; } /* 16px */
.w-5 { width: 1.25rem; } /* 20px */
.w-6 { width: 1.5rem; } /* 24px */
.w-8 { width: 2rem; } /* 32px */
.w-10 { width: 2.5rem; } /* 40px */
.w-12 { width: 3rem; } /* 48px */
.w-16 { width: 4rem; } /* 64px */
.w-20 { width: 5rem; } /* 80px */
.w-24 { width: 6rem; } /* 96px */
.w-32 { width: 8rem; } /* 128px */
.w-40 { width: 10rem; } /* 160px */
.w-48 { width: 12rem; } /* 192px */
.w-56 { width: 14rem; } /* 224px */
.w-64 { width: 16rem; } /* 256px */
.w-auto { width: auto; }
.w-full { width: 100%; }
.w-screen{ width: 100vw; }
.w-min { width: min-content; }
.w-max { width: max-content; }
.h-0 { height: 0px; }
.h-px { height: 1px; }
.h-1 { height: 0.25rem; } /* 4px */
.h-2 { height: 0.5rem; } /* 8px */
.h-3 { height: 0.75rem; } /* 12px */
.h-4 { height: 1rem; } /* 16px */
.h-5 { height: 1.25rem; } /* 20px */
.h-6 { height: 1.5rem; } /* 24px */
.h-8 { height: 2rem; } /* 32px */
.h-10 { height: 2.5rem; } /* 40px */
.h-12 { height: 3rem; } /* 48px */
.h-16 { height: 4rem; } /* 64px */
.h-20 { height: 5rem; } /* 80px */
.h-24 { height: 6rem; } /* 96px */
.h-32 { height: 8rem; } /* 128px */
.h-40 { height: 10rem; } /* 160px */
.h-48 { height: 12rem; } /* 192px */
.h-56 { height: 14rem; } /* 224px */
.h-64 { height: 16rem; } /* 256px */
.h-auto { height: auto; }
.h-full { height: 100%; }
.h-screen{ height: 100vh; }
.h-min { height: min-content; }
.h-max { height: max-content; }
/* ==========================
Base Font Sizes (mobile first)
========================== */
.text-xxs { font-size: 0.55rem; } /* 8px */
.text-xs { font-size: 0.75rem; } /* 12px */
.text-sm { font-size: 0.875rem; } /* 14px */
.text-base{ font-size: 1rem; } /* 16px */
.text-lg { font-size: 1.125rem; } /* 18px */
.text-xl { font-size: 1.25rem; } /* 20px */
.text-2xl { font-size: 1.5rem; } /* 24px */
.text-3xl { font-size: 1.875rem; } /* 30px */
.text-4xl { font-size: 2.25rem; } /* 36px */
.text-5xl { font-size: 3rem; } /* 48px */
.text-6xl { font-size: 3.75rem; } /* 60px */
.text-7xl { font-size: 4.5rem; } /* 72px */
.text-8xl { font-size: 6rem; } /* 96px */
.text-9xl { font-size: 8rem; } /* 128px */
/* ==========================
Base Heights
========================== */
.h-0 { height: 0; }
.h-px { height: 1px; }
.h-1 { height: 0.25rem; } /* 4px */
.h-2 { height: 0.5rem; } /* 8px */
.h-3 { height: 0.75rem; } /* 12px */
.h-4 { height: 1rem; } /* 16px */
.h-5 { height: 1.25rem; } /* 20px */
.h-6 { height: 1.5rem; } /* 24px */
.h-8 { height: 2rem; } /* 32px */
.h-10 { height: 2.5rem; } /* 40px */
.h-12 { height: 3rem; } /* 48px */
.h-16 { height: 4rem; } /* 64px */
.h-20 { height: 5rem; } /* 80px */
.h-24 { height: 6rem; } /* 96px */
.h-32 { height: 8rem; } /* 128px */
.h-40 { height: 10rem; } /* 160px */
.h-48 { height: 12rem; } /* 192px */
.h-56 { height: 14rem; } /* 224px */
.h-64 { height: 16rem; } /* 256px */
.h-full { height: 100%; }
.h-screen{ height: 100vh; }
/* ==========================
Base Widths
========================== */
.w-0 { width: 0; }
.w-px { width: 1px; }
.w-1 { width: 0.25rem; }
.w-2 { width: 0.5rem; }
.w-3 { width: 0.75rem; }
.w-4 { width: 1rem; }
.w-5 { width: 1.25rem; }
.w-6 { width: 1.5rem; }
.w-8 { width: 2rem; }
.w-10 { width: 2.5rem; }
.w-12 { width: 3rem; }
.w-16 { width: 4rem; }
.w-20 { width: 5rem; }
.w-24 { width: 6rem; }
.w-32 { width: 8rem; }
.w-40 { width: 10rem; }
.w-48 { width: 12rem; }
.w-56 { width: 14rem; }
.w-64 { width: 16rem; }
.w-full { width: 100%; }
.w-screen{ width: 100vw; }
/* ==========================
Responsive Variants
========================== */
@media (min-width: 576px) { /* sm */
/* Font */
.text-xxs-sm { font-size: 0.55rem; }
.text-xs-sm { font-size: 0.75rem; }
.text-sm-sm { font-size: 0.875rem; }
.text-base-sm{ font-size: 1rem; }
.text-lg-sm { font-size: 1.125rem; }
.text-xl-sm { font-size: 1.25rem; }
.text-2xl-sm{ font-size: 1.5rem; }
/* Height */
.h-1-sm{ height: 0.25rem; }
.h-2-sm{ height: 0.5rem; }
.h-3-sm{ height: 0.75rem; }
.h-4-sm{ height: 1rem; }
.h-5-sm{ height: 1.25rem; }
.h-6-sm{ height: 1.5rem; }
.h-8-sm{ height: 2rem; }
.h-10-sm{ height: 2.5rem; }
/* Width */
.w-1-sm{ width: 0.25rem; }
.w-2-sm{ width: 0.5rem; }
.w-3-sm{ width: 0.75rem; }
.w-4-sm{ width: 1rem; }
.w-5-sm{ width: 1.25rem; }
.w-6-sm{ width: 1.5rem; }
.w-8-sm{ width: 2rem; }
.w-10-sm{ width: 2.5rem; }
}
@media (min-width: 768px) { /* md */
/* Font */
.text-xxs-md { font-size: 0.55rem; }
.text-xs-md { font-size: 0.75rem; }
.text-sm-md { font-size: 0.875rem; }
.text-base-md{ font-size: 1rem; }
.text-lg-md { font-size: 1.125rem; }
.text-xl-md { font-size: 1.25rem; }
.text-2xl-md{ font-size: 1.5rem; }
/* Height */
.h-1-md{ height: 0.25rem; }
.h-2-md{ height: 0.5rem; }
.h-3-md{ height: 0.75rem; }
.h-4-md{ height: 1rem; }
.h-5-md{ height: 1.25rem; }
.h-6-md{ height: 1.5rem; }
.h-8-md{ height: 2rem; }
.h-10-md{ height: 2.5rem; }
/* Width */
.w-1-md{ width: 0.25rem; }
.w-2-md{ width: 0.5rem; }
.w-3-md{ width: 0.75rem; }
.w-4-md{ width: 1rem; }
.w-5-md{ width: 1.25rem; }
.w-6-md{ width: 1.5rem; }
.w-8-md{ width: 2rem; }
.w-10-md{ width: 2.5rem; }
}
@media (min-width: 992px) { /* lg */
/* Font */
.text-xxs-lg { font-size: 0.55rem; }
.text-xs-lg { font-size: 0.75rem; }
.text-sm-lg { font-size: 0.875rem; }
.text-base-lg{ font-size: 1rem; }
.text-lg-lg { font-size: 1.125rem; }
.text-xl-lg { font-size: 1.25rem; }
.text-2xl-lg{ font-size: 1.5rem; }
/* Height */
.h-1-lg{ height: 0.25rem; }
.h-2-lg{ height: 0.5rem; }
.h-3-lg{ height: 0.75rem; }
.h-4-lg{ height: 1rem; }
.h-5-lg{ height: 1.25rem; }
.h-6-lg{ height: 1.5rem; }
.h-8-lg{ height: 2rem; }
.h-10-lg{ height: 2.5rem; }
/* Width */
.w-1-lg{ width: 0.25rem; }
.w-2-lg{ width: 0.5rem; }
.w-3-lg{ width: 0.75rem; }
.w-4-lg{ width: 1rem; }
.w-5-lg{ width: 1.25rem; }
.w-6-lg{ width: 1.5rem; }
.w-8-lg{ width: 2rem; }
.w-10-lg{ width: 2.5rem; }
}
@media (min-width: 1200px) { /* xl */
/* Font */
.text-xxs-xl { font-size: 0.55rem; }
.text-xs-xl { font-size: 0.75rem; }
.text-sm-xl { font-size: 0.875rem; }
.text-base-xl{ font-size: 1rem; }
.text-lg-xl { font-size: 1.125rem; }
.text-xl-xl { font-size: 1.25rem; }
.text-2xl-xl{ font-size: 1.5rem; }
/* Height */
.h-1-xl{ height: 0.25rem; }
.h-2-xl{ height: 0.5rem; }
.h-3-xl{ height: 0.75rem; }
.h-4-xl{ height: 1rem; }
.h-5-xl{ height: 1.25rem; }
.h-6-xl{ height: 1.5rem; }
.h-8-xl{ height: 2rem; }
.h-10-xl{ height: 2.5rem; }
/* Width */
.w-1-xl{ width: 0.25rem; }
.w-2-xl{ width: 0.5rem; }
.w-3-xl{ width: 0.75rem; }
.w-4-xl{ width: 1rem; }
.w-5-xl{ width: 1.25rem; }
.w-6-xl{ width: 1.5rem; }
.w-8-xl{ width: 2rem; }
.w-10-xl{ width: 2.5rem; }
}

View File

@ -1,5 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -18613,10 +18613,6 @@ li:not(:first-child) .dropdown-item,
min-height: 70vh !important;
}
.modal-min-h{
min-height: 60vh !important;
}
.flex-fill {
flex: 1 1 auto !important;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 411 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 787 KiB

After

Width:  |  Height:  |  Size: 787 KiB

View File

Before

Width:  |  Height:  |  Size: 786 KiB

After

Width:  |  Height:  |  Size: 786 KiB

View File

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 237 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 401 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 860 B

After

Width:  |  Height:  |  Size: 860 B

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 860 KiB

After

Width:  |  Height:  |  Size: 860 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

View File

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 308 KiB

View File

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 165 KiB

View File

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View File

Before

Width:  |  Height:  |  Size: 278 KiB

After

Width:  |  Height:  |  Size: 278 KiB

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -1 +0,0 @@
<svg idth="64" height="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path opacity="0.2" fill-rule="evenodd" clip-rule="evenodd" d="M0 142.1L0 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-240c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32l0 240c0 17.7 14.3 32 32 32s32-14.3 32-32l0-337.9c0-27.5-17.6-52-43.8-60.7L303.2 5.1c-9.9-3.3-20.5-3.3-30.4 0L43.8 81.4C17.6 90.1 0 114.6 0 142.1zM464 256l-352 0 0 64 352 0 0-64zM112 416l352 0 0-64-352 0 0 64zm352 32l-352 0 0 64 352 0 0-64z"/></svg>

Before

Width:  |  Height:  |  Size: 500 B

View File

@ -4,7 +4,6 @@ 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";
@ -12,7 +11,6 @@ const App = () => {
return (
<div className="app">
<QueryClientProvider client={queryClient}>
<ModalProvider/>
<DireProvider>
<AppRoutes />
</DireProvider>

112
src/ModalContext.jsx Normal file
View File

@ -0,0 +1,112 @@
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",
};

View File

@ -1,22 +0,0 @@
import React, { useEffect } from "react";
import { useOrganizationModal } from "./hooks/useOrganization";
import OrganizationModal from "./components/Organization/OrganizationModal";
import { useAuthModal, useModal } from "./hooks/useAuth";
import SwitchTenant from "./pages/authentication/SwitchTenant";
import ChangePasswordPage from "./pages/authentication/ChangePassword";
const ModalProvider = () => {
const { isOpen, onClose } = useOrganizationModal();
const { isOpen: isAuthOpen } = useAuthModal();
const {isOpen:isChangePass} = useModal("ChangePassword")
return (
<>
{isOpen && <OrganizationModal />}
{isAuthOpen && <SwitchTenant />}
{isChangePass && <ChangePasswordPage /> }
</>
);
};
export default ModalProvider;

View File

@ -12,19 +12,22 @@ import { useQueryClient } from "@tanstack/react-query";
import eventBus from "../../services/eventBus";
import { useSelectedProject } from "../../slices/apiDataManager";
const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizationId, }) => {
const Attendance = ({ getRole, handleModalData, searchTerm }) => {
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, organizationId);
} = useAttendance(selectedProject);
const filteredAttendance = ShowPending
? attendance?.filter(
(att) => att?.checkInTime !== null && att?.checkOutTime === null
@ -59,11 +62,12 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
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
@ -112,7 +116,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
<>
<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>
@ -138,7 +142,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
<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
@ -187,8 +190,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
</td>
<td>{item.jobRoleName}</td>
<td>{item.organizationName || "--"}</td>
<td>
{item.checkInTime
? convertShortTime(item.checkInTime)
@ -212,11 +213,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
))}
{!attendance && (
<tr>
<td
colSpan={7}
className="text-center text-secondary"
style={{ height: "200px" }}
>
<td colSpan={6} className="text-center text-secondary" style={{ height: "200px" }}>
No employees assigned to the project!
</td>
</tr>
@ -224,7 +221,6 @@ const Attendance = ({ getRole, handleModalData, searchTerm, projectId, organizat
</tbody>
</table>
{!loading && finalFilteredData.length > ITEMS_PER_PAGE && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1">

View File

@ -4,12 +4,9 @@ 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 { clearCacheKey, getCachedData, useSelectedProject } from "../../slices/apiDataManager";
import eventBus from "../../services/eventBus";
import AttendanceRepository from "../../repositories/AttendanceRepository";
import { useAttendancesLogs } from "../../hooks/useAttendance";
@ -37,142 +34,195 @@ const usePagination = (data, itemsPerPage) => {
};
};
const AttendanceLog = ({ handleModalData, searchTerm, organizationId }) => {
const selectedProject = useSelectedProject();
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const [showPending, setShowPending] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const AttendanceLog = ({ handleModalData, searchTerm }) => {
// const selectedProject = useSelector(
// (store) => store.localVariables.projectId
// );
const selectedProject = useSelectedProject();
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const [showPending, setShowPending] = useState(false)
const today = new Date();
today.setHours(0, 0, 0, 0);
const [isRefreshing, setIsRefreshing] = useState(false);
const [processedData, setProcessedData] = useState([]);
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const today = new Date();
today.setHours(0, 0, 0, 0);
const isSameDay = (dateStr) => {
if (!dateStr) return false;
const d = new Date(dateStr);
d.setHours(0, 0, 0, 0);
return d.getTime() === today.getTime();
};
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const isBeforeToday = (dateStr) => {
if (!dateStr) return false;
const d = new Date(dateStr);
d.setHours(0, 0, 0, 0);
return d.getTime() < today.getTime();
};
const isSameDay = (dateStr) => {
if (!dateStr) return false;
const d = new Date(dateStr);
d.setHours(0, 0, 0, 0);
return d.getTime() === today.getTime();
};
const sortByName = (a, b) => {
const nameA = (a.firstName + a.lastName).toLowerCase();
const nameB = (b.firstName + b.lastName).toLowerCase();
return nameA.localeCompare(nameB);
};
const isBeforeToday = (dateStr) => {
if (!dateStr) return false;
const d = new Date(dateStr);
d.setHours(0, 0, 0, 0);
return d.getTime() < today.getTime();
};
const { data = [], isLoading, error, refetch, isFetching } = useAttendancesLogs(
selectedProject,
dateRange.startDate,
dateRange.endDate,
organizationId
);
const sortByName = (a, b) => {
const nameA = a.firstName.toLowerCase() + a.lastName.toLowerCase();
const nameB = b.firstName.toLowerCase() + b.lastName.toLowerCase();
return nameA?.localeCompare(nameB);
};
const processedData = useMemo(() => {
const filteredData = showPending
? data.filter((item) => item.checkOutTime === null)
: data;
const group1 = filteredData.filter((d) => d.activity === 1 && isSameDay(d.checkInTime)).sort(sortByName);
const group2 = filteredData.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime)).sort(sortByName);
const group3 = filteredData.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime)).sort(sortByName);
const group4 = filteredData.filter((d) => d.activity === 4 && isBeforeToday(d.checkOutTime));
const group5 = filteredData.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime)).sort(sortByName);
const group6 = filteredData.filter((d) => d.activity === 5).sort(sortByName);
const sortedList = [...group1, ...group2, ...group3, ...group4, ...group5, ...group6];
const groupedByDate = sortedList.reduce((acc, item) => {
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
if (date) {
acc[date] = acc[date] || [];
acc[date].push(item);
}
return acc;
}, {});
const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
return sortedDates.flatMap((date) => groupedByDate[date]);
}, [data, showPending]);
const filteredSearchData = useMemo(() => {
if (!searchTerm) return processedData;
const lowercased = searchTerm.toLowerCase();
return processedData.filter((item) =>
`${item.firstName} ${item.lastName}`.toLowerCase().includes(lowercased)
const {
data = [],
isLoading,
error,
refetch,
isFetching,
} = useAttendancesLogs(
selectedProject,
dateRange.startDate,
dateRange.endDate
);
}, [processedData, searchTerm]);
const filtering = (data) => {
const filteredData = showPending
? data.filter((item) => item.checkOutTime === null)
: data;
const {
currentPage,
totalPages,
currentItems: paginatedAttendances,
paginate,
resetPage,
} = usePagination(filteredSearchData, 20);
const group1 = filteredData
.filter((d) => d.activity === 1 && isSameDay(d.checkInTime))
.sort(sortByName);
const group2 = filteredData
.filter((d) => d.activity === 4 && isSameDay(d.checkOutTime))
.sort(sortByName);
const group3 = filteredData
.filter((d) => d.activity === 1 && isBeforeToday(d.checkInTime))
.sort(sortByName);
const group4 = filteredData.filter(
(d) => d.activity === 4 && isBeforeToday(d.checkOutTime)
);
const group5 = filteredData
.filter((d) => d.activity === 2 && isBeforeToday(d.checkOutTime))
.sort(sortByName);
const group6 = filteredData
.filter((d) => d.activity === 5)
.sort(sortByName);
useEffect(() => {
resetPage();
}, [filteredSearchData]);
const sortedList = [
...group1,
...group2,
...group3,
...group4,
...group5,
...group6,
];
const handler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
const checkIn = msg.response.checkInTime.substring(0, 10);
// Group by date
const groupedByDate = sortedList.reduce((acc, item) => {
const date = (item.checkInTime || item.checkOutTime)?.split("T")[0];
if (date) {
acc[date] = acc[date] || [];
acc[date].push(item);
}
return acc;
}, {});
if (selectedProject === msg.projectId && startDate <= checkIn && checkIn <= endDate) {
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
if (!oldData) {
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
return;
}
return oldData.map((record) =>
record.id === msg.response.id ? { ...record, ...msg.response } : record
);
});
resetPage();
const sortedDates = Object.keys(groupedByDate).sort(
(a, b) => new Date(b) - new Date(a)
);
const finalData = sortedDates.flatMap((date) => groupedByDate[date]);
setProcessedData(finalData);
};
useEffect(() => {
filtering(data);
}, [data, showPending]);
// New useEffect to handle search filtering
const filteredSearchData = useMemo(() => {
if (!searchTerm) {
return processedData;
}
},
[selectedProject, dateRange, resetPage]
);
const lowercasedSearchTerm = searchTerm.toLowerCase();
return processedData.filter((item) => {
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [processedData, searchTerm]);
useEffect(() => {
eventBus.on("attendance_log", handler);
return () => eventBus.off("attendance_log", handler);
}, [handler]);
const {
currentPage,
totalPages,
currentItems: paginatedAttendances,
paginate,
resetPage,
} = usePagination(filteredSearchData, 20);
const employeeHandler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
if (data.some((item) => item.employeeId == msg.employeeId)) {
refetch();
}
},
[data, refetch]
);
useEffect(() => {
resetPage();
}, [filteredSearchData, resetPage]);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
const handler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
const checkIn = msg.response.checkInTime.substring(0, 10);
if (
selectedProject === msg.projectId &&
startDate <= checkIn &&
checkIn <= endDate
) {
queryClient.setQueriesData(["attendanceLogs"], (oldData) => {
if (!oldData) {
queryClient.invalidateQueries({ queryKey: ["attendanceLogs"] });
return;
}
const updatedAttendance = oldData.map((record) =>
record.id === msg.response.id
? { ...record, ...msg.response }
: record
);
filtering(updatedAttendance);
return updatedAttendance;
});
resetPage();
}
},
[selectedProject, dateRange, filtering, resetPage]
);
useEffect(() => {
eventBus.on("attendance_log", handler);
return () => eventBus.off("attendance_log", handler);
}, [handler]);
const employeeHandler = useCallback(
(msg) => {
const { startDate, endDate } = dateRange;
if (data.some((item) => item.employeeId == msg.employeeId)) {
// dispatch(
// fetchAttendanceData({
// ,
// fromDate: startDate,
// toDate: endDate,
// })
// );
refetch()
}
},
[selectedProject, dateRange, data, refetch]
);
useEffect(() => {
eventBus.on("employee", employeeHandler);
return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]);
return (
<>
<div
className="dataTables_length text-start py-2 d-flex justify-content-between "
className="dataTables_length text-start py-2 d-flex justify-content-between"
id="DataTables_Table_0_length"
>
<div className="d-flex align-items-center my-0 ">
@ -180,7 +230,7 @@ useEffect(() => {
onRangeChange={setDateRange}
defaultStartDate={yesterday}
/>
<div className="form-check form-switch text-start ms-1 ms-md-2 align-items-center mb-0">
<div className="form-check form-switch text-start m-0 ms-5">
<input
type="checkbox"
className="form-check-input"
@ -193,16 +243,18 @@ useEffect(() => {
<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" }}
>
<div className="table-responsive text-nowrap" style={{ minHeight: "200px" }}>
{isLoading ? (
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }}
>
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
<p className="text-secondary">Loading...</p>
</div>
) : filteredSearchData?.length > 0 ? (
@ -213,7 +265,6 @@ useEffect(() => {
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
@ -232,9 +283,9 @@ useEffect(() => {
const previousAttendance = arr[index - 1];
const previousDate = previousAttendance
? moment(
previousAttendance.checkInTime ||
previousAttendance.checkOutTime
).format("YYYY-MM-DD")
previousAttendance.checkInTime ||
previousAttendance.checkOutTime
).format("YYYY-MM-DD")
: null;
if (!previousDate || currentDate !== previousDate) {
@ -243,7 +294,7 @@ useEffect(() => {
key={`header-${currentDate}`}
className="table-row-header"
>
<td colSpan={8} className="text-start">
<td colSpan={6} className="text-start">
<strong>
{moment(currentDate).format("DD-MM-YYYY")}
</strong>
@ -273,7 +324,6 @@ useEffect(() => {
attendance.checkInTime || attendance.checkOutTime
).format("DD-MMM-YYYY")}
</td>
<td>{attendance.organizationName || "--"}</td>
<td>{convertShortTime(attendance.checkInTime)}</td>
<td>
{attendance.checkOutTime
@ -295,12 +345,7 @@ useEffect(() => {
</tbody>
</table>
) : (
<div className="my-12">
<span className="text-secondary">
No data available for the selected date range. Please Select
another date.
</span>
</div>
<div className="my-4"><span className="text-secondary">No Record Available !</span></div>
)}
</div>
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && (
@ -326,9 +371,8 @@ useEffect(() => {
(pageNumber) => (
<li
key={pageNumber}
className={`page-item ${
currentPage === pageNumber ? "active" : ""
}`}
className={`page-item ${currentPage === pageNumber ? "active" : ""
}`}
>
<button
className="page-link"
@ -340,9 +384,8 @@ useEffect(() => {
)
)}
<li
className={`page-item ${
currentPage === totalPages ? "disabled" : ""
}`}
className={`page-item ${currentPage === totalPages ? "disabled" : ""
}`}
>
<button
className="page-link"

View File

@ -5,6 +5,7 @@ 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";
@ -33,7 +34,7 @@ const createSchema = (modeldata) => {
const checkOut = new Date(checkIn);
checkOut.setHours(hour, minute, 0, 0);
return checkOut >= checkIn;
return checkOut > checkIn;
}
return true;
}, {
@ -96,12 +97,12 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
};
return (
<form className="row p-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 d-flex justify-content-center mb-4">
<label className="fs-5 tex-semibold text-center">
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 d-flex justify-content-center">
<label className="fs-5 text-dark text-center">
{modeldata?.checkInTime && !modeldata?.checkOutTime
? "Check-Out "
: "Check-In "}
? "Check-out :"
: "Check-in :"}
</label>
</div>
@ -120,7 +121,7 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
modeldata?.checkInTime && !modeldata?.checkOutTime
? formatDate(modeldata?.checkInTime?.split("T")[0]) || ""
: formatDate(today)
}
}
disabled
/>
</div>

View File

@ -1,3 +1,4 @@
import React, { useState, useEffect } from "react";
import "../../components/Project/ProjectInfra.css";
import BuildingModel from "../Project/Infrastructure/BuildingModel";
@ -7,75 +8,64 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
import TaskModel from "../Project/Infrastructure/TaskModel";
import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb";
import {
useCurrentService,
useProjectDetails,
useProjectInfra,
useProjects,
} from "../../hooks/useProjects";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import {
APPROVE_TASK,
ASSIGN_REPORT_TASK,
MANAGE_PROJECT_INFRA,
} from "../../utils/constants";
import { useDispatch, useSelector } from "react-redux";
import { useProfile } from "../../hooks/useProfile";
import { refreshData, setProjectId } from "../../slices/localVariablesSlice";
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {APPROVE_TASK, ASSIGN_REPORT_TASK, MANAGE_PROJECT_INFRA} from "../../utils/constants";
import {useDispatch, useSelector} from "react-redux";
import {useProfile} from "../../hooks/useProfile";
import {refreshData, setProjectId} from "../../slices/localVariablesSlice";
import InfraTable from "../Project/Infrastructure/InfraTable";
import { useSelectedProject } from "../../slices/apiDataManager";
import Loader from "../common/Loader";
const InfraPlanning = () => {
const { profile: LoggedUser, refetch: fetchData } = useProfile();
const dispatch = useDispatch();
const InfraPlanning = () =>
{
const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch()
// const selectedProject = useSelector((store)=>store.localVariables.projectId)
const selectedProject = useSelectedProject();
const selectedService = useCurrentService();
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
const { projectInfra, isLoading, isError, error, isFetched } =
useProjectInfra(selectedProject, selectedService || "" );
const canManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const canApproveTask = useHasUserPermission(APPROVE_TASK);
const canReportTask = useHasUserPermission(ASSIGN_REPORT_TASK);
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK)
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK)
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
const reloadedData = useSelector((store) => store.localVariables.reload);
// useEffect( () =>
// {
// if (reloadedData)
// {
// refetch()
// dispatch( refreshData( false ) )
// }
const hasAccess = canManageInfra || canApproveTask || canReportTask;
if (isError) {
return <div>{error?.response?.data?.message || error?.message}</div>;
}
if (!hasAccess && !isLoading) {
return (
<div className="text-center">
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
<p>Access Denied: You don't have permission to perform this action.</p>
</div>
);
}
if (isLoading) {
return <Loader />;
}
if (isFetched && (!projectInfra || projectInfra.length === 0)) {
return (
<div className="text-center">
<p className="my-3">No Result Found</p>
</div>
);
}
// },[reloadedData])
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} />
{(ApprovedTaskRights || ReportTaskRights) ? (
<div className="align-items-center">
<div className="row ">
{isLoading && (<Loader/> )}
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
</div>
</div>
) : (
<div className="text-center">
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
<p>Access Denied: You don't have permission to perform this action. !</p>
</div>
)}
</div>
</div>
</div>
);
};

View File

@ -1,43 +1,25 @@
import React, { useCallback, useEffect, useState, useMemo } from "react";
import Avatar from "../common/Avatar";
import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils";
import { convertShortTime } from "../../utils/dateUtils";
import RegularizationActions from "./RegularizationActions";
import { useSelector } from "react-redux";
import { useRegularizationRequests } from "../../hooks/useAttendance";
import moment from "moment";
import usePagination from "../../hooks/usePagination";
import eventBus from "../../services/eventBus";
import {
cacheData,
clearCacheKey,
useSelectedProject,
} from "../../slices/apiDataManager";
import { cacheData, clearCacheKey, useSelectedProject } from "../../slices/apiDataManager";
import { useQueryClient } from "@tanstack/react-query";
import Pagination from "../../components/common/Pagination";
const Regularization = ({
handleRequest,
searchTerm,
projectId,
organizationId,
IncludeInActive,
}) => {
const Regularization = ({ handleRequest, searchTerm }) => {
const queryClient = useQueryClient();
// var selectedProject = useSelector((store) => store.localVariables.projectId);
const selectedProject = useSelectedProject();
const [regularizesList, setregularizedList] = useState([]);
const { regularizes, loading, error, refetch } = useRegularizationRequests(
selectedProject,
organizationId,
IncludeInActive
);
const { regularizes, loading, error, refetch } =
useRegularizationRequests(selectedProject);
useEffect(() => {
if(!regularizes) return
if(regularizes?.length) {
setregularizedList(regularizes);
}
}, [regularizes]);
const sortByName = (a, b) => {
@ -72,15 +54,18 @@ const Regularization = ({
}
const lowercasedSearchTerm = searchTerm.toLowerCase();
return sortedList.filter((item) => {
const fullName = `${item?.firstName} ${item?.lastName}`.toLowerCase();
const fullName = `${item.firstName} ${item.lastName}`.toLowerCase();
return fullName.includes(lowercasedSearchTerm);
});
}, [regularizesList, searchTerm]);
const { currentPage, totalPages, currentItems, paginate } = usePagination(
filteredSearchData,
20
);
const { currentPage, totalPages, currentItems, paginate } =
usePagination(filteredSearchData, 20);
// Reset pagination when the search term or data changes
useEffect(() => {
}, [filteredSearchData]);
useEffect(() => {
eventBus.on("regularization", handler);
@ -102,15 +87,9 @@ const Regularization = ({
}, [employeeHandler]);
return (
<div
className="table-responsive text-nowrap pb-4"
style={{ minHeight: "200px" }}
>
<div className="table-responsive text-nowrap pb-4" style={{ minHeight: "200px" }}>
{loading ? (
<div
className="d-flex justify-content-center align-items-center"
style={{ height: "200px" }}
>
<div className="d-flex justify-content-center align-items-center" style={{ height: "200px" }}>
<p className="text-secondary">Loading...</p>
</div>
) : currentItems?.length > 0 ? (
@ -119,28 +98,24 @@ const Regularization = ({
<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>
<th>
<i className="bx bxs-up-arrow-alt text-danger"></i>Check-Out
</th>
<th colSpan={2}>
Requested By
</th>
<th >
Requested At
</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{currentItems?.map((att, index) => (
<tr key={index}>
<td colSpan={2}>
<td colSpan={2}>
<div className="d-flex justify-content-start align-items-center">
<Avatar firstName={att.firstName} lastName={att.lastName} />
<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">
@ -151,28 +126,9 @@ const Regularization = ({
</div>
</td>
<td>{moment(att.checkOutTime).format("DD-MMM-YYYY")}</td>
<td>{att.organizationName || "--"}</td>
<td>{convertShortTime(att.checkInTime)}</td>
<td>
{att.requestedAt ? convertShortTime(att.checkOutTime) : "--"}
</td>
<td colSpan={2}>
{att.requestedBy ? ( <div className="d-flex justify-content-start align-items-center">
<Avatar firstName={att?.requestedBy?.firstName} lastName={att?.requestedBy?.lastName} />
<div className="d-flex flex-column">
<a href="#" className="text-heading text-truncate">
<span className="fw-normal">
{att?.requestedBy?.firstName} {att?.requestedBy?.lastName}
</span>
</a>
</div>
</div>):(<small>--</small>)}
</td>
<td>
{att?.requestedAt ? formatUTCToLocalTime(att.requestedAt,true) : "--"}
{att.checkOutTime ? convertShortTime(att.checkOutTime) : "--"}
</td>
<td className="text-center ">
<RegularizationActions
@ -180,6 +136,7 @@ const Regularization = ({
handleRequest={handleRequest}
refresh={refetch}
/>
{/* </div> */}
</td>
</tr>
))}
@ -197,7 +154,7 @@ const Regularization = ({
</span>
</div>
)}
{/* {!loading && totalPages > 1 && (
{!loading && totalPages > 1 && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1 mt-3">
<li className={`page-item ${currentPage === 1 ? "disabled" : ""}`}>
@ -235,14 +192,6 @@ const Regularization = ({
</li>
</ul>
</nav>
)} */}
{totalPages > 0 && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={paginate}
/>
)}
</div>
);

View File

@ -1,7 +1,9 @@
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';

View File

@ -77,7 +77,7 @@ export const ReportTask = ({ report, closeModal }) => {
return (
<div className="container m-0">
<div className="text-center">
<p className="fs-5 fw-semibold">Report Task</p>
<p className="fs-6 fw-semibold">Report Task</p>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-text-input" className="col-md-4 col-form-label">
@ -101,14 +101,16 @@ export const ReportTask = ({ report, closeModal }) => {
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
Wrok Area :
</label>
<div className="col-md-8 text-start">
<div className="text-wrap">
{report?.workItem?.workArea?.floor?.building?.name} <i className="bx bx-chevron-right"></i>
{report?.workItem?.workArea?.floor?.floorName} <i className="bx bx-chevron-right"></i>
{report?.workItem?.workArea?.areaName}
</div>
</div>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{" "}
{report?.workItem?.workArea?.floor?.building?.name}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{report?.workItem?.workArea?.floor?.floorName}{" "}
<i className="bx bx-chevron-right"> </i>
{report?.workItem?.workArea?.areaName}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">

View File

@ -110,8 +110,6 @@ const ReportTaskComments = ({
approvedTask: defaultCompletedTask || 0,
});
}, [defaultCompletedTask]);
const completed_Task = watch("approvedTask")
return (
<div className="p-2 p-sm-1">
<div className="modal-body p-sm-4 p-0">
@ -341,13 +339,13 @@ const ReportTaskComments = ({
<div
className={` ${
actionAllow && !commentsData.approvedBy
? " d-flex justify-content-between align-items-center"
? " d-flex justify-content-between"
: "text-end"
} mt-2`}
>
<div
className={`form-check ${
!(actionAllow && !commentsData.approvedBy && defaultCompletedTask > completed_Task ) && "d-none"
!(actionAllow && !commentsData.approvedBy) && "d-none"
} `}
>
<input
@ -385,7 +383,7 @@ const ReportTaskComments = ({
: "Comment"}
</button>
</span>
</div>
</div>
</form>
<ul

View File

@ -4,7 +4,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { string, z } from "zod";
import {
useActivitiesMaster,
useServices,
useWorkCategoriesMaster,
} from "../../hooks/masterHook/useMaster";
import showToast from "../../services/toastService";
@ -26,8 +25,6 @@ const SubTask = ({ activity, onClose }) => {
const { activities, loading } = useActivitiesMaster();
const { categories, categoryLoading } = useWorkCategoriesMaster();
const { Task, loading: TaskLoading } = useTaskById(activity?.id);
const {data,isError,isLoading,error} = useServices();
const {
register,
handleSubmit,
@ -100,8 +97,8 @@ const SubTask = ({ activity, onClose }) => {
};
return (
<div className="container-xxl my-1">
<p className="fw-semibold fs-5">Create Sub Task</p>
<form className="row g-2 text-start" onSubmit={handleSubmit(onSubmitForm)}>
<p className="fw-semibold">Create Sub Task</p>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
<div className="col-6">
<label className="form-label">Building</label>
<input
@ -131,15 +128,27 @@ const SubTask = ({ activity, onClose }) => {
disabled
/>
</div>
<div className="col-12">
<label className="form-label">Service</label>
<input
type="text"
className="form-control form-control-sm"
value={activity?.workItem?.activityMaster?.activityGroup?.service?.name || ""}
disabled
/>
</div>
<div className="col-12">
<label className="form-label">Work Category</label>
<select
className="form-select form-select-sm"
{...register("workCategoryId")}
onChange={handleCategoryChange}
>
<option value="">
{categoryLoading ? "Loading..." : "-- Select Category --"}
</option>
{categoryData.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.workCategoryId && (
<div className="danger-text">{errors.workCategoryId.message}</div>
)}
</div>
<div className="col-12">
<label className="form-label">Select Activity</label>
<select
@ -163,27 +172,6 @@ const SubTask = ({ activity, onClose }) => {
)}
</div>
<div className="col-12">
<label className="form-label">Work Category</label>
<select
className="form-select form-select-sm"
{...register("workCategoryId")}
onChange={handleCategoryChange}
>
<option value="">
{categoryLoading ? "Loading..." : "-- Select Category --"}
</option>
{categoryData.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
{errors.workCategoryId && (
<div className="danger-text">{errors.workCategoryId.message}</div>
)}
</div>
<div className="col-4">
<label className="form-label">Planned Work</label>
<input
@ -231,15 +219,7 @@ const SubTask = ({ activity, onClose }) => {
)}
</div>
<div className="d-flex flex-row gap-3 justify-content-end py-2">
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={() => onClose()}
disabled={isPending}
>
Cancel
</button>
<div className="col-12 text-center">
<button
type="submit"
className="btn btn-sm btn-primary me-2"
@ -247,7 +227,14 @@ const SubTask = ({ activity, onClose }) => {
>
{isPending ? "Please wait..." : "Submit"}
</button>
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={() => onClose()}
disabled={isPending}
>
Cancel
</button>
</div>
</form>
</div>

View File

@ -0,0 +1,42 @@
import React, { createContext, useState, useContext } from "react";
import ChangePasswordPage from "../../pages/authentication/ChangePassword";
const ChangePasswordContext = createContext();
export const ChangePasswordProvider = ({ children }) => {
const [isChangePasswordOpen, setIsChangePasswordOpen] = useState(false);
const openChangePassword = () => setIsChangePasswordOpen(true);
const closeChangePassword = () => setIsChangePasswordOpen(false);
return (
<ChangePasswordContext.Provider
value={{ isChangePasswordOpen, openChangePassword, closeChangePassword }}
>
{children}
{isChangePasswordOpen && (
<>
{/* This is the main Bootstrap modal container */}
{/* It provides the fixed positioning and high z-index */}
<div
className="modal fade show" // 'fade' for animation, 'show' to make it visible
style={{ display: 'block' }} // Explicitly set display: block for immediate visibility
tabIndex="-1" // Makes the modal focusable
role="dialog" // ARIA role for accessibility
aria-labelledby="changePasswordModalLabel" // Link to a heading for accessibility
aria-modal="true" // Indicate it's a modal dialog
>
{/* The ChangePasswordPage component itself contains the modal-dialog and modal-content */}
<ChangePasswordPage onClose={closeChangePassword} />
</div>
{/* The modal backdrop */}
<div className="modal-backdrop fade show"></div>
</>
)}
</ChangePasswordContext.Provider>
);
};
export const useChangePassword = () => useContext(ChangePasswordContext);

View File

@ -1,107 +0,0 @@
import React, { useState } from "react";
import { useCurrentService } from "../../hooks/useProjects";
import { useSelectedProject } from "../../slices/apiDataManager";
import { FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
TaskReportDefaultValue,
TaskReportFilterSchema,
} from "./TaskRportScheam";
import { DateRangePicker1 } from "../common/DateRangePicker";
import SelectMultiple from "../common/SelectMultiple";
import { localToUtc } from "../../utils/appUtils";
import { useTaskFilter } from "../../hooks/useTasks";
const TaskReportFilterPanel = ({ handleFilter }) => {
const [resetKey, setResetKey] = useState(0);
const selectedProject = useSelectedProject();
const selectedService = useCurrentService();
const { data } = useTaskFilter(selectedProject);
const methods = useForm({
resolver: zodResolver(TaskReportFilterSchema),
defaultValues: TaskReportDefaultValue,
});
const {
register,
reset,
handleSubmit,
formState: { errors },
} = methods;
const onSubmit = (formData) => {
const filterPayload = {
...formData,
dateFrom: localToUtc(formData.dateFrom),
dateTo: localToUtc(formData.dateTo),
};
handleFilter(filterPayload);
};
const onClear = () => {
setResetKey((prev) => prev + 1);
handleFilter(TaskReportDefaultValue);
reset(TaskReportDefaultValue);
};
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
<div className="mb-3 w-100">
<label className="fw-semibold">Choose Date Range:</label>
<DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="dateFrom"
endField="dateTo"
resetSignal={resetKey}
defaultRange={false}
maxDate={new Date()}
/>
</div>
<div className="row mb-2">
<SelectMultiple
name="buildingIds"
label="Building"
options={data?.buildings}
labelKey="name"
valueKey="id"
/>
</div>
<div className="row mb-2">
<SelectMultiple
name="floorIds"
label="Floor"
options={data?.floors}
labelKey="name"
valueKey="id"
/>
</div>
<div className="row mb-2">
<SelectMultiple
name="activityIds"
label="Activities"
options={data?.activities}
labelKey="name"
valueKey="id"
/>
</div>
<div className="d-flex justify-content-end py-3 gap-2">
<button
type="button"
className="btn btn-label-secondary btn-sm"
onClick={onClear}
>
Clear
</button>
<button type="submit" className="btn btn-primary btn-sm">
Apply
</button>
</div>
</form>
</FormProvider>
);
};
export default TaskReportFilterPanel;

View File

@ -1,301 +0,0 @@
import React, { useState, useEffect, useMemo } from "react";
import { useTaskList } from "../../hooks/useTasks";
import { useSelectedProject } from "../../slices/apiDataManager";
import { useProjectName } from "../../hooks/useProjects";
import DailyProgrssReport, {
useDailyProgrssContext,
} from "../../pages/DailyProgressReport/DailyProgrssReport";
import { useDispatch } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice";
import {
APPROVE_TASK,
ASSIGN_REPORT_TASK,
ITEMS_PER_PAGE,
} from "../../utils/constants";
import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../common/Pagination";
import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton";
import HoverPopup from "../common/HoverPopup";
const TaskReportList = () => {
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
const dispatch = useDispatch();
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
const { service, openModal, closeModal,filter } = useDailyProgrssContext();
const selectedProject = useSelectedProject();
const { projectNames } = useProjectName();
const { data, isLoading, isError, error } = useTaskList(
selectedProject,
ITEMS_PER_PAGE,
currentPage,
service,filter
);
const ProgrssReportColumn = [
{
key: "activity",
label: "Activity",
getValue: (task) => task.workItem.activityMaster?.activityName || "N/A",
align: "text-start",
},
{
key: "assigned",
label: "Total Assigned",
getValue: (task) => task.plannedTask ?? "N/A",
align: "text-start",
},
{
key: "completed",
label: "Completed",
getValue: (task) => task.completedTask ?? "N/A",
align: "text-start",
},
{
key: "assignAt",
label: "Assign Date",
getValue: (task) =>
task.assignmentDate ? formatUTCToLocalTime(task.assignmentDate) : "N/A",
align: "text-start",
},
{
key: "team",
label: "Team",
getValue: (task) =>
task.teamMembers?.map((m) => `${m.firstName} ${m.lastName}`).join(", ") ||
"N/A",
align: "text-start",
},
];
const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page);
}
};
useEffect(() => {
if (!selectedProject && projectNames.length > 0) {
dispatch(setProjectId(projectNames[0].id));
}
}, [selectedProject, projectNames, dispatch]);
useEffect(() => {
setFilters({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
}, [selectedProject]);
// Filter and Group wise data
const filteredTasks = useMemo(() => {
if (!data?.data) return [];
return data?.data.filter((task) => {
const { selectedBuilding, selectedFloors, selectedActivities } = filters;
if (
selectedBuilding &&
task?.workItem?.workArea?.floor?.building?.name !== selectedBuilding
)
return false;
if (
selectedFloors.length > 0 &&
!selectedFloors.includes(task?.workItem?.workArea?.floor?.floorName)
)
return false;
if (
selectedActivities.length > 0 &&
!selectedActivities.includes(
task?.workItem?.activityMaster?.activityName
)
)
return false;
return true;
});
}, [data?.data, filters, currentPage]);
const groupedTasks = useMemo(() => {
const groups = {};
filteredTasks.forEach((task) => {
const date = task.assignmentDate.split("T")[0];
if (!groups[date]) groups[date] = [];
groups[date].push(task);
});
return Object.keys(groups)
.sort((a, b) => new Date(b) - new Date(a))
.map((date) => ({ date, tasks: groups[date] }));
}, [filteredTasks, paginate, currentPage, selectedProject]);
const renderTeamMembers = (task, refIndex) => (
<div
key={refIndex}
tabIndex="0"
className="d-flex align-items-center avatar-group justify-content-center"
data-bs-toggle="popover"
data-bs-trigger="focus"
data-bs-placement="left"
data-bs-html="true"
data-bs-content={`
<div class="border border-secondary rounded custom-popover p-2 px-3">
${task.teamMembers
.map(
(m) => `
<div class="d-flex align-items-center gap-2 mb-2">
<div class="avatar avatar-xs">
<span class="avatar-initial rounded-circle bg-label-primary">
${m?.firstName?.charAt(0) || ""}${m?.lastName?.charAt(0) || ""
}
</span>
</div>
<span>${m.firstName} ${m.lastName}</span>
</div>`
)
.join("")}
</div>
`}
>
{task.teamMembers.slice(0, 3).map((m) => (
<div
key={m.id}
className="avatar avatar-xs"
title={`${m.firstName} ${m.lastName}`}
>
<span className="avatar-initial rounded-circle bg-label-primary">
{m?.firstName.slice(0, 1)}
</span>
</div>
))}
{task.teamMembers.length > 3 && (
<div
className="avatar avatar-xs"
title={`${task.teamMembers.length - 3} more`}
>
<span className="avatar-initial rounded-circle bg-label-secondary">
+{task.teamMembers.length - 3}
</span>
</div>
)}
</div>
);
if (isLoading) return <TaskReportListSkeleton />;
if (isError) return <div>Loading....</div>;
return (
<div className="mt-2 table-responsive text-nowrap">
<table className="table">
<thead>
<tr>
<th className="text-start">Activity</th>
<th>
<span>
Total Pending{" "}
<HoverPopup
title="Total Pending Task"
content={<p>This shows the total pending tasks for each activity on that date.</p>}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>
<span>
Reported/Planned{" "}
<HoverPopup
title="Reported and Planned Task"
content={<p>This shows the reported versus planned tasks for each activity on that date.</p>}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>Assign Date</th>
<th>Team</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
{groupedTasks.length === 0 && (
<tr>
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
No reports available
</td>
</tr>
)}
{groupedTasks.map(({ date, tasks }) => (
<React.Fragment key={date}>
<tr className="table-row-header text-start">
<td colSpan={6}>
<strong>{formatUTCToLocalTime(date)}</strong>
</td>
</tr>
{tasks.map((task, idx) => (
<tr key={task.id || idx}>
<td className="flex-wrap text-start">
<div>
{task.workItem.activityMaster?.activityName || "No Activity Name"}
</div>
<div className="text-sm py-2">
{task.workItem.workArea?.floor?.building?.name} {" "}
{task.workItem.workArea?.floor?.floorName} {" "}
{task.workItem.workArea?.areaName}
</div>
</td>
<td>
{formatNumber(task.workItem.plannedWork)}
</td>
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
<td className="text-center">{renderTeamMembers(task, idx)}</td>
<td className="text-center">
<div className="d-flex justify-content-end gap-2">
{ReportTaskRights && !task.reportedDate && (
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
Report
</button>
)}
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
<button
className="btn btn-xs btn-warning"
onClick={() => openModal("comments", { task, isActionAllow: true })}
>
QC
</button>
)}
<button
className="btn btn-xs btn-primary"
onClick={() => openModal("comments", { task, isActionAllow: false })}
>
Comment
</button>
</div>
</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)}
</div>
);
};
export default TaskReportList;

View File

@ -1,62 +0,0 @@
const SkeletonLine = ({ height = 20, width = "100%", className = "" }) => (
<div
className={`skeleton mb-2 ${className}`}
style={{
height,
width,
}}
></div>
);
export const TaskReportListSkeleton = () => {
const skeletonRows = 8; // Number of placeholder rows
return (
<div>
<table className="table">
<thead>
<tr>
<th>Activity</th>
<th>Assigned</th>
<th>Completed</th>
<th>Assign On</th>
<th>Team</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{[...Array(skeletonRows)].map((_, idx) => (
<tr key={idx}>
<td>
<SkeletonLine height={16} width="70%" />
<SkeletonLine height={12} width="50%" />
</td>
<td>
<SkeletonLine height={16} width="60%" />
</td>
<td>
<SkeletonLine height={16} width="60%" />
</td>
<td>
<SkeletonLine height={16} width="80%" />
</td>
<td className="text-center">
<div className="d-flex justify-content-center gap-1">
{[...Array(3)].map((_, i) => (
<SkeletonLine key={i} height={24} width={24} className="rounded-circle" />
))}
</div>
</td>
<td>
<div className="d-flex justify-content-end gap-2">
<SkeletonLine height={24} width="60px" />
<SkeletonLine height={24} width="60px" />
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

View File

@ -1,17 +0,0 @@
import { z } from "zod";
export const TaskReportFilterSchema = z.object({
buildingIds: z.array(z.string()).optional(),
floorIds: z.array(z.string()).optional(),
activityIds: z.array(z.string()).optional(),
dateFrom: z.string().optional(),
dateTo: z.string().optional(),
});
export const TaskReportDefaultValue = {
buildingIds:[],
floorIds:[],
activityIds:[],
dateFrom:null,
dateTo:null
}

View File

@ -1,194 +1,194 @@
// import React, { useState, useEffect } from "react";
// import LineChart from "../Charts/LineChart";
// import { useProjects } from "../../hooks/useProjects";
// import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
// import ApexChart from "../Charts/Circlechart";
import React, { useState, useEffect } from "react";
import LineChart from "../Charts/LineChart";
import { useProjects } from "../../hooks/useProjects";
import { useDashboard_ActivityData } from "../../hooks/useDashboard_Data";
import ApexChart from "../Charts/Circlechart";
// const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
const LOCAL_STORAGE_PROJECT_KEY = "selectedActivityProjectId";
// const Activity = () => {
// const { projects } = useProjects();
// const today = new Date().toISOString().split("T")[0]; // Format: 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("all");
const Activity = () => {
const { projects } = useProjects();
const today = new Date().toISOString().split("T")[0]; // Format: 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("all");
// const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
// useDashboard_ActivityData(selectedDate, selectedProjectId);
const { dashboard_Activitydata: ActivityData, isLoading, error: isError } =
useDashboard_ActivityData(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");
// }
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);
// }, [selectedProjectId, projects]);
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
}, [selectedProjectId, projects]);
// const handleProjectSelect = (projectId) => {
// setSelectedProjectId(projectId);
// };
const handleProjectSelect = (projectId) => {
setSelectedProjectId(projectId);
};
// const handleDateChange = (e) => {
// setSelectedDate(e.target.value);
// };
const handleDateChange = (e) => {
setSelectedDate(e.target.value);
};
// return (
// <div className="card h-100">
// <div className="card-header">
// <div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
// <div className="card-title mb-0 text-start">
// <h5 className="mb-1">Activity</h5>
// <p className="card-subtitle text-primary">Activity Progress Chart</p>
// </div>
return (
<div className="card h-100">
<div className="card-header">
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0">
<div className="card-title mb-0 text-start">
<h5 className="mb-1">Activity</h5>
<p className="card-subtitle">Activity Progress Chart</p>
</div>
// <div className="btn-group">
// <button
// className="btn btn-outline-primary btn-sm dropdown-toggle"
// type="button"
// data-bs-toggle="dropdown"
// aria-expanded="false"
// >
// {displayedProjectName}
// </button>
// <ul className="dropdown-menu">
// <li>
// <button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
// All Projects
// </button>
// </li>
// {projects?.map((project) => (
// <li key={project.id}>
// <button
// className="dropdown-item"
// onClick={() => handleProjectSelect(project.id)}
// >
// {project.name}
// </button>
// </li>
// ))}
// </ul>
// </div>
// </div>
// </div>
<div className="btn-group">
<button
className="btn btn-outline-primary btn-sm dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{displayedProjectName}
</button>
<ul className="dropdown-menu">
<li>
<button className="dropdown-item" onClick={() => handleProjectSelect("all")}>
All Projects
</button>
</li>
{projects?.map((project) => (
<li key={project.id}>
<button
className="dropdown-item"
onClick={() => handleProjectSelect(project.id)}
>
{project.name}
</button>
</li>
))}
</ul>
</div>
</div>
</div>
// {/* Date Picker Aligned Left with Padding */}
// <div className="d-flex justify-content-start ps-3 mb-3">
// <div style={{ width: "150px" }}>
// <input
// type="date"
// className="form-control"
// value={selectedDate}
// onChange={handleDateChange}
// />
// </div>
// </div>
{/* ✅ Date Picker Aligned Left with Padding */}
<div className="d-flex justify-content-start ps-3 mb-3">
<div style={{ width: "150px" }}>
<input
type="date"
className="form-control"
value={selectedDate}
onChange={handleDateChange}
/>
</div>
</div>
// {/* Tabs */}
// <ul className="nav nav-tabs " role="tablist">
// <li className="nav-item">
// <button
// type="button"
// className={`nav-link ${activeTab === "all" ? "active" : ""}`}
// onClick={() => setActiveTab("all")}
// data-bs-toggle="tab"
// >
// Summary
// </button>
// </li>
// <li className="nav-item">
// <button
// type="button"
// className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
// onClick={() => setActiveTab("logs")}
// data-bs-toggle="tab"
// >
// Details
// </button>
// </li>
// </ul>
{/* Tabs */}
<ul className="nav nav-tabs " role="tablist">
<li className="nav-item">
<button
type="button"
className={`nav-link ${activeTab === "all" ? "active" : ""}`}
onClick={() => setActiveTab("all")}
data-bs-toggle="tab"
>
Summary
</button>
</li>
<li className="nav-item">
<button
type="button"
className={`nav-link ${activeTab === "logs" ? "active" : ""}`}
onClick={() => setActiveTab("logs")}
data-bs-toggle="tab"
>
Details
</button>
</li>
</ul>
// <div className="card-body">
// {activeTab === "all" && (
// <div className="row justify-content-between">
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
// {isLoading ? (
// <p>Loading activity data...</p>
// ) : isError ? (
// <p>No data available.</p>
// ) : (
// ActivityData && (
// <>
// <h5 className="fw-bold mb-0 text-start w-80">
// <i className="bx bx-task text-info"></i> Allocated Task
// </h5>
// <h4 className="mb-0 fw-bold">
// {ActivityData.totalCompletedWork?.toLocaleString()}/
// {ActivityData.totalPlannedWork?.toLocaleString()}
// </h4>
// <small className="text-muted">Completed / Assigned</small>
// <div style={{ maxWidth: "180px" }}>
// <ApexChart />
// </div>
// </>
// )
// )}
// </div>
<div className="card-body">
{activeTab === "all" && (
<div className="row justify-content-between">
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
{isLoading ? (
<p>Loading activity data...</p>
) : isError ? (
<p>No data available.</p>
) : (
ActivityData && (
<>
<h5 className="fw-bold mb-0 text-start w-80">
<i className="bx bx-task text-info"></i> Allocated Task
</h5>
<h4 className="mb-0 fw-bold">
{ActivityData.totalCompletedWork?.toLocaleString()}/
{ActivityData.totalPlannedWork?.toLocaleString()}
</h4>
<small className="text-muted">Completed / Assigned</small>
<div style={{ maxWidth: "180px" }}>
<ApexChart />
</div>
</>
)
)}
</div>
// <div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
// {!isLoading && !isError && ActivityData && (
// <>
// <h5 className="fw-bold mb-0 text-start w-110">
// <i className="bx bx-task text-info"></i> Activities
// </h5>
// <h4 className="mb-0 fw-bold">
// {ActivityData.totalCompletedWork?.toLocaleString()}/
// {ActivityData.totalPlannedWork?.toLocaleString()}
// </h4>
// <small className="text-muted ">Pending / Assigned</small>
// <div style={{ maxWidth: "180px" }}>
// <ApexChart />
// </div>
// </>
// )}
// </div>
// </div>
// )}
<div className="col-md-6 d-flex flex-column align-items-center text-center mb-4">
{!isLoading && !isError && ActivityData && (
<>
<h5 className="fw-bold mb-0 text-start w-110">
<i className="bx bx-task text-info"></i> Activities
</h5>
<h4 className="mb-0 fw-bold">
{ActivityData.totalCompletedWork?.toLocaleString()}/
{ActivityData.totalPlannedWork?.toLocaleString()}
</h4>
<small className="text-muted ">Pending / Assigned</small>
<div style={{ maxWidth: "180px" }}>
<ApexChart />
</div>
</>
)}
</div>
</div>
)}
// {activeTab === "logs" && (
// <div className="table-responsive">
// <table className="table table-bordered table-hover">
// <thead>
// <tr>
// <th>Activity / Location</th>
// <th>Assigned / Completed</th>
// </tr>
// </thead>
// <tbody>
// {[{
// activity: "Code Review / Remote",
// assignedToday: 3,
// completed: 2
// }].map((log, index) => (
// <tr key={index}>
// <td>{log.activity}</td>
// <td>{log.assignedToday} / {log.completed}</td>
// </tr>
// ))}
// </tbody>
// </table>
// </div>
// )}
// </div>
// </div>
// );
// };
{activeTab === "logs" && (
<div className="table-responsive">
<table className="table table-bordered table-hover">
<thead>
<tr>
<th>Activity / Location</th>
<th>Assigned / Completed</th>
</tr>
</thead>
<tbody>
{[{
activity: "Code Review / Remote",
assignedToday: 3,
completed: 2
}].map((log, index) => (
<tr key={index}>
<td>{log.activity}</td>
<td>{log.assignedToday} / {log.completed}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);
};
// export default Activity;
export default Activity;

View File

@ -1,16 +1,21 @@
import React, { useState, useMemo } from "react";
import ApexChart from "../Charts/Circle";
import React, { useState, useEffect } from "react";
import LineChart from "../Charts/LineChart";
import { useProjects } from "../../hooks/useProjects";
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
import { useSelectedProject } from "../../hooks/useSelectedProject"; // your custom hook
import ApexChart from "../Charts/Circle";
const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId";
const Attendance = () => {
const { projects } = useProjects();
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD
const [selectedDate, setSelectedDate] = useState(today);
// central project selection hook
const selectedProjectId = useSelectedProject()
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");
const {
dashboard_Attendancedata: AttendanceData,
@ -18,24 +23,38 @@ const selectedProjectId = useSelectedProject()
error: isError,
} = useDashboard_AttendanceData(selectedDate, 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";
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);
}, [selectedProjectId, projects]);
const handleProjectSelect = (projectId) => {
setSelectedProjectId(projectId);
};
const handleDateChange = (e) => {
setSelectedDate(e.target.value);
};
return (
<div className="card h-100">
{/* Header */}
<div className="card-header mb-1 pb-0">
<div className="d-flex flex-wrap justify-content-between align-items-center">
<div className="card-header mb-1 pb-0 ">
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 pb-0 ">
<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"
@ -49,7 +68,7 @@ const selectedProjectId = useSelectedProject()
<li>
<button
className="dropdown-item"
onClick={() => setSelectedProjectId("all")}
onClick={() => handleProjectSelect("all")}
>
All Projects
</button>
@ -58,7 +77,7 @@ const selectedProjectId = useSelectedProject()
<li key={project.id}>
<button
className="dropdown-item"
onClick={() => setSelectedProjectId(project.id)}
onClick={() => handleProjectSelect(project.id)}
>
{project.name}
</button>
@ -69,43 +88,52 @@ const selectedProjectId = useSelectedProject()
</div>
</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 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>
</div>
</div>
{/* Body */}
<div className="card-body">
{/* Summary */}
{AttendanceData?.activeTab === "Summary" && (
{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 ? (
@ -115,7 +143,7 @@ const selectedProjectId = useSelectedProject()
) : (
AttendanceData && (
<>
<h5 className="fw-bold mb-0">
<h5 className="fw-bold mb-0 text-center w-100">
<i className="bx bx-task text-info"></i> Attendance
</h5>
<h4 className="mb-0 fw-bold">
@ -136,9 +164,11 @@ const selectedProjectId = useSelectedProject()
</div>
)}
{/* Details */}
{AttendanceData?.activeTab === "Details" && (
<div className="table-responsive" style={{ maxHeight: "300px" }}>
{activeTab === "Details" && (
<div
className="table-responsive"
style={{ maxHeight: "300px", overflowY: "auto" }}
>
<table className="table table-hover mb-0 text-start">
<thead>
<tr>
@ -148,17 +178,32 @@ const selectedProjectId = useSelectedProject()
</tr>
</thead>
<tbody>
{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>
{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>
</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>

View File

@ -99,7 +99,9 @@ const AttendanceOverview = () => {
};
return (
<div className="bg-white p-4 rounded shadow d-flex flex-column">
<div
className="bg-white p-4 rounded shadow d-flex flex-column"
>
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start">
@ -117,22 +119,18 @@ const AttendanceOverview = () => {
<option value={30}>Last 30 Days</option>
</select>
<button
className={`btn btn-sm p-1 ${
view === "chart" ? "btn-primary" : "btn-outline-primary"
}`}
className={`btn btn-sm ${view === "chart" ? "btn-primary" : "btn-outline-primary"}`}
onClick={() => setView("chart")}
title="Chart View"
>
<i className="bx bx-bar-chart-alt-2"></i>
</button>
<button
className={`btn btn-sm p-1 ${
view === "table" ? "btn-primary" : "btn-outline-primary"
}`}
className={`btn btn-sm ${view === "table" ? "btn-primary" : "btn-outline-primary"}`}
onClick={() => setView("table")}
title="Table View"
>
<i className="bx bx-list-ul fs-5"></i>
<i className="bx bx-task text-success"></i>
</button>
</div>
</div>

View File

@ -1,28 +1,33 @@
import React, { useEffect } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus";
import GlobalRepository from "../../repositories/GlobalRepository";
const Projects = () => {
const {
data: projectsCardData,
isLoading,
isError,
error,
refetch,
} = useDashboardProjectsCardData();
const { projectsCardData } = useDashboardProjectsCardData();
const [projectData, setProjectsData] = useState(projectsCardData);
useEffect(() => {
// When "project" event happens, just refetch
const handler = () => {
refetch();
};
setProjectsData(projectsCardData);
}, [projectsCardData]);
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);
}, [refetch]);
const totalProjects = projectsCardData?.totalProjects ?? 0;
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
}, [handler]);
return (
<div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -32,29 +37,20 @@ const Projects = () => {
Projects
</h5>
</div>
{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 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>
</div>
) : isError ? (
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{error?.message || "Error loading data"}
<div>
<h4 className="mb-0 fw-bold">
{projectData.ongoingProjects?.toLocaleString()}
</h4>
<small className="text-muted">Ongoing</small>
</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>
</div>
);
};

View File

@ -1,16 +1,10 @@
import React from "react";
import { useSelectedProject } from "../../slices/apiDataManager";
import { useSelector } from "react-redux";
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
const TasksCard = () => {
const projectId = useSelectedProject();
const {
data: tasksCardData,
isLoading,
isError,
error,
} = useDashboardTasksCardData(projectId);
const projectId = useSelector((store) => store.localVariables?.projectId);
const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId);
return (
<div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -20,30 +14,28 @@ const TasksCard = () => {
</h5>
</div>
{isLoading ? (
// Loader while fetching
{loading ? (
// Loader will be displayed when loading is true
<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>
) : isError ? (
// Show error
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{error?.message || "Error loading data"}
</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>
) : (
// Show data
// Actual data when loaded successfully
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">
{tasksCardData?.totalTasks?.toLocaleString() ?? 0}
{tasksCardData?.totalTasks?.toLocaleString()}
</h4>
<small className="text-muted">Total</small>
</div>
<div>
<h4 className="mb-0 fw-bold">
{tasksCardData?.completedTasks?.toLocaleString() ?? 0}
{tasksCardData?.completedTasks?.toLocaleString()}
</h4>
<small className="text-muted">Completed</small>
</div>
@ -53,4 +45,4 @@ const TasksCard = () => {
);
};
export default TasksCard;
export default TasksCard;

View File

@ -1,45 +1,33 @@
import React, { useCallback, useEffect } from "react";
import React, { useCallback, useEffect, useState } 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 queryClient = useQueryClient();
const projectId = useSelectedProject()
const projectId = useSelector((store) => store.localVariables?.projectId);
const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId);
const {
data: teamsCardData,
isLoading,
isError,
error,
} = useDashboardTeamsCardData(projectId);
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]);
// Handle real-time updates via eventBus
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]
);
const handler = useCallback((msg) => {
if (msg.activity === 1) {
setInToday((prev) => prev + 1);
}
}, []);
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">
@ -48,17 +36,18 @@ const Teams = () => {
</h5>
</div>
{isLoading ? (
{loading ? (
// Blue spinner loader
<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>
) : isError ? (
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{error?.message || "Error loading data"}
</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>
) : (
// 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>
@ -74,4 +63,4 @@ const Teams = () => {
);
};
export default Teams;
export default Teams;

View File

@ -1,68 +0,0 @@
import { useState, useEffect } from "react";
import EmployeeList from "./EmployeeList";
import { useAllEmployees } from "../../hooks/useEmployees";
import showToast from "../../services/toastService";
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
import { useAssignEmpToBucket } from "../../hooks/useDirectory";
const AssignedBucket = ({ selectedBucket, handleClose }) => {
const { employeesList } = useAllEmployees(false);
const [selectedEmployees, setSelectedEmployees] = useState([]);
useEffect(() => {
if (selectedBucket) {
const preselected = employeesList
.filter((emp) => selectedBucket?.employeeIds?.includes(emp.employeeId))
.map((emp) => ({ ...emp, isActive: true }));
setSelectedEmployees(preselected);
}
}, [selectedBucket, employeesList]);
const { mutate: AssignEmployee, isPending } = useAssignEmpToBucket(() =>
handleClose()
);
const handleSubmit = async (e) => {
e.preventDefault();
const existingEmployeeIds = selectedBucket?.employeeIds || [];
const employeesToUpdate = selectedEmployees.filter((emp) => {
const isExisting = existingEmployeeIds.includes(emp.employeeId);
return (!isExisting && emp.isActive) || (isExisting && !emp.isActive);
});
if (employeesToUpdate.length === 0) {
showToast("No changes to update", "info");
return;
}
AssignEmployee({
bucketId: selectedBucket.id,
EmployeePayload: employeesToUpdate.map((emp) => ({
employeeId: emp.employeeId,
isActive: emp.isActive,
})),
});
};
return (
<form onSubmit={handleSubmit}>
<EmployeeList
employees={employeesList}
bucket={selectedBucket}
selectedEmployees={selectedEmployees}
onChange={setSelectedEmployees}
/>
<div className="mt-3 d-flex justify-content-end gap-3">
<button type="submit" className="btn btn-sm btn-primary" disabled={isPending}>
{isPending ? "Please Wait...":"Assign"}
</button>
</div>
</form>
);
};
export default AssignedBucket;

View File

@ -1,95 +0,0 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { bucketScheam } from "./DirectorySchema";
import { zodResolver } from "@hookform/resolvers/zod";
import Label from "../common/Label";
const BucketForm = ({ selectedBucket, mode, onSubmit, onCancel, isPending }) => {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(bucketScheam),
defaultValues: selectedBucket || { name: "", description: "" },
});
useEffect(() => {
reset(selectedBucket || { name: "", description: "" });
}, [selectedBucket, reset]);
const isEditMode = mode === "edit";
const isCreateMode = mode === "create";
return (
<div className="row">
<div className="d-flex justify-content-between align-items-center">
<i
className="bx bx-left-arrow-alt bx-md cursor-pointer"
onClick={onCancel}
></i>
{/* Show edit toggle only for existing bucket in edit mode */}
{/* {isEditMode && (
<i className="bx bx-edit bx-sm text-primary cursor-pointer"></i>
)} */}
</div>
{(isCreateMode || isEditMode) ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3 mt-5">
<Label htmlFor="Name" className="text-start" required>
Name
</Label>
<input
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
</div>
<div className="mb-3">
<Label htmlFor="description" className="text-start" required>
Description
</Label>
<textarea
className="form-control form-control-sm"
{...register("description")}
rows="3"
/>
{errors.description && (
<small className="danger-text">{errors.description.message}</small>
)}
</div>
<div className="mt-4 mb-3 d-flex gap-3 justify-content-end">
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onCancel}
disabled={isPending}
>
Cancel
</button>
<button type="submit" className="btn btn-sm btn-primary" disabled={isPending}>
{isPending ? "Please Wait " : isEditMode ? "Update" : "Create"}
</button>
</div>
</form>
) : (
<dl className="row text-start my-2">
<dt className="col-sm-2">Name</dt>
<dd className="col-sm-10">{selectedBucket?.name || "-"}</dd>
<dt className="col-sm-2">Description</dt>
<dd className="col-sm-10">{selectedBucket?.description || "-"}</dd>
</dl>
)}
</div>
);
};
export default BucketForm;

View File

@ -1,53 +0,0 @@
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useProfile } from "../../hooks/useProfile";
import { DIRECTORY_ADMIN, DIRECTORY_MANAGER } from "../../utils/constants";
const BucketList = ({ buckets, loading, searchTerm, onEdit, onDelete }) => {
const { profile } = useProfile();
const IsDirecrory_Admin = useHasUserPermission(DIRECTORY_ADMIN);
const IsDirectory_Manager = useHasUserPermission(DIRECTORY_MANAGER);
const sorted = buckets.filter((bucket) =>
bucket.name.toLowerCase().includes(searchTerm.toLowerCase())
);
if (loading) return <div>Loading...</div>;
if (!loading && sorted.length === 0) return <div>No buckets found</div>;
return (
<div className="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 pt-3 px-2 px-sm-0">
{sorted.map((bucket) => (
<div className="col" key={bucket.id}>
<div className="card h-100">
<div className="card-body p-4">
<h6 className="card-title d-flex justify-content-between">
<span>{bucket.name}</span>
{(IsDirecrory_Admin ||
IsDirectory_Manager ||
bucket?.createdBy?.id === profile?.employeeInfo?.id) && (
<div className="d-flex gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => onEdit(bucket)}
/>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => onDelete(bucket?.id)}
/>
</div>
)}
</h6>
<h6 className="card-subtitle mb-2 text-muted">
Contacts: {bucket.numberOfContacts || 0}
</h6>
<p className="card-text">
{bucket.description || "No description"}
</p>
</div>
</div>
</div>
))}
</div>
);
};
export default BucketList;

View File

@ -1,217 +0,0 @@
import React, { useState } from "react";
import Avatar from "../common/Avatar";
import { getBucketNameById } from "./DirectoryUtils";
import { useActiveInActiveContact, useBuckets } from "../../hooks/useDirectory";
import { getPhoneIcon } from "./DirectoryUtils";
import { useDir } from "../../Context/DireContext";
import { useDirectoryContext } from "../../pages/Directory/DirectoryPage";
import ConfirmModal from "../common/ConfirmModal";
const CardViewContact = ({
IsActive,
contact,
setSelectedContact,
setIsOpenModal,
setOpen_contact,
setIsOpenModalNote,
IsDeleted,
restore,
}) => {
const { data, setManageContact, setContactOpen } = useDirectoryContext();
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { mutate: ActiveInActive, isPending } = useActiveInActiveContact();
const handleActiveInactive = (contactId) => {
ActiveInActive({ contactId, contactStatus: !IsActive });
};
return (
<>
<ConfirmModal
type="delete"
header="Delete Contact"
message="Are you sure you want delete?"
onSubmit={handleActiveInactive}
onClose={() => setIsDeleteModalOpen(false)}
loading={isPending}
paramData={contact.id}
isOpen={IsDeleteModalOpen}
/>
<div
className="card text-start border-1"
style={{ background: `${!IsActive ? "#f8f6f6" : ""}` }}
>
<div className="card-body px-1 py-2 pb-0">
<div className="d-flex justify-content-between">
<div
className={`d-flex align-items-center ${
IsActive && "cursor-pointer"
}`}
onClick={() => {
if (IsActive) {
setContactOpen({ contact: contact, Open: true });
}
}}
>
<Avatar
size="xs"
firstName={
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
}
lastName={
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
}
/>{" "}
<span className="text-heading fs-6"> {contact?.name}</span>
</div>
<div>
{IsActive && (
<div className="dropdown z-2">
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i
className="bx bx-dots-vertical-rounded text-muted p-0"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
<li
onClick={() =>
setManageContact({
isOpen: true,
contactId: contact?.id,
})
}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit bx-xs text-primary me-2"></i>
<span className="align-left ">Modify</span>
</a>
</li>
<li>
<a
className="dropdown-item px-2 cursor-pointer py-1"
onClick={() => setIsDeleteModalOpen(true)}
>
<i className="bx bx-trash text-danger bx-xs me-2"></i>
<span className="align-left">Delete</span>
</a>
</li>
</ul>
</div>
)}
{!IsActive && (
<i
className={`bx ${
isPending ? "bx-loader-alt bx-spin" : "bx-recycle"
} me-1 text-primary cursor-pointer`}
title="Restore"
onClick={() => handleActiveInactive(contact.id)}
></i>
)}
</div>
</div>
<ul className="list-inline m-0 ps-4 d-flex align-items-start">
<li className="list-inline-item text-break small px-1 ms-5">
{contact?.organization}
</li>
</ul>
</div>
<div
className={`card-footer text-start px-9 py-1 ${
IsActive && "cursor-pointer"
}`}
onClick={() => {
if (IsActive) {
setIsOpenModalNote(true);
setOpen_contact(contact);
}
}}
>
<hr className="my-0" />
{contact?.designation && (
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
<li className="me-2">
<i className="fa-solid fa-id-badge ms-1"></i>
</li>
<li className="flex-grow-1 text-break small">
{contact.designation}
</li>
</ul>
)}
{contact.contactEmails[0] && (
<ul className="list-unstyled my-1 d-flex align-items-start ms-2">
<li className="me-2">
<i className="bx bx-envelope bx-xs mt-1"></i>
</li>
<li className="flex-grow-1 text-break small">
{contact.contactEmails[0].emailAddress}
</li>
</ul>
)}
{contact.contactPhones[0] && (
<ul className="list-inline m-0 ms-2">
<li className="list-inline-item me-1">
<i
className={` ${getPhoneIcon(
contact.contactPhones[0].label
)} bx-xs`}
></i>
</li>
<li className="list-inline-item text-small">
{contact.contactPhones[0]?.phoneNumber}
</li>
</ul>
)}
{contact?.tags?.length > 0 ? (
<ul className="list-inline m-0 ms-2">
<li className="list-inline-item me-2 my-1">
<i className="fa-solid fa-tag fs-6 ms-1"></i>
</li>
{contact.tags.map((tag, index) => (
<li key={index} className="list-inline-item text-small active">
{tag.name}
</li>
))}
</ul>
) : (
<ul className="list-inline m-0 ms-2">
<li className="list-inline-item me-2 my-1">
<i className="fa-solid fa-tag fs-6 ms-1"></i>
</li>
<li className="list-inline-item text-small active">Other</li>
</ul>
)}
<ul className="list-inline m-0 ms-2">
{contact?.bucketIds?.map((bucketId) => (
<li key={bucketId} className="list-inline-item me-1">
<span
className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1"
style={{ padding: "0.1rem 0.3rem" }}
>
<i className="bx bx-pin bx-xs"></i>
<span className="small-text">
{getBucketNameById(data, bucketId)}
</span>
</span>
</li>
))}
</ul>
</div>
</div>
</>
);
};
export default CardViewContact;

Some files were not shown because too many files have changed in this diff Show More