diff --git a/.scannerwork/.sonar_lock b/.scannerwork/.sonar_lock new file mode 100644 index 00000000..e69de29b diff --git a/.scannerwork/report-task.txt b/.scannerwork/report-task.txt new file mode 100644 index 00000000..df1f2fb1 --- /dev/null +++ b/.scannerwork/report-task.txt @@ -0,0 +1,6 @@ +projectKey=pms-react +serverUrl=https://sonar.marcoaiot.com +serverVersion=25.5.0.107428 +dashboardUrl=https://sonar.marcoaiot.com/dashboard?id=pms-react +ceTaskId=ad6ba36a-08cb-400b-903e-94f173cac03f +ceTaskUrl=https://sonar.marcoaiot.com/api/ce/task?id=ad6ba36a-08cb-400b-903e-94f173cac03f diff --git a/package-lock.json b/package-lock.json index 75c151b9..dfe50b1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "react-router-dom": "^6.20.1", "react-toastify": "^11.0.2", "sort-by": "^1.2.0", + "swiper": "^11.2.10", "xlsx": "^0.18.5", "zod": "^3.24.1" }, @@ -5547,6 +5548,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swiper": { + "version": "11.2.10", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.10.tgz", + "integrity": "sha512-RMeVUUjTQH+6N3ckimK93oxz6Sn5la4aDlgPzB+rBrG/smPdCTicXyhxa+woIpopz+jewEloiEE3lKo1h9w2YQ==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/swiperjs" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "engines": { + "node": ">= 4.7.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", diff --git a/package.json b/package.json index 33e1f019..02c59115 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-router-dom": "^6.20.1", "react-toastify": "^11.0.2", "sort-by": "^1.2.0", + "swiper": "^11.2.10", "xlsx": "^0.18.5", "zod": "^3.24.1" }, diff --git a/public/assets/css/default.css b/public/assets/css/default.css index 2abd37ee..db070129 100644 --- a/public/assets/css/default.css +++ b/public/assets/css/default.css @@ -30,9 +30,7 @@ width: 45px; } -.app-brand-logo-login { - width: 100px; -} + .app-brand-logo-border { border: 1px solid #d5d5d5; @@ -164,3 +162,24 @@ thead tr { border-top: 1px solid white; } + +.app-brand-logo-login { + max-width: 50px; /* default for mobile */ + height: auto; /* keep aspect ratio */ +} + + +/* Tablet and up (≥768px) */ +@media (min-width: 768px) { + .app-brand-logo-login { + max-width: 60px; + } +} + +/* Desktop and up (≥1200px) */ +@media (min-width: 1200px) { + .app-brand-logo-login { + max-width: 80px; + } +} + diff --git a/public/assets/vendor/css/core.css b/public/assets/vendor/css/core.css index 6e3d5d14..9be75669 100644 --- a/public/assets/vendor/css/core.css +++ b/public/assets/vendor/css/core.css @@ -32544,3 +32544,29 @@ body:not(.modal-open) .layout-content-navbar .layout-navbar { var(--bs-dark-contrast) ); } + +/* App colors classes */ +.bg-gray-60{ + background-color:var(--bs-gray-60) +} +.text-gray-60{ + color:var(--bs-gray-60) +} +.bg-blue { + background-color:var(--bs-blue) +} +.text-blue{ + color:var(--bs-blue) +} +.bg-indigo { + background-color:var(--bs-indigo) +} +.text-indigo{ + color:var(--bs-indigo) +} +.bg-red { + background-color:var(--bs-red) +} +.text-red{ + color:var(--bs-red) +} \ No newline at end of file diff --git a/public/img/app/dashboard-light-01.png b/public/img/app/dashboard-light-01.png new file mode 100644 index 00000000..8e1d4956 Binary files /dev/null and b/public/img/app/dashboard-light-01.png differ diff --git a/public/img/app/dashboard-light-02.png b/public/img/app/dashboard-light-02.png new file mode 100644 index 00000000..ede59aed Binary files /dev/null and b/public/img/app/dashboard-light-02.png differ diff --git a/public/img/app/dashboard-light-03.png b/public/img/app/dashboard-light-03.png new file mode 100644 index 00000000..1e45e519 Binary files /dev/null and b/public/img/app/dashboard-light-03.png differ diff --git a/public/img/app/dashboard-light-04.png b/public/img/app/dashboard-light-04.png new file mode 100644 index 00000000..998d30ea Binary files /dev/null and b/public/img/app/dashboard-light-04.png differ diff --git a/public/img/app/dashboard-light-05.png b/public/img/app/dashboard-light-05.png new file mode 100644 index 00000000..c7e6f5ac Binary files /dev/null and b/public/img/app/dashboard-light-05.png differ diff --git a/public/img/app/dashboard-light-06.png b/public/img/app/dashboard-light-06.png new file mode 100644 index 00000000..386d7289 Binary files /dev/null and b/public/img/app/dashboard-light-06.png differ diff --git a/public/img/app/dashboard-light-07.png b/public/img/app/dashboard-light-07.png new file mode 100644 index 00000000..b358f986 Binary files /dev/null and b/public/img/app/dashboard-light-07.png differ diff --git a/public/img/app/dashboard-light-08.png b/public/img/app/dashboard-light-08.png new file mode 100644 index 00000000..2ae124a9 Binary files /dev/null and b/public/img/app/dashboard-light-08.png differ diff --git a/public/img/backgrounds/cta-bg-dark.png b/public/img/backgrounds/cta-bg-dark.png new file mode 100644 index 00000000..7c4e84ae Binary files /dev/null and b/public/img/backgrounds/cta-bg-dark.png differ diff --git a/public/img/backgrounds/cta-bg-light.png b/public/img/backgrounds/cta-bg-light.png new file mode 100644 index 00000000..f2cdb6fb Binary files /dev/null and b/public/img/backgrounds/cta-bg-light.png differ diff --git a/public/img/backgrounds/footer-bg-dark.png b/public/img/backgrounds/footer-bg-dark.png new file mode 100644 index 00000000..150c6ee6 Binary files /dev/null and b/public/img/backgrounds/footer-bg-dark.png differ diff --git a/public/img/backgrounds/footer-bg-light.png b/public/img/backgrounds/footer-bg-light.png new file mode 100644 index 00000000..ff217349 Binary files /dev/null and b/public/img/backgrounds/footer-bg-light.png differ diff --git a/public/img/backgrounds/footer-bg.png b/public/img/backgrounds/footer-bg.png new file mode 100644 index 00000000..5890f0d6 Binary files /dev/null and b/public/img/backgrounds/footer-bg.png differ diff --git a/public/img/backgrounds/hero-bg.png b/public/img/backgrounds/hero-bg.png new file mode 100644 index 00000000..7d175c58 Binary files /dev/null and b/public/img/backgrounds/hero-bg.png differ diff --git a/public/img/brand/logo-1.png b/public/img/brand/logo-1.png new file mode 100644 index 00000000..7f551d70 Binary files /dev/null and b/public/img/brand/logo-1.png differ diff --git a/public/img/brand/logo-2.png b/public/img/brand/logo-2.png new file mode 100644 index 00000000..5e3f2698 Binary files /dev/null and b/public/img/brand/logo-2.png differ diff --git a/public/img/brand/logo-3.png b/public/img/brand/logo-3.png new file mode 100644 index 00000000..e854e939 Binary files /dev/null and b/public/img/brand/logo-3.png differ diff --git a/public/img/brand/logo-4.png b/public/img/brand/logo-4.png new file mode 100644 index 00000000..6c5d3f3c Binary files /dev/null and b/public/img/brand/logo-4.png differ diff --git a/public/img/brand/logo-5.png b/public/img/brand/logo-5.png new file mode 100644 index 00000000..bf3cc14e Binary files /dev/null and b/public/img/brand/logo-5.png differ diff --git a/public/img/brand/logo-6.png b/public/img/brand/logo-6.png new file mode 100644 index 00000000..99980b2a Binary files /dev/null and b/public/img/brand/logo-6.png differ diff --git a/public/img/brand/logo_1-dark.png b/public/img/brand/logo_1-dark.png new file mode 100644 index 00000000..cb1a58d2 Binary files /dev/null and b/public/img/brand/logo_1-dark.png differ diff --git a/public/img/brand/logo_1-light.png b/public/img/brand/logo_1-light.png new file mode 100644 index 00000000..e0308e90 Binary files /dev/null and b/public/img/brand/logo_1-light.png differ diff --git a/public/img/brand/logo_2-dark.png b/public/img/brand/logo_2-dark.png new file mode 100644 index 00000000..f5e92478 Binary files /dev/null and b/public/img/brand/logo_2-dark.png differ diff --git a/public/img/brand/logo_2-light.png b/public/img/brand/logo_2-light.png new file mode 100644 index 00000000..719e6103 Binary files /dev/null and b/public/img/brand/logo_2-light.png differ diff --git a/public/img/brand/logo_3-dark.png b/public/img/brand/logo_3-dark.png new file mode 100644 index 00000000..27c68e49 Binary files /dev/null and b/public/img/brand/logo_3-dark.png differ diff --git a/public/img/brand/logo_3-light.png b/public/img/brand/logo_3-light.png new file mode 100644 index 00000000..5ec4f174 Binary files /dev/null and b/public/img/brand/logo_3-light.png differ diff --git a/public/img/brand/logo_4-dark.png b/public/img/brand/logo_4-dark.png new file mode 100644 index 00000000..3e5bfc3d Binary files /dev/null and b/public/img/brand/logo_4-dark.png differ diff --git a/public/img/brand/logo_4-light.png b/public/img/brand/logo_4-light.png new file mode 100644 index 00000000..0929f535 Binary files /dev/null and b/public/img/brand/logo_4-light.png differ diff --git a/public/img/brand/logo_5-dark.png b/public/img/brand/logo_5-dark.png new file mode 100644 index 00000000..34342001 Binary files /dev/null and b/public/img/brand/logo_5-dark.png differ diff --git a/public/img/brand/logo_5-light.png b/public/img/brand/logo_5-light.png new file mode 100644 index 00000000..deb1071f Binary files /dev/null and b/public/img/brand/logo_5-light.png differ diff --git a/public/img/brand/marco.png b/public/img/brand/marco.png index ca14f303..446e998f 100644 Binary files a/public/img/brand/marco.png and b/public/img/brand/marco.png differ diff --git a/public/img/icons/Join-community-arrow.png b/public/img/icons/Join-community-arrow.png new file mode 100644 index 00000000..9bde454a Binary files /dev/null and b/public/img/icons/Join-community-arrow.png differ diff --git a/public/img/icons/apple-icon.png b/public/img/icons/apple-icon.png new file mode 100644 index 00000000..82527801 Binary files /dev/null and b/public/img/icons/apple-icon.png differ diff --git a/public/img/icons/check-warning.svg b/public/img/icons/check-warning.svg new file mode 100644 index 00000000..4c16161c --- /dev/null +++ b/public/img/icons/check-warning.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/icons/check.svg b/public/img/icons/check.svg new file mode 100644 index 00000000..59b5961e --- /dev/null +++ b/public/img/icons/check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/icons/contact-border.png b/public/img/icons/contact-border.png new file mode 100644 index 00000000..1eb721d6 Binary files /dev/null and b/public/img/icons/contact-border.png differ diff --git a/public/img/icons/diamond-info.svg b/public/img/icons/diamond-info.svg new file mode 100644 index 00000000..5f24f9ef --- /dev/null +++ b/public/img/icons/diamond-info.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/img/icons/facebook.svg b/public/img/icons/facebook.svg new file mode 100644 index 00000000..42672a2d --- /dev/null +++ b/public/img/icons/facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icons/github.svg b/public/img/icons/github.svg new file mode 100644 index 00000000..75bdf8d1 --- /dev/null +++ b/public/img/icons/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icons/google-play-icon.png b/public/img/icons/google-play-icon.png new file mode 100644 index 00000000..117bcb16 Binary files /dev/null and b/public/img/icons/google-play-icon.png differ diff --git a/public/img/icons/instagram.svg b/public/img/icons/instagram.svg new file mode 100644 index 00000000..9a6830c8 --- /dev/null +++ b/public/img/icons/instagram.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/img/icons/inventory.svg b/public/img/icons/inventory.svg new file mode 100644 index 00000000..6e56346a --- /dev/null +++ b/public/img/icons/inventory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/keyboard.svg b/public/img/icons/keyboard.svg new file mode 100644 index 00000000..dd6b8775 --- /dev/null +++ b/public/img/icons/keyboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/icons/laptop.svg b/public/img/icons/laptop.svg new file mode 100644 index 00000000..befca175 --- /dev/null +++ b/public/img/icons/laptop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/icons/paper-airplane.png b/public/img/icons/paper-airplane.png new file mode 100644 index 00000000..a4911dc4 Binary files /dev/null and b/public/img/icons/paper-airplane.png differ diff --git a/public/img/icons/paper.svg b/public/img/icons/paper.svg new file mode 100644 index 00000000..6d933f2a --- /dev/null +++ b/public/img/icons/paper.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/icons/plane.png b/public/img/icons/plane.png new file mode 100644 index 00000000..497cc1e4 Binary files /dev/null and b/public/img/icons/plane.png differ diff --git a/public/img/icons/pricing-plans-arrow.png b/public/img/icons/pricing-plans-arrow.png new file mode 100644 index 00000000..ed8f60bb Binary files /dev/null and b/public/img/icons/pricing-plans-arrow.png differ diff --git a/public/img/icons/rocket.svg b/public/img/icons/rocket.svg new file mode 100644 index 00000000..700323aa --- /dev/null +++ b/public/img/icons/rocket.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/icons/section-title-icon.png b/public/img/icons/section-title-icon.png new file mode 100644 index 00000000..6e8d1730 Binary files /dev/null and b/public/img/icons/section-title-icon.png differ diff --git a/public/img/icons/shuttle-rocket.png b/public/img/icons/shuttle-rocket.png new file mode 100644 index 00000000..154b17e7 Binary files /dev/null and b/public/img/icons/shuttle-rocket.png differ diff --git a/public/img/icons/twitter.svg b/public/img/icons/twitter.svg new file mode 100644 index 00000000..78fa4dc2 --- /dev/null +++ b/public/img/icons/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/img/icons/user-success.svg b/public/img/icons/user-success.svg new file mode 100644 index 00000000..32d4b3bb --- /dev/null +++ b/public/img/icons/user-success.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/img/icons/user.svg b/public/img/icons/user.svg new file mode 100644 index 00000000..af4eac79 --- /dev/null +++ b/public/img/icons/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/img/illustrations/faq-boy-with-logos.png b/public/img/illustrations/faq-boy-with-logos.png new file mode 100644 index 00000000..b64dbfcc Binary files /dev/null and b/public/img/illustrations/faq-boy-with-logos.png differ diff --git a/public/img/illustrations/girl-unlock-password-light.png b/public/img/illustrations/girl-unlock-password-light.png new file mode 100644 index 00000000..18f4b274 Binary files /dev/null and b/public/img/illustrations/girl-unlock-password-light.png differ diff --git a/public/img/illustrations/registration.jpg b/public/img/illustrations/registration.jpg new file mode 100644 index 00000000..69cf7b8e Binary files /dev/null and b/public/img/illustrations/registration.jpg differ diff --git a/public/img/illustrations/worker_01.svg b/public/img/illustrations/worker_01.svg new file mode 100644 index 00000000..2170c2f4 --- /dev/null +++ b/public/img/illustrations/worker_01.svg @@ -0,0 +1,4955 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/illustrations/worker_02.jpg b/public/img/illustrations/worker_02.jpg new file mode 100644 index 00000000..274c55c3 Binary files /dev/null and b/public/img/illustrations/worker_02.jpg differ diff --git a/public/img/illustrations/worker_02.svg b/public/img/illustrations/worker_02.svg new file mode 100644 index 00000000..c673e01c --- /dev/null +++ b/public/img/illustrations/worker_02.svg @@ -0,0 +1,1117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/illustrations/worker_03.jpg b/public/img/illustrations/worker_03.jpg new file mode 100644 index 00000000..99c9be24 Binary files /dev/null and b/public/img/illustrations/worker_03.jpg differ diff --git a/public/img/illustrations/worker_03.png b/public/img/illustrations/worker_03.png new file mode 100644 index 00000000..d5fdab98 Binary files /dev/null and b/public/img/illustrations/worker_03.png differ diff --git a/public/img/illustrations/worker_03.svg b/public/img/illustrations/worker_03.svg new file mode 100644 index 00000000..ee552ecf --- /dev/null +++ b/public/img/illustrations/worker_03.svg @@ -0,0 +1,383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/images/contact-customer-service.png b/public/img/images/contact-customer-service.png new file mode 100644 index 00000000..4e5aaaad Binary files /dev/null and b/public/img/images/contact-customer-service.png differ diff --git a/public/img/teams/team-member-1.png b/public/img/teams/team-member-1.png new file mode 100644 index 00000000..2a007f12 Binary files /dev/null and b/public/img/teams/team-member-1.png differ diff --git a/public/img/teams/team-member-2.png b/public/img/teams/team-member-2.png new file mode 100644 index 00000000..b1b7e7c0 Binary files /dev/null and b/public/img/teams/team-member-2.png differ diff --git a/public/img/teams/team-member-3.png b/public/img/teams/team-member-3.png new file mode 100644 index 00000000..805b2825 Binary files /dev/null and b/public/img/teams/team-member-3.png differ diff --git a/public/img/teams/team-member-4.png b/public/img/teams/team-member-4.png new file mode 100644 index 00000000..8718f3c1 Binary files /dev/null and b/public/img/teams/team-member-4.png differ diff --git a/src/components/Activities/AttendLogs.jsx b/src/components/Activities/AttendLogs.jsx index b94c80c6..7c1e686d 100644 --- a/src/components/Activities/AttendLogs.jsx +++ b/src/components/Activities/AttendLogs.jsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import { useEmployeeAttendacesLog } from "../../hooks/useAttendance"; -import { convertShortTime } from "../../utils/dateUtils"; +import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils"; import { useNavigate } from "react-router-dom"; import { THRESH_HOLD } from "../../utils/constants"; @@ -128,7 +128,7 @@ const AttendLogs = ({ Id }) => {

