From cb793354bfdda10b75691052eb8b541e035704b2 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Tue, 27 Jan 2026 19:09:54 +0530 Subject: [PATCH] login api with username/password temporary and api integrated for zone state anad user role and permision , user management page created for super admin --- package-lock.json | 390 +++ package.json | 6 +- src/App.tsx | 81 +- src/api/API.ts | 26 + src/api/client.ts | 32 + src/components/admin/UserManagementPage.tsx | 406 +++ src/components/applications/MasterPage.tsx | 2620 +++++++++---------- src/components/auth/LoginPage.tsx | 66 +- src/components/layout/Header.tsx | 19 +- src/components/layout/Sidebar.tsx | 73 +- src/lib/mock-data.ts | 36 +- src/main.tsx | 6 +- src/services/admin.service.ts | 43 + src/services/master.service.ts | 45 + src/store/index.ts | 11 + src/store/slices/authSlice.ts | 99 + 16 files changed, 2523 insertions(+), 1436 deletions(-) create mode 100644 src/api/API.ts create mode 100644 src/api/client.ts create mode 100644 src/components/admin/UserManagementPage.tsx create mode 100644 src/services/admin.service.ts create mode 100644 src/services/master.service.ts create mode 100644 src/store/index.ts create mode 100644 src/store/slices/authSlice.ts diff --git a/package-lock.json b/package-lock.json index af0bf44..150ad9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,9 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", + "@reduxjs/toolkit": "^2.11.2", + "apisauce": "^3.2.2", + "axios": "^1.13.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^1.0.0", @@ -46,6 +49,7 @@ "react-day-picker": "^8.10.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.0", + "react-redux": "^9.2.0", "react-resizable-panels": "^2.0.12", "react-router-dom": "^6.22.3", "recharts": "^2.12.2", @@ -2912,6 +2916,32 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.23.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", @@ -2956,6 +2986,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3397,6 +3439,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.53.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", @@ -3730,6 +3778,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/apisauce": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/apisauce/-/apisauce-3.2.2.tgz", + "integrity": "sha512-YvZ6v7KgGWSjqHEDcGxE+U5bI8U8ckVqVFvfs0SKGtMrvbX0o3MDMlMzlL9lvWgg9hb//162vAHPconN/Ed1oA==", + "license": "MIT", + "dependencies": { + "axios": "^1.11.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3749,6 +3806,12 @@ "node": ">=10" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.23", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", @@ -3786,6 +3849,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz", + "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3847,6 +3921,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3958,6 +4045,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4155,6 +4254,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4181,6 +4289,20 @@ "csstype": "^3.0.2" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -4230,6 +4352,51 @@ "node": ">=10.13.0" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -4566,6 +4733,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -4607,6 +4810,15 @@ } } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -4617,6 +4829,30 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -4626,6 +4862,19 @@ "node": ">=6" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4652,6 +4901,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4669,6 +4930,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -4696,6 +4996,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", + "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5203,6 +5513,36 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5525,6 +5865,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5596,6 +5942,29 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -5780,6 +6149,27 @@ "decimal.js-light": "^2.4.1" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/package.json b/package.json index 9d56d8c..20752ea 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,9 @@ "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle-group": "^1.0.4", + "@reduxjs/toolkit": "^2.11.2", + "apisauce": "^3.2.2", + "axios": "^1.13.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^1.0.0", @@ -48,6 +51,7 @@ "react-day-picker": "^8.10.0", "react-dom": "^18.2.0", "react-hook-form": "^7.51.0", + "react-redux": "^9.2.0", "react-resizable-panels": "^2.0.12", "react-router-dom": "^6.22.3", "recharts": "^2.12.2", @@ -74,4 +78,4 @@ "typescript-eslint": "^8.53.1", "vite": "^6.0.0" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index f7e2d56..7ba130e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,7 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from './store'; +import { setCredentials, logout as logoutAction, initializeAuth } from './store/slices/authSlice'; import { ApplicationFormPage } from './components/public/ApplicationFormPage'; import { LoginPage } from './components/auth/LoginPage'; import { Sidebar } from './components/layout/Sidebar'; @@ -22,6 +25,7 @@ import { FinanceFnFPage } from './components/applications/FinanceFnFPage'; import { FinancePaymentDetailsPage } from './components/applications/FinancePaymentDetailsPage'; import { FinanceFnFDetailsPage } from './components/applications/FinanceFnFDetailsPage'; import { MasterPage } from './components/applications/MasterPage'; +import { UserManagementPage } from './components/admin/UserManagementPage'; import { ConstitutionalChangePage } from './components/applications/ConstitutionalChangePage'; import { ConstitutionalChangeDetails } from './components/applications/ConstitutionalChangeDetails'; import { RelocationRequestPage } from './components/applications/RelocationRequestPage'; @@ -31,15 +35,16 @@ import { DealerResignationPage } from './components/dealer/DealerResignationPage import { DealerConstitutionalChangePage } from './components/dealer/DealerConstitutionalChangePage'; import { DealerRelocationPage } from './components/dealer/DealerRelocationPage'; import { Toaster } from './components/ui/sonner'; -import { mockUsers, User } from './lib/mock-data'; +import { User } from './lib/mock-data'; import { toast } from 'sonner'; +import { API } from './api/API'; type View = 'dashboard' | 'applications' | 'all-applications' | 'opportunity-requests' | 'unopportunity-requests' | 'tasks' | 'reports' | 'settings' | 'users' | 'resignation' | 'termination' | 'fnf' | 'finance-onboarding' | 'finance-fnf' | 'master' | 'constitutional-change' | 'relocation-requests' | 'worknote' | 'dealer-resignation' | 'dealer-constitutional' | 'dealer-relocation'; export default function App() { + const dispatch = useDispatch(); + const { user: currentUser, isAuthenticated, loading } = useSelector((state: RootState) => state.auth); const [showAdminLogin, setShowAdminLogin] = useState(false); - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [currentUser, setCurrentUser] = useState(null); const [currentView, setCurrentView] = useState('dashboard'); const [selectedApplicationId, setSelectedApplicationId] = useState(null); const [selectedResignationId, setSelectedResignationId] = useState(null); @@ -56,22 +61,46 @@ export default function App() { requestTitle: string; } | null>(null); - const handleLogin = (email: string, password: string) => { - // Find user in mock data - const user = mockUsers.find(u => u.email === email && u.password === password); + useEffect(() => { + dispatch(initializeAuth()); + }, [dispatch]); - if (user) { - setCurrentUser(user); - setIsAuthenticated(true); - toast.success(`Welcome back, ${user.name}! (${user.role})`); - } else { - toast.error('Invalid credentials'); + const handleLogin = async (email: string, password: string) => { + try { + const response = await API.login({ email, password }); + + if (response.ok && response.data) { + const { token, user } = response.data as any; + + // Store token for persistence + localStorage.setItem('token', token); + + // Use backend user data + const simplifiedUser: User = { + id: user.id, + name: user.fullName || email.split('@')[0], + email: user.email, + password: password, + role: typeof user.role === 'string' ? user.role : (user.roleCode || 'User') + }; + + dispatch(setCredentials({ + user: simplifiedUser, + token + })); + toast.success(`Welcome back, ${simplifiedUser.name}!`); + } else { + const errorMsg = (response.data as any)?.message || 'Invalid credentials'; + toast.error(errorMsg); + } + } catch (error) { + console.error('Login error:', error); + toast.error('Something went wrong. Please try again.'); } }; const handleLogout = () => { - setIsAuthenticated(false); - setCurrentUser(null); + dispatch(logoutAction()); setCurrentView('dashboard'); setSelectedApplicationId(null); setShowAdminLogin(false); @@ -246,6 +275,15 @@ export default function App() { } }; + // Show loading state while initializing auth + if (loading) { + return ( +
+
+
+ ); + } + // Show public application form if not authenticated and not trying to log in as admin if (!isAuthenticated && !showAdminLogin) { return ( @@ -272,13 +310,11 @@ export default function App() { activeView={currentView} onNavigate={handleNavigate} onLogout={handleLogout} - currentUser={currentUser} />
window.location.reload()} /> @@ -442,16 +478,7 @@ export default function App() { )} {currentView === 'users' && ( -
-

User Management

-

Manage system users and their roles

-
-

• Add/Edit/Remove users

-

• Assign roles and permissions

-

• View user activity logs

-

• Manage access controls

-
-
+ )} {currentView === 'resignation' && ( diff --git a/src/api/API.ts b/src/api/API.ts new file mode 100644 index 0000000..1e5f09e --- /dev/null +++ b/src/api/API.ts @@ -0,0 +1,26 @@ +import client from './client'; + +export const API = { + // Auth routes + login: (data: any) => client.post('/auth/login', data), + logout: () => client.post('/auth/logout'), + getCurrentUser: () => client.get('/auth/me'), + + // Master module routes + getRoles: () => client.get('/admin/roles'), + getPermissions: () => client.get('/admin/permissions'), + updateRole: (id: string, data: any) => client.put(`/admin/roles/${id}`, data), + + getZones: () => client.get('/master/zones'), + getRegions: () => client.get('/master/regions'), + getStates: (zoneId?: string) => client.get('/master/states', { zoneId }), + getDistricts: (stateId?: string) => client.get('/master/districts', { stateId }), + + // User management routes + getUsers: () => client.get('/admin/users'), + updateUser: (id: string, data: any) => client.put(`/admin/users/${id}`, data), + updateUserStatus: (id: string, data: any) => client.patch(`/admin/users/${id}/status`, data), + deleteUser: (id: string) => client.delete(`/admin/users/${id}`), +}; + +export default API; diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..6175426 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,32 @@ +import { create } from 'apisauce'; + +const API_BASE_URL = (import.meta as any).env?.VITE_API_URL || 'http://localhost:5000/api'; + +const client = create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + timeout: 10000, +}); + +// Interceptor for Auth Token +client.addRequestTransform((request) => { + const token = localStorage.getItem('token'); + if (token && request.headers) { + request.headers.Authorization = `Bearer ${token}`; + } +}); + +// Response transform for global error handling +client.addResponseTransform((response) => { + if (!response.ok) { + if (response.status === 401) { + console.error('Unauthorized access - potential token expiration'); + // Potential logic to logout user or refresh token + } + } +}); + +export default client; diff --git a/src/components/admin/UserManagementPage.tsx b/src/components/admin/UserManagementPage.tsx new file mode 100644 index 0000000..94b96d9 --- /dev/null +++ b/src/components/admin/UserManagementPage.tsx @@ -0,0 +1,406 @@ +import { useState, useEffect } from 'react'; +import { adminService } from '../../services/admin.service'; +import { masterService } from '../../services/master.service'; +import { Card, CardContent } from '../ui/card'; +import { Badge } from '../ui/badge'; +import { Button } from '../ui/button'; +import { Input } from '../ui/input'; +import { Label } from '../ui/label'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; +import { Switch } from '../ui/switch'; +import { + Users, + UserPlus, + Search, + Edit2, + Trash2, + Shield, + Mail, + Phone, + Filter, + RefreshCw, + CheckCircle, + XCircle +} from 'lucide-react'; +import { toast } from 'sonner'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '../ui/dialog'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; + +export function UserManagementPage() { + const [users, setUsers] = useState([]); + const [roles, setRoles] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [roleFilter, setRoleFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + + // Edit/Add Modal State + const [showUserModal, setShowUserModal] = useState(false); + const [editingUser, setEditingUser] = useState(null); + const [formData, setFormData] = useState({ + fullName: '', + email: '', + roleCode: '', + status: 'active', + isActive: true, + mobileNumber: '', + department: '', + designation: '', + employeeId: '' + }); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + setLoading(true); + try { + const usersRes = await adminService.getAllUsers() as any; + const rolesRes = await masterService.getRoles() as any; + + if (usersRes.success) setUsers(usersRes.data); + if (rolesRes.success) setRoles(rolesRes.data); + } catch (error) { + toast.error('Failed to load user management data'); + } finally { + setLoading(false); + } + }; + + const handleEditUser = (user: any) => { + setEditingUser(user); + setFormData({ + fullName: user.fullName || '', + email: user.email || '', + roleCode: user.roleCode || '', + status: user.status || 'active', + isActive: user.isActive ?? true, + mobileNumber: user.mobileNumber || '', + department: user.department || '', + designation: user.designation || '', + employeeId: user.employeeId || '' + }); + setShowUserModal(true); + }; + + const handleSubmit = async () => { + if (!formData.fullName || !formData.email || !formData.roleCode) { + toast.error('Please fill in all required fields'); + return; + } + + try { + if (editingUser) { + const res = await adminService.updateUser(editingUser.id, formData); + if (res.success) { + setShowUserModal(false); + fetchData(); + } + } else { + // Implementation for Create User can be added here + toast.info('Create user functionality coming soon'); + } + } catch (error) { + toast.error('Operation failed'); + } + }; + + const toggleUserStatus = async (user: any) => { + const newStatus = user.status === 'active' ? 'inactive' : 'active'; + const newActive = !user.isActive; + + const res = await adminService.updateUserStatus(user.id, newStatus, newActive); + if (res.success) { + fetchData(); + } + }; + + const filteredUsers = users.filter(user => { + const matchesSearch = + user.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) || + user.email?.toLowerCase().includes(searchQuery.toLowerCase()) || + user.employeeId?.toLowerCase().includes(searchQuery.toLowerCase()); + + const matchesRole = roleFilter === 'all' || user.roleCode === roleFilter; + const matchesStatus = statusFilter === 'all' || user.status === statusFilter; + + return matchesSearch && matchesRole && matchesStatus; + }); + + return ( +
+ {/* Header section */} +
+
+

+ + User Management +

+

Manage system users, roles, and access permissions.

+
+ +
+ + {/* Filters section */} + + +
+
+ + setSearchQuery(e.target.value)} + /> +
+ + + +
+
+
+ + {/* Users Table */} + + + + + + User Information + Account Details + Role & Department + Status + Actions + + + + {loading ? ( + + +
+ +

Loading users...

+
+
+
+ ) : filteredUsers.length === 0 ? ( + + + + No users found matching your criteria. + + + ) : ( + filteredUsers.map((user) => ( + + +
+
+ {user.fullName?.charAt(0) || user.email?.charAt(0)} +
+
+
{user.fullName}
+
+ + {user.email} +
+
+
+
+ +
+
ID: {user.employeeId || 'N/A'}
+
+ + {user.mobileNumber || 'No phone'} +
+
+
+ +
+ + + {user.roleCode} + +
{user.department || 'No department'}
+
+
+ +
+
+ + {user.status === 'active' ? ( + + ) : ( + + )} + {user.status} + +
+ toggleUserStatus(user)} + /> +
+
+ +
+ + +
+
+
+ )) + )} +
+
+
+
+ + {/* User Edit/Add Modal */} + + + + {editingUser ? 'Edit User' : 'Add New User'} + + Modify user profiles and system access settings. + + + +
+
+ + setFormData({ ...formData, fullName: e.target.value })} + /> +
+
+ + setFormData({ ...formData, email: e.target.value })} + /> +
+
+ + setFormData({ ...formData, employeeId: e.target.value })} + /> +
+
+ + +
+
+ + setFormData({ ...formData, department: e.target.value })} + /> +
+
+ + setFormData({ ...formData, designation: e.target.value })} + /> +
+
+ + setFormData({ ...formData, mobileNumber: e.target.value })} + /> +
+
+ + +
+
+ + + + + +
+
+
+ ); +} diff --git a/src/components/applications/MasterPage.tsx b/src/components/applications/MasterPage.tsx index 4edb5da..f74d680 100644 --- a/src/components/applications/MasterPage.tsx +++ b/src/components/applications/MasterPage.tsx @@ -1,4 +1,5 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import { masterService } from '../../services/master.service'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'; import { Badge } from '../ui/badge'; import { Button } from '../ui/button'; @@ -8,11 +9,11 @@ import { Textarea } from '../ui/textarea'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; import { Switch } from '../ui/switch'; -import { +import { Settings, - Shield, - Clock, - Mail, + Shield, + Clock, + Mail, MapPin, Plus, Edit2, @@ -238,6 +239,7 @@ interface UserAssignment { email: string; phone: string; status: 'Active' | 'Inactive'; + permissions?: string[]; } export function MasterPage() { @@ -268,14 +270,15 @@ export function MasterPage() { const [selectedUserZone, setSelectedUserZone] = useState(''); const [selectedUserRegion, setSelectedUserRegion] = useState(''); const [selectedUserDistricts, setSelectedUserDistricts] = useState([]); - + const [selectedUserForEdit, setSelectedUserForEdit] = useState(null); + // Permission management state const [userLookupValue, setUserLookupValue] = useState(''); const [fetchedUser, setFetchedUser] = useState(null); const [actionPermissions, setActionPermissions] = useState([]); const [viewPermissions, setViewPermissions] = useState([]); const [stageAccess, setStageAccess] = useState([]); - + // Role editing state const [showEditRoleDialog, setShowEditRoleDialog] = useState(false); const [selectedRoleForEdit, setSelectedRoleForEdit] = useState(null); @@ -288,7 +291,7 @@ export function MasterPage() { const [slaTAT, setSlaTAT] = useState(0); const [slaReminders, setSlaReminders] = useState([]); const [slaEscalations, setSlaEscalations] = useState([]); - + // Zone form state const [zoneCode, setZoneCode] = useState(''); const [zoneName, setZoneName] = useState(''); @@ -296,24 +299,116 @@ export function MasterPage() { const [zbhName, setZbhName] = useState(''); const [zbhEmail, setZbhEmail] = useState(''); const [zbhPhone, setZbhPhone] = useState(''); - const [zonalManagersData, setZonalManagersData] = useState<{[key: number]: ZonalManager}>({}); + const [zonalManagersData, setZonalManagersData] = useState<{ [key: number]: ZonalManager }>({}); - // Mock data for roles - const [roles] = useState([ - { id: '1', name: 'DD', permissions: ['view_applications', 'review_level1', 'schedule_interview'], userCount: 25 }, - { id: '2', name: 'DD-ZM', permissions: ['view_applications', 'approve_ranking'], userCount: 12 }, - { id: '3', name: 'RBM', permissions: ['view_applications', 'review_documents'], userCount: 8 }, - { id: '4', name: 'ZBH', permissions: ['view_applications', 'final_approval'], userCount: 5 }, - { id: '5', name: 'DD Lead', permissions: ['view_all', 'manage_opportunities', 'assign_users'], userCount: 3 }, - { id: '6', name: 'Finance', permissions: ['view_payments', 'verify_payments', 'fnf_settlement'], userCount: 10 }, - ]); + // Real data state + const [availablePermissions, setAvailablePermissions] = useState([]); + const [roles, setRoles] = useState([]); + const [zones, setZones] = useState([]); + const [regionalOffices, setRegionalOffices] = useState([]); + const [asms, setAsms] = useState([]); + const [zonalManagerMappings, setZonalManagerMappings] = useState([]); + const [userAssignedData, setUserAssignedData] = useState([]); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchInitialData(); + }, []); + + const fetchInitialData = async () => { + setLoading(true); + try { + const [rolesRes, zonesRes, permsRes, regionsRes, usersRes] = await Promise.all([ + masterService.getRoles() as Promise, + masterService.getZones() as Promise, + masterService.getPermissions() as Promise, + masterService.getRegions() as Promise, + masterService.getUsers() as Promise + ]); + + if (rolesRes.success) { + setRoles(rolesRes.data.map((r: any) => ({ + id: r.id, + name: r.roleName, + permissions: r.permissions?.map((p: any) => p.permissionCode) || [], + userCount: r.userCount || 0 + }))); + } + + if (zonesRes.success) { + setZones(zonesRes.data.map((z: any) => ({ + id: z.id, + code: z.zoneCode, + name: z.zoneName, + description: z.description || '', + states: z.states?.map((s: any) => s.stateName) || [], + zmCount: z.managers?.length || 0, + zbh: { + name: z.zonalBusinessHead?.fullName || 'Not Assigned', + email: z.zonalBusinessHead?.email || '', + phone: z.zonalBusinessHead?.mobileNumber || '' + }, + zonalManagers: z.managers?.map((m: any) => ({ + name: m.user?.fullName || 'Unknown', + email: m.user?.email || '', + phone: m.user?.mobileNumber || '', + districts: [] + })) || [] + }))); + } + + if (permsRes.success) { + setAvailablePermissions(permsRes.data); + } + + if (regionsRes.success) { + setRegionalOffices(regionsRes.data?.map((r: any) => ({ + id: r.id, + code: r.regionCode, + name: r.regionName, + zoneId: r.zoneId, + zoneName: r.zone?.zoneName || 'Unknown', + states: r.states?.map((s: any) => s.stateName) || [], + cities: [], + regionalOfficerCount: 0, + asmCount: 0, + status: r.isActive ? 'Active' : 'Inactive' + }))); + } + + if (usersRes.success) { + setUserAssignedData(usersRes.data.map((u: any) => ({ + id: u.id, + name: u.fullName, + role: u.role?.roleName || u.roleCode || 'User', + roleCode: u.roleCode, + region: u.region?.regionName || 'Not Assigned', + regionId: u.regionId, + zone: u.zone?.zoneName || 'Not Assigned', + zoneId: u.zoneId, + email: u.email, + phone: u.mobileNumber || 'N/A', + status: u.isActive ? 'Active' : 'Inactive', + employeeId: u.employeeId, + permissions: u.role?.permissions?.map((p: any) => p.permissionCode) || [] + }))); + } + + } catch (error) { + console.error('Error fetching master data:', error); + toast.error('Failed to load configuration data'); + } finally { + setLoading(false); + } + }; // Mock data for SLA const [slaConfigs] = useState([ - { - id: '1', - stage: 'Initial Review (DD)', - days: 3, + { + id: '1', + stage: 'Initial Review (DD)', + days: 3, reminders: [ { id: 'r1', time: 2, unit: 'days' }, { id: 'r2', time: 12, unit: 'hours' } @@ -322,12 +417,12 @@ export function MasterPage() { { id: 'e1', level: 1, time: 1, unit: 'days', userEmail: 'dd.lead@royalenfield.com' }, { id: 'e2', level: 2, time: 2, unit: 'days', userEmail: 'dd.head@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '2', - stage: 'Level 1 Interview', - days: 5, + { + id: '2', + stage: 'Level 1 Interview', + days: 5, reminders: [ { id: 'r1', time: 3, unit: 'days' }, { id: 'r2', time: 1, unit: 'days' } @@ -335,36 +430,36 @@ export function MasterPage() { escalations: [ { id: 'e1', level: 1, time: 6, unit: 'hours', userEmail: 'zm@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '3', - stage: 'Document Verification', - days: 7, + { + id: '3', + stage: 'Document Verification', + days: 7, reminders: [ { id: 'r1', time: 5, unit: 'days' } ], escalations: [ { id: 'e1', level: 1, time: 1, unit: 'days', userEmail: 'legal@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '4', - stage: 'Ranking & DD-ZM Review', - days: 3, + { + id: '4', + stage: 'Ranking & DD-ZM Review', + days: 3, reminders: [ { id: 'r1', time: 24, unit: 'hours' } ], escalations: [ { id: 'e1', level: 1, time: 12, unit: 'hours', userEmail: 'rbm@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '5', - stage: 'RBM Review', - days: 5, + { + id: '5', + stage: 'RBM Review', + days: 5, reminders: [ { id: 'r1', time: 3, unit: 'days' }, { id: 'r2', time: 1, unit: 'days' } @@ -372,12 +467,12 @@ export function MasterPage() { escalations: [ { id: 'e1', level: 1, time: 1, unit: 'days', userEmail: 'zbh@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '6', - stage: 'ZBH Final Approval', - days: 7, + { + id: '6', + stage: 'ZBH Final Approval', + days: 7, reminders: [ { id: 'r1', time: 5, unit: 'days' }, { id: 'r2', time: 2, unit: 'days' } @@ -386,12 +481,12 @@ export function MasterPage() { { id: 'e1', level: 1, time: 1, unit: 'days', userEmail: 'nbh@royalenfield.com' }, { id: 'e2', level: 2, time: 3, unit: 'days', userEmail: 'superadmin@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '7', - stage: 'Payment Verification', - days: 2, + { + id: '7', + stage: 'Payment Verification', + days: 2, reminders: [ { id: 'r1', time: 24, unit: 'hours' }, { id: 'r2', time: 6, unit: 'hours' } @@ -399,12 +494,12 @@ export function MasterPage() { escalations: [ { id: 'e1', level: 1, time: 3, unit: 'hours', userEmail: 'finance.head@royalenfield.com' } ], - enabled: true + enabled: true }, - { - id: '8', - stage: 'F&F Settlement', - days: 30, + { + id: '8', + stage: 'F&F Settlement', + days: 30, reminders: [ { id: 'r1', time: 7, unit: 'days' }, { id: 'r2', time: 3, unit: 'days' } @@ -413,7 +508,7 @@ export function MasterPage() { { id: 'e1', level: 1, time: 5, unit: 'days', userEmail: 'finance@royalenfield.com' }, { id: 'e2', level: 2, time: 10, unit: 'days', userEmail: 'cfo@royalenfield.com' } ], - enabled: true + enabled: true }, ]); @@ -429,194 +524,9 @@ export function MasterPage() { ]); // Mock data for locations - const [locations] = useState([ - { id: '1', state: 'Karnataka', city: 'Bangalore', district: 'Bangalore Urban', activeFrom: '2024-01-01', activeTo: '2025-12-31', status: 'Active' }, - { id: '2', state: 'Karnataka', city: 'Mysore', district: 'Mysuru', activeFrom: '2024-02-15', activeTo: '2025-06-30', status: 'Active' }, - { id: '3', state: 'Tamil Nadu', city: 'Chennai', district: 'Chennai', activeFrom: '2024-01-01', activeTo: '2025-12-31', status: 'Active' }, - { id: '4', state: 'Tamil Nadu', city: 'Coimbatore', district: 'Coimbatore', activeFrom: '2024-03-01', activeTo: '2025-09-30', status: 'Active' }, - { id: '5', state: 'Maharashtra', city: 'Mumbai', district: 'Mumbai City', activeFrom: '2024-01-01', activeTo: '2026-12-31', status: 'Active' }, - { id: '6', state: 'Maharashtra', city: 'Pune', district: 'Pune', status: 'Active', activeFrom: '2024-04-01', activeTo: '2025-03-31' }, - { id: '7', state: 'Delhi', city: 'New Delhi', district: 'Central Delhi', activeFrom: '2024-01-15', activeTo: '2025-12-31', status: 'Active' }, - { id: '8', state: 'Rajasthan', city: 'Jaipur', district: 'Jaipur', activeFrom: '2023-06-01', activeTo: '2024-05-31', status: 'Inactive' }, - ]); + const [locations] = useState([]); - // Mock data for zones - Top level geographical divisions - const [zones, setZones] = useState([ - { - id: 'north', - code: 'NZ', - name: 'North Zone', - states: ['Delhi', 'Haryana', 'Punjab', 'Himachal Pradesh', 'Jammu & Kashmir', 'Uttarakhand', 'Uttar Pradesh', 'Rajasthan', 'Chandigarh'], - zmCount: 3, - description: 'Covers northern states including NCR, UP, Punjab, and Rajasthan', - zbh: { name: 'Rajesh Kumar', email: 'rajesh.k@royalenfield.com', phone: '+91 98100 00001' }, - zonalManagers: [ - { name: 'Amit Sharma', email: 'amit.s@royalenfield.com', phone: '+91 98100 00011', districts: ['North Delhi', 'Central Delhi', 'Gurugram', 'Faridabad'] }, - { name: 'Priya Gupta', email: 'priya.g@royalenfield.com', phone: '+91 98100 00012', districts: ['Amritsar', 'Ludhiana', 'Jalandhar', 'Patiala'] }, - { name: 'Vikram Singh', email: 'vikram.s@royalenfield.com', phone: '+91 98100 00013', districts: ['Lucknow', 'Kanpur', 'Agra', 'Meerut'] } - ] - }, - { - id: 'south', - code: 'SZ', - name: 'South Zone', - states: ['Karnataka', 'Tamil Nadu', 'Kerala', 'Andhra Pradesh', 'Telangana', 'Puducherry'], - zmCount: 4, - description: 'Covers southern states including Karnataka, Tamil Nadu, AP, and Telangana', - zbh: { name: 'Suresh Reddy', email: 'suresh.r@royalenfield.com', phone: '+91 98200 00001' }, - zonalManagers: [ - { name: 'Lakshmi Iyer', email: 'lakshmi.i@royalenfield.com', phone: '+91 98200 00011', districts: ['Bengaluru Urban', 'Mysuru', 'Mangaluru'] }, - { name: 'Karthik Nair', email: 'karthik.n@royalenfield.com', phone: '+91 98200 00012', districts: ['Chennai', 'Coimbatore', 'Madurai'] }, - { name: 'Arjun Menon', email: 'arjun.m@royalenfield.com', phone: '+91 98200 00013', districts: ['Thiruvananthapuram', 'Kochi', 'Kozhikode'] }, - { name: 'Deepika Rao', email: 'deepika.r@royalenfield.com', phone: '+91 98200 00014', districts: ['Hyderabad', 'Vijayawada', 'Visakhapatnam'] } - ] - }, - { - id: 'east', - code: 'EZ', - name: 'East Zone', - states: ['West Bengal', 'Odisha', 'Bihar', 'Jharkhand', 'Assam', 'Arunachal Pradesh', 'Manipur', 'Meghalaya', 'Mizoram', 'Nagaland', 'Sikkim', 'Tripura'], - zmCount: 2, - description: 'Covers eastern and north-eastern states', - zbh: { name: 'Sourav Banerjee', email: 'sourav.b@royalenfield.com', phone: '+91 98300 00001' }, - zonalManagers: [ - { name: 'Debjani Das', email: 'debjani.d@royalenfield.com', phone: '+91 98300 00011', districts: ['Kolkata', 'Howrah', 'Durgapur'] }, - { name: 'Arijit Sen', email: 'arijit.s@royalenfield.com', phone: '+91 98300 00012', districts: ['Patna', 'Gaya', 'Ranchi', 'Jamshedpur'] } - ] - }, - { - id: 'west', - code: 'WZ', - name: 'West Zone', - states: ['Maharashtra', 'Gujarat', 'Goa', 'Daman & Diu', 'Dadra & Nagar Haveli'], - zmCount: 3, - description: 'Covers western states including Maharashtra and Gujarat', - zbh: { name: 'Rohit Patel', email: 'rohit.p@royalenfield.com', phone: '+91 98400 00001' }, - zonalManagers: [ - { name: 'Neha Shah', email: 'neha.s@royalenfield.com', phone: '+91 98400 00011', districts: ['Mumbai', 'Thane', 'Pune', 'Nagpur'] }, - { name: 'Karan Mehta', email: 'karan.m@royalenfield.com', phone: '+91 98400 00012', districts: ['Ahmedabad', 'Surat', 'Vadodara', 'Rajkot'] }, - { name: 'Sanjay Desai', email: 'sanjay.d@royalenfield.com', phone: '+91 98400 00013', districts: ['Jaipur', 'Jodhpur', 'Udaipur'] } - ] - }, - { - id: 'central', - code: 'CZ', - name: 'Central Zone', - states: ['Madhya Pradesh', 'Chhattisgarh'], - zmCount: 2, - description: 'Covers central Indian states', - zbh: { name: 'Amit Jain', email: 'amit.j@royalenfield.com', phone: '+91 98500 00001' }, - zonalManagers: [ - { name: 'Pooja Verma', email: 'pooja.v@royalenfield.com', phone: '+91 98500 00011', districts: ['Indore', 'Bhopal', 'Jabalpur', 'Gwalior'] }, - { name: 'Rahul Tiwari', email: 'rahul.t@royalenfield.com', phone: '+91 98500 00012', districts: ['Raipur', 'Bhilai', 'Bilaspur'] } - ] - }, - ]); - // Mock data for regional offices - const [regionalOffices] = useState([ - // North Zone Regions - { id: 'n-r1', code: 'NZ-R1', name: 'Delhi NCR Region', zoneId: 'north', zoneName: 'North', states: ['Delhi', 'Haryana', 'Chandigarh'], cities: ['Delhi', 'Gurgaon', 'Noida', 'Faridabad', 'Chandigarh'], regionalOfficerCount: 2, asmCount: 8, status: 'Active' }, - { id: 'n-r2', code: 'NZ-R2', name: 'Punjab Region', zoneId: 'north', zoneName: 'North', states: ['Punjab'], cities: ['Ludhiana', 'Amritsar', 'Jalandhar', 'Patiala'], regionalOfficerCount: 1, asmCount: 5, status: 'Active' }, - { id: 'n-r3', code: 'NZ-R3', name: 'UP West Region', zoneId: 'north', zoneName: 'North', states: ['Uttar Pradesh'], cities: ['Meerut', 'Ghaziabad', 'Agra', 'Aligarh'], regionalOfficerCount: 2, asmCount: 10, status: 'Active' }, - { id: 'n-r4', code: 'NZ-R4', name: 'UP East Region', zoneId: 'north', zoneName: 'North', states: ['Uttar Pradesh'], cities: ['Lucknow', 'Kanpur', 'Varanasi', 'Allahabad'], regionalOfficerCount: 2, asmCount: 9, status: 'Active' }, - { id: 'n-r5', code: 'NZ-R5', name: 'Rajasthan Region', zoneId: 'north', zoneName: 'North', states: ['Rajasthan'], cities: ['Jaipur', 'Jodhpur', 'Udaipur', 'Kota'], regionalOfficerCount: 2, asmCount: 7, status: 'Active' }, - { id: 'n-r6', code: 'NZ-R6', name: 'Hill States Region', zoneId: 'north', zoneName: 'North', states: ['Himachal Pradesh', 'Uttarakhand', 'Jammu & Kashmir'], cities: ['Shimla', 'Dehradun', 'Jammu', 'Srinagar'], regionalOfficerCount: 1, asmCount: 4, status: 'Active' }, - - // South Zone Regions - { id: 's-r1', code: 'SZ-R1', name: 'Karnataka North Region', zoneId: 'south', zoneName: 'South', states: ['Karnataka'], cities: ['Bangalore', 'Mysore', 'Hubli', 'Belgaum'], regionalOfficerCount: 2, asmCount: 9, status: 'Active' }, - { id: 's-r2', code: 'SZ-R2', name: 'Karnataka South Region', zoneId: 'south', zoneName: 'South', states: ['Karnataka'], cities: ['Mangalore', 'Udupi'], regionalOfficerCount: 1, asmCount: 4, status: 'Active' }, - { id: 's-r3', code: 'SZ-R3', name: 'Tamil Nadu North Region', zoneId: 'south', zoneName: 'South', states: ['Tamil Nadu'], cities: ['Chennai', 'Vellore', 'Tiruvallur'], regionalOfficerCount: 2, asmCount: 8, status: 'Active' }, - { id: 's-r4', code: 'SZ-R4', name: 'Tamil Nadu West Region', zoneId: 'south', zoneName: 'South', states: ['Tamil Nadu'], cities: ['Coimbatore', 'Salem', 'Erode'], regionalOfficerCount: 2, asmCount: 7, status: 'Active' }, - { id: 's-r5', code: 'SZ-R5', name: 'Kerala Region', zoneId: 'south', zoneName: 'South', states: ['Kerala'], cities: ['Kochi', 'Trivandrum', 'Kozhikode', 'Thrissur'], regionalOfficerCount: 2, asmCount: 6, status: 'Active' }, - { id: 's-r6', code: 'SZ-R6', name: 'Andhra Pradesh Region', zoneId: 'south', zoneName: 'South', states: ['Andhra Pradesh'], cities: ['Visakhapatnam', 'Vijayawada', 'Guntur'], regionalOfficerCount: 1, asmCount: 5, status: 'Active' }, - { id: 's-r7', code: 'SZ-R7', name: 'Telangana Region', zoneId: 'south', zoneName: 'South', states: ['Telangana'], cities: ['Hyderabad', 'Warangal', 'Nizamabad'], regionalOfficerCount: 2, asmCount: 7, status: 'Active' }, - - // East Zone Regions - { id: 'e-r1', code: 'EZ-R1', name: 'West Bengal Region', zoneId: 'east', zoneName: 'East', states: ['West Bengal'], cities: ['Kolkata', 'Howrah', 'Durgapur', 'Siliguri'], regionalOfficerCount: 2, asmCount: 7, status: 'Active' }, - { id: 'e-r2', code: 'EZ-R2', name: 'Odisha Region', zoneId: 'east', zoneName: 'East', states: ['Odisha'], cities: ['Bhubaneswar', 'Cuttack', 'Rourkela'], regionalOfficerCount: 1, asmCount: 5, status: 'Active' }, - { id: 'e-r3', code: 'EZ-R3', name: 'Bihar-Jharkhand Region', zoneId: 'east', zoneName: 'East', states: ['Bihar', 'Jharkhand'], cities: ['Patna', 'Ranchi', 'Jamshedpur', 'Dhanbad'], regionalOfficerCount: 2, asmCount: 6, status: 'Active' }, - { id: 'e-r4', code: 'EZ-R4', name: 'North East Region', zoneId: 'east', zoneName: 'East', states: ['Assam', 'Meghalaya', 'Manipur', 'Nagaland', 'Tripura', 'Arunachal Pradesh', 'Mizoram', 'Sikkim'], cities: ['Guwahati', 'Shillong', 'Imphal', 'Agartala'], regionalOfficerCount: 1, asmCount: 4, status: 'Active' }, - - // West Zone Regions - { id: 'w-r1', code: 'WZ-R1', name: 'Mumbai Metropolitan Region', zoneId: 'west', zoneName: 'West', states: ['Maharashtra'], cities: ['Mumbai', 'Navi Mumbai', 'Thane', 'Kalyan'], regionalOfficerCount: 3, asmCount: 10, status: 'Active' }, - { id: 'w-r2', code: 'WZ-R2', name: 'Pune Region', zoneId: 'west', zoneName: 'West', states: ['Maharashtra'], cities: ['Pune', 'Pimpri-Chinchwad', 'Solapur'], regionalOfficerCount: 2, asmCount: 7, status: 'Active' }, - { id: 'w-r3', code: 'WZ-R3', name: 'Maharashtra Central Region', zoneId: 'west', zoneName: 'West', states: ['Maharashtra'], cities: ['Nagpur', 'Nashik', 'Aurangabad'], regionalOfficerCount: 2, asmCount: 6, status: 'Active' }, - { id: 'w-r4', code: 'WZ-R4', name: 'Gujarat North Region', zoneId: 'west', zoneName: 'West', states: ['Gujarat'], cities: ['Ahmedabad', 'Gandhinagar', 'Mehsana'], regionalOfficerCount: 2, asmCount: 8, status: 'Active' }, - { id: 'w-r5', code: 'WZ-R5', name: 'Gujarat South Region', zoneId: 'west', zoneName: 'West', states: ['Gujarat'], cities: ['Surat', 'Vadodara', 'Rajkot'], regionalOfficerCount: 2, asmCount: 7, status: 'Active' }, - { id: 'w-r6', code: 'WZ-R6', name: 'Goa & UT Region', zoneId: 'west', zoneName: 'West', states: ['Goa', 'Daman & Diu', 'Dadra & Nagar Haveli'], cities: ['Panaji', 'Margao', 'Daman'], regionalOfficerCount: 1, asmCount: 3, status: 'Active' }, - - // Central Zone Regions - { id: 'c-r1', code: 'CZ-R1', name: 'MP North Region', zoneId: 'central', zoneName: 'Central', states: ['Madhya Pradesh'], cities: ['Bhopal', 'Indore', 'Gwalior'], regionalOfficerCount: 2, asmCount: 6, status: 'Active' }, - { id: 'c-r2', code: 'CZ-R2', name: 'MP South Region', zoneId: 'central', zoneName: 'Central', states: ['Madhya Pradesh'], cities: ['Jabalpur', 'Ujjain'], regionalOfficerCount: 1, asmCount: 4, status: 'Active' }, - { id: 'c-r3', code: 'CZ-R3', name: 'Chhattisgarh Region', zoneId: 'central', zoneName: 'Central', states: ['Chhattisgarh'], cities: ['Raipur', 'Bhilai', 'Bilaspur'], regionalOfficerCount: 1, asmCount: 4, status: 'Active' }, - ]); - - // Mock data for ASMs - const [asms] = useState([ - // North Zone ASMs - { id: 'asm-n1', name: 'Rajesh Kumar', code: 'ASM-NZ-001', email: 'rajesh.k@re.com', phone: '+91 98100 12345', zoneId: 'north', zoneName: 'North', regionId: 'n-r1', regionName: 'Delhi NCR Region', areasManaged: ['Central Delhi', 'South Delhi'], status: 'Active' }, - { id: 'asm-n2', name: 'Priya Sharma', code: 'ASM-NZ-002', email: 'priya.s@re.com', phone: '+91 98100 12346', zoneId: 'north', zoneName: 'North', regionId: 'n-r1', regionName: 'Delhi NCR Region', areasManaged: ['Gurgaon', 'Noida'], status: 'Active' }, - { id: 'asm-n3', name: 'Vikram Singh', code: 'ASM-NZ-003', email: 'vikram.s@re.com', phone: '+91 98100 12347', zoneId: 'north', zoneName: 'North', regionId: 'n-r2', regionName: 'Punjab Region', areasManaged: ['Ludhiana', 'Jalandhar'], status: 'Active' }, - { id: 'asm-n4', name: 'Anjali Gupta', code: 'ASM-NZ-004', email: 'anjali.g@re.com', phone: '+91 98100 12348', zoneId: 'north', zoneName: 'North', regionId: 'n-r3', regionName: 'UP West Region', areasManaged: ['Meerut', 'Agra'], status: 'Active' }, - - // South Zone ASMs - { id: 'asm-s1', name: 'Arjun Reddy', code: 'ASM-SZ-001', email: 'arjun.r@re.com', phone: '+91 98200 12345', zoneId: 'south', zoneName: 'South', regionId: 's-r1', regionName: 'Karnataka North Region', areasManaged: ['Bangalore North', 'Bangalore East'], status: 'Active' }, - { id: 'asm-s2', name: 'Lakshmi Iyer', code: 'ASM-SZ-002', email: 'lakshmi.i@re.com', phone: '+91 98200 12346', zoneId: 'south', zoneName: 'South', regionId: 's-r3', regionName: 'Tamil Nadu North Region', areasManaged: ['Chennai Central', 'Chennai South'], status: 'Active' }, - { id: 'asm-s3', name: 'Karthik Nair', code: 'ASM-SZ-003', email: 'karthik.n@re.com', phone: '+91 98200 12347', zoneId: 'south', zoneName: 'South', regionId: 's-r5', regionName: 'Kerala Region', areasManaged: ['Kochi', 'Trivandrum'], status: 'Active' }, - - // East Zone ASMs - { id: 'asm-e1', name: 'Sourav Banerjee', code: 'ASM-EZ-001', email: 'sourav.b@re.com', phone: '+91 98300 12345', zoneId: 'east', zoneName: 'East', regionId: 'e-r1', regionName: 'West Bengal Region', areasManaged: ['Kolkata North', 'Howrah'], status: 'Active' }, - { id: 'asm-e2', name: 'Debjani Das', code: 'ASM-EZ-002', email: 'debjani.d@re.com', phone: '+91 98300 12346', zoneId: 'east', zoneName: 'East', regionId: 'e-r2', regionName: 'Odisha Region', areasManaged: ['Bhubaneswar', 'Cuttack'], status: 'Active' }, - - // West Zone ASMs - { id: 'asm-w1', name: 'Rohit Patel', code: 'ASM-WZ-001', email: 'rohit.p@re.com', phone: '+91 98400 12345', zoneId: 'west', zoneName: 'West', regionId: 'w-r1', regionName: 'Mumbai Metropolitan Region', areasManaged: ['Bandra', 'Andheri'], status: 'Active' }, - { id: 'asm-w2', name: 'Neha Shah', code: 'ASM-WZ-002', email: 'neha.s@re.com', phone: '+91 98400 12346', zoneId: 'west', zoneName: 'West', regionId: 'w-r2', regionName: 'Pune Region', areasManaged: ['Pune City', 'Pimpri'], status: 'Active' }, - { id: 'asm-w3', name: 'Karan Mehta', code: 'ASM-WZ-003', email: 'karan.m@re.com', phone: '+91 98400 12347', zoneId: 'west', zoneName: 'West', regionId: 'w-r4', regionName: 'Gujarat North Region', areasManaged: ['Ahmedabad West', 'Gandhinagar'], status: 'Active' }, - - // Central Zone ASMs - { id: 'asm-c1', name: 'Amit Jain', code: 'ASM-CZ-001', email: 'amit.j@re.com', phone: '+91 98500 12345', zoneId: 'central', zoneName: 'Central', regionId: 'c-r1', regionName: 'MP North Region', areasManaged: ['Bhopal', 'Indore'], status: 'Active' }, - { id: 'asm-c2', name: 'Pooja Verma', code: 'ASM-CZ-002', email: 'pooja.v@re.com', phone: '+91 98500 12346', zoneId: 'central', zoneName: 'Central', regionId: 'c-r3', regionName: 'Chhattisgarh Region', areasManaged: ['Raipur', 'Bhilai'], status: 'Active' }, - ]); - - // Mock data for Zonal Manager Mappings - const [zonalManagerMappings] = useState([ - // North Zone ZMs - { id: 'zm-n1', name: 'Amit Sharma', code: 'ZM-NZ-001', email: 'amit.s@royalenfield.com', phone: '+91 98100 00011', zoneId: 'north', zoneName: 'North', regionId: 'n-r1', regionName: 'Delhi NCR Region', districts: ['North Delhi', 'Central Delhi', 'Gurugram', 'Faridabad'], status: 'Active' }, - { id: 'zm-n2', name: 'Priya Gupta', code: 'ZM-NZ-002', email: 'priya.g@royalenfield.com', phone: '+91 98100 00012', zoneId: 'north', zoneName: 'North', regionId: 'n-r2', regionName: 'Punjab Region', districts: ['Amritsar', 'Ludhiana', 'Jalandhar', 'Patiala'], status: 'Active' }, - { id: 'zm-n3', name: 'Vikram Singh', code: 'ZM-NZ-003', email: 'vikram.s@royalenfield.com', phone: '+91 98100 00013', zoneId: 'north', zoneName: 'North', regionId: 'n-r3', regionName: 'UP West Region', districts: ['Lucknow', 'Kanpur', 'Agra', 'Meerut'], status: 'Active' }, - - // South Zone ZMs - { id: 'zm-s1', name: 'Lakshmi Iyer', code: 'ZM-SZ-001', email: 'lakshmi.i@royalenfield.com', phone: '+91 98200 00011', zoneId: 'south', zoneName: 'South', regionId: 's-r1', regionName: 'Karnataka North Region', districts: ['Bengaluru Urban', 'Mysuru', 'Mangaluru'], status: 'Active' }, - { id: 'zm-s2', name: 'Karthik Nair', code: 'ZM-SZ-002', email: 'karthik.n@royalenfield.com', phone: '+91 98200 00012', zoneId: 'south', zoneName: 'South', regionId: 's-r3', regionName: 'Tamil Nadu North Region', districts: ['Chennai', 'Coimbatore', 'Madurai'], status: 'Active' }, - { id: 'zm-s3', name: 'Arjun Menon', code: 'ZM-SZ-003', email: 'arjun.m@royalenfield.com', phone: '+91 98200 00013', zoneId: 'south', zoneName: 'South', regionId: 's-r5', regionName: 'Kerala Region', districts: ['Thiruvananthapuram', 'Kochi', 'Kozhikode'], status: 'Active' }, - { id: 'zm-s4', name: 'Deepika Rao', code: 'ZM-SZ-004', email: 'deepika.r@royalenfield.com', phone: '+91 98200 00014', zoneId: 'south', zoneName: 'South', regionId: 's-r6', regionName: 'AP & Telangana Region', districts: ['Hyderabad', 'Vijayawada', 'Visakhapatnam'], status: 'Active' }, - - // East Zone ZMs - { id: 'zm-e1', name: 'Debjani Das', code: 'ZM-EZ-001', email: 'debjani.d@royalenfield.com', phone: '+91 98300 00011', zoneId: 'east', zoneName: 'East', regionId: 'e-r1', regionName: 'West Bengal Region', districts: ['Kolkata', 'Howrah', 'Durgapur'], status: 'Active' }, - { id: 'zm-e2', name: 'Arijit Sen', code: 'ZM-EZ-002', email: 'arijit.s@royalenfield.com', phone: '+91 98300 00012', zoneId: 'east', zoneName: 'East', regionId: 'e-r3', regionName: 'Bihar-Jharkhand Region', districts: ['Patna', 'Gaya', 'Ranchi', 'Jamshedpur'], status: 'Active' }, - - // West Zone ZMs - { id: 'zm-w1', name: 'Neha Shah', code: 'ZM-WZ-001', email: 'neha.s@royalenfield.com', phone: '+91 98400 00011', zoneId: 'west', zoneName: 'West', regionId: 'w-r1', regionName: 'Mumbai Metropolitan Region', districts: ['Mumbai', 'Thane', 'Pune', 'Nagpur'], status: 'Active' }, - { id: 'zm-w2', name: 'Karan Mehta', code: 'ZM-WZ-002', email: 'karan.m@royalenfield.com', phone: '+91 98400 00012', zoneId: 'west', zoneName: 'West', regionId: 'w-r4', regionName: 'Gujarat North Region', districts: ['Ahmedabad', 'Surat', 'Vadodara', 'Rajkot'], status: 'Active' }, - { id: 'zm-w3', name: 'Sanjay Desai', code: 'ZM-WZ-003', email: 'sanjay.d@royalenfield.com', phone: '+91 98400 00013', zoneId: 'west', zoneName: 'West', regionId: 'w-r6', regionName: 'Goa & UT Region', districts: ['Jaipur', 'Jodhpur', 'Udaipur'], status: 'Active' }, - - // Central Zone ZMs - { id: 'zm-c1', name: 'Pooja Verma', code: 'ZM-CZ-001', email: 'pooja.v@royalenfield.com', phone: '+91 98500 00011', zoneId: 'central', zoneName: 'Central', regionId: 'c-r1', regionName: 'MP North Region', districts: ['Indore', 'Bhopal', 'Jabalpur', 'Gwalior'], status: 'Active' }, - { id: 'zm-c2', name: 'Rahul Tiwari', code: 'ZM-CZ-002', email: 'rahul.t@royalenfield.com', phone: '+91 98500 00012', zoneId: 'central', zoneName: 'Central', regionId: 'c-r3', regionName: 'Chhattisgarh Region', districts: ['Raipur', 'Bhilai', 'Bilaspur'], status: 'Active' }, - ]); - - // Mock data for user assignments - const [userAssignments] = useState([ - { id: '1', name: 'Rahul Verma', role: 'DD', region: 'North', zone: 'N-Z1', email: 'rahul.v@re.com', phone: '+91 98765 43210', status: 'Active' }, - { id: '2', name: 'Priya Sharma', role: 'DD-ZM', region: 'North', zone: 'N-Z1', email: 'priya.s@re.com', phone: '+91 98765 43211', status: 'Active' }, - { id: '3', name: 'Arjun Kapoor', role: 'RBM', region: 'North', zone: 'N-Z1', email: 'arjun.k@re.com', phone: '+91 98765 43212', status: 'Active' }, - { id: '4', name: 'Sneha Reddy', role: 'DD', region: 'South', zone: 'S-Z1', email: 'sneha.r@re.com', phone: '+91 98765 43213', status: 'Active' }, - { id: '5', name: 'Vikram Singh', role: 'DD-ZM', region: 'South', zone: 'S-Z1', email: 'vikram.s@re.com', phone: '+91 98765 43214', status: 'Active' }, - { id: '6', name: 'Anjali Gupta', role: 'DD', region: 'East', zone: 'E-Z1', email: 'anjali.g@re.com', phone: '+91 98765 43215', status: 'Active' }, - { id: '7', name: 'Karan Patel', role: 'DD', region: 'West', zone: 'W-Z1', email: 'karan.p@re.com', phone: '+91 98765 43216', status: 'Active' }, - { id: '8', name: 'Neha Jain', role: 'RBM', region: 'Central', zone: 'C-Z1', email: 'neha.j@re.com', phone: '+91 98765 43217', status: 'Active' }, - ]); const handleSaveRole = () => { toast.success('Role saved successfully!'); @@ -645,7 +555,7 @@ export function MasterPage() { }; const handleUpdateReminder = (id: string, field: 'time' | 'unit', value: number | 'days' | 'hours') => { - setSlaReminders(slaReminders.map(r => + setSlaReminders(slaReminders.map(r => r.id === id ? { ...r, [field]: value } : r )); }; @@ -669,14 +579,14 @@ export function MasterPage() { }; const handleUpdateEscalation = (id: string, field: 'time' | 'unit' | 'userEmail', value: number | 'days' | 'hours' | string) => { - setSlaEscalations(slaEscalations.map(e => + setSlaEscalations(slaEscalations.map(e => e.id === id ? { ...e, [field]: value } : e )); }; const handleSaveSLA = () => { if (!selectedSLA) return; - + toast.success(`SLA configuration for ${selectedSLA.stage} updated successfully!`); setShowSLADialog(false); setSelectedSLA(null); @@ -766,28 +676,68 @@ export function MasterPage() { const handleEditRole = (role: Role) => { setSelectedRoleForEdit(role); - // Pre-populate with current permissions - setRoleActionPermissions(role.permissions.filter(p => - ['view_applications', 'review_level1', 'schedule_interview', 'approve_ranking', 'review_documents', 'final_approval'].includes(p) + + // Map current permissions from backend-like codes to our category states + // In our new system, role.permissions is string[] of permissionCodes + setRoleActionPermissions(role.permissions.filter(code => + availablePermissions.find((p: any) => p.permissionCode === code && p.permissionCategory === 'ACTION') )); - setRoleViewPermissions(role.permissions.filter(p => - ['view_all', 'view_payments', 'verify_payments'].includes(p) + setRoleViewPermissions(role.permissions.filter(code => + availablePermissions.find((p: any) => p.permissionCode === code && p.permissionCategory === 'VIEW') )); - setRoleStageAccess(role.permissions.filter(p => - ['manage_opportunities', 'assign_users', 'fnf_settlement'].includes(p) + setRoleStageAccess(role.permissions.filter(code => + availablePermissions.find((p: any) => p.permissionCode === code && p.permissionCategory === 'STAGE') )); + setShowEditRoleDialog(true); }; - const handleSaveRolePermissions = () => { + const handleSaveRolePermissions = async () => { if (!selectedRoleForEdit) return; - - toast.success(`Permissions for ${selectedRoleForEdit.name} role updated successfully!`); - setShowEditRoleDialog(false); - setSelectedRoleForEdit(null); - setRoleActionPermissions([]); - setRoleViewPermissions([]); - setRoleStageAccess([]); + + const allPermissionCodes = [ + ...roleActionPermissions, + ...roleViewPermissions, + ...roleStageAccess + ]; + + // Find IDs for these codes + const permissionIds = availablePermissions + .filter((p: any) => allPermissionCodes.includes(p.permissionCode)) + .map((p: any) => p.id); + + try { + const response = await masterService.updateRole(selectedRoleForEdit.id, { + roleName: selectedRoleForEdit.name, + permissionIds + }) as any; + + if (response.success) { + toast.success(`Permissions for ${selectedRoleForEdit.name} role updated successfully!`); + setShowEditRoleDialog(false); + setSelectedRoleForEdit(null); + setRoleActionPermissions([]); + setRoleViewPermissions([]); + setRoleStageAccess([]); + fetchInitialData(); // Refresh data + } else { + toast.error(response.message || 'Failed to update permissions'); + } + } catch (error) { + console.error('Update role error:', error); + toast.error('Error saving role permissions'); + } + }; + + const handleEditUserAssignment = (user: any) => { + setSelectedUserForEdit(user); + setFetchedUser({ + ...user, + fullName: user.name, // Mapping for dialog + }); + setSelectedUserZone(user.zoneId || ''); + setSelectedUserRegion(user.regionId || ''); + setShowUserAssignDialog(true); }; const handleFetchUser = () => { @@ -815,22 +765,33 @@ export function MasterPage() { toast.success('User details fetched successfully!'); }; - const handleSaveUserAssignment = () => { + const handleSaveUserAssignment = async () => { if (!fetchedUser) { toast.error('Please fetch user details first'); return; } - toast.success('User permissions updated successfully!'); - setShowUserAssignDialog(false); - setUserLookupValue(''); - setFetchedUser(null); - setActionPermissions([]); - setViewPermissions([]); - setStageAccess([]); - setSelectedUserZone(''); - setSelectedUserRegion(''); - setSelectedUserDistricts([]); + try { + const response = await masterService.updateUser(fetchedUser.id, { + roleCode: fetchedUser.roleCode, + zoneId: selectedUserZone, + regionId: selectedUserRegion, + // Add other fields if needed + }) as any; + + if (response.success) { + toast.success('User updated successfully!'); + setShowUserAssignDialog(false); + setSelectedUserForEdit(null); + setFetchedUser(null); + fetchInitialData(); // Refresh table + } else { + toast.error(response.message || 'Failed to update user'); + } + } catch (error) { + console.error('Update user error:', error); + toast.error('Error saving user data'); + } }; return ( @@ -845,898 +806,904 @@ export function MasterPage() {
{/* Tabs */} - - - - - Organisation - - - - Roles & Permissions - - - - SLA Configuration - - - - Email Templates - - - - Locations - - + {loading ? ( +
+
+

Loading configuration...

+
+ ) : ( + + + + + Organisation + + + + Roles & Permissions + + + + SLA Configuration + + + + Email Templates + + + + Locations + + - {/* Regional Hierarchy Tab */} - - {/* Zones Overview */} -
- {zones.map((zone) => { - const zoneRegions = regionalOffices.filter(r => r.zoneId === zone.id); - const zoneASMs = asms.filter(a => a.zoneId === zone.id); - const totalRegionalOfficers = zoneRegions.reduce((sum, region) => sum + region.regionalOfficerCount, 0); - - return ( - setSelectedZone(selectedZone === zone.id ? 'all' : zone.id)} - > - -
-
- - {zone.name} Zone -
- {zone.code} -
-
- -

{zone.description}

-
-
- States - {zone.states.length} -
-
- Regions - {zoneRegions.length} -
-
- Regional Officers - {totalRegionalOfficers} -
-
- ASMs - {zoneASMs.length} -
-
- ZMs - {zone.zmCount} -
-
-
-
- ); - })} -
+ {/* Regional Hierarchy Tab */} + + {/* Zones Overview */} +
+ {zones.map((zone) => { + const zoneRegions = regionalOffices.filter(r => r.zoneId === zone.id); + const zoneASMs = asms.filter(a => a.zoneId === zone.id); + const totalRegionalOfficers = zoneRegions.reduce((sum, region) => sum + region.regionalOfficerCount, 0); - {/* Zones Detailed Management */} - - -
-
- Zone Details - Geographical coverage and state mappings for each zone + return ( + setSelectedZone(selectedZone === zone.id ? 'all' : zone.id)} + > + +
+
+ + {zone.name} Zone +
+ {zone.code} +
+
+ +

{zone.description}

+
+
+ States + {zone.states.length} +
+
+ Regions + {zoneRegions.length} +
+
+ Regional Officers + {totalRegionalOfficers} +
+
+ ASMs + {zoneASMs.length} +
+
+ ZMs + {zone.zmCount} +
+
+
+
+ ); + })} +
+ + {/* Zones Detailed Management */} + + +
+
+ Zone Details + Geographical coverage and state mappings for each zone +
+
- -
-
- - -
- {zones.filter(z => selectedZone === 'all' || z.id === selectedZone).map((zone) => ( -
-
-
-
- -
-
-

{zone.name}

-

{zone.code}

-
-
-
- -
-
- - {zone.description && ( -
-

{zone.description}

-
- )} - -
- -
- {zone.states.map((state, idx) => ( - - {state} - - ))} -
-
- - {zone.zbh && zone.zbh.name && ( -
- -
-
- - {zone.zbh.name} + + + +
+ {zones.filter(z => selectedZone === 'all' || z.id === selectedZone).map((zone) => ( +
+
+
+
+
- {zone.zbh.email && ( -
- - {zone.zbh.email} -
- )} - {zone.zbh.phone && ( -
- {zone.zbh.phone} -
- )} +
+

{zone.name}

+

{zone.code}

+
+
+
+
- )} - {zone.zonalManagers && zone.zonalManagers.length > 0 && ( -
- -
- {zone.zonalManagers.map((zm, idx) => ( -
-
- - {zm.name} - ZM-{idx + 1} -
- {zm.email && ( -
- - {zm.email} -
- )} - {zm.phone && ( -
- {zm.phone} -
- )} - {zm.districts && zm.districts.length > 0 && ( -
- -
- {zm.districts.map((district, dIdx) => ( - - - {district} - - ))} -
-
- )} -
+ {zone.description && ( +
+

{zone.description}

+
+ )} + +
+ +
+ {zone.states.map((state, idx) => ( + + {state} + ))}
- )} -
- ))} -
- - - - {/* Regional Offices Management */} - - -
-
- Regional Offices - Manage regional offices within zones -
- -
-
- - - - - Region Code - Region Name - Zone - States - Key Cities - Regional Officers - ASMs - Status - Actions - - - - {regionalOffices - .filter(r => selectedZone === 'all' || r.zoneId === selectedZone) - .map((region) => ( - - -
- - {region.code} -
-
- {region.name} - - {region.zoneName} - - -
- {region.states.slice(0, 2).map((state, idx) => ( - - {state} - - ))} - {region.states.length > 2 && ( - - +{region.states.length - 2} - - )} -
-
- -
- {region.cities.slice(0, 3).map((city, idx) => ( - - {city}{idx < Math.min(region.cities.length, 3) - 1 ? ',' : ''} - - ))} - {region.cities.length > 3 && ( - +{region.cities.length - 3} - )} -
-
- - {region.regionalOfficerCount} - - - {region.asmCount} - - - {region.status === 'Active' ? ( - Active - ) : ( - Inactive - )} - - -
- - -
-
-
- ))} -
-
-
-
- - {/* Zonal Manager Mapping */} - - -
-
- Zonal Managers (ZM) - Map Zonal Managers to zones, regions, and districts -
- -
-
- - - - - ZM Code - Name - Zone - Region - Districts Managed - Contact - Status - Actions - - - - {zonalManagerMappings - .filter(zm => selectedZone === 'all' || zm.zoneId === selectedZone) - .map((zm) => ( - - -
- - {zm.code} -
-
- {zm.name} - - {zm.zoneName} - - {zm.regionName} - -
- {zm.districts.slice(0, 3).map((district, idx) => ( - - {district} - - ))} - {zm.districts.length > 3 && ( - - +{zm.districts.length - 3} - - )} -
-
- -
-

{zm.email}

-

{zm.phone}

-
-
- - {zm.status === 'Active' ? ( - Active - ) : ( - Inactive - )} - - -
- - -
-
-
- ))} -
-
-
-
- - {/* ASM Management */} - - -
-
- Area Sales Managers (ASM) - Manage ASMs across all regions and zones -
- -
-
- - - - - ASM Code - Name - Zone - Region - Areas Managed - Contact - Status - Actions - - - - {asms - .filter(a => selectedZone === 'all' || a.zoneId === selectedZone) - .map((asm) => ( - - -
- - {asm.code} -
-
- {asm.name} - - {asm.zoneName} - - {asm.regionName} - -
- {asm.areasManaged.map((area, idx) => ( - - {area} - - ))} -
-
- -
-

{asm.email}

-

{asm.phone}

-
-
- - {asm.status === 'Active' ? ( - Active - ) : ( - Inactive - )} - - -
- - -
-
-
- ))} -
-
-
-
- - - {/* Roles & Permissions Tab */} - - {/* User Assignments with Geographical Mapping */} - - -
-
- User Permissions Management - Configure granular permissions for users based on their roles and geographical assignments -
- -
-
- - - - - Name - Role - Zone - Region - Permissions Configured - Contact - Status - Actions - - - - {userAssignments.map((user) => ( - - -
- - {user.name} -
-
- - - {user.role} - - - - {user.zone} - - {user.region} - -
- - 5 Actions - - - 7 Views - - - 4 Stages - -
-
- -
-

{user.email}

-

{user.phone}

-
-
- - {user.status === 'Active' ? ( - Active - ) : ( - Inactive - )} - - -
- - -
-
-
- ))} -
-
-
-
- - {/* Role Definitions Reference Card */} - - - Role Definitions - Overview of available roles and their access levels - - -
- {roles.map((role) => ( -
-
-
- -

{role.name}

-
- {role.userCount} users -
-
- -
- {role.permissions.slice(0, 3).map((perm, idx) => ( - - {perm.replace('_', ' ')} - - ))} - {role.permissions.length > 3 && ( - - +{role.permissions.length - 3} - - )} -
-
- -
- ))} -
-
-
-
- - {/* SLA Configuration Tab */} - - - - SLA Configuration - Configure service level agreements, reminders, and escalations for each stage - - -
- {slaConfigs.map((sla) => ( -
-
-
- -
-

{sla.stage}

-

TAT: {sla.days} days

-
-
-
- {sla.enabled ? ( - - - Active - - ) : ( - - - Inactive - - )} - -
-
- -
- {/* Reminders */} -
-
- - Reminders ({sla.reminders.length}) -
-
- {sla.reminders.map((reminder, index) => ( -
- - {reminder.time} {reminder.unit} - - before SLA -
- ))} - {sla.reminders.length === 0 && ( -

No reminders set

- )} -
-
- - {/* Escalations */} -
-
- - Escalations ({sla.escalations.length}) -
-
- {sla.escalations.map((escalation) => ( -
-
- - L{escalation.level} - - after {escalation.time} {escalation.unit} + {zone.zbh && zone.zbh.name && ( +
+ +
+
+ + {zone.zbh.name}
-

→ {escalation.userEmail}

+ {zone.zbh.email && ( +
+ + {zone.zbh.email} +
+ )} + {zone.zbh.phone && ( +
+ {zone.zbh.phone} +
+ )}
- ))} - {sla.escalations.length === 0 && ( -

No escalations set

- )} -
+
+ )} + + {zone.zonalManagers && zone.zonalManagers.length > 0 && ( +
+ +
+ {zone.zonalManagers.map((zm, idx) => ( +
+
+ + {zm.name} + ZM-{idx + 1} +
+ {zm.email && ( +
+ + {zm.email} +
+ )} + {zm.phone && ( +
+ {zm.phone} +
+ )} + {zm.districts && zm.districts.length > 0 && ( +
+ +
+ {zm.districts.map((district, dIdx) => ( + + + {district} + + ))} +
+
+ )} +
+ ))} +
+
+ )}
-
+ ))}
- ))} -
- - - + + + - {/* Email Templates Tab */} - - - -
-
- Email & Letter Templates - Manage automated email and letter templates + {/* Regional Offices Management */} + + +
+
+ Regional Offices + Manage regional offices within zones +
+
- -
- - - - - - Template Name - Subject - Trigger - Last Modified - Actions - - - - {emailTemplates.map((template) => ( - - -
- - {template.name} -
-
- {template.subject} - - {template.trigger} - - {template.lastModified} - -
- - -
-
+ + +
+ + + Region Code + Region Name + Zone + States + Key Cities + Regional Officers + ASMs + Status + Actions - ))} - -
-
- + + + {regionalOffices + .filter(r => selectedZone === 'all' || r.zoneId === selectedZone) + .map((region) => ( + + +
+ + {region.code} +
+
+ {region.name} + + {region.zoneName} + + +
+ {region.states.slice(0, 2).map((state, idx) => ( + + {state} + + ))} + {region.states.length > 2 && ( + + +{region.states.length - 2} + + )} +
+
+ +
+ {region.cities.slice(0, 3).map((city, idx) => ( + + {city}{idx < Math.min(region.cities.length, 3) - 1 ? ',' : ''} + + ))} + {region.cities.length > 3 && ( + +{region.cities.length - 3} + )} +
+
+ + {region.regionalOfficerCount} + + + {region.asmCount} + + + {region.status === 'Active' ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ))} +
+ + + - {/* Template Variables Card */} - - - Available Variables - Use these variables in your templates - - -
- {[ - '{{applicant_name}}', - '{{application_id}}', - '{{location}}', - '{{interview_date}}', - '{{interview_time}}', - '{{reviewer_name}}', - '{{status}}', - '{{reason}}', - '{{payment_amount}}', - '{{due_date}}', - '{{company_name}}', - '{{support_email}}' - ].map((variable) => ( - - {variable} - - ))} -
-
-
- - - {/* Locations Tab */} - - - -
-
- Dealership Locations - Manage locations where dealerships are offered + {/* Zonal Manager Mapping */} + + +
+
+ Zonal Managers (ZM) + Map Zonal Managers to zones, regions, and districts +
+
- -
- - - - - - State - City - District - Active Period - Status - Actions - - - - {locations.map((location) => { - const activeFrom = new Date(location.activeFrom); - const activeTo = new Date(location.activeTo); - const today = new Date(); - const isCurrentlyActive = today >= activeFrom && today <= activeTo; - const daysRemaining = Math.ceil((activeTo.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); - - return ( - + + +
+ + + ZM Code + Name + Zone + Region + Districts Managed + Contact + Status + Actions + + + + {zonalManagerMappings + .filter(zm => selectedZone === 'all' || zm.zoneId === selectedZone) + .map((zm) => ( + + +
+ + {zm.code} +
+
+ {zm.name} + + {zm.zoneName} + + {zm.regionName} + +
+ {zm.districts.slice(0, 3).map((district, idx) => ( + + {district} + + ))} + {zm.districts.length > 3 && ( + + +{zm.districts.length - 3} + + )} +
+
+ +
+

{zm.email}

+

{zm.phone}

+
+
+ + {zm.status === 'Active' ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ))} +
+
+
+ + + {/* ASM Management */} + + +
+
+ Area Sales Managers (ASM) + Manage ASMs across all regions and zones +
+ +
+
+ + + + + ASM Code + Name + Zone + Region + Areas Managed + Contact + Status + Actions + + + + {asms + .filter(a => selectedZone === 'all' || a.zoneId === selectedZone) + .map((asm) => ( + + +
+ + {asm.code} +
+
+ {asm.name} + + {asm.zoneName} + + {asm.regionName} + +
+ {asm.areasManaged.map((area, idx) => ( + + {area} + + ))} +
+
+ +
+

{asm.email}

+

{asm.phone}

+
+
+ + {asm.status === 'Active' ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ))} +
+
+
+
+ + + {/* Roles & Permissions Tab */} + + {/* User Assignments with Geographical Mapping */} + + +
+
+ User Permissions Management + Configure granular permissions for users based on their roles and geographical assignments +
+ +
+
+ + + + + Name + Role + Zone + Region + Permissions Configured + Contact + Status + Actions + + + + {userAssignedData.map((user) => ( +
- - {location.state} -
-
- {location.city} - {location.district} - -
-
- From: - - {activeFrom.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })} - -
-
- To: - - {activeTo.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })} - -
- {isCurrentlyActive && daysRemaining > 0 && daysRemaining <= 30 && ( -

- {daysRemaining} days remaining -

- )} + + {user.name}
- {location.status === 'Active' ? ( + + {user.role} + + + + {user.zone} + + {user.region} + +
+ + {user.permissions?.filter(p => availablePermissions.find(ap => ap.permissionCode === p && ap.permissionCategory === 'ACTION'))?.length || 0} Actions + + + {user.permissions?.filter(p => availablePermissions.find(ap => ap.permissionCode === p && ap.permissionCategory === 'VIEW'))?.length || 0} Views + + + {user.permissions?.filter(p => availablePermissions.find(ap => ap.permissionCode === p && ap.permissionCategory === 'STAGE'))?.length || 0} Stages + +
+
+ +
+

{user.email}

+

{user.phone}

+
+
+ + {user.status === 'Active' ? ( Active ) : ( Inactive )} - -
- - + +
+
+
+ ))} +
+
+
+
+ + {/* Role Definitions Reference Card */} + + + Role Definitions + Overview of available roles and their access levels + + +
+ {roles.map((role) => ( +
+
+
+ +

{role.name}

+
+ {role.userCount} users +
+
+ +
+ {role.permissions.slice(0, 3).map((perm, idx) => ( + + {perm.replace('_', ' ')} + + ))} + {role.permissions.length > 3 && ( + + +{role.permissions.length - 3} + + )} +
+
+ +
+ ))} +
+
+
+
+ + {/* SLA Configuration Tab */} + + + + SLA Configuration + Configure service level agreements, reminders, and escalations for each stage + + +
+ {slaConfigs.map((sla) => ( +
+
+
+ +
+

{sla.stage}

+

TAT: {sla.days} days

+
+
+
+ {sla.enabled ? ( + + + Active + + ) : ( + + + Inactive + + )} +
- +
+ +
+ {/* Reminders */} +
+
+ + Reminders ({sla.reminders.length}) +
+
+ {sla.reminders.map((reminder, index) => ( +
+ + {reminder.time} {reminder.unit} + + before SLA +
+ ))} + {sla.reminders.length === 0 && ( +

No reminders set

+ )} +
+
+ + {/* Escalations */} +
+
+ + Escalations ({sla.escalations.length}) +
+
+ {sla.escalations.map((escalation) => ( +
+
+ + L{escalation.level} + + after {escalation.time} {escalation.unit} +
+

→ {escalation.userEmail}

+
+ ))} + {sla.escalations.length === 0 && ( +

No escalations set

+ )} +
+
+
+
+ ))} +
+
+
+
+ + {/* Email Templates Tab */} + + + +
+
+ Email & Letter Templates + Manage automated email and letter templates +
+ +
+
+ + + + + Template Name + Subject + Trigger + Last Modified + Actions - ); - })} - -
-
-
+ + + {emailTemplates.map((template) => ( + + +
+ + {template.name} +
+
+ {template.subject} + + {template.trigger} + + {template.lastModified} + +
+ + +
+
+
+ ))} +
+ + + - {/* Bulk Upload Card */} + {/* Template Variables Card */} + + + Available Variables + Use these variables in your templates + + +
+ {[ + '{{applicant_name}}', + '{{application_id}}', + '{{location}}', + '{{interview_date}}', + '{{interview_time}}', + '{{reviewer_name}}', + '{{status}}', + '{{reason}}', + '{{payment_amount}}', + '{{due_date}}', + '{{company_name}}', + '{{support_email}}' + ].map((variable) => ( + + {variable} + + ))} +
+
+
+
- - + {/* Locations Tab */} + + + +
+
+ Dealership Locations + Manage locations where dealerships are offered +
+ +
+
+ + + + + State + City + District + Active Period + Status + Actions + + + + {locations.map((location) => { + const activeFrom = new Date(location.activeFrom); + const activeTo = new Date(location.activeTo); + const today = new Date(); + const isCurrentlyActive = today >= activeFrom && today <= activeTo; + const daysRemaining = Math.ceil((activeTo.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); + + return ( + + +
+ + {location.state} +
+
+ {location.city} + {location.district} + +
+
+ From: + + {activeFrom.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })} + +
+
+ To: + + {activeTo.toLocaleDateString('en-IN', { day: 'numeric', month: 'short', year: 'numeric' })} + +
+ {isCurrentlyActive && daysRemaining > 0 && daysRemaining <= 30 && ( +

+ {daysRemaining} days remaining +

+ )} +
+
+ + {location.status === 'Active' ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ); + })} +
+
+
+
+ + {/* Bulk Upload Card */} + +
+ + )} {/* Add/Edit Role Dialog */} @@ -1817,12 +1784,12 @@ export function MasterPage() {
Turn Around Time (TAT)
- setSlaTAT(Number(e.target.value))} - placeholder="Enter days" - className="mt-2" + placeholder="Enter days" + className="mt-2" />
@@ -1840,7 +1807,7 @@ export function MasterPage() {

Configure when to send reminder notifications before SLA time

- +
{slaReminders.length === 0 ? (

No reminders configured. Click "Add Reminder" to create one.

@@ -1852,15 +1819,15 @@ export function MasterPage() {
Send reminder - handleUpdateReminder(reminder.id, 'time', Number(e.target.value))} className="w-20" min="1" /> - before SLA
-

Configure escalation levels if task is not completed after SLA breach

- +
{slaEscalations.length === 0 ? (

No escalations configured. Click "Add Escalation" to create one.

@@ -1913,15 +1880,15 @@ export function MasterPage() {
Escalate after - handleUpdateEscalation(escalation.id, 'time', Number(e.target.value))} className="w-20" min="1" /> - of breach
- -
-