Attendance logs for{" "} {logs[0]?.employee?.firstName + " " + logs[0]?.employee?.lastName}{" "} - on {logs[0]?.activityTime.slice(0, 10)}{" "} + on {formatUTCToLocalTime(logs[0]?.activityTime)}

)} @@ -156,7 +156,7 @@ const AttendLogs = ({ Id }) => { .sort((a, b) => b.id - a.id) .map((log, index) => ( - {log.activityTime.slice(0, 10)} + {formatUTCToLocalTime(log.activityTime)} {convertShortTime(log.activityTime)} {whichActivityPerform(log.activity, log.activityTime)} diff --git a/src/components/Activities/Attendance.jsx b/src/components/Activities/Attendance.jsx index 2dde2938..6d751b12 100644 --- a/src/components/Activities/Attendance.jsx +++ b/src/components/Activities/Attendance.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useCallback, useMemo } from "react"; import moment from "moment"; import Avatar from "../common/Avatar"; -import { convertShortTime } from "../../utils/dateUtils"; +import { convertShortTime, formatUTCToLocalTime } from "../../utils/dateUtils"; import RenderAttendanceStatus from "./RenderAttendanceStatus"; import usePagination from "../../hooks/usePagination"; import { useNavigate } from "react-router-dom"; @@ -10,7 +10,7 @@ import { useAttendance } from "../../hooks/useAttendance"; import { useSelector } from "react-redux"; import { useQueryClient } from "@tanstack/react-query"; import eventBus from "../../services/eventBus"; -import { useSelectedproject } from "../../slices/apiDataManager"; +import { useSelectedProject } from "../../slices/apiDataManager"; const Attendance = ({ getRole, handleModalData, searchTerm }) => { const queryClient = useQueryClient(); @@ -21,7 +21,7 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { // const selectedProject = useSelector( // (store) => store.localVariables.projectId // ); - const selectedProject = useSelectedproject(); + const selectedProject = useSelectedProject(); const { attendance, loading: attLoading, @@ -114,9 +114,12 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { return ( <> -
+
- Date : {todayDate.toLocaleDateString("en-GB")} + Date : {formatUTCToLocalTime(todayDate)}
{ ))} {!attendance && ( - No employees assigned to the project! + + + No employees assigned to the project! + + )} @@ -258,7 +265,10 @@ const Attendance = ({ getRole, handleModalData, searchTerm }) => { )} ) : ( -
+
{searchTerm ? "No results found for your search." : attendanceList.length === 0 diff --git a/src/components/Activities/AttendcesLogs.jsx b/src/components/Activities/AttendcesLogs.jsx index 548175d9..faf4bdc5 100644 --- a/src/components/Activities/AttendcesLogs.jsx +++ b/src/components/Activities/AttendcesLogs.jsx @@ -6,11 +6,12 @@ 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"; import { queryClient } from "../../layouts/AuthLayout"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; const usePagination = (data, itemsPerPage) => { const [currentPage, setCurrentPage] = useState(1); @@ -37,11 +38,11 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { // const selectedProject = useSelector( // (store) => store.localVariables.projectId // ); - const selectedProject = useSelectedproject(); + const selectedProject = useSelectedProject(); const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); const dispatch = useDispatch(); const [loading, setLoading] = useState(false); - const [showPending,setShowPending] = useState(false) + const [showPending, setShowPending] = useState(false) const [isRefreshing, setIsRefreshing] = useState(false); const [processedData, setProcessedData] = useState([]); @@ -244,17 +245,16 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => {
refetch()} />
-
+
{isLoading ? ( -
+

Loading...

) : filteredSearchData?.length > 0 ? ( @@ -283,9 +283,9 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { 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) { @@ -345,15 +345,18 @@ const AttendanceLog = ({ handleModalData, searchTerm }) => { ) : ( -
No Record Available !
+
No Record Available !
)}
{paginatedAttendances?.length == 0 && filteredSearchData?.length > 0 && ( -
- No Pending Record Available ! +
+ No Record Available !
)} - {filteredSearchData.length > 10 && ( + {filteredSearchData.length > ITEMS_PER_PAGE && (
-
- +
+ +
); }; - export default ReportTask; \ No newline at end of file +export default ReportTask; \ No newline at end of file diff --git a/src/components/Activities/ReportTaskComments.jsx b/src/components/Activities/ReportTaskComments.jsx index c48f9796..60a81ff3 100644 --- a/src/components/Activities/ReportTaskComments.jsx +++ b/src/components/Activities/ReportTaskComments.jsx @@ -10,6 +10,7 @@ import { getBgClassFromHash } from "../../utils/projectStatus"; import { cacheData, getCachedData } from "../../slices/apiDataManager"; import ImagePreview from "../common/ImagePreview"; import { useAuditStatus, useSubmitTaskComment } from "../../hooks/useTasks"; +import Label from "../common/Label"; const ReportTaskComments = ({ commentsData, @@ -291,10 +292,10 @@ const ReportTaskComments = ({

)}
-
-
+ return ( +
+ {/* Header */} +
+
+
Attendance Overview
+

Role-wise present count

+
+
+ + + +
+
{/* Content */}
diff --git a/src/components/Dashboard/ProjectCompletionChart.jsx b/src/components/Dashboard/ProjectCompletionChart.jsx index f0c85179..8ce4b13a 100644 --- a/src/components/Dashboard/ProjectCompletionChart.jsx +++ b/src/components/Dashboard/ProjectCompletionChart.jsx @@ -19,7 +19,7 @@ const ProjectCompletionChart = () => {
-
Projects
+
Projects

Projects Completion Status

diff --git a/src/components/Dashboard/ProjectProgressChart.jsx b/src/components/Dashboard/ProjectProgressChart.jsx index f80bb0d5..61747f6c 100644 --- a/src/components/Dashboard/ProjectProgressChart.jsx +++ b/src/components/Dashboard/ProjectProgressChart.jsx @@ -90,7 +90,7 @@ const ProjectProgressChart = ({
{/* Left: Title */}
-
Project Progress
+
Project Progress

Progress Overview by Project

diff --git a/src/components/Directory/AssignedBucket.jsx b/src/components/Directory/AssignedBucket.jsx new file mode 100644 index 00000000..b0db2ef0 --- /dev/null +++ b/src/components/Directory/AssignedBucket.jsx @@ -0,0 +1,68 @@ +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 ( +
+ + +
+ +
+ + ); +}; + +export default AssignedBucket; diff --git a/src/components/Directory/BucketForm.jsx b/src/components/Directory/BucketForm.jsx new file mode 100644 index 00000000..b45f17a0 --- /dev/null +++ b/src/components/Directory/BucketForm.jsx @@ -0,0 +1,95 @@ +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 ( +
+
+ + + {/* Show edit toggle only for existing bucket in edit mode */} + {/* {isEditMode && ( + + )} */} +
+ + {(isCreateMode || isEditMode) ? ( +
+
+ + + {errors.name && ( + {errors.name.message} + )} +
+ +
+ + + {errors.description && ( +
{errors.description.message}
+ )} +
+ + {/* Buttons */} +
+ + +
+
+ +
+ ); +}; + +export default ManageDocument; diff --git a/src/components/Documents/VersionListSkeleton.jsx b/src/components/Documents/VersionListSkeleton.jsx new file mode 100644 index 00000000..d239315c --- /dev/null +++ b/src/components/Documents/VersionListSkeleton.jsx @@ -0,0 +1,47 @@ +import React from "react"; + +const SkeletonLine = ({ height = 16, width = "100%", className = "" }) => ( +
+); + +const VersionListSkeleton = ({ items = 5 }) => { + return ( +
+ {[...Array(items)].map((_, idx) => ( +
+ {/* Top row: document name + version/status */} +
+ +
+ + +
+
+ + {/* Upload by row */} +
+ + +
+ + {/* Updated at row */} +
+ +
+
+ ))} +
+ ); +}; + +export default VersionListSkeleton; diff --git a/src/components/Documents/ViewDocument.jsx b/src/components/Documents/ViewDocument.jsx new file mode 100644 index 00000000..6405a3ed --- /dev/null +++ b/src/components/Documents/ViewDocument.jsx @@ -0,0 +1,175 @@ +import React, { useState } from "react"; +import { + useDocumentDetails, + useDocumentVersionList, + useVerifyDocument, +} from "../../hooks/useDocument"; +import { getDocuementsStatus, useDocumentContext } from "./Documents"; +import { formatUTCToLocalTime } from "../../utils/dateUtils"; +import Avatar from "../common/Avatar"; +import { + DOWNLOAD_DOCUMENT, + ITEMS_PER_PAGE, + VERIFY_DOCUMENT, +} from "../../utils/constants"; +import DocumentDetailsSkeleton from "./DocumentDetailsSkeleton "; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import DocumentVersionList from "./DocumentVersionList"; + +const ViewDocument = () => { + const { viewDoc, setOpenDocument } = useDocumentContext(); + const [currentPage, setCurrentPage] = useState(1); + const [showVersions, setShowVersions] = useState(false); + const canVerifyDocument = useHasUserPermission(VERIFY_DOCUMENT); + + // Document Details + const { data, isLoading, isError, error } = useDocumentDetails( + viewDoc?.document + ); + + // Document Versions (fetch only if toggle is ON) + const { + data: versionList, + isLoading: versionLoading, + } = useDocumentVersionList( + showVersions ? data?.parentAttachmentId : null, + ITEMS_PER_PAGE - 10, + currentPage + ); + + const paginate = (page) => { + if (page >= 1 && page <= (versionList?.totalPages ?? 1)) { + setCurrentPage(page); + } + }; + + // Verify / Reject + const { mutate: VerifyDoc, isPending } = useVerifyDocument(); + const VerifyDocument = () => { + VerifyDoc({ documentId: viewDoc?.document, isVerify: true }); + }; + const RejectDocument = () => { + VerifyDoc({ documentId: viewDoc?.document, isVerify: false }); + }; + + if (isLoading) return ; + if (isError) + return ( +
+

{error?.response?.data?.message || error?.message}

+

{error?.response?.status}

+
+ ); + + return ( +
+

Document Details

+ + {/* Document Info Rows */} +
+
+ + Category: + + + {data.documentType?.documentCategory?.name || "-"} + +
+
+ + Type: + + {data.documentType?.name || "-"} +
+
+ +
+
+ + Document Name: + + {data.name || "-"} +
+
+ + Document ID: + + {data.documentId || "-"} +
+
+ +
+
+ + Uploaded At: + + + {formatUTCToLocalTime(data.uploadedAt)} + +
+ +
+ +
+
+ + Tags: + +
+ {data.tags?.length > 0 ? ( + data.tags.map((t, i) => ( + + {t.name} + + )) + ) : ( + - + )} +
+
+
+ +
+
+ + Description: + + {data.description || "-"} +
+
+ + {/* Toggle for Versions */} +
+

Documents:

+
+ + setShowVersions(e.target.checked)} + /> +
+
+ + +
+ ); +}; + +export default ViewDocument; \ No newline at end of file diff --git a/src/components/Employee/EmpActivities.jsx b/src/components/Employee/EmpActivities.jsx index f5d9e8c2..e79b181d 100644 --- a/src/components/Employee/EmpActivities.jsx +++ b/src/components/Employee/EmpActivities.jsx @@ -25,7 +25,7 @@ error,
diff --git a/src/components/Employee/EmpAttendance.jsx b/src/components/Employee/EmpAttendance.jsx index b8ff8b83..c441b826 100644 --- a/src/components/Employee/EmpAttendance.jsx +++ b/src/components/Employee/EmpAttendance.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import moment from "moment"; -import DateRangePicker from "../common/DateRangePicker"; +import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker"; import { useDispatch, useSelector } from "react-redux"; import { fetchEmployeeAttendanceData } from "../../slices/apiSlice/employeeAttendanceSlice"; import usePagination from "../../hooks/usePagination"; @@ -11,13 +11,32 @@ import AttendLogs from "../Activities/AttendLogs"; import { useAttendanceByEmployee } from "../../hooks/useAttendance"; import GlobalModel from "../common/GlobalModel"; import { ITEMS_PER_PAGE } from "../../utils/constants"; +import { FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { localToUtc } from "../../utils/appUtils"; const EmpAttendance = ({ employee }) => { const [attendances, setAttendnaces] = useState([]); const [selectedDate, setSelectedDate] = useState(""); - const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); const [isModalOpen, setIsModalOpen] = useState(false); const [attendanceId, setAttendanecId] = useState(); + + const methods = useForm({ + resolver: zodResolver( + z.object({ + startDate: z.string(), + endDate: z.string(), + }) + ), + defaultValues: { + startDate: "", + endDate: "", + }, + }); + const { control, register, handleSubmit, reset, watch } = methods; + const startDate = watch("startDate"); + const endDate = watch("endDate"); const { data = [], isLoading: loading, @@ -25,15 +44,13 @@ const EmpAttendance = ({ employee }) => { isError, error, refetch, - } = useAttendanceByEmployee(employee, dateRange.startDate, dateRange.endDate); + } = useAttendanceByEmployee( + employee, + localToUtc(startDate), + localToUtc(endDate) + ); const dispatch = useDispatch(); - // const { data, loading, error } = useSelector( - // (store) => store.employeeAttendance - // ); - - const [isRefreshing, setIsRefreshing] = useState(true); - const today = new Date(); today.setHours(0, 0, 0, 0); @@ -71,13 +88,6 @@ const EmpAttendance = ({ employee }) => { .sort(sortByName); const group5 = data.filter((d) => d.activity === 5).sort(sortByName); - // const sortedFinalList = [ - // ...group1, - // ...group2, - // ...group3, - // ...group4, - // ...group5, - // ]; const uniqueMap = new Map(); @@ -114,6 +124,7 @@ const EmpAttendance = ({ employee }) => { }; const closeModal = () => setIsModalOpen(false); + const onSubmit = (formData) => {}; return ( <> {isModalOpen && ( @@ -126,12 +137,22 @@ const EmpAttendance = ({ employee }) => { className="dataTables_length text-start py-2 d-flex justify-content-between " id="DataTables_Table_0_length" > -
- +
+ <> + +
+ + +
+
{ const { openChangePassword } = useChangePassword(); @@ -59,25 +60,25 @@ const EmpBanner = ({ profile, loggedInUser }) => {
  • - + {profile?.jobRole || NA}
  • - + {" "} {profile?.phoneNumber || NA}
  • - + {" "} Joined on{" "} {profile?.joiningDate ? ( - new Date(profile.joiningDate).toLocaleDateString() + formatUTCToLocalTime(profile.joiningDate) ) : ( NA )} @@ -85,18 +86,21 @@ const EmpBanner = ({ profile, loggedInUser }) => {
    -
  • - -
  • -
  • - {profile?.id == loggedInUser?.employeeInfo?.id && ( + {profile?.isActive && ( // ✅ show only if active +
  • +
  • + )} + +
  • + {profile?.id === loggedInUser?.employeeInfo?.id && ( +
  • ))} diff --git a/src/components/Employee/EmpDocuments.jsx b/src/components/Employee/EmpDocuments.jsx index 46aa33d0..b4c8046b 100644 --- a/src/components/Employee/EmpDocuments.jsx +++ b/src/components/Employee/EmpDocuments.jsx @@ -1,10 +1,15 @@ import React, { useState, useEffect } from "react"; import { ComingSoonPage } from "../../pages/Misc/ComingSoonPage"; +import DocumentPage from "../../pages/Documents/DocumentPage"; +import Documents from "../Documents/Documents"; +import { useParams } from "react-router-dom"; +import { DOCUMENTS_ENTITIES } from "../../utils/constants"; const EmpDocuments = ({ profile, loggedInUser }) => { + const {employeeId} = useParams() return ( <> - + ); }; diff --git a/src/components/Employee/EmpOverview.jsx b/src/components/Employee/EmpOverview.jsx index 6b396745..cf4ce4af 100644 --- a/src/components/Employee/EmpOverview.jsx +++ b/src/components/Employee/EmpOverview.jsx @@ -142,13 +142,14 @@ const EmpOverview = ({ profile }) => {
{/* Address */} -
- +
+ - Address + Address + : - + {profile?.currentAddress || NA}
diff --git a/src/components/Employee/EmployeeList.jsx b/src/components/Employee/EmployeeList.jsx index 2ae93d5e..5a79c6ce 100644 --- a/src/components/Employee/EmployeeList.jsx +++ b/src/components/Employee/EmployeeList.jsx @@ -4,4 +4,4 @@ const EmployeeList = () => { return
EmployeeList
; }; -export default EmployeeList; +export default EmployeeList; \ No newline at end of file diff --git a/src/components/Employee/EmployeeNav.jsx b/src/components/Employee/EmployeeNav.jsx index d9a6b3e0..8c25aa22 100644 --- a/src/components/Employee/EmployeeNav.jsx +++ b/src/components/Employee/EmployeeNav.jsx @@ -1,12 +1,31 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import { VIEW_DOCUMENT } from "../../utils/constants"; +import { useProfile } from "../../hooks/useProfile"; +import { useParams } from "react-router-dom"; const EmployeeNav = ({ onPillClick, activePill }) => { + const { employeeId } = useParams(); + const [isAbleToViewDocuments, setIsAbleToViewDocuments] = useState(false); + + const canViewDocuments = useHasUserPermission(VIEW_DOCUMENT); + const { profile } = useProfile(); + + useEffect(() => { + if (profile?.employeeInfo?.id) { + setIsAbleToViewDocuments(profile.employeeInfo.id === employeeId); + } + }, [profile?.employeeInfo?.id, employeeId]); + const tabs = [ { key: "profile", icon: "bx bx-user", label: "Profile" }, { key: "attendance", icon: "bx bx-group", label: "Attendances" }, - { key: "documents", icon: "bx bx-user", label: "Documents" }, + (isAbleToViewDocuments || canViewDocuments) && { + key: "documents", + icon: "bx bx-file", + label: "Documents", + }, { key: "activities", icon: "bx bx-grid-alt", label: "Activities" }, - ]; - + ].filter(Boolean); return (
diff --git a/src/components/Employee/ManageEmployee.jsx b/src/components/Employee/ManageEmployee.jsx index 1805220b..81a4c4d2 100644 --- a/src/components/Employee/ManageEmployee.jsx +++ b/src/components/Employee/ManageEmployee.jsx @@ -17,6 +17,8 @@ import { } from "../../slices/apiDataManager"; import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { useMutation } from "@tanstack/react-query"; +import Label from "../common/Label"; +import DatePicker from "../common/DatePicker"; const mobileNumberRegex = /^[0-9]\d{9}$/; @@ -220,10 +222,10 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { return ( <>
-

{employee ? "Update Employee" : "Create Employee"}

+

{employee ? "Update Employee" : "Create Employee"}

-
First Name
+ {
-
Last Name
+ { )}
-
Phone Number
+ {
-
Gender
+
+ {errors.birthDate && ( -
+
{errors.birthDate.message}
)}
-
Joining Date
+
-
+ {errors.joiningDate && ( -
+
{errors.joiningDate.message}
)} @@ -412,7 +416,7 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
-
Current Address
+ +
+
+ +
+
+ +
+
+
+
+
+ + {/* Contact Us: End */} +
+ + {/* / Sections:End */} + + {/* Footer: Start */} + + {/* Footer: End */} +
+ ); +}; +export default LandingPage; diff --git a/src/pages/Home/SubscriptionPlans.jsx b/src/pages/Home/SubscriptionPlans.jsx new file mode 100644 index 00000000..0d44472c --- /dev/null +++ b/src/pages/Home/SubscriptionPlans.jsx @@ -0,0 +1,235 @@ +// // src/components/SubscriptionPlans.jsx +// import React, { useState, useEffect } from "react"; +// import axios from "axios"; + +// const SubscriptionPlans = () => { +// const [plans, setPlans] = useState([]); +// const [frequency, setFrequency] = useState(1); // 1=Monthly, 2=Quarterly, 3=Half-Yearly, 4=Yearly +// const [loading, setLoading] = useState(false); + +// useEffect(() => { +// const fetchPlans = async () => { +// try { +// setLoading(true); +// const res = await axios.get( +// `/api/market/list/subscription-plan?frequency=${frequency}` +// ); +// setPlans(res.data?.data || []); +// } catch (err) { +// console.error("Error fetching plans:", err); +// } finally { +// setLoading(false); +// } +// }; + +// fetchPlans(); +// }, [frequency]); + +// return ( +//
+// {/* Frequency Switcher */} +//
+//
+// {["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map((label, idx) => ( +// +// ))} +//
+//
+ +// {/* Cards */} +//
+// {loading ? ( +//
Loading...
+// ) : plans.length === 0 ? ( +//
No plans found
+// ) : ( +// plans.map((plan) => ( +//
+//
+//
+//

{plan.planName}

+//

{plan.description}

+//
+// +// {plan.currency?.symbol} +// {plan.price} +// +// /mo +//
+//
+// Max Users: {plan.maxUser} | Storage: {plan.maxStorage} MB +//
+//
+ +//
+//
    +// {plan.features?.modules && +// Object.values(plan.features.modules).map((mod) => ( +//
  • +// +// {mod.name}{" "} +// {mod.enabled ? ( +// Enabled +// ) : ( +// Disabled +// )} +//
  • +// ))} +//
  • +// +// Support:{" "} +// {plan.features?.supports?.prioritySupport +// ? "Priority" +// : plan.features?.supports?.phoneSupport +// ? "Phone & Email" +// : "Email Only"} +//
  • +//
+//
+ +//
+// +//
+//
+//
+// )) +// )} +//
+//
+// ); +// }; + +// export default SubscriptionPlans; + +import React, { useState, useEffect } from "react"; +import axios from "axios"; + +const SubscriptionPlans = () => { + const [plans, setPlans] = useState([]); + const [frequency, setFrequency] = useState(1); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const fetchPlans = async () => { + try { + setLoading(true); + const res = await axios.get(`http://localhost:5032/api/market/list/subscription-plan?frequency=${frequency}`, { + headers: { + "Content-Type": "application/json" + } + }); + setPlans(res.data?.data || []); + } catch (err) { + console.error("Error fetching plans:", err); + } finally { + setLoading(false); + } + }; + fetchPlans(); + }, [frequency]); + + const frequencyLabel = (freq) => { + switch (freq) { + case 0: return "mo"; + case 1: return "3mo"; + case 2: return "6mo"; + case 3: return "yr"; + default: return "mo"; + } + }; + + return ( +
+ {/* Frequency Switcher */} +
+
+ {["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map((label, idx) => ( + + ))} +
+
+ + + {/* Cards */} +
+ {loading ? ( +
Loading...
+ ) : plans.length === 0 ? ( +
No plans found
+ ) : ( + plans.map((plan) => ( +
+
+
+

{plan.planName}

+

{plan.description}

+
+ + {plan.currency?.symbol}{plan.price} + + /{frequencyLabel(frequency)} +
+
+ Max Users: {plan.maxUser} | Storage: {plan.maxStorage} MB +
+
+ +
+
    + {plan.features?.modules && + Object.values(plan.features.modules).map((modGroup) => + Object.values(modGroup).map((mod) => + mod && mod.name ? ( +
  • + + {mod.name}{" "} + {mod.enabled ? ( + Enabled + ) : ( + Disabled + )} +
  • + ) : null + ) + )} + +
  • + + Support:{" "} + {plan.features?.supports?.prioritySupport + ? "Priority" + : plan.features?.supports?.phoneSupport + ? "Phone & Email" + : "Email Only"} +
  • +
+
+ +
+ +
+
+
+ )) + )} +
+
+ ); +}; + +export default SubscriptionPlans; + diff --git a/src/pages/Home/SwaperBlogContent.jsx b/src/pages/Home/SwaperBlogContent.jsx new file mode 100644 index 00000000..ed9c6836 --- /dev/null +++ b/src/pages/Home/SwaperBlogContent.jsx @@ -0,0 +1,47 @@ +const SwaperBlogContent = ({ + ImageUrl = "", + Title = "", + Body = "", + ContentAlign = "right", +}) => { + return ( +
+
+
+
+ +
{Title}
+
+

{Body}

+
+ + + + + +
+
+
+ Avatar +
+
+
Cecilia Payne
+

CEO of Airbnb

+
+
+
+
+
+ ); +}; + +export default SwaperBlogContent; diff --git a/src/pages/Home/SwaperSlideContent.jsx b/src/pages/Home/SwaperSlideContent.jsx new file mode 100644 index 00000000..13ed6c7f --- /dev/null +++ b/src/pages/Home/SwaperSlideContent.jsx @@ -0,0 +1,59 @@ +const SwaperSlideContent = ({ + ImageUrl = "", + Title = "", + Body = "", + ContentAlign = "right", +}) => { + return ( +
+
+ {/* Overlay */} +
+ + {/* Text Content */} +
+

+ {Title} +

+

{Body}

+ {/* + Get Started + */} +
+
+
+ ); +}; + +export default SwaperSlideContent; diff --git a/src/pages/Tenant/TenantPage.jsx b/src/pages/Tenant/TenantPage.jsx index f93163d9..ef33efbf 100644 --- a/src/pages/Tenant/TenantPage.jsx +++ b/src/pages/Tenant/TenantPage.jsx @@ -121,66 +121,67 @@ const TenantPage = () => { { label: "Tenant", link: null }, ]} /> +
+ {/* Super Tenant Actions */} + {isSuperTenant && ( +
+
+ {/* Search */} +
+ setSearchText(e.target.value)} + className="form-control form-control" + placeholder="Search Tenant" + /> +
- {/* Super Tenant Actions */} - {isSuperTenant && ( -
-
- {/* Search */} -
- setSearchText(e.target.value)} - className="form-control form-control-sm" - placeholder="Search Tenant" - /> -
+ {/* Actions */} +
+ refetchFn && refetchFn()} + > + Refresh{" "} + + - {/* Actions */} -
- refetchFn && refetchFn()} - > - Refresh{" "} - - - - + +
-
- )} + )} - {/* Tenant List or Access Denied */} - {isSuperTenant ? ( - - ) : !isSelfTenant ? ( -
- -

- Access Denied: You don't have permission to perform this action! -

-
- ) : null} + {/* Tenant List or Access Denied */} + {isSuperTenant ? ( + + ) : !isSelfTenant ? ( +
+ +

+ Access Denied: You don't have permission to perform this action! +

+
+ ) : null} +
); diff --git a/src/pages/TermsAndConditions/LegalInfoCard.jsx b/src/pages/TermsAndConditions/LegalInfoCard.jsx index baa5d3da..cd7c9e05 100644 --- a/src/pages/TermsAndConditions/LegalInfoCard.jsx +++ b/src/pages/TermsAndConditions/LegalInfoCard.jsx @@ -4,25 +4,10 @@ const LegalInfoPage = () => { return ( <>
MARCO SECURE SOLUTIONS @@ -41,7 +26,7 @@ const LegalInfoPage = () => { overflowY: 'auto', flexGrow: 1, - marginBottom: '2rem', // Adds space below the card + marginBottom: '2rem', }} >

Terms & Conditions

diff --git a/src/pages/authentication/AuthWrapper.jsx b/src/pages/authentication/AuthWrapper.jsx index c065bac2..5c8f0eb9 100644 --- a/src/pages/authentication/AuthWrapper.jsx +++ b/src/pages/authentication/AuthWrapper.jsx @@ -4,17 +4,17 @@ import "./page-auth.css"; export const AuthWrapper = ({ children }) => { return (
-
+ {/*
-
+
*/}
-
+
- + marco-logo { /> -
+ {/*
*/} {children}
-
-
+ //
+ //
); -}; +}; \ No newline at end of file diff --git a/src/pages/authentication/ForgotPasswordPage.jsx b/src/pages/authentication/ForgotPasswordPage.jsx index 508b7a56..0e234bd3 100644 --- a/src/pages/authentication/ForgotPasswordPage.jsx +++ b/src/pages/authentication/ForgotPasswordPage.jsx @@ -1,89 +1,88 @@ import { useState } from "react"; -import {Link} from "react-router-dom"; +import { Link } from "react-router-dom"; import { AuthWrapper } from "./AuthWrapper" import "./page-auth.css"; import AuthRepository from "../../repositories/AuthRepository"; import showToast from "../../services/toastService"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import {z} from "zod"; +import { z } from "zod"; -const forgotPassSceham = z.object( { - email: z.string().email(), -} ) +const forgotPassSceham = z.object({ + email: z.string().trim().email(), +}) const ForgotPasswordPage = () => { - const[loding,setLoading] = useState(false) + const [loding, setLoading] = useState(false) - const {register, + const { register, handleSubmit, formState: { errors }, reset, - getValues } = useForm( { - resolver: zodResolver( forgotPassSceham ), - defaultValues: { - email:"" - } - }) + getValues } = useForm({ + resolver: zodResolver(forgotPassSceham), + defaultValues: { + email: "" + } + }) - const onSubmit = async (data) => - { - try - { + const onSubmit = async (data) => { + try { setLoading(true) - const response = await AuthRepository.forgotPassword(data) - if ( response.data && response.success ) - showToast( "verification email has been sent to your registered email address", "success" ) + const response = await AuthRepository.forgotPassword(data) + if (response.data && response.success) + showToast("verification email has been sent to your registered email address", "success") reset() - setLoading( false ) - } catch ( err ) - { + setLoading(false) + } catch (err) { reset() - if(err.response.status === 404){ - showToast( "verification email has been sent to your registered email address", "success" ) - }else{ - showToast("Something wrong","error") - } - + if (err.response.status === 404) { + showToast("verification email has been sent to your registered email address", "success") + } else { + showToast("Something wrong", "error") + } + setLoading(false) } } return ( - -

Forgot Password? 🔒

-

- Enter your email and we'll send you instructions to reset your password -

-
-
- - - {errors.email && ( -
- {errors.email.message} -
- )} -
- -
-
+
+
+

Forgot Password? 🔒

+

+ Enter your email and we'll send you instructions to reset your password +

+ +
+
+ + + {errors.email && ( +
+ {errors.email.message} +
+ )} +
+ +
+
{ Back to login
- + + {/* Footer Text */} + +
+
); }; -export default ForgotPasswordPage; +export default ForgotPasswordPage; \ No newline at end of file diff --git a/src/pages/authentication/LoginPage.jsx b/src/pages/authentication/LoginPage.jsx index c9eb5e0d..db99e6a1 100644 --- a/src/pages/authentication/LoginPage.jsx +++ b/src/pages/authentication/LoginPage.jsx @@ -1,35 +1,26 @@ import { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; -import { AuthWrapper } from "./AuthWrapper"; -import { useNavigate } from "react-router-dom"; -import "./page-auth.css"; +import { Link, useNavigate } from "react-router-dom"; import AuthRepository from "../../repositories/AuthRepository"; import showToast from "../../services/toastService"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; +import { AuthWrapper } from "./AuthWrapper"; const LoginPage = () => { const navigate = useNavigate(); const [loading, setLoading] = useState(false); const [hidepass, setHidepass] = useState(true); const [IsLoginWithOTP, setLoginWithOtp] = useState(false); - const [IsTriedOTPThrough, setIsTriedOTPThrough] = useState(false); const now = Date.now(); const loginSchema = IsLoginWithOTP ? z.object({ - username: z.string() - .trim() - .email({ message: "Valid email required" }), + username: z.string().trim().email({ message: "Valid email required" }), }) : z.object({ - username: z.string() - .trim() - .email({ message: "Valid email required" }), - password: z.string() - .trim() - .min(1, { message: "Password required" }), + username: z.string().trim().email({ message: "Valid email required" }), + password: z.string().trim().min(1, { message: "Password required" }), rememberMe: z.boolean(), }); @@ -37,34 +28,27 @@ const LoginPage = () => { register, handleSubmit, formState: { errors }, - reset, - getValues, } = useForm({ resolver: zodResolver(loginSchema), }); const onSubmit = async (data) => { setLoading(true); - try { - const username = data.username.trim(); - const password = data.password?.trim(); - if (!IsLoginWithOTP) { const userCredential = { - username, - password, + username: data.username, + password: data.password, }; - const response = await AuthRepository.login(userCredential); localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("refreshToken", response.data.refreshToken); setLoading(false); navigate("/dashboard"); } else { - await AuthRepository.sendOTP({ email: username }); + await AuthRepository.sendOTP({ email: data.username }); showToast("OTP has been sent to your email.", "success"); - localStorage.setItem("otpUsername", username); + localStorage.setItem("otpUsername", data.username); localStorage.setItem("otpSentTime", now.toString()); navigate("/auth/login-otp"); } @@ -74,7 +58,6 @@ const LoginPage = () => { } }; - useEffect(() => { const otpSentTime = localStorage.getItem("otpSentTime"); if ( @@ -85,138 +68,181 @@ const LoginPage = () => { navigate("/auth/login-otp"); } }, [IsLoginWithOTP]); + return ( - -

Welcome to PMS!

-

- {IsLoginWithOTP - ? "Enter your email to receive a one-time password (OTP)." - : "Please sign-in to your account and start the adventure."} -

-
-
- - - {errors.username && ( -
- {errors.username.message} -
- )} -
+
+
+

Welcome to PMS!

+

+ {IsLoginWithOTP + ? "Enter your email to receive a one-time password (OTP)." + : "Please sign in to your account and start the adventure"} +

- {!IsLoginWithOTP && ( - <> -
- -
- - + + {/* Email */} +
+ + + {errors.username && ( +
+ {errors.username.message}
- {errors.password && ( -
- {errors.password.message} -
- )} -
+ )} +
- -
-
- - -
- Forgot Password? -
- - )} - -
- - {!IsLoginWithOTP &&
OR
} + {/* Password */} {!IsLoginWithOTP && ( - - )} -
- + <> + {/*
+ +
+ + setHidepass(!hidepass)} + > + + +
+ {errors.password && ( +
+ {errors.password.message} +
+ )} +
*/} -

- New on our platform? - {IsLoginWithOTP ? ( - setLoginWithOtp(false)} +

+ + {/* Remember Me + Forgot Password */} +
+
+ + +
+ + Forgot Password? + +
+ + )} + + {/* Submit */} +
+ + {/* Login With OTP Button */} + {!IsLoginWithOTP && ( + <> +
+
or
+
+ + + )} + + + {/* Footer Text */} + {!IsLoginWithOTP ? ( +

+ New on our platform? + + Request a Demo + +

) : ( - - Request a Demo - +
+ +
)} -

- +
+
); }; -export default LoginPage; +export default LoginPage; \ No newline at end of file diff --git a/src/pages/authentication/LoginWithOtp.jsx b/src/pages/authentication/LoginWithOtp.jsx index 2157b4e1..8b07f8fb 100644 --- a/src/pages/authentication/LoginWithOtp.jsx +++ b/src/pages/authentication/LoginWithOtp.jsx @@ -52,7 +52,7 @@ const LoginWithOtp = () => { setLoading(false); localStorage.removeItem("otpUsername"); localStorage.removeItem("otpSentTime"); - navigate("/dashboard"); + navigate("/dashboard"); } catch (err) { showToast("Invalid or expired OTP.", "error"); @@ -123,8 +123,9 @@ const LoginWithOtp = () => { return ( - -
+ // +
+

Verify Your OTP

Please enter the 4-digit code sent to your email.

@@ -209,8 +210,9 @@ const LoginWithOtp = () => { )} +
-
+ // ); }; diff --git a/src/pages/authentication/MainForgetPage.jsx b/src/pages/authentication/MainForgetPage.jsx new file mode 100644 index 00000000..caae1016 --- /dev/null +++ b/src/pages/authentication/MainForgetPage.jsx @@ -0,0 +1,26 @@ +import React from "react"; +// import LoginPage from "./LoginPage"; +import ForgotPasswordPage from "./ForgotPasswordPage"; + +const MainForgetPage = () => { + return ( + <> +
+
+
+ Login image +
+
+ +
+ + ); +}; +export default MainForgetPage; diff --git a/src/pages/authentication/MainLogin.jsx b/src/pages/authentication/MainLogin.jsx new file mode 100644 index 00000000..03575f40 --- /dev/null +++ b/src/pages/authentication/MainLogin.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import LoginPage from "./LoginPage"; +const MainLogin = () => { + return ( + <> +
+
+
+ Login image +
+
+ +
+ + ); +}; +export default MainLogin; diff --git a/src/pages/authentication/MainLoginWithOTPPage.jsx b/src/pages/authentication/MainLoginWithOTPPage.jsx new file mode 100644 index 00000000..53b9fec7 --- /dev/null +++ b/src/pages/authentication/MainLoginWithOTPPage.jsx @@ -0,0 +1,26 @@ +import React from 'react' +import LoginWithOtp from './LoginWithOtp' + +const MainLoginWithOTPPage = () => { + return ( + <> +
+
+
+ Login image +
+
+ +
+ + ) +} + +export default MainLoginWithOTPPage \ No newline at end of file diff --git a/src/pages/authentication/MainRegisterPage.jsx b/src/pages/authentication/MainRegisterPage.jsx new file mode 100644 index 00000000..a2a1a5ca --- /dev/null +++ b/src/pages/authentication/MainRegisterPage.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import RegisterPage from "./RegisterPage"; + +const MainRegisterPage = () => { + return ( + <> +
+
+
+ Login image +
+
+ +
+ + ); +}; +export default MainRegisterPage; diff --git a/src/pages/authentication/MainResetPasswordPage.jsx b/src/pages/authentication/MainResetPasswordPage.jsx new file mode 100644 index 00000000..36fd71e6 --- /dev/null +++ b/src/pages/authentication/MainResetPasswordPage.jsx @@ -0,0 +1,26 @@ +import React from 'react' +import ResetPasswordPage from './ResetPassword' + +const MainResetPasswordPage = () => { + return ( + <> +
+
+
+ Login image +
+
+ +
+ + ) +} + +export default MainResetPasswordPage \ No newline at end of file diff --git a/src/pages/authentication/RegisterPage.jsx b/src/pages/authentication/RegisterPage.jsx index 16517816..65b7d619 100644 --- a/src/pages/authentication/RegisterPage.jsx +++ b/src/pages/authentication/RegisterPage.jsx @@ -37,28 +37,33 @@ const registerSchema = z.object({ const RegisterPage = () => { const [registered, setRegristered] = useState(false); const [industries, setIndustries] = useState([]); + const [Loading,setLoading] = useState(false) const { register, handleSubmit, - formState: { errors }, + formState: { errors },reset } = useForm({ resolver: zodResolver(registerSchema), }); const onSubmit = async (data) => { try { + setLoading(true) const response = await MarketRepository.requestDemo(data); - showToast("Your Registration SuccessFully !"); + showToast("Your request has been sent successfully. Please stay in touch!"); setRegristered(true); + setLoading(false) + reset() } catch (error) { showToast(error.message, "error"); + setLoading(false) } }; useEffect(() => { fetchIndustries(); }, []); - useEffect(() => {}, [industries]); + useEffect(() => { }, [industries]); const fetchIndustries = async () => { try { @@ -71,23 +76,27 @@ const RegisterPage = () => { }; return ( <> - {!registered && ( - -

Adventure starts here 🚀

+ +
+
+ +

Adventure starts here

Make your app management easy and fun!

-
+ +
+
{
)}
-
+
{
)}
-
+
+
-
-
{errors.contactPerson && (
{
)}
-
+
-
-
{errors.contactNumber && (
{
)}
-
+
-
-
{errors.about && (
{
)}
-
+
-
-
{errors.oragnizationSize && (
{
)}
-
+
-
-
{errors.industryId && (
{
)}
-
+
{ {...register("terms")} /> +
{errors.terms && (
{ )}

- Already have an account? + Already have an account? - + Back to login

- - )} - {registered && ( - -
Thank you for contacting us
-

We will get back to you soon

- - - Back to login - -
- )} +
+
); }; -export default RegisterPage; +export default RegisterPage; \ No newline at end of file diff --git a/src/pages/authentication/ResetPassword.jsx b/src/pages/authentication/ResetPassword.jsx new file mode 100644 index 00000000..02a8084c --- /dev/null +++ b/src/pages/authentication/ResetPassword.jsx @@ -0,0 +1,248 @@ +import { useState } from "react"; +import { Link, useSearchParams } from "react-router-dom"; +import "./page-auth.css"; +import { AuthWrapper } from "./AuthWrapper"; +import showToast from "../../services/toastService"; +import AuthRepository from "../../repositories/AuthRepository"; +import { z } from "zod"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useNavigate } from "react-router-dom"; +import { clearAllCache } from "../../slices/apiDataManager"; + +const resetPasswordSchema = z + .object({ + email: z.string().email(), + password: z + .string() + .min(8, "Password must be at least 8 characters") + .regex(/[A-Z]/, "Password must contain at least one uppercase letter") + .regex(/[a-z]/, "Password must contain at least one lowercase letter") + .regex(/\d/, "Password must contain at least one number") + .regex( + /[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/, + "Password must contain at least one special character" + ), + + confirmPassword: z + .string() + .min(8, "Password must be at least 8 characters"), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], + }); + +const ResetPassword = () => { + const [searchParams] = useSearchParams(); + const [loading, setLoading] = useState(false); + const [hidepass, setHidepass] = useState(true); + const [hidepass1, setHidepass1] = useState(true); + const [tokenExpired, setTokenExpired] = useState(false); + + const token = searchParams.get("token"); + const navigate = useNavigate(); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(resetPasswordSchema), + }); + + const onSubmitResetPassword = async (data) => { + try { + setLoading(true); + const { email, password, confirmPassword } = data; + + let reqObject = { + email, + token: token, + newPassword: password, + }; + let response = await AuthRepository.resetPassword(reqObject); + showToast("Password Reseted", "success"); + clearAllCache(); + navigate("/auth/login", { replace: true }); + // setLoading(false); + } catch (error) { + setLoading(false); + if (error?.response?.status === 400) { + showToast("Please check valid Credentials", "error"); + } else { + setTokenExpired(true); + } + } + }; + + if (tokenExpired) { + return ( + +

Invalid Link 🔒

+

+ This link appears to be invalid or expired. Please use the 'Forgot + Password' feature to set your new password. +

+
+ + Go to Forgot Password + +
+
+ ); + } + return ( +
+
+

Reset Password? 🔒

+

Enter your email and new password to update.

+
+
+ + + {errors.email && ( +
+ {errors.email.message} +
+ )} +
+ +
+
+ +
+ +
+ + +
+ + {errors.password && ( +
+ {errors.password.message} +
+ )} +
+ {" "} + {" "} +
+
+ + +
+ {errors.confirmPassword && ( +
+ {errors.confirmPassword.message} +
+ )} +
+ +
+

+ Password must be at least 8 characters +

+

+ Password must contain at least one uppercase letter +

+

+ Password must contain at least one number +

+

+ Password must contain at least one special character +

+
+ + +
+ + + Back to login + +
+
+
+ ); +}; + +export default ResetPassword; diff --git a/src/pages/authentication/ResetPasswordPage.jsx b/src/pages/authentication/ResetPasswordPage.jsx deleted file mode 100644 index 21b03df0..00000000 --- a/src/pages/authentication/ResetPasswordPage.jsx +++ /dev/null @@ -1,245 +0,0 @@ -import { useState } from "react"; -import { Link, useSearchParams } from "react-router-dom"; -import "./page-auth.css"; -import { AuthWrapper } from "./AuthWrapper"; -import showToast from "../../services/toastService"; -import AuthRepository from "../../repositories/AuthRepository"; -import { z } from "zod"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useNavigate } from "react-router-dom"; -import { clearAllCache } from "../../slices/apiDataManager"; - -const resetPasswordSchema = z - .object({ - email: z.string().email(), - password: z - .string() - .min(8, "Password must be at least 8 characters") - .regex(/[A-Z]/, "Password must contain at least one uppercase letter") - .regex(/[a-z]/, "Password must contain at least one lowercase letter") - .regex(/\d/, "Password must contain at least one number") - .regex( - /[!@#$%^&*()_+{}\[\]:;<>,.?~\\/-]/, - "Password must contain at least one special character" - ), - - confirmPassword: z - .string() - .min(8, "Password must be at least 8 characters"), - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords do not match", - path: ["confirmPassword"], - }); - -const ResetPasswordPage = () => { - const [searchParams] = useSearchParams(); - const [loading, setLoading] = useState(false); - const [hidepass, setHidepass] = useState(true); - const [hidepass1, setHidepass1] = useState(true); - const [tokenExpired, setTokenExpired] = useState(false); - - const token = searchParams.get("token"); - const navigate = useNavigate(); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(resetPasswordSchema), - }); - - const onSubmitResetPassword = async (data) => { - try { - setLoading(true); - const { email, password, confirmPassword } = data; - - let reqObject = { - email, - token: token, - newPassword: password, - }; - let response = await AuthRepository.resetPassword(reqObject); - showToast("Password Reseted", "success"); - clearAllCache(); - navigate("/auth/login", { replace: true }); - // setLoading(false); - } catch (error) { - debugger; - setLoading(false); - if (error?.response?.status === 400) { - showToast("Please check valid Credentials", "error"); - } else { - setTokenExpired(true); - } - } - }; - - if (tokenExpired) { - return ( - -

Invalid Link 🔒

-

- This link appears to be invalid or expired. Please use the 'Forgot - Password' feature to set your new password. -

-
- - Go to Forgot Password - -
-
- ); - } - return ( - -

Reset Password? 🔒

-

Enter your email and new password to update.

-
-
- - - {errors.email && ( -
- {errors.email.message} -
- )} -
- -
-
- -
-
- - -
- {errors.password && ( -
- {errors.password.message} -
- )} -
- {" "} - {" "} -
-
- - -
- {errors.confirmPassword && ( -
- {errors.confirmPassword.message} -
- )} -
- -
-

- Password must be at least 8 characters -

-

- Password must contain at least one uppercase letter -

-

- Password must contain at least one number -

-

- Password must contain at least one special character -

-
- - -
- - - Back to login - -
-
- ); -}; - -export default ResetPasswordPage; diff --git a/src/pages/authentication/page-auth.css b/src/pages/authentication/page-auth.css index 86ceb214..8492495b 100644 --- a/src/pages/authentication/page-auth.css +++ b/src/pages/authentication/page-auth.css @@ -18,6 +18,7 @@ } .authentication-wrapper.authentication-cover .authentication-inner { height: 100vh; + background-color: #fff; } .authentication-wrapper.authentication-basic .authentication-inner { max-width: 400px; diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index 935add9b..648b736b 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -18,7 +18,7 @@ import { VIEW_ALL_EMPLOYEES, VIEW_TEAM_MEMBERS, } from "../../utils/constants"; -import { clearCacheKey, useSelectedproject } from "../../slices/apiDataManager"; +import { clearCacheKey } from "../../slices/apiDataManager"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp import { @@ -38,10 +38,9 @@ import usePagination from "../../hooks/usePagination"; import { setProjectId } from "../../slices/localVariablesSlice"; const EmployeeList = () => { - // const selectedProjectId = useSelector( - // (store) => store.localVariables.projectId - // ); - const selectedProjectId = useSelectedproject(); + const selectedProjectId = useSelector( + (store) => store.localVariables.projectId + ); const { projectNames, loading: projectLoading, fetchData } = useProjectName(); const dispatch = useDispatch(); @@ -177,10 +176,12 @@ const EmployeeList = () => { useEffect(() => { if (!loading && Array.isArray(employees)) { const sorted = [...employees].sort((a, b) => { - const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || "" - }`.toLowerCase(); - const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || "" - }`.toLowerCase(); + const nameA = `${a.firstName || ""}${a.middleName || ""}${ + a.lastName || "" + }`.toLowerCase(); + const nameB = `${b.firstName || ""}${b.middleName || ""}${ + b.lastName || "" + }`.toLowerCase(); return nameA?.localeCompare(nameB); }); @@ -259,37 +260,27 @@ const EmployeeList = () => { )} {IsDeleteModalOpen && ( -
- - suspendEmployee({ - employeeId: selectedEmpFordelete.id, - active: !selectedEmpFordelete.isActive, - }) - } - onClose={() => setIsDeleteModalOpen(false)} - loading={employeeLodaing} - /> -
+ + suspendEmployee({ + employeeId: id, + active: !selectedEmpFordelete.isActive, + }) + } + onClose={() => setIsDeleteModalOpen(false)} + loading={employeeLodaing} + paramData={selectedEmpFordelete.id} + /> )}
@@ -303,11 +294,11 @@ const EmployeeList = () => { {ViewTeamMember ? ( //
-
+
{/* Switches: All Employees + Inactive */} @@ -327,7 +318,7 @@ const EmployeeList = () => { className="form-check-label ms-0" htmlFor="allEmployeesCheckbox" > - All Employees + Show All Employees
)} @@ -363,7 +354,7 @@ const EmployeeList = () => { value={searchText} onChange={handleSearch} className="form-control form-control-sm" - placeholder="Search User" + placeholder="Search Employee" aria-controls="DataTables_Table_0" /> @@ -511,8 +502,9 @@ const EmployeeList = () => { Status { )} {/* Conditional messages for no data or no search results */} {!loading && - displayData?.length === 0 && - searchText && - !showAllEmployees ? ( + displayData?.length === 0 && + searchText && + !showAllEmployees ? ( @@ -544,8 +536,8 @@ const EmployeeList = () => { ) : null} {!loading && - displayData?.length === 0 && - (!searchText || showAllEmployees) ? ( + displayData?.length === 0 && + (!searchText || showAllEmployees) ? ( { ) : ( - NA + - )} @@ -639,7 +631,9 @@ const EmployeeList = () => {
{/* View always visible */} {/* Suspend only when active */} @@ -661,7 +658,8 @@ const EmployeeList = () => { className="dropdown-item py-1" onClick={() => handleOpenDelete(item)} > - Suspend + {" "} + Suspend )} @@ -670,11 +668,13 @@ const EmployeeList = () => { type="button" data-bs-toggle="modal" data-bs-target="#managerole-modal" - onClick={() => setEmpForManageRole(item.id)} + onClick={() => + setEmpForManageRole(item.id) + } > - Manage Role + {" "} + Manage Role - )} @@ -684,7 +684,8 @@ const EmployeeList = () => { className="dropdown-item py-1" onClick={() => handleOpenDelete(item)} > - Re-activate + {" "} + Re-activate )}
@@ -703,8 +704,9 @@ const EmployeeList = () => {
-
{renderContent()}
+
{renderContent()}
diff --git a/src/pages/master/MasterPage.jsx b/src/pages/master/MasterPage.jsx index 72710679..1640f2ba 100644 --- a/src/pages/master/MasterPage.jsx +++ b/src/pages/master/MasterPage.jsx @@ -1,192 +1,189 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useState, useMemo, useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useQueryClient } from "@tanstack/react-query"; import Breadcrumb from "../../components/common/Breadcrumb"; import MasterModal from "../../components/master/MasterModal"; -import { mastersList } from "../../data/masters"; -import { useDispatch, useSelector } from "react-redux"; -import { changeMaster } from "../../slices/localVariablesSlice"; -import useMaster, { useMasterMenu } from "../../hooks/masterHook/useMaster" +import ConfirmModal from "../../components/common/ConfirmModal"; import MasterTable from "./MasterTable"; -import { getCachedData } from "../../slices/apiDataManager"; +import useMaster, { + useDeleteMasterItem, + useMasterMenu, +} from "../../hooks/masterHook/useMaster"; +import { changeMaster } from "../../slices/localVariablesSlice"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { MANAGE_MASTER } from "../../utils/constants"; -import { useQueryClient } from "@tanstack/react-query"; - +import GlobalModel from "../../components/common/GlobalModel"; const MasterPage = () => { - const {data,isLoading,isError,error:menuError} = useMasterMenu() - const [modalConfig, setModalConfig] = useState({ modalType: "", item: null, masterType: null }); - const [searchTerm, setSearchTerm] = useState(''); - const [filteredResults, setFilteredResults] = useState([]); - const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); - - const hasMasterPermission = useHasUserPermission(MANAGE_MASTER); const dispatch = useDispatch(); - const selectedMaster = useSelector((store) => store.localVariables.selectedMaster); const queryClient = useQueryClient(); + const selectedMaster = useSelector( + (store) => store.localVariables.selectedMaster + ); + const hasMasterPermission = useHasUserPermission(MANAGE_MASTER); - const { data: masterData = [], loading, error, RecallApi,isError:isMasterError } = useMaster(); + const { + data: menuData, + isLoading: menuLoading, + isError: menuErrorFlag, + error: menuError, + } = useMasterMenu(); + const { + data: masterData = [], + loading, + isError: isMasterError, + } = useMaster(); + const { mutate: DeleteMaster, isPending: isDeleting } = useDeleteMasterItem(); - const openModal = () => setIsCreateModalOpen(true); + const [modalConfig, setModalConfig] = useState(null); + const [deleteData, setDeleteData] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); - const closeModal = () => { - setIsCreateModalOpen(false); - setModalConfig(null); - - // Clean up Bootstrap modal manually - const modalEl = document.getElementById('master-modal'); - modalEl?.classList.remove('show'); - if (modalEl) modalEl.style.display = 'none'; - - document.body.classList.remove('modal-open'); - document.body.style.overflow = 'auto'; - - document.querySelectorAll('.modal-backdrop').forEach((el) => el.remove()); - }; - - const handleModalData = (modalType, item, masterType = selectedMaster) => { - setModalConfig({ modalType, item, masterType }); - }; - - const handleSearch = (e) => { - const value = e.target.value.toLowerCase(); - setSearchTerm(value); - - if (!masterData?.length) return; - - const results = masterData.filter((item) => - Object.values(item).some( - (field) => field?.toString().toLowerCase().includes(value) + const displayData = useMemo(() => { + const dataSource = + queryClient.getQueryData(["masterData", selectedMaster]) || masterData; + if (!searchTerm) return dataSource; + return dataSource.filter((item) => + Object.values(item).some((val) => + val?.toString().toLowerCase().includes(searchTerm.toLowerCase()) ) ); - setFilteredResults(results); - }; - const displayData = useMemo(() => { - if (searchTerm) return filteredResults; - return queryClient.getQueryData(["masterData", selectedMaster]) || masterData; - }, [searchTerm, filteredResults, selectedMaster, masterData]); + }, [searchTerm, masterData, selectedMaster, queryClient]); const columns = useMemo(() => { - if (!displayData?.length) return []; + if (!displayData.length) return []; return Object.keys(displayData[0]).map((key) => ({ key, label: key.toUpperCase(), })); }, [displayData]); - useEffect(() => { - if (modalConfig) openModal(); - }, [modalConfig]); + const handleModalData = (type, item = null, masterType = selectedMaster) => { + if (type === "delete") setDeleteData({ item, masterType }); + else setModalConfig({ modalType: type, item, masterType }); + }; - useEffect(() => { - return () => { - setIsCreateModalOpen(false); - closeModal(); - }; - }, []); + const handleDeleteSubmit = () => { + if (!deleteData) return; + DeleteMaster( + { masterType: deleteData.masterType, item: deleteData.item }, + { + onSuccess: () => setDeleteData(null), + } + ); + }; + + if (menuErrorFlag || isMasterError) + return ( +
+

+ Oops, an error + occurred +

+

+ {menuError?.message || "Error fetching master data"} +

+
+ ); - if(isError || isMasterError) return
-

Oops, an error occurred

-

{error?.message || menuError?.message}

-
return ( <> - {isCreateModalOpen && ( - - + {modalConfig && ( + setModalConfig(null)} + > + setModalConfig(null)} + /> + )} + setDeleteData(null)} + /> +
+ data={[{ label: "Home", link: "/dashboard" }, { label: "Masters" }]} + /> +
-
-
-
-
-
-
- -
-
-
-
-
-
- -
-
- {" "} -
- - {" "} -
-
-
-
+
+
+
+ +
+
+
+ setSearchTerm(e.target.value)} + /> +
+ {hasMasterPermission && ( + + )}
- - -
+ +
-
); diff --git a/src/pages/master/MasterTable.jsx b/src/pages/master/MasterTable.jsx index bc7c10c0..b2285842 100644 --- a/src/pages/master/MasterTable.jsx +++ b/src/pages/master/MasterTable.jsx @@ -9,7 +9,7 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { const selectedMaster = useSelector( (store) => store.localVariables.selectedMaster ); - const hiddenColumns = [ + const hiddenColumns = [ "id", "featurePermission", "tenant", @@ -20,7 +20,14 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { "noOfPersonsRequired", "color", "displayName", - "permissionIds" + "permissionIds", + "entityTypeId", + "regexExpression", + "isMandatory", + "maxFilesAllowed", + "maxSizeAllowedInMB", + "isValidationRequired", + "documentCategory", ]; const safeData = Array.isArray(data) ? data : []; @@ -64,28 +71,35 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { : col.label, })); - const handleSystemDefined = (message) =>{ - if(message){ - showToast(`The system-defined item ${selectedMaster} cannot be ${message}.`) - } + const handleSystemDefined = (message) => { + if (message) { + showToast( + `The system-defined item ${selectedMaster} cannot be ${message}.` + ); } + }; return (
{loading ? (

Loading...

) : ( - - +
+ - - + + @@ -120,28 +134,28 @@ const MasterTable = ({ data, columns, loading, handleModalData }) => { ))}
{selectedMaster === "Activity" ? "Activity" : "Name"} {selectedMaster === "Activity" ? "Unit" : "Description"} + {" "} + {selectedMaster === "Activity" ? "Activity" : "Name"} + + {" "} + {selectedMaster === "Activity" + ? "Unit" + : selectedMaster === "Document Type" + ? "Content Type" + : "Description"} + Actions - {(selectedMaster === "Application Role" || selectedMaster === "Work Category") && item?.isSystem ? ( + {(selectedMaster === "Application Role" || + selectedMaster === "Work Category") && + item?.isSystem ? ( <> - + - - + + ) : ( <> diff --git a/src/pages/project/ProjectDetails.jsx b/src/pages/project/ProjectDetails.jsx index 75f7705b..deaf8a9e 100644 --- a/src/pages/project/ProjectDetails.jsx +++ b/src/pages/project/ProjectDetails.jsx @@ -13,32 +13,41 @@ import { cacheData, clearCacheKey, getCachedData, - useSelectedproject, + useSelectedProject, } from "../../slices/apiDataManager"; import "./ProjectDetails.css"; -import { - useProjectDetails, -} from "../../hooks/useProjects"; +import { useProjectDetails } from "../../hooks/useProjects"; import { ComingSoonPage } from "../Misc/ComingSoonPage"; -import Directory from "../Directory/Directory"; import eventBus from "../../services/eventBus"; import ProjectProgressChart from "../../components/Dashboard/ProjectProgressChart"; import { useProjectName } from "../../hooks/useProjects"; import AttendanceOverview from "../../components/Dashboard/AttendanceChart"; import { setProjectId } from "../../slices/localVariablesSlice"; +import ProjectDocument from "../../components/Project/ProjectDocuments"; +import ProjectDocuments from "../../components/Project/ProjectDocuments"; +import ProjectSetting from "../../components/Project/ProjectSetting"; +import DirectoryPage from "../Directory/DirectoryPage"; +import { useHasAnyPermission } from "../../hooks/useExpense"; +import { VIEW_PROJECTS } from "../../utils/constants"; +import { useNavigate, useRoutes } from "react-router-dom"; const ProjectDetails = () => { - const projectId = useSelectedproject() + const projectId = useSelectedProject() + const CanViewProject = useHasAnyPermission(VIEW_PROJECTS); + const navigate = useNavigate() const { projectNames, fetchData } = useProjectName(); - const dispatch = useDispatch() + const dispatch = useDispatch(); useEffect(() => { + if(!CanViewProject){ + navigate("/dashboard") + } if (projectId == null) { dispatch(setProjectId(projectNames[0]?.id)); } - }, [projectNames]) + }, [projectNames]); const { projects_Details, @@ -49,12 +58,15 @@ const ProjectDetails = () => { // const [activePill, setActivePill] = useState("profile"); const [activePill, setActivePill] = useState(() => { - return localStorage.getItem("lastActiveProjectTab") || "profile"; -}); + return localStorage.getItem("lastActiveProjectTab") || "profile"; + }); const handler = useCallback( (msg) => { - if (msg.keyword === "Update_Project" && projects_Details?.id === msg.response.id) { + if ( + msg.keyword === "Update_Project" && + projects_Details?.id === msg.response.id + ) { refetch(); } }, @@ -66,11 +78,10 @@ const ProjectDetails = () => { return () => eventBus.off("project", handler); }, [handler]); - const handlePillClick = (pillKey) => { - setActivePill(pillKey); - localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage -}; - + const handlePillClick = (pillKey) => { + setActivePill(pillKey); + localStorage.setItem("lastActiveProjectTab", pillKey); // ✅ Save to localStorage + }; const renderContent = () => { if (projectLoading || !projects_Details) return ; @@ -80,14 +91,19 @@ const ProjectDetails = () => { return ( <>
-
+
- -
- + +
+ {" "} + +
@@ -103,19 +119,27 @@ const ProjectDetails = () => { ); case "infra": - return ( - - ); + return ; case "workplan": - return ( - - ); + return ; case "directory": return (
- + +
+ ); + case "documents": + return ( +
+ +
+ ); + case "setting": + return ( +
+
); @@ -142,4 +166,4 @@ const ProjectDetails = () => { ); }; -export default ProjectDetails; \ No newline at end of file +export default ProjectDetails; diff --git a/src/repositories/DirectoryRepository.jsx b/src/repositories/DirectoryRepository.jsx index e19571cc..da3286c3 100644 --- a/src/repositories/DirectoryRepository.jsx +++ b/src/repositories/DirectoryRepository.jsx @@ -3,16 +3,27 @@ import { api } from "../utils/axiosClient"; export const DirectoryRepository = { GetOrganizations: () => api.get("/api/directory/organization"), GetDesignations: () => api.get("/api/directory/designations"), - GetContacts: (isActive, projectId) => { - const params = new URLSearchParams(); - params.append("active", isActive); + GetContact: (id) => api.get(`/api/Directory/profile/${id}`), - if (projectId) { - params.append("projectId", projectId); - } - - return api.get(`/api/Directory?${params.toString()}`); + GetContacts: ( + isActive, + projectId, + pageSize, + pageNumber, + filter, + searchString + ) => { + const payloadJsonString = JSON.stringify(filter); + return api.get( + `/api/Directory/list?active=${isActive}` + + (projectId ? `&projectId=${projectId}` : "") + + `&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${encodeURIComponent( + payloadJsonString + )}&searchString=${encodeURIComponent(searchString)}` + ); }, + + GetContactFilter: () => api.get("/api/directory/contact/filter"), CreateContact: (data) => api.post("/api/directory", data), UpdateContact: (id, data) => api.put(`/api/directory/${id}`, data), DeleteContact: (id, isActive) => @@ -28,12 +39,28 @@ export const DirectoryRepository = { GetContactProfile: (id) => api.get(`/api/directory/profile/${id}`), CreateNote: (data) => api.post("/api/directory/note", data), - GetNote: (id, isActive) => + GetContactNotes: (id, isActive) => api.get(`/api/directory/notes/${id}?active=${isActive}`), + UpdateNote: (id, data) => api.put(`/api/directory/note/${id}`, data), DeleteNote: (id, isActive) => api.delete(`/api/directory/note/${id}?active=${isActive}`), - GetNotes: (pageSize, pageNumber) => - api.get(`/api/directory/notes?pageSize=${pageSize}&pageNumber=${pageNumber}`), + GetNotes: ( + projectId, + pageSize, + pageNumber, + filter, + searchString + ) => { + const payloadJsonString = JSON.stringify(filter); + return api.get( + `/api/directory/notes?` + + (projectId ? `projectId=${projectId}&` : "&") + + `pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${encodeURIComponent( + payloadJsonString + )}&searchString=${encodeURIComponent(searchString)}` + ); + }, + GetNoteFilter:()=>api.get("/api/directory/notes/filter") }; diff --git a/src/repositories/DocumentRepository.jsx b/src/repositories/DocumentRepository.jsx new file mode 100644 index 00000000..0f9376dd --- /dev/null +++ b/src/repositories/DocumentRepository.jsx @@ -0,0 +1,26 @@ +import { api } from "../utils/axiosClient"; + +export const DocumentRepository = { + uploadDocument:(data)=> api.post(`/api/Document/upload`,data), + getDocumentList:(entityTypeId,entityId,pageSize, pageNumber, filter,searchString,isActive)=>{ + const payloadJsonString = JSON.stringify(filter); + return api.get(`/api/Document/list/${entityTypeId}/entity/${entityId}/?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}&isActive=${isActive}`) + }, + getDocumentById:(id)=>api.get(`/api/Document/get/details/${id}`), + + getFilterEntities:(entityTypeId)=>api.get(`/api/Document/get/filter/${entityTypeId}`), + + UpdateDocument:(documentId,data)=>api.put(`/api/Document/edit/${documentId}`,data), + + getDocumentVersionList:(parentAttachmentId,pageSize,pageNumber)=>api.get(`/api/Document/list/versions/${parentAttachmentId}/?pageSize=${pageSize}&pageNumber=${pageNumber}`), + + getDocumentVersion:(id)=>api.get(`/api/Document/get/version/${id}`), + + verifyDocument:(id,isVerify)=>api.post(`/api/Document/verify/${id}/?isVerify=${isVerify}`), + + deleteDocument:(id,isActive)=>api.delete(`/api/Document/delete/${id}/?isActive=${isActive}`), + + getDocumentTags:()=>api.get('/api/Document/get/tags') + + +} \ No newline at end of file diff --git a/src/repositories/MarketRepository.jsx b/src/repositories/MarketRepository.jsx index 4baa3c74..e71393d8 100644 --- a/src/repositories/MarketRepository.jsx +++ b/src/repositories/MarketRepository.jsx @@ -1,6 +1,6 @@ import { api } from "../utils/axiosClient"; export const MarketRepository = { - requestDemo: (data) => api.post("/api/market/inquiry", data), + requestDemo: (data) => api.post("/api/market/enquire", data), getIndustries: () => api.get("api/market/industries"), }; diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index cfb9a890..a853fdbe 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -18,7 +18,7 @@ export const RolesRepository = { }; export const MasterRespository = { - getMasterMenus:()=>api.get("/api/AppMenu/get/master-list"), + getMasterMenus: () => api.get("/api/AppMenu/get/master-list"), getRoles: () => api.get("/api/roles"), createRole: (data) => api.post("/api/roles", data), @@ -48,6 +48,9 @@ export const MasterRespository = { api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)), "Expense Status": (id, isActive) => api.delete(`/api/Master/expenses-status/delete/${id}`, (isActive = false)), + "Document Type": (id) => api.delete(`/api/Master/document-type/delete/${id}`), + "Document Category": (id) => + api.delete(`/api/Master/document-category/delete/${id}`), getWorkCategory: () => api.get(`/api/master/work-categories`), createWorkCategory: (data) => api.post(`/api/master/work-category`, data), @@ -81,4 +84,26 @@ export const MasterRespository = { createExpenseStatus: (data) => api.post("/api/Master/expenses-status", data), updateExepnseStatus: (id, data) => api.put(`/api/Master/expenses-status/edit/${id}`, data), + + getDocumentCategories: (entityType) => + api.get( + `/api/Master/document-category/list${ + entityType ? `?entityTypeId=${entityType}` : "" + }` + ), + createDocumenyCategory: (data) => + api.post(`/api/Master/document-category`, data), + updateDocumentCategory: (id, data) => + api.put(`/api/Master/document-category/edit/${id}`, data), + + getDocumentTypes: (category) => + api.get( + `/api/Master/document-type/list${ + category ? `?documentCategoryId=${category}` : "" + }` + ), + + createDocumentType: (data) => api.post(`/api/Master/document-type`, data), + updateDocumentType: (id, data) => + api.put(`/api/Master/document-type/edit/${id}`, data), }; diff --git a/src/repositories/ProjectRepository.jsx b/src/repositories/ProjectRepository.jsx index b2f0f7ff..fc1c648d 100644 --- a/src/repositories/ProjectRepository.jsx +++ b/src/repositories/ProjectRepository.jsx @@ -37,6 +37,15 @@ const ProjectRepository = { api.get( `/api/project/tasks-employee/${id}?fromDate=${fromDate}&toDate=${toDate}` ), + + + // Permission Managment for Employee at Project Level + + getProjectLevelEmployeeList:(projectId)=>api.get(`/api/Project/get/proejct-level/employees/${projectId}`), + getProjectLevelModules:()=>api.get(`/api/Project/get/proejct-level/modules`), + getProjectLevelEmployeePermissions:(employeeId,projectId)=>api.get(`/api/Project/get/project-level-permission/employee/${employeeId}/project/${projectId}`), + updateProjectLevelEmployeePermission:(data)=>api.post(`/api/Project/assign/project-level-permission`,data), + getAllProjectLevelPermission:(projectId)=>api.get(`/api/Project/get/all/project-level-permission/${projectId}`) }; export const TasksRepository = { diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index ac2577c1..616b8893 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -1,4 +1,3 @@ -// AppRoutes.jsx import React from "react"; import { createBrowserRouter, RouterProvider, Outlet } from "react-router-dom"; @@ -10,7 +9,6 @@ import HomeLayout from "../layouts/HomeLayout"; import LoginPage from "../pages/authentication/LoginPage"; import RegisterPage from "../pages/authentication/RegisterPage"; import ForgotPasswordPage from "../pages/authentication/ForgotPasswordPage"; -import ResetPasswordPage from "../pages/authentication/ResetPasswordPage"; import ChangePasswordPage from "../pages/authentication/ChangePassword"; // Home & Protected Pages @@ -35,26 +33,38 @@ import LegalInfoCard from "../pages/TermsAndConditions/LegalInfoCard"; // Protected Route Wrapper import ProtectedRoute from "./ProtectedRoute"; -import Directory from "../pages/Directory/Directory"; import LoginWithOtp from "../pages/authentication/LoginWithOtp"; -import TenantPage from "../pages/Tenant/TenantPage"; -import CreateTenant from "../pages/Tenant/CreateTenant"; import ExpensePage from "../pages/Expense/ExpensePage"; import TenantDetails from "../pages/Tenant/TenantDetails"; import SelfTenantDetails from "../pages/Tenant/SelfTenantDetails"; import SuperTenantDetails from "../pages/Tenant/SuperTenantDetails"; import ImageGalleryPage from "../pages/ImageGallery/ImageGalleryPage"; +import DirectoryPage from "../pages/Directory/DirectoryPage"; +import RootRedirect from "./RootRedirect"; +import MainLogin from "../pages/authentication/MainLogin"; +import MainLoginWithOTPPage from "../pages/authentication/MainLoginWithOTPPage"; +import MainRegisterPage from "../pages/authentication/MainRegisterPage"; +import MainForgetPage from "../pages/authentication/MainForgetPage"; +import MainResetPasswordPage from "../pages/authentication/MainResetPasswordPage"; +import TenantPage from "../pages/Tenant/TenantPage"; +import { Navigate } from "react-router-dom"; +import CreateTenant from "../pages/Tenant/CreateTenant"; +import LandingPage from "../pages/Home/LandingPage"; const router = createBrowserRouter( [ + { + path: "/", + element: , + }, { element: , children: [ - { path: "/auth/login", element: }, - { path: "/auth/login-otp", element: }, - { path: "/auth/reqest/demo", element: }, - { path: "/auth/forgot-password", element: }, - { path: "/reset-password", element: }, + { path: "/auth/login", element: }, + { path: "/auth/login-otp", element: }, + { path: "/auth/reqest/demo", element: }, + { path: "/auth/forgot-password", element: }, + { path: "/reset-password", element: }, { path: "/legal-info", element: }, { path: "/auth/changepassword", element: }, ], @@ -66,7 +76,6 @@ const router = createBrowserRouter( { element: , children: [ - { path: "/", element: }, { path: "/dashboard", element: }, { path: "/projects", element: }, { path: "/projects/details", element: }, @@ -75,7 +84,7 @@ const router = createBrowserRouter( { path: "/employee/:employeeId", element: }, // { path: "/employee/manage", element: }, // {path: "/employee/manage/:employeeId", element: }, - { path: "/directory", element: }, + { path: "/directory", element: }, { path: "/inventory", element: }, { path: "/activities/attendance", element: }, { path: "/activities/records/:projectId?", element: }, @@ -101,7 +110,6 @@ const router = createBrowserRouter( }, ], { - // ✅ Enables the v7 splat path resolution behavior future: { v7_relativeSplatPath: true, v7_startTransition: true, diff --git a/src/router/RootRedirect.jsx b/src/router/RootRedirect.jsx new file mode 100644 index 00000000..d00f99fe --- /dev/null +++ b/src/router/RootRedirect.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Navigate } from "react-router-dom"; + +const RootRedirect = () => { + const isAuthenticated = !!localStorage.getItem("jwtToken"); + return isAuthenticated ? ( + + ) : ( + + ); +}; + +export default RootRedirect; diff --git a/src/slices/apiDataManager.jsx b/src/slices/apiDataManager.jsx index d0f8bc67..ad4d38a1 100644 --- a/src/slices/apiDataManager.jsx +++ b/src/slices/apiDataManager.jsx @@ -4,10 +4,9 @@ import { clearApiCacheKey, flushApiCache, } from "../slices/apiCacheSlice"; -import {setLoginUserPermmisions} from "./globalVariablesSlice"; +import { setLoginUserPermmisions } from "./globalVariablesSlice"; import { useSelector } from "react-redux"; - // Cache data export const cacheData = (key, data) => { store.dispatch(cacheApiResponse({ key, data })); @@ -28,26 +27,29 @@ export const clearAllCache = () => { store.dispatch(flushApiCache()); }; - - -export const cacheProfileData = ( data) => { +export const cacheProfileData = (data) => { store.dispatch(setLoginUserPermmisions(data)); }; - // Get cached data export const getCachedProfileData = () => { return store.getState().globalVariables.loginUser; }; -export const useSelectedproject = () => { - const selectedProject = useSelector((store)=> store.localVariables.projectId); - var project = localStorage.getItem("project"); - if(project){ - return project - } else{ - return selectedProject +export const useSelectedProject = () => { + const selectedProject = useSelector( + (store) => store.localVariables.projectId + ); + + const project = localStorage.getItem("project"); + + if (project) { + try { + const parsed = JSON.parse(project); + return parsed ?? selectedProject; + } catch (e) { + return selectedProject; + } } - // return project ? selectedProject - - -}; \ No newline at end of file + + return selectedProject; +} \ No newline at end of file diff --git a/src/slices/localVariablesSlice.jsx b/src/slices/localVariablesSlice.jsx index 7c996608..0b0a8405 100644 --- a/src/slices/localVariablesSlice.jsx +++ b/src/slices/localVariablesSlice.jsx @@ -23,7 +23,7 @@ const localVariablesSlice = createSlice({ setProjectId: (state, action) => { localStorage.setItem("project",null) state.projectId = action.payload; - localStorage.setItem("project",state.projectId) + localStorage.setItem("project",state.projectId || null) }, refreshData: ( state, action ) => { diff --git a/src/utils/FileIcon.jsx b/src/utils/FileIcon.jsx new file mode 100644 index 00000000..67088713 --- /dev/null +++ b/src/utils/FileIcon.jsx @@ -0,0 +1,26 @@ +const contentTypeIcons = { + "application/pdf": "fa-solid fa-file-pdf text-danger", + "application/msword": "fa-solid fa-file-word text-primary", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + "fa-solid fa-file-word text-primary", + "application/vnd.ms-excel": "fa-solid fa-file-excel text-success", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + "fa-solid fa-file-excel text-success", + "application/vnd.ms-powerpoint": "fa-solid fa-file-powerpoint text-warning", + "application/vnd.openxmlformats-officedocument.presentationml.presentation": + "fa-solid fa-file-powerpoint text-warning", + "image/jpg": "fa-solid fa-file-image text-info", + "image/jpeg": "fa-solid fa-file-image text-info", + "image/png": "fa-solid fa-file-image text-info", + "image/gif": "fa-solid fa-file-image text-info", + "text/plain": "fa-solid fa-file-lines text-secondary", + "text/csv": "fa-solid fa-file-csv text-success", + "application/json": "fa-solid fa-file-code text-dark", + folder: "fa-solid fa-folder text-warning", // special for folders + default: "fa-solid fa-file text-muted", +}; + +export const FileIcon = ({ type, size = "fs-4", className = "" }) => { + const iconClass = contentTypeIcons[type] || contentTypeIcons.default; + return ; +}; \ No newline at end of file diff --git a/src/utils/appUtils.js b/src/utils/appUtils.js index 17686ced..21f7fa5a 100644 --- a/src/utils/appUtils.js +++ b/src/utils/appUtils.js @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; - +import { format, parseISO } from "date-fns"; export const formatFileSize=(bytes)=> { if (bytes < 1024) return bytes + " B"; else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; @@ -61,3 +61,19 @@ export const getIconByFileType = (type = "") => { return "bx bx-file"; }; + + +export const normalizeAllowedContentTypes = (allowedContentType) => { + if (!allowedContentType) return []; + if (Array.isArray(allowedContentType)) return allowedContentType; + if (typeof allowedContentType === "string") return allowedContentType.split(","); + return []; +}; + + +export function localToUtc(localDateString) { + if (!localDateString || localDateString.trim() === "") return null; // return null instead of undefined + const date = new Date(localDateString); + if (isNaN(date.getTime())) return null; // invalid date check + return date.toISOString(); +} \ No newline at end of file diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 555e3a0b..1af801ca 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -1,7 +1,7 @@ export const THRESH_HOLD = 48; // hours export const DURATION_TIME = 10; // minutes export const ITEMS_PER_PAGE = 20; -export const OTP_EXPIRY_SECONDS = 600 // OTP time +export const OTP_EXPIRY_SECONDS = 300 // OTP time export const MANAGE_MASTER = "588a8824-f924-4955-82d8-fc51956cf323"; @@ -17,6 +17,7 @@ export const VIEW_ALL_EMPLOYEES = "60611762-7f8a-4fb5-b53f-b1139918796b" export const VIEW_TEAM_MEMBERS = "b82d2b7e-0d52-45f3-997b-c008ea460e7f" +export const MANAGE_TEAM = "b94802ce-0689-4643-9e1d-11c86950c35b" export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373" @@ -39,12 +40,14 @@ export const VIEW_TASK = "9fcc5f87-25e3-4846-90ac-67a71ab92e3c" export const ASSIGN_REPORT_TASK = "6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2" +// ------------------------Directory------------------------------------- export const DIRECTORY_ADMIN = "4286a13b-bb40-4879-8c6d-18e9e393beda" export const DIRECTORY_MANAGER = "62668630-13ce-4f52-a0f0-db38af2230c5" export const DIRECTORY_USER = "0f919170-92d4-4337-abd3-49b66fc871bb" +// -----------------------Expense---------------------------------------- export const VIEW_SELF_EXPENSE = "385be49f-8fde-440e-bdbc-3dffeb8dd116" export const VIEW_ALL_EXPNESE = "01e06444-9ca7-4df4-b900-8c3fa051b92f"; @@ -55,7 +58,6 @@ export const REVIEW_EXPENSE = "1f4bda08-1873-449a-bb66-3e8222bd871b"; export const APPROVE_EXPENSE = "eaafdd76-8aac-45f9-a530-315589c6deca"; - export const PROCESS_EXPENSE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" export const EXPENSE_MANAGE = "ea5a1529-4ee8-4828-80ea-0e23c9d4dd11" @@ -64,10 +66,19 @@ export const EXPENSE_REJECTEDBY = ["d1ee5eec-24b6-4364-8673-a8f859c60729","965ed export const EXPENSE_DRAFT = "297e0d8f-f668-41b5-bfea-e03b354251c8" +// ----------------------------Tenant------------------------- export const SUPPER_TENANT = "d032cb1a-3f30-462c-bef0-7ace73a71c0b" export const MANAGE_TENANTS = "00e20637-ce8d-4417-bec4-9b31b5e65092" export const VIEW_TENANTS = "647145c6-2108-4c98-aab4-178602236e55" export const ActiveTenant = "297e0d8f-f668-41b5-bfea-e03b354251c8" + +// ---------------------Documents--------------------------------- +export const VIEW_DOCUMENT = "71189504-f1c8-4ca5-8db6-810497be2854"; +export const UPLOAD_DOCUMENT = "3f6d1f67-6fa5-4b7c-b17b-018d4fe4aab8"; +export const MODIFY_DOCUMENT = "c423fd81-6273-4b9d-bb5e-76a0fb343833"; +export const DELETE_DOCUMENT = "40863a13-5a66-469d-9b48-135bc5dbf486"; +export const DOWNLOAD_DOCUMENT = "404373d0-860f-490e-a575-1c086ffbce1d"; +export const VERIFY_DOCUMENT = "13a1f30f-38d1-41bf-8e7a-b75189aab8e0"; // -------------------Application Role------------------------------ // 1 - Expense Manage @@ -79,6 +90,12 @@ export const TENANT_STATUS = [ {id:"35d7840a-164a-448b-95e6-efb2ec84a751",name:"Supspended"} ] +export const DOCUMENTS_ENTITIES = { + ProjectEntity : "c8fe7115-aa27-43bc-99f4-7b05fabe436e", + EmployeeEntity:"dbb9555a-7a0c-40f2-a9ed-f0463f1ceed7", +} + + export const CONSTANT_TEXT = { } diff --git a/src/utils/exportUtils.js b/src/utils/exportUtils.js new file mode 100644 index 00000000..40f7d423 --- /dev/null +++ b/src/utils/exportUtils.js @@ -0,0 +1,27 @@ +// utils/exportUtils.js +export const exportToCSV = (data, filename = "export.csv") => { + if (!data || data.length === 0) return; + + const headers = Object.keys(data[0]); + const csvRows = []; + + // Add headers + csvRows.push(headers.join(",")); + + // Add values + data.forEach(row => { + const values = headers.map(header => `"${row[header] ?? ""}"`); + csvRows.push(values.join(",")); + }); + + // Create CSV Blob + const csvContent = csvRows.join("\n"); + const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); + + // Create download link + const link = document.createElement("a"); + const url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename); + link.click(); +};