Compare commits

...

12 Commits

54 changed files with 1411 additions and 1335 deletions

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "es5"
}

View File

@@ -1,8 +1,10 @@
import js from '@eslint/js' import js from '@eslint/js';
import globals from 'globals' import globals from 'globals';
import react from 'eslint-plugin-react' import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from 'eslint-plugin-react-refresh';
import prettier from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';
export default [ export default [
{ ignores: ['dist'] }, { ignores: ['dist'] },
@@ -22,17 +24,17 @@ export default [
react, react,
'react-hooks': reactHooks, 'react-hooks': reactHooks,
'react-refresh': reactRefresh, 'react-refresh': reactRefresh,
prettier,
}, },
rules: { rules: {
...js.configs.recommended.rules, ...js.configs.recommended.rules,
...react.configs.recommended.rules, ...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules, ...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules, ...reactHooks.configs.recommended.rules,
...prettierConfig.rules,
'react/jsx-no-target-blank': 'off', 'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'warn', 'prettier/prettier': 'error',
{ allowConstantExport: true },
],
}, },
}, },
] ];

115
package-lock.json generated
View File

@@ -28,11 +28,14 @@
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.9.0", "eslint": "^9.9.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.35.0", "eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9", "eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0", "globals": "^15.9.0",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"prettier": "^3.6.2",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"vite": "^5.4.1" "vite": "^5.4.1"
} }
@@ -1090,6 +1093,19 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
@@ -2492,6 +2508,53 @@
} }
} }
}, },
"node_modules/eslint-config-prettier": {
"version": "10.1.8",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
"funding": {
"url": "https://opencollective.com/eslint-config-prettier"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
"integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.11.7"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-react": { "node_modules/eslint-plugin-react": {
"version": "7.37.0", "version": "7.37.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.0.tgz",
@@ -2738,6 +2801,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -4655,6 +4725,35 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/prop-types": { "node_modules/prop-types": {
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5371,6 +5470,22 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.13", "version": "3.4.13",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",

View File

@@ -7,6 +7,7 @@
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
@@ -30,11 +31,14 @@
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.9.0", "eslint": "^9.9.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.35.0", "eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0", "eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9", "eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0", "globals": "^15.9.0",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"prettier": "^3.6.2",
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"vite": "^5.4.1" "vite": "^5.4.1"
} }

View File

@@ -3,4 +3,4 @@ export default {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View File

@@ -1,54 +1,54 @@
{ {
"appName": "সমন্বিত PACS কিষাণ সমাধান", "appName": "সমন্বিত PACS কিষাণ সমাধান",
"username": "ব্যবহারকারীর নাম", "username": "ব্যবহারকারীর নাম",
"pacsName": "PACS নাম", "pacsName": "PACS নাম",
"userType": "ব্যবহারকারীর ধরন", "userType": "ব্যবহারকারীর ধরন",
"moduleName": "মডিউলের নাম", "moduleName": "মডিউলের নাম",
"date": "তারিখ", "date": "তারিখ",
"home": "হোম", "home": "হোম",
"moduleList": "মডিউল তালিকা", "moduleList": "মডিউল তালিকা",
"enquiry": "অনুসন্ধান", "enquiry": "অনুসন্ধান",
"lockerOperation": "লকার অপারেশন", "lockerOperation": "লকার অপারেশন",
"reports": "রিপোর্ট", "reports": "রিপোর্ট",
"worklist": "কাজের", "worklist": "কাজের",
"userManagement": "ব্যবহারকারী ব্যবস্থাপনা", "userManagement": "ব্যবহারকারী ব্যবস্থাপনা",
"logout": "প্রস্থান", "logout": "প্রস্থান",
"kcc": "কেসিসি", "kcc": "কেসিসি",
"trading": "বাণিজ্য", "trading": "বাণিজ্য",
"asset": "সম্পদ", "asset": "সম্পদ",
"locker": "লকার", "locker": "লকার",
"pacsTeller": "PACS টেলার", "pacsTeller": "PACS টেলার",
"authoriser": "অনুমোদনকারী", "authoriser": "অনুমোদনকারী",
"cashOfficer": "নগদ কর্মকর্তা", "cashOfficer": "নগদ কর্মকর্তা",
"selfHelpGroup": "স্বনির্ভর গোষ্ঠী", "selfHelpGroup": "স্বনির্ভর গোষ্ঠী",
"deposit": "আমানত", "deposit": "আমানত",
"loan": "ঋণ", "loan": "ঋণ",
"share": "শেয়ার", "share": "শেয়ার",
"lockerEnquiry": "লকার অনুসন্ধান", "lockerEnquiry": "লকার অনুসন্ধান",
"accountEnquiry": "অ্যাকাউন্ট অনুসন্ধান", "accountEnquiry": "অ্যাকাউন্ট অনুসন্ধান",
"accountCreation": "অ্যাকাউন্ট তৈরি", "accountCreation": "অ্যাকাউন্ট তৈরি",
"cabinetMaintenance": "ক্যাবিনেট রক্ষণাবেক্ষণ", "cabinetMaintenance": "ক্যাবিনেট রক্ষণাবেক্ষণ",
"lockerMaintenance": "লকার রক্ষণাবেক্ষণ", "lockerMaintenance": "লকার রক্ষণাবেক্ষণ",
"rentPenaltyCollection": "ভাড়া / জরিমানা সংগ্রহ", "rentPenaltyCollection": "ভাড়া / জরিমানা সংগ্রহ",
"chargeManagement": "চার্জ ব্যবস্থাপনা", "chargeManagement": "চার্জ ব্যবস্থাপনা",
"checkInOutManagement": "চেক ইন/আউট ব্যবস্থাপনা", "checkInOutManagement": "চেক ইন/আউট ব্যবস্থাপনা",
"accountSurrender": "অ্যাকাউন্ট সমর্পণ", "accountSurrender": "অ্যাকাউন্ট সমর্পণ",
"myIntimation": "আমার বিজ্ঞপ্তি", "myIntimation": "আমার বিজ্ঞপ্তি",
"changePassword": "পাসওয়ার্ড পরিবর্তন", "changePassword": "পাসওয়ার্ড পরিবর্তন",
"resetLogin": "লগইন স্ট্যাটাস রিসেট করুন", "resetLogin": "লগইন স্ট্যাটাস রিসেট করুন",
"currentDate" : "{{val, datetime}}", "currentDate": "{{val, datetime}}",
"notifications": "বিজ্ঞপ্তি", "notifications": "বিজ্ঞপ্তি",
"holidayList": "ছুটির তালিকা", "holidayList": "ছুটির তালিকা",
"information": "তথ্য", "information": "তথ্য",
"hpn_complete_before_31": "অনুগ্রহ করে ৩১শে মার্চের সমস্ত লেনদেন ২১:00 ঘটিকার আগে সম্পন্ন করুন", "hpn_complete_before_31": "অনুগ্রহ করে ৩১শে মার্চের সমস্ত লেনদেন ২১:00 ঘটিকার আগে সম্পন্ন করুন",
"hpn_npa": "আগামী ৩১শে মার্চ ২১:00 ঘটিকার আগে ঋণ এনপিএ রিপোর্ট থেকে প্রস্তাবিত এনপিএ স্থিতি যাচাই করার পরে প্রযোজ্য ঋণ অ্যাকাউন্টের জন্য এনপিএ চিহ্নিত/অচিহ্নিত করুন", "hpn_npa": "আগামী ৩১শে মার্চ ২১:00 ঘটিকার আগে ঋণ এনপিএ রিপোর্ট থেকে প্রস্তাবিত এনপিএ স্থিতি যাচাই করার পরে প্রযোজ্য ঋণ অ্যাকাউন্টের জন্য এনপিএ চিহ্নিত/অচিহ্নিত করুন",
"hpn_helpdesk_contact": "ফোনে সমস্যার সমাধানের জন্য আমাদের সাথে যোগাযোগ করুন 033-4065-9546 / 4071-8135 (সকাল ৯:00 থেকে রাত ৯:00 পর্যন্ত)", "hpn_helpdesk_contact": "ফোনে সমস্যার সমাধানের জন্য আমাদের সাথে যোগাযোগ করুন 033-4065-9546 / 4071-8135 (সকাল ৯:00 থেকে রাত ৯:00 পর্যন্ত)",
"hpn_rupay_kcc_time": "সমস্ত রূপে কিসান ক্রেডিট কার্ড লেনদেন ১৯:00 ঘটিকার আগে সম্পন্ন করুন", "hpn_rupay_kcc_time": "সমস্ত রূপে কিসান ক্রেডিট কার্ড লেনদেন ১৯:00 ঘটিকার আগে সম্পন্ন করুন",
"hpn_rupay_kcc_atm": "কিছু অভ্যন্তরীণ পরিবর্তনের কারণে, মাইক্রো-এটিএম এর মাধ্যমে রূপে কিসান ক্রেডিট কার্ড কার্যক্রম বন্ধ করা হয়েছে। সেবা আগামীকাল থেকে পুনরায় চালু হবে।", "hpn_rupay_kcc_atm": "কিছু অভ্যন্তরীণ পরিবর্তনের কারণে, মাইক্রো-এটিএম এর মাধ্যমে রূপে কিসান ক্রেডিট কার্ড কার্যক্রম বন্ধ করা হয়েছে। সেবা আগামীকাল থেকে পুনরায় চালু হবে।",
"copyright_statement": "কপিরাইট © ২০২৩, টাটা কনসাল্টেন্সি সার্ভিসেস। সমস্ত অধিকার সংরক্ষিত", "copyright_statement": "কপিরাইট © ২০২৩, টাটা কনসাল্টেন্সি সার্ভিসেস। সমস্ত অধিকার সংরক্ষিত",
"privacy_policy": "গোপনীয়তা নীতি", "privacy_policy": "গোপনীয়তা নীতি",
"create": "তৈরি করুন", "create": "তৈরি করুন",
"operation": "অপারেশন", "operation": "অপারেশন",
"next": "পরবর্তী", "next": "পরবর্তী",
"select": "নির্বাচন করুন" "select": "নির্বাচন করুন"
} }

View File

@@ -1,83 +1,83 @@
{ {
"appName": "Integrated PACS Kisan Solution", "appName": "Integrated PACS Kisan Solution",
"username": "Username", "username": "Username",
"pacsName": "PACS Name", "pacsName": "PACS Name",
"userType": "User Type", "userType": "User Type",
"moduleName": "Module Name", "moduleName": "Module Name",
"languageSelect": "Language", "languageSelect": "Language",
"date": "Date", "date": "Date",
"home": "Home", "home": "Home",
"moduleList": "Module List", "moduleList": "Module List",
"enquiry": "Enquiry", "enquiry": "Enquiry",
"lockerOperation": "Locker Operation", "lockerOperation": "Locker Operation",
"reports": "Reports", "reports": "Reports",
"worklist": "Worklist", "worklist": "Worklist",
"userManagement": "User Management", "userManagement": "User Management",
"logout": "Logout", "logout": "Logout",
"kcc": "KCCP", "kcc": "KCCP",
"trading": "Trading", "trading": "Trading",
"asset": "Asset", "asset": "Asset",
"selfHelpGroup": "Self Help Group", "selfHelpGroup": "Self Help Group",
"deposit": "Deposit", "deposit": "Deposit",
"loan": "Loan", "loan": "Loan",
"share": "Share", "share": "Share",
"locker": "Locker", "locker": "Locker",
"pacsTeller": "PACS Teller", "pacsTeller": "PACS Teller",
"authoriser": "Authoriser", "authoriser": "Authoriser",
"cashOfficer": "Cash Officer", "cashOfficer": "Cash Officer",
"lockerEnquiry": "Locker Enquiry", "lockerEnquiry": "Locker Enquiry",
"accountEnquiry": "Account Enquiry", "accountEnquiry": "Account Enquiry",
"accountCreation": "Account Creation", "accountCreation": "Account Creation",
"cabinetMaintenance": "Cabinet Maintenance", "cabinetMaintenance": "Cabinet Maintenance",
"lockerMaintenance": "Locker Maintenance", "lockerMaintenance": "Locker Maintenance",
"rentPenaltyCollection": "Rent / Penalty Collection", "rentPenaltyCollection": "Rent / Penalty Collection",
"chargeManagement": "Charge Management", "chargeManagement": "Charge Management",
"checkInOutManagement": "Check in/out Management", "checkInOutManagement": "Check in/out Management",
"accountSurrender": "Account Surrender", "accountSurrender": "Account Surrender",
"myIntimation": "My Intimation", "myIntimation": "My Intimation",
"changePassword": "Change Password", "changePassword": "Change Password",
"resetLogin": "Reset Login Status", "resetLogin": "Reset Login Status",
"currentDate" : "{{val, datetime}}", "currentDate": "{{val, datetime}}",
"notifications": "Notification", "notifications": "Notification",
"holidayList": "Holiday List", "holidayList": "Holiday List",
"information": "Information", "information": "Information",
"hpn_complete_before_31": "Please complete all transactions of 31st March before 21:00 Hrs", "hpn_complete_before_31": "Please complete all transactions of 31st March before 21:00 Hrs",
"hpn_npa": "Do Mark/Unmark NPA for applicable loan accounts after verifying suggestive NPA status from loan NPA reports before upcoming 31st March 21:00 Hrs", "hpn_npa": "Do Mark/Unmark NPA for applicable loan accounts after verifying suggestive NPA status from loan NPA reports before upcoming 31st March 21:00 Hrs",
"hpn_helpdesk_contact": "For on call issue resolution reach us at 033-4065-9546 / 4071-8135 (9:00 AM to 9:00 PM)", "hpn_helpdesk_contact": "For on call issue resolution reach us at 033-4065-9546 / 4071-8135 (9:00 AM to 9:00 PM)",
"hpn_rupay_kcc_time": "Complete all Rupay KCC transactions before 19:00 Hrs", "hpn_rupay_kcc_time": "Complete all Rupay KCC transactions before 19:00 Hrs",
"hpn_rupay_kcc_atm": "Due to some internal changes, Rupay KCC Operations through Micro-ATM has been stopped. The service will resume starting tomorrow.", "hpn_rupay_kcc_atm": "Due to some internal changes, Rupay KCC Operations through Micro-ATM has been stopped. The service will resume starting tomorrow.",
"copyright_statement": "Copyright © 2023, Tata Consultancy Services. All rights reserved", "copyright_statement": "Copyright © 2023, Tata Consultancy Services. All rights reserved",
"privacy_policy": "Privacy Policy", "privacy_policy": "Privacy Policy",
"create": "Create", "create": "Create",
"operation": "Operation", "operation": "Operation",
"next": "Next", "next": "Next",
"select": "Select", "select": "Select",
"cabinetCreation": "Cabinet Creation", "cabinetCreation": "Cabinet Creation",
"cabinetId": "Cabinet ID", "cabinetId": "Cabinet ID",
"cabinetKeyId": "Cabinet Key ID", "cabinetKeyId": "Cabinet Key ID",
"noOfLockers": "No of Lockers", "noOfLockers": "No of Lockers",
"productCode": "Product Code", "productCode": "Product Code",
"interestCategory": "Interest Category", "interestCategory": "Interest Category",
"segmentCode": "Segment Code", "segmentCode": "Segment Code",
"accountHolderType": "Account Holder Type", "accountHolderType": "Account Holder Type",
"primaryCifNumber": "Primary CIF Number", "primaryCifNumber": "Primary CIF Number",
"nomineeCifNumber": "Nominee CIF Number", "nomineeCifNumber": "Nominee CIF Number",
"activityCode": "Activity Code", "activityCode": "Activity Code",
"customerType": "Customer Type", "customerType": "Customer Type",
"collateralFDAccount": "Collateral FD Account", "collateralFDAccount": "Collateral FD Account",
"rentPayAccount": "Rent Pay Account", "rentPayAccount": "Rent Pay Account",
"submit": "Submit", "submit": "Submit",
"lockerId": "Locker ID", "lockerId": "Locker ID",
"status": "Status", "status": "Status",
"lockerStatus": "Locker Status", "lockerStatus": "Locker Status",
"open": "Open", "open": "Open",
"close": "Close", "close": "Close",
"reasonForChange": "Reason for Change", "reasonForChange": "Reason for Change",
"oldKeyId": "Old Key ID", "oldKeyId": "Old Key ID",
"newKeyId": "New Key ID", "newKeyId": "New Key ID",
"confirmNewKeyId": "Confirm New Key ID", "confirmNewKeyId": "Confirm New Key ID",
"highlightedFieldsInvalid": "Highlighted fields are invalid", "highlightedFieldsInvalid": "Highlighted fields are invalid",
"changeStatus": "Change Status", "changeStatus": "Change Status",
"keySwap": "Key Swap", "keySwap": "Key Swap",
"chargeEdit": "Edit Charges" "chargeEdit": "Edit Charges"
} }

View File

@@ -1,55 +1,56 @@
{ {
"appName": "समन्वित PACS किसान समाधान", "appName": "समन्वित PACS किसान समाधान",
"username": "उपयोगकर्ता नाम", "username": "उपयोगकर्ता नाम",
"pacsName": "PACS नाम", "pacsName": "PACS नाम",
"userType": "उपयोगकर्ता प्रकार", "userType": "उपयोगकर्ता प्रकार",
"moduleName": "मॉड्यूल नाम", "moduleName": "मॉड्यूल नाम",
"languageSelect": "भाषा", "languageSelect": "भाषा",
"date": "तारीख", "date": "तारीख",
"home": "होम", "home": "होम",
"moduleList": "मॉड्यूल सूची", "moduleList": "मॉड्यूल सूची",
"enquiry": "पूछताछ", "enquiry": "पूछताछ",
"lockerOperation": "लॉकर ऑपरेशन", "lockerOperation": "लॉकर ऑपरेशन",
"reports": "रिपोर्ट", "reports": "रिपोर्ट",
"worklist": "कार्य सूची", "worklist": "कार्य सूची",
"userManagement": "उपयोगकर्ता प्रबंधन", "userManagement": "उपयोगकर्ता प्रबंधन",
"logout": "लॉगआउट", "logout": "लॉगआउट",
"kcc": "केसीसी", "kcc": "केसीसी",
"trading": "व्यापार", "trading": "व्यापार",
"asset": "संपत्ति", "asset": "संपत्ति",
"selfHelpGroup": "स्वयं सहायता समूह", "selfHelpGroup": "स्वयं सहायता समूह",
"deposit": "जमा", "deposit": "जमा",
"loan": "ऋण", "loan": "ऋण",
"share": "शेयर", "share": "शेयर",
"locker": "लॉकर", "locker": "लॉकर",
"pacsTeller": "PACS टेलर", "pacsTeller": "PACS टेलर",
"authoriser": "PACS अधिकृत", "authoriser": "PACS अधिकृत",
"cashOfficer": "कैश अधिकारी", "cashOfficer": "कैश अधिकारी",
"lockerEnquiry": "लॉकर पूछताछ", "lockerEnquiry": "लॉकर पूछताछ",
"accountEnquiry": "खाता पूछताछ", "accountEnquiry": "खाता पूछताछ",
"accountCreation": "खाता निर्माण", "accountCreation": "खाता निर्माण",
"cabinetMaintenance": "कैबिनेट रखरखाव", "cabinetMaintenance": "कैबिनेट रखरखाव",
"lockerMaintenance": "लॉकर रखरखाव", "lockerMaintenance": "लॉकर रखरखाव",
"rentPenaltyCollection": "किराया / जुर्माना संग्रह", "rentPenaltyCollection": "किराया / जुर्माना संग्रह",
"chargeManagement": "शुल्क प्रबंधन", "chargeManagement": "शुल्क प्रबंधन",
"checkInOutManagement": "चेक इन/आउट प्रबंधन", "checkInOutManagement": "चेक इन/आउट प्रबंधन",
"accountSurrender": "खाता समर्पण", "accountSurrender": "खाता समर्पण",
"myIntimation": "मेरा सूचितकरण", "myIntimation": "मेरा सूचितकरण",
"changePassword": "पासवर्ड बदलें", "changePassword": "पासवर्ड बदलें",
"resetLogin": "लॉगिन स्थिति रीसेट करें", "resetLogin": "लॉगिन स्थिति रीसेट करें",
"currentDate" : "{{val, datetime}}", "currentDate": "{{val, datetime}}",
"notifications": "सूचना", "notifications": "सूचना",
"holidayList": "छुट्टी की सूची", "holidayList": "छुट्टी की सूची",
"information": "जानकारी", "information": "जानकारी",
"hpn_complete_before_31": "कृपया 31 मार्च के सभी लेनदेन 21:00 बजे से पहले पूरा करें", "hpn_complete_before_31": "कृपया 31 मार्च के सभी लेनदेन 21:00 बजे से पहले पूरा करें",
"hpn_npa": "कृपया आगामी 31 मार्च 21:00 बजे से पहले ऋण एनपीए रिपोर्ट से सुझावित एनपीए स्थिति की जांच के बाद संबंधित ऋण खातों के लिए एनपीए को चिह्नित/अचिह्नित करें", "hpn_npa": "कृपया आगामी 31 मार्च 21:00 बजे से पहले ऋण एनपीए रिपोर्ट से सुझावित एनपीए स्थिति की जांच के बाद संबंधित ऋण खातों के लिए एनपीए को चिह्नित/अचिह्नित करें",
"hpn_helpdesk_contact": "किसी भी समस्या के समाधान के लिए हमें 033-4065-9546 / 4071-8135 (सुबह 9:00 से रात 9:00 बजे तक) पर संपर्क करें", "hpn_helpdesk_contact": "किसी भी समस्या के समाधान के लिए हमें 033-4065-9546 / 4071-8135 (सुबह 9:00 से रात 9:00 बजे तक) पर संपर्क करें",
"hpn_rupay_kcc_time": "सभी रुपे केसीसी लेनदेन 19:00 बजे से पहले पूरा करें", "hpn_rupay_kcc_time": "सभी रुपे केसीसी लेनदेन 19:00 बजे से पहले पूरा करें",
"hpn_rupay_kcc_atm": "कुछ आंतरिक बदलावों के कारण, माइक्रो-एटीएम के माध्यम से रुपे केसीसी संचालन बंद कर दिया गया है। सेवा कल से फिर से शुरू होगी।", "hpn_rupay_kcc_atm": "कुछ आंतरिक बदलावों के कारण, माइक्रो-एटीएम के माध्यम से रुपे केसीसी संचालन बंद कर दिया गया है। सेवा कल से फिर से शुरू होगी।",
"copyright_statement": "कॉपीराइट © 2023, टाटा कंसल्टेंसी सर्विसेज। सभी अधिकार सुरक्षित", "copyright_statement": "कॉपीराइट © 2023, टाटा कंसल्टेंसी सर्विसेज। सभी अधिकार सुरक्षित",
"privacy_policy": "गोपनीयता नीति", "privacy_policy": "गोपनीयता नीति",
"create": "बनाएं", "create": "बनाएं",
"operation": "ऑपरेशन", "operation": "ऑपरेशन",
"next": "अगला", "next": "अगला",
"select": "चुनें" "select": "चुनें",
} "productCode": "प्रोडक्ट कोड"
}

View File

@@ -1,13 +1,13 @@
import { useLocation, useOutlet } from "react-router"; import { useLocation, useOutlet } from 'react-router';
import { useState } from "react"; import { useState } from 'react';
import Header from "./components/Header"; import Header from './components/Header';
import Footer from "./components/Footer"; import Footer from './components/Footer';
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from 'motion/react';
import { motion } from "motion/react"; import { motion } from 'motion/react';
import { ToastProvider } from "./contexts/Toast"; import { ToastProvider } from './contexts/Toast';
import { useLoading } from "./hooks/useLoading"; import { useLoading } from './hooks/useLoading';
import LoadingBar from "./components/LoadingBar"; import LoadingBar from './components/LoadingBar';
import { LoadingProvider } from "./contexts/Loading"; import { LoadingProvider } from './contexts/Loading';
const AnimatedOutlet = () => { const AnimatedOutlet = () => {
const o = useOutlet(); const o = useOutlet();

View File

@@ -3,19 +3,18 @@ import DarkModeToggle from './DarkModeToggle';
import LanguageSelector from './LanguageSelector'; import LanguageSelector from './LanguageSelector';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function AppTitle() { function AppTitle() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className='flex items-center justify-between pt-3 pb-5'> <div className="flex items-center justify-between pt-3 pb-5">
<div className='flex items-center gap-5'> <div className="flex items-center gap-5">
<img src={IpksLogo} alt="IPKS Logo" className="h-16" /> <img src={IpksLogo} alt="IPKS Logo" className="h-16" />
<h1 className="font-bold text-title font-display">{t('appName')}</h1> <h1 className="font-bold text-title font-display">{t('appName')}</h1>
</div> </div>
<div className='flex items-center gap-2'> <div className="flex items-center gap-2">
<DarkModeToggle /> <DarkModeToggle />
<LanguageSelector /> <LanguageSelector />
</div> </div>
</div> </div>
); );
} }

View File

@@ -1,31 +1,30 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import BannerInfoElement from "./BannerInfoElement"; import BannerInfoElement from './BannerInfoElement';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
function BannerInfo({info}) { function BannerInfo({ info }) {
const {t} = useTranslation(); const { t } = useTranslation();
const infoElements = Object.keys(info).map((key) => ( const infoElements = Object.keys(info).map((key) => (
<BannerInfoElement key={key} title={t(key)} description={t(info[key])} /> <BannerInfoElement key={key} title={t(key)} description={t(info[key])} />
)) ));
infoElements.push( infoElements.push(
<BannerInfoElement <BannerInfoElement
key="date" key="date"
title={t('date')} title={t('date')}
description={t('currentDate',{val: new Date(), formatParams: { description={t('currentDate', {
val: { month: 'long', day: '2-digit', year: 'numeric'}, val: new Date(),
},})} formatParams: {
/> val: { month: 'long', day: '2-digit', year: 'numeric' },
); },
return ( })}
<div className="flex justify-between pb-1"> />
{infoElements} );
</div> return <div className="flex justify-between pb-1">{infoElements}</div>;
)
} }
BannerInfo.propTypes = { BannerInfo.propTypes = {
info: PropTypes.object.isRequired info: PropTypes.object.isRequired,
} };
export default BannerInfo; export default BannerInfo;

View File

@@ -2,16 +2,16 @@ import PropTypes from 'prop-types';
function BannerInfoElement({ title, description }) { function BannerInfoElement({ title, description }) {
return ( return (
<div className='font-body'> <div className="font-body">
<div className='text-base '>{title}</div> <div className="text-base ">{title}</div>
<div className='text-[18px] font-medium'>{description}</div> <div className="text-[18px] font-medium">{description}</div>
</div> </div>
); );
} }
BannerInfoElement.propTypes = { BannerInfoElement.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired description: PropTypes.string.isRequired,
}; };
export default BannerInfoElement; export default BannerInfoElement;

View File

@@ -2,24 +2,27 @@ import PropTypes from 'prop-types';
import { motion } from 'motion/react'; import { motion } from 'motion/react';
import clsx from 'clsx'; import clsx from 'clsx';
function Button({text, onClick, disabled}) { function Button({ text, onClick, disabled }) {
return ( return (
<motion.button <motion.button
whileHover={!disabled && { scale: 1.05 }} whileHover={!disabled && { scale: 1.05 }}
whileTap={!disabled && { scale: 0.95 }} whileTap={!disabled && { scale: 0.95 }}
className={clsx("px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark", disabled && "bg-[#cccccc] dark:bg-[#cccccc]")} className={clsx(
onClick={onClick} 'px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark',
disabled={disabled} disabled && 'bg-[#cccccc] dark:bg-[#cccccc]'
> )}
{text} onClick={onClick}
</motion.button> disabled={disabled}
) >
{text}
</motion.button>
);
} }
Button.propTypes = { Button.propTypes = {
text: PropTypes.string.isRequired, text: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
disabled: PropTypes.bool, disabled: PropTypes.bool,
}; };
export default Button; export default Button;

View File

@@ -17,7 +17,7 @@ const DarkModeToggle = () => {
}, [darkMode]); }, [darkMode]);
const toggleDarkMode = () => { const toggleDarkMode = () => {
setDarkMode(prevMode => !prevMode); setDarkMode((prevMode) => !prevMode);
}; };
return ( return (
@@ -31,4 +31,4 @@ const DarkModeToggle = () => {
); );
}; };
export default DarkModeToggle; export default DarkModeToggle;

View File

@@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import { motion } from 'motion/react';
function FieldError({ text }) {
return (
<motion.div
className="text-sm text-error ml-3 pt-1"
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
>
{text}
</motion.div>
);
}
FieldError.propTypes = {
text: PropTypes.string.isRequired,
};
export default FieldError;

View File

@@ -0,0 +1,12 @@
import PropTypes from 'prop-types';
function FieldsWrapper({ children, className = '' }) {
return <div className={`flex flex-col gap-4 m-2 my-7 ${className}`}>{children}</div>;
}
FieldsWrapper.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
};
export default FieldsWrapper;

View File

@@ -10,4 +10,4 @@ function Footer() {
); );
} }
export default Footer; export default Footer;

View File

@@ -1,21 +1,21 @@
import { PropTypes } from "prop-types"; import { PropTypes } from 'prop-types';
import clsx from "clsx"; import clsx from 'clsx';
function FormBox({ title, children, alt = false }) { function FormBox({ title, children, alt = false }) {
return ( return (
<form <form
className={clsx( className={clsx(
alt alt
? "bg-secondary-variant dark:bg-secondary-variant-dark border-secondary-variant dark:border-secondary-variant-dark" ? 'bg-secondary-variant dark:bg-secondary-variant-dark border-secondary-variant dark:border-secondary-variant-dark'
: "bg-surface-variant dark:bg-surface-variant-dark", : 'bg-surface-variant dark:bg-surface-variant-dark',
"transition-color-mode font-body border-secondary dark:border-secondary-dark border-2 p-4 rounded-3xl relative h-full" 'transition-color-mode font-body border-secondary dark:border-secondary-dark border-2 p-4 rounded-3xl relative h-full'
)} )}
> >
<label <label
className={clsx( className={clsx(
alt && alt &&
"bg-surface dark:bg-surface-dark border-3 border-secondary-variant dark:border-secondary-variant-dark", 'bg-surface dark:bg-surface-dark border-3 border-secondary-variant dark:border-secondary-variant-dark',
"font-body absolute left-11 -top-4 bg-secondary dark:bg-secondary-dark text-primary dark:text-primary-dark font-medium py-1 px-4 rounded-full z-20" 'font-body absolute left-11 -top-4 bg-secondary dark:bg-secondary-dark text-primary dark:text-primary-dark font-medium py-1 px-4 rounded-full z-20'
)} )}
> >
{title} {title}

View File

@@ -1,27 +1,35 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { motion } from "motion/react"; import { motion } from 'motion/react';
import clsx from "clsx"; import clsx from 'clsx';
function FormField({ label, children, icon }) { function FormField({ label, children, icon, variant }) {
return ( return (
<div className="flex"> <div className="flex items-center">
<label className="mr-4 text-lg text-black dark:text-primary-dark md:w-[30%] xl:w-[20%] 2xl:w-[17%]"> <label
className={clsx(
'mr-20 text-lg text-black dark:text-primary-dark whitespace-nowrap',
variant === 'long' && 'sm:w-[5%]'
)}
>
{label} {label}
</label> </label>
<div className="flex w-full gap-4 items-center"> <div className={clsx('flex w-full gap-4 items-center', variant === 'long' && 'gap-10')}>
{children} {children}
{icon && ( {icon && (
<motion.div <motion.div
whileHover={{ scale: 1.1 }} whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
className={clsx(icon.mode === "plain" ? "text-[#444]" : "bg-primary rounded-full p-2 text-white cursor-pointer")} className={clsx(
onClick={icon.onClick} icon.mode === 'plain'
> ? 'text-[#444]'
{icon.icon} : 'bg-primary rounded-full p-2 text-white cursor-pointer'
</motion.div> )}
onClick={icon.onClick}
>
{icon.icon}
</motion.div>
)} )}
</div> </div>
</div> </div>
); );
} }
@@ -29,7 +37,8 @@ function FormField({ label, children, icon }) {
FormField.propTypes = { FormField.propTypes = {
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
icon: PropTypes.object.isRequired, icon: PropTypes.object,
variant: PropTypes.string,
}; };
export default FormField; export default FormField;

View File

@@ -0,0 +1,11 @@
import PropTypes from 'prop-types';
function FormHeader({ text }) {
return <h1 className="text-2xl font-medium text-primary mt-5">{text}</h1>;
}
FormHeader.propTypes = {
text: PropTypes.string.isRequired,
};
export default FormHeader;

View File

@@ -1,23 +1,16 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { motion, AnimatePresence } from "motion/react"; import { motion, AnimatePresence } from 'motion/react';
function FormInput({ import clsx from 'clsx';
value,
onChange, function FormInput({ props, valid = true, className = '' }) {
valid = true,
maxLength = 17,
readOnly = false,
className = "",
type = "text",
}) {
return ( return (
<div> <div>
<input <input
readOnly={readOnly} {...props}
value={value} className={clsx(
className={`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`} `w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`,
onChange={onChange} !valid && 'border-error'
type={type} )}
maxLength={maxLength}
/> />
<AnimatePresence> <AnimatePresence>
{!valid && ( {!valid && (
@@ -37,13 +30,9 @@ function FormInput({
} }
FormInput.propTypes = { FormInput.propTypes = {
value: PropTypes.string.isRequired, props: PropTypes.object,
onChange: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
className: PropTypes.string,
type: PropTypes.string,
maxLength: PropTypes.number,
valid: PropTypes.bool, valid: PropTypes.bool,
className: PropTypes.string,
}; };
export default FormInput; export default FormInput;

View File

@@ -1,46 +1,36 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { motion, AnimatePresence } from "motion/react"; import { AnimatePresence } from 'motion/react';
import clsx from 'clsx';
import FieldError from './FieldError';
import { useTranslation } from 'react-i18next';
function FormSelect({ value, onChange, options, className, valid = true }) { function FormSelect({ props, valid = true, className = '', options }) {
const { t } = useTranslation();
return ( return (
<div> <div>
<select <select
value={value} {...props}
className={ className={clsx(
"w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey " + `w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`,
className !valid && 'border-error'
}
onChange={onChange}
>
<option disabled value="">
Select
</option>
{options.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
<AnimatePresence>
{!valid && (
<motion.div
className="text-sm text-error ml-3 pt-1"
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
key="cabinetIdError"
>
Invalid Value
</motion.div>
)} )}
</AnimatePresence> >
</div> <option disabled value="">
{t('select')}
</option>
{options?.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
<AnimatePresence>{!valid && <FieldError text={'Invalid value'} />}</AnimatePresence>
</div>
); );
} }
FormSelect.propTypes = { FormSelect.propTypes = {
value: PropTypes.string.isRequired, props: PropTypes.object,
onChange: PropTypes.func.isRequired,
className: PropTypes.string, className: PropTypes.string,
valid: PropTypes.bool, valid: PropTypes.bool,
options: PropTypes.arrayOf( options: PropTypes.arrayOf(

View File

@@ -1,43 +1,52 @@
import { getUserInfoFromSession } from "../util/util"; import { getUserInfoFromSession } from '../util/util';
import AppTitle from "./AppTitle"; import AppTitle from './AppTitle';
import BannerInfo from "./BannerInfo"; import BannerInfo from './BannerInfo';
import MenuBar from "./MenuBar"; import MenuBar from './MenuBar';
import Separator from "./Separator"; import Separator from './Separator';
function Header() { function Header() {
const userInfo = getUserInfoFromSession(); const userInfo = getUserInfoFromSession();
const menuItems = [ const menuItems = [
{ name: "home", submenu: [], path: "/" }, { name: 'home', submenu: [], path: '/' },
{ {
name: "moduleList", name: 'moduleList',
submenu: [ submenu: [
{ name: "kcc", path: "kcc" }, { name: 'kcc', path: 'kcc' },
{ name: "trading", path: "trading" }, { name: 'trading', path: 'trading' },
{ name: "asset", path: "asset" }, { name: 'asset', path: 'asset' },
{ name: "selfHelpGroup", path: "shg" }, { name: 'selfHelpGroup', path: 'shg' },
{ name: "deposit", path: "deposit" }, { name: 'deposit', path: 'deposit' },
{ name: "loan", path: "loan" }, { name: 'loan', path: 'loan' },
{ name: "share", path: "share" }, { name: 'share', path: 'share' },
], ],
}, },
{ name: "enquiry", submenu: [{ name: "lockerEnquiry", path: "locker-enquiry" }, { name: "accountEnquiry", path: "account-enquiry" }] },
{ {
name: "lockerOperation", name: 'enquiry',
submenu: [ submenu: [
{ name: "accountCreation", path: "operation/account" }, { name: 'lockerEnquiry', path: 'locker-enquiry' },
{ name: "cabinetMaintenance", path: "operation/cabinet" }, { name: 'accountEnquiry', path: 'account-enquiry' },
{ name: "lockerMaintenance", path: "operation/locker" },
{ name: "chargeManagement", path: "operation/charge-management" },
{ name: "checkInOutManagement", path: "operation/check-in-out" },
{ name: "accountSurrender", path: "operation/account-surrender" }
], ],
}, },
{ name: "worklist", submenu: [{ name: "myIntimation", path: "my-intimation" }] },
{ {
name: "userManagement", name: 'lockerOperation',
submenu: [{ name: "resetLogin", path: "reset-login" }, { name: "changePassword", path: "change-password" }], submenu: [
{ name: 'accountCreation', path: 'operation/account' },
{ name: 'cabinetMaintenance', path: 'operation/cabinet' },
{ name: 'lockerMaintenance', path: 'operation/locker' },
{ name: 'chargeManagement', path: 'operation/charge-management' },
{ name: 'checkInOutManagement', path: 'operation/check-in-out' },
{ name: 'accountSurrender', path: 'operation/account-surrender' },
],
}, },
{ name: "logout", submenu: [] }, { name: 'worklist', submenu: [{ name: 'myIntimation', path: 'my-intimation' }] },
{
name: 'userManagement',
submenu: [
{ name: 'resetLogin', path: 'reset-login' },
{ name: 'changePassword', path: 'change-password' },
],
},
{ name: 'logout', submenu: [] },
]; ];
return ( return (
@@ -47,7 +56,7 @@ function Header() {
<Separator /> <Separator />
<MenuBar menuItems={menuItems} /> <MenuBar menuItems={menuItems} />
</div> </div>
) );
} }
export default Header; export default Header;

View File

@@ -9,7 +9,12 @@ const LanguageSelector = () => {
return ( return (
<div> <div>
<select className='rounded-md font-body bg-secondary dark:bg-secondary-dark focus:outline-none' id="language-select" onChange={changeLanguage} value={i18n.language}> <select
className="rounded-md font-body bg-secondary dark:bg-secondary-dark focus:outline-none"
id="language-select"
onChange={changeLanguage}
value={i18n.language}
>
<option value="en">English</option> <option value="en">English</option>
<option value="bn"></option> <option value="bn"></option>
<option value="hi">ि</option> <option value="hi">ि</option>
@@ -18,4 +23,4 @@ const LanguageSelector = () => {
); );
}; };
export default LanguageSelector; export default LanguageSelector;

View File

@@ -1,12 +1,12 @@
import { motion } from "framer-motion"; import { motion } from 'framer-motion';
function LoadingBar() { function LoadingBar() {
return ( return (
<div className="h-1 bg-grey relative overflow-hidden"> <div className="h-1 bg-grey relative overflow-hidden">
<motion.div <motion.div
className="bg-primary dark:bg-primary-dark w-full h-full rounded-sm absolute" className="bg-primary dark:bg-primary-dark w-full h-full rounded-sm absolute"
animate={{ x: ["-100%", "100%"] }} animate={{ x: ['-100%', '100%'] }}
transition={{ repeat: Infinity, duration: 2, ease: "anticipate" }} transition={{ repeat: Infinity, duration: 2, ease: 'anticipate' }}
></motion.div> ></motion.div>
</div> </div>
); );

View File

@@ -1,9 +1,9 @@
import { Link } from "react-router-dom"; import { Link } from 'react-router-dom';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import clsx from "clsx"; import clsx from 'clsx';
import { useState } from "react"; import { useState } from 'react';
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion } from 'motion/react';
function SubMenu({ items, isVisible, onLinkClick }) { function SubMenu({ items, isVisible, onLinkClick }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -13,12 +13,12 @@ function SubMenu({ items, isVisible, onLinkClick }) {
<div> <div>
<motion.div <motion.div
className={clsx( className={clsx(
"absolute left-0 z-50 shadow-sm top-full border-t-3 border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark rounded-2xl shadow-surface-variant-dark dark:shadow-primary" 'absolute left-0 z-50 shadow-sm top-full border-t-3 border-primary dark:border-primary-dark bg-secondary dark:bg-secondary-dark rounded-2xl shadow-surface-variant-dark dark:shadow-primary'
)} )}
initial={{ y: 15, opacity: 0 }} initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }} exit={{ y: 15, opacity: 0 }}
transition={{ duration: 0.3, type: "spring" }} transition={{ duration: 0.3, type: 'spring' }}
> >
{items.map((subItem, index) => ( {items.map((subItem, index) => (
<div <div

View File

@@ -1,7 +1,7 @@
import clsx from "clsx"; import clsx from 'clsx';
import { Copy } from "lucide-react"; import { Copy } from 'lucide-react';
import { motion } from "motion/react"; import { motion } from 'motion/react';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
function Notification({ message, type }) { function Notification({ message, type }) {
return ( return (
@@ -10,17 +10,17 @@ function Notification({ message, type }) {
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }} exit={{ y: 15, opacity: 0 }}
className={clsx( className={clsx(
"p-2 pl-8 mb-8 font-body text-lg border-2 rounded-3xl flex items-center gap-2", 'p-2 pl-8 mb-8 font-body text-lg border-2 rounded-3xl flex items-center gap-2',
type === "error" type === 'error'
? "bg-error-surface text-white border-error" ? 'bg-error-surface text-white border-error'
: type === "success" : type === 'success'
? "bg-success text-white border-green-700" ? 'bg-success text-white border-green-700'
: type === "warning" : type === 'warning'
? "bg-warning-surface text-white border-warning" ? 'bg-warning-surface text-white border-warning'
: "" : ''
)} )}
> >
{message.split(":").map((msg, index) => { {message.split(':').map((msg, index) => {
return index === 1 ? ( return index === 1 ? (
<span key={index} className="border-b border-dashed"> <span key={index} className="border-b border-dashed">
{msg} {msg}
@@ -29,11 +29,11 @@ function Notification({ message, type }) {
<span key={index}>{msg}</span> <span key={index}>{msg}</span>
); );
})} })}
{message.split(":")[1] && ( {message.split(':')[1] && (
<Copy <Copy
cursor={"pointer"} cursor={'pointer'}
size={15} size={15}
onClick={navigator.clipboard.writeText(message.split(":")[1].trim())} onClick={navigator.clipboard.writeText(message.split(':')[1].trim())}
/> />
)} )}
</motion.div> </motion.div>

View File

@@ -1,5 +1,5 @@
import clsx from "clsx"; import clsx from 'clsx';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
function ProductListTable({ productInfo, onSelectProduct }) { function ProductListTable({ productInfo, onSelectProduct }) {
return ( return (
@@ -29,32 +29,32 @@ function ProductListTable({ productInfo, onSelectProduct }) {
> >
<td <td
className={clsx( className={clsx(
"border border-l-2 border-primary p-2", 'border border-l-2 border-primary p-2',
idx === productInfo.length - 1 && "rounded-bl-2xl border-b-2" idx === productInfo.length - 1 && 'rounded-bl-2xl border-b-2'
)} )}
> >
{prod.productCode} {prod.productCode}
</td> </td>
<td <td
className={clsx( className={clsx(
"border border-primary p-2", 'border border-primary p-2',
idx === productInfo.length - 1 && "border-b-2" idx === productInfo.length - 1 && 'border-b-2'
)} )}
> >
{prod.productCodeDescription} {prod.productCodeDescription}
</td> </td>
<td <td
className={clsx( className={clsx(
"border border-primary p-2", 'border border-primary p-2',
idx === productInfo.length - 1 && "border-b-2" idx === productInfo.length - 1 && 'border-b-2'
)} )}
> >
{prod.interestCategory} {prod.interestCategory}
</td> </td>
<td <td
className={clsx( className={clsx(
"border border-r-2 border-primary p-2", 'border border-r-2 border-primary p-2',
idx === productInfo.length - 1 && "rounded-br-2xl border-b-2" idx === productInfo.length - 1 && 'rounded-br-2xl border-b-2'
)} )}
> >
{prod.interestCategoryDescription} {prod.interestCategoryDescription}

View File

@@ -0,0 +1,32 @@
import { motion } from 'motion/react';
import ProductListTable from './ProductListTable';
import PropTypes from 'prop-types';
function ProductModal({ productInfo, handleProductSelect }) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
key="productList"
className="fixed z-50 inset-0 flex items-center justify-center bg-black/50"
>
<motion.div
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
className="flex flex-col items-center bg-white p-4 py-8 rounded-3xl w-[60%] max-h-[80%] overflow-auto font-body"
>
<h2 className="text-xl mb-4">Select Product</h2>
<ProductListTable productInfo={productInfo} onSelectProduct={handleProductSelect} />
</motion.div>
</motion.div>
);
}
ProductModal.propTypes = {
productInfo: PropTypes.object.isRequired,
handleProductSelect: PropTypes.func.isRequired,
};
export default ProductModal;

View File

@@ -3,7 +3,7 @@ function Separator() {
<div className="h-[2px]"> <div className="h-[2px]">
<div className="w-full h-full bg-white rounded-md dark:bg-primary-dark"></div> <div className="w-full h-full bg-white rounded-md dark:bg-primary-dark"></div>
</div> </div>
) );
} }
export default Separator; export default Separator;

View File

@@ -16,4 +16,3 @@ export function LoadingProvider({ children }) {
LoadingProvider.propTypes = { LoadingProvider.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
}; };

View File

@@ -1,22 +1,22 @@
import { createContext, useState } from "react"; import { createContext, useState } from 'react';
import { CircleAlert, X, CircleX, Check } from "lucide-react"; import { CircleAlert, X, CircleX, Check } from 'lucide-react';
import { toTitleCase } from "../util/util"; import { toTitleCase } from '../util/util';
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
export const ToastContext = createContext(); export const ToastContext = createContext();
export const ToastProvider = ({ children }) => { export const ToastProvider = ({ children }) => {
const [toast, setToast] = useState({ show: false, message: "", type: "" }); const [toast, setToast] = useState({ show: false, message: '', type: '' });
const playAudio = (type) => { const playAudio = (type) => {
let audioSrc; let audioSrc;
if(type === "warning") audioSrc = "/audio/warning.mp3"; if (type === 'warning') audioSrc = '/audio/warning.mp3';
else if (type === "error") audioSrc = "/audio/error.mp3"; else if (type === 'error') audioSrc = '/audio/error.mp3';
if (audioSrc) { if (audioSrc) {
const audio = new Audio(audioSrc); const audio = new Audio(audioSrc);
audio.play().catch((error) => console.error("Error playing audio:", error)); audio.play().catch((error) => console.error('Error playing audio:', error));
} }
}; };
@@ -24,31 +24,31 @@ export const ToastProvider = ({ children }) => {
playAudio(type); playAudio(type);
setToast({ show: true, message, type }); setToast({ show: true, message, type });
setTimeout(() => { setTimeout(() => {
setToast({ show: false, message: "", type: "" }); setToast({ show: false, message: '', type: '' });
}, 7000); }, 7000);
}; };
let toastIcon; let toastIcon;
let surfaceColor; let surfaceColor;
let borderColor; let borderColor;
if(toast.type === "warning") { if (toast.type === 'warning') {
toastIcon = <CircleAlert size={30} fill="#EA7000" stroke="#FDF1E5" />; toastIcon = <CircleAlert size={30} fill="#EA7000" stroke="#FDF1E5" />;
surfaceColor = "bg-warning-surface"; surfaceColor = 'bg-warning-surface';
borderColor = "border-warning"; borderColor = 'border-warning';
} else if(toast.type === "success") { } else if (toast.type === 'success') {
toastIcon = <Check size={30} color="green"/>; toastIcon = <Check size={30} color="green" />;
surfaceColor = "bg-success-surface"; surfaceColor = 'bg-success-surface';
borderColor = "border-success"; borderColor = 'border-success';
} else if (toast.type === "error") { } else if (toast.type === 'error') {
toastIcon = <CircleX size={30} fill="#E5254B" stroke="#FCE9ED" />; toastIcon = <CircleX size={30} fill="#E5254B" stroke="#FCE9ED" />;
surfaceColor = "bg-error-surface"; surfaceColor = 'bg-error-surface';
borderColor = "border-error"; borderColor = 'border-error';
} }
return ( return (
<ToastContext.Provider value={ showToast }> <ToastContext.Provider value={showToast}>
{children} {children}
{toast.show && ( {toast.show && (
<div <div
className={`fixed bottom-10 right-5 px-5 py-2 ${surfaceColor} border-2 border-l-8 ${borderColor} rounded-xl font-medium text-onToast z-10 flex gap-5 items-center animate-[slideIn_0.5s]`} className={`fixed bottom-10 right-5 px-5 py-2 ${surfaceColor} border-2 border-l-8 ${borderColor} rounded-xl font-medium text-onToast z-10 flex gap-5 items-center animate-[slideIn_0.5s]`}
role="alert" role="alert"
> >
@@ -57,7 +57,7 @@ export const ToastProvider = ({ children }) => {
<div className="text-lg text-onToast">{toTitleCase(toast.type)}</div> <div className="text-lg text-onToast">{toTitleCase(toast.type)}</div>
<div className="text-sm font-body">{toast.message}</div> <div className="text-sm font-body">{toast.message}</div>
</div> </div>
<X onClick={() => setToast({ show: false, message: "", type: "" })}/> <X onClick={() => setToast({ show: false, message: '', type: '' })} />
</div> </div>
)} )}
</ToastContext.Provider> </ToastContext.Provider>

View File

@@ -1,4 +1,4 @@
import { useContext } from "react"; import { useContext } from 'react';
import { LoadingContext } from "../contexts/Loading"; import { LoadingContext } from '../contexts/Loading';
export const useLoading = () => useContext(LoadingContext); export const useLoading = () => useContext(LoadingContext);

View File

@@ -1,4 +1,4 @@
import { useContext } from "react"; import { useContext } from 'react';
import { ToastContext } from "../contexts/Toast"; import { ToastContext } from '../contexts/Toast';
export const useToast = () => useContext(ToastContext); export const useToast = () => useContext(ToastContext);

View File

@@ -16,4 +16,4 @@ i18n
}, },
}); });
export default i18n; export default i18n;

View File

@@ -1,25 +1,25 @@
import { StrictMode } from "react"; import { StrictMode } from 'react';
import { createRoot } from "react-dom/client"; import { createRoot } from 'react-dom/client';
import App from "./App.jsx"; import App from './App.jsx';
import "./index.css"; import './index.css';
import "./i18n"; import './i18n';
import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from "./pages/Home.jsx"; import Home from './pages/Home.jsx';
import CabinetMaintenace from "./pages/CabinetMaintenance.jsx"; import CabinetMaintenace from './pages/CabinetMaintenance.jsx';
import CabinetCreation from "./pages/CabinetCreation.jsx"; import CabinetCreation from './pages/CabinetCreation.jsx';
import LockersRegistration from "./pages/LockersRegistration.jsx"; import LockersRegistration from './pages/LockersRegistration.jsx';
import AccountCreation from "./pages/AccountCreation.jsx"; import AccountCreation from './pages/AccountCreation.jsx';
import LockerMaintenance from "./pages/LockerMaintenance.jsx"; import LockerMaintenance from './pages/LockerMaintenance.jsx';
import LockerStatus from "./pages/LockerStatus.jsx"; import LockerStatus from './pages/LockerStatus.jsx';
import KeySwap from "./pages/KeySwap.jsx"; import KeySwap from './pages/KeySwap.jsx';
import ChargeManagement from "./pages/ChargeManagement.jsx"; import ChargeManagement from './pages/ChargeManagement.jsx';
import ChargeEdit from "./pages/ChargeEdit.jsx"; import ChargeEdit from './pages/ChargeEdit.jsx';
import CheckInOutManagement from "./pages/CheckInOutManagement.jsx"; import CheckInOutManagement from './pages/CheckInOutManagement.jsx';
import CheckInOutLog from "./pages/CheckInOutLog.jsx"; import CheckInOutLog from './pages/CheckInOutLog.jsx';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/", path: '/',
element: <App />, element: <App />,
children: [ children: [
{ {
@@ -27,54 +27,54 @@ const router = createBrowserRouter([
element: <Home />, element: <Home />,
}, },
{ {
path: "operation/cabinet", path: 'operation/cabinet',
element: <CabinetMaintenace />, element: <CabinetMaintenace />,
}, },
{ {
path: "operation/cabinet/create", path: 'operation/cabinet/create',
element: <CabinetCreation />, element: <CabinetCreation />,
}, },
{ {
path: "operation/cabinet/create/register-lockers", path: 'operation/cabinet/create/register-lockers',
element: <LockersRegistration />, element: <LockersRegistration />,
}, },
{ {
path: "operation/account", path: 'operation/account',
element: <AccountCreation />, element: <AccountCreation />,
}, },
{ {
path: "operation/locker", path: 'operation/locker',
element: <LockerMaintenance />, element: <LockerMaintenance />,
}, },
{ {
path: "operation/locker/status", path: 'operation/locker/status',
element: <LockerStatus />, element: <LockerStatus />,
}, },
{ {
path: "operation/locker/key-swap", path: 'operation/locker/key-swap',
element: <KeySwap />, element: <KeySwap />,
}, },
{ {
path: "operation/charge-management", path: 'operation/charge-management',
element: <ChargeManagement />, element: <ChargeManagement />,
}, },
{ {
path: "operation/charge-management/change", path: 'operation/charge-management/change',
element: <ChargeEdit />, element: <ChargeEdit />,
}, },
{ {
path: "operation/check-in-out", path: 'operation/check-in-out',
element: <CheckInOutManagement /> element: <CheckInOutManagement />,
}, },
{ {
path: "operation/check-in-out/log", path: 'operation/check-in-out/log',
element: <CheckInOutLog /> element: <CheckInOutLog />,
} },
], ],
}, },
]); ]);
createRoot(document.getElementById("root")).render( createRoot(document.getElementById('root')).render(
<StrictMode> <StrictMode>
<RouterProvider router={router} /> <RouterProvider router={router} />
</StrictMode> </StrictMode>

View File

@@ -1,37 +1,33 @@
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from 'motion/react';
import clsx from "clsx"; import { PackageSearch, UserSearch } from 'lucide-react';
import { PackageSearch, Copy, UserSearch } from "lucide-react"; import { useState } from 'react';
import { useState } from "react"; import FormBox from '../components/FormBox';
import { motion } from "motion/react"; import { useTranslation } from 'react-i18next';
import FormBox from "../components/FormBox"; import FormField from '../components/FormField';
import { useTranslation } from "react-i18next"; import FormInput from '../components/FormInput';
import FormField from "../components/FormField"; import FormSelect from '../components/FormSelect';
import FormInput from "../components/FormInput"; import Button from '../components/Button';
import FormSelect from "../components/FormSelect"; import productInfo from '../util/productList';
import Button from "../components/Button"; import Notification from '../components/Notification';
import productInfo from "../util/productList"; import ProductModal from '../components/ProductModal';
import ProductListTable from "../components/ProductListTable"; import FormHeader from '../components/FormHeader';
import FieldsWrapper from '../components/FieldsWrapper';
function AccountCreation() { function AccountCreation() {
const { t } = useTranslation(); const { t } = useTranslation();
const [notification] = useState({ const [notification] = useState({ message: '', type: '' });
visible: false,
message: "",
type: "",
});
const [showProductModal, setShowProductModal] = useState(false); const [showProductModal, setShowProductModal] = useState(false);
const [submitting] = useState(false);
const [accountDetails, setAccountDetails] = useState({ const [accountDetails, setAccountDetails] = useState({
productCode: "", productCode: '',
interestCategory: "", interestCategory: '',
segmentCode: "", segmentCode: '',
accountHolderType: "", accountHolderType: '',
primaryCifNumber: "", primaryCifNumber: '',
nomineeCifNumber: "", nomineeCifNumber: '',
activityCode: "", activityCode: '',
customerType: "", customerType: '',
collateralFDAccount: "", collateralFDAccount: '',
rentPayAccount: "", rentPayAccount: '',
productCodeValid: true, productCodeValid: true,
interestCategoryValid: true, interestCategoryValid: true,
segmentCodeValid: true, segmentCodeValid: true,
@@ -48,113 +44,115 @@ function AccountCreation() {
const newAccountDetails = { ...accountDetails }; const newAccountDetails = { ...accountDetails };
newAccountDetails.productCode = product.productCode; newAccountDetails.productCode = product.productCode;
newAccountDetails.interestCategory = product.interestCategory; newAccountDetails.interestCategory = product.interestCategory;
newAccountDetails.productCodeValid = true;
newAccountDetails.interestCategoryValid = true;
setAccountDetails(newAccountDetails); setAccountDetails(newAccountDetails);
setShowProductModal(false); setShowProductModal(false);
}; };
const accountDetailsFields = [ const accountDetailsFields = [
{ {
label: t("productCode"), label: t('productCode'),
name: "productCode", name: 'productCode',
type: "input", type: 'input',
subType: "number", subType: 'number',
readOnly: true, readOnly: true,
validate: (value) => value !== "", validate: (value) => value !== '',
icon: { icon: {
icon: <PackageSearch size={18} />, icon: <PackageSearch size={18} />,
onClick: () => setShowProductModal(true), onClick: () => setShowProductModal(true),
}, },
}, },
{ {
label: t("interestCategory"), label: t('interestCategory'),
name: "interestCategory", name: 'interestCategory',
type: "input", type: 'input',
subType: "number", subType: 'number',
readOnly: true, readOnly: true,
validate: (value) => value !== "", validate: (value) => value !== '',
}, },
{ {
label: t("segmentCode"), label: t('segmentCode'),
name: "segmentCode", name: 'segmentCode',
type: "select", type: 'select',
options: [ options: [
{ value: "0706", label: "0706: Individual" }, { value: '0706', label: '0706: Individual' },
{ value: "0306", label: "0306: Staff" }, { value: '0306', label: '0306: Staff' },
{ value: "5003", label: "5003: Senior Citizen" }, { value: '5003', label: '5003: Senior Citizen' },
{ value: "5010", label: "5010: SHG" }, { value: '5010', label: '5010: SHG' },
{ value: "5000", label: "5000: Bank" }, { value: '5000', label: '5000: Bank' },
{ value: "5009", label: "5009: Institutions" }, { value: '5009', label: '5009: Institutions' },
{ value: "5050", label: "5050: Others" }, { value: '5050', label: '5050: Others' },
{ value: "5007", label: "5007: Society" }, { value: '5007', label: '5007: Society' },
], ],
validate: (value) => value !== "", validate: (value) => value !== '',
}, },
{ {
label: t("accountHolderType"), label: t('accountHolderType'),
name: "accountHolderType", name: 'accountHolderType',
type: "select", type: 'select',
options: [ options: [
{ value: "1", label: "Single" }, { value: '1', label: 'Single' },
{ value: "2", label: "Joint" }, { value: '2', label: 'Joint' },
], ],
validate: (value) => value === "1" || value === "2", validate: (value) => value === '1' || value === '2',
}, },
{ {
label: t("primaryCifNumber"), label: t('primaryCifNumber'),
name: "primaryCifNumber", name: 'primaryCifNumber',
type: "input", type: 'input',
subType: "number", subType: 'number',
maxLength: 17, maxLength: 17,
validate: (value) => /^[0-9]{17}$/.test(value), validate: (value) => /^[0-9]{17}$/.test(value),
icon: { icon: <UserSearch size={18} /> }, icon: { icon: <UserSearch size={18} /> },
}, },
{ {
label: t("nomineeCifNumber"), label: t('nomineeCifNumber'),
name: "nomineeCifNumber", name: 'nomineeCifNumber',
type: "input", type: 'input',
subType: "number", subType: 'number',
maxLength: 17, maxLength: 17,
validate: (value) => /^[0-9]{17}$/.test(value), validate: (value) => /^[0-9]{17}$/.test(value),
}, },
]; ];
const additionalDetailsFields = [ const additionalDetailsFields = [
{ {
label: t("activityCode"), label: t('activityCode'),
name: "activityCode", name: 'activityCode',
type: "select", type: 'select',
options: [ options: [
{ value: "0701", label: "Direct Agriculture" }, { value: '0701', label: 'Direct Agriculture' },
{ value: "0702", label: "Indirect Agriculture" }, { value: '0702', label: 'Indirect Agriculture' },
{ value: "0703", label: "Agricultural Services Unit" }, { value: '0703', label: 'Agricultural Services Unit' },
{ value: "0704", label: "Farm Irrigation" }, { value: '0704', label: 'Farm Irrigation' },
{ value: "0705", label: "Fruits & Vegetables" }, { value: '0705', label: 'Fruits & Vegetables' },
{ value: "0706", label: "Non-Agriculture" }, { value: '0706', label: 'Non-Agriculture' },
], ],
validate: (value) => value !== "", validate: (value) => value !== '',
}, },
{ {
label: t("customerType"), label: t('customerType'),
name: "customerType", name: 'customerType',
type: "select", type: 'select',
options: [ options: [
{ value: "0709", label: "Individual" }, { value: '0709', label: 'Individual' },
{ value: "0701", label: "Corporate" }, { value: '0701', label: 'Corporate' },
], ],
validate: (value) => value === "0709" || value === "0701", validate: (value) => value === '0709' || value === '0701',
}, },
{ {
label: t("collateralFDAccount"), label: t('collateralFDAccount'),
name: "collateralFDAccount", name: 'collateralFDAccount',
type: "input", type: 'input',
subType: "number", subType: 'number',
maxLength: 17, maxLength: 17,
validate: (value) => /^[0-9]{17}$/.test(value), validate: (value) => /^[0-9]{17}$/.test(value),
}, },
{ {
label: t("rentPayAccount"), label: t('rentPayAccount'),
name: "rentPayAccount", name: 'rentPayAccount',
type: "input", type: 'input',
subType: "number", subType: 'number',
maxLength: 17, maxLength: 17,
validate: (value) => /^[0-9]{17}$/.test(value), validate: (value) => /^[0-9]{17}$/.test(value),
}, },
@@ -179,41 +177,12 @@ function AccountCreation() {
if (!isValid) { if (!isValid) {
return; return;
} }
console.log("Form is valid", accountDetails); console.log('Form is valid', accountDetails);
};
const renderProductModal = () => {
return (
<AnimatePresence mode="popLayout">
{showProductModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
key="productList"
className="fixed z-50 inset-0 flex items-center justify-center bg-black/50"
>
<motion.div
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
className="flex flex-col items-center bg-white p-4 py-8 rounded-3xl w-[60%] max-h-[80%] overflow-auto font-body"
>
<h2 className="text-xl mb-4">Select Product</h2>
<ProductListTable
productInfo={productInfo}
onSelectProduct={handleProductSelect}
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}; };
const renerField = (field) => { const renderField = (field) => {
const commonProps = { const commonProps = {
value: accountDetails[field.name], value: accountDetails[field.name],
valid: accountDetails[`${field.name}Valid`],
onChange: (e) => { onChange: (e) => {
const newAccountDetails = { ...accountDetails }; const newAccountDetails = { ...accountDetails };
newAccountDetails[field.name] = e.target.value; newAccountDetails[field.name] = e.target.value;
@@ -221,19 +190,17 @@ function AccountCreation() {
setAccountDetails(newAccountDetails); setAccountDetails(newAccountDetails);
}, },
maxLength: field.maxLength, maxLength: field.maxLength,
className: clsx(!accountDetails[`${field.name}Valid`] && "border-error"), type: field.subType,
readOnly: field.readOnly,
}; };
const valid = accountDetails[`${field.name}Valid`];
return ( return (
<FormField key={field.name} label={field.label} icon={field.icon}> <FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? ( {field.type === 'input' ? (
<FormInput <FormInput props={commonProps} valid={valid} />
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} valid={valid} options={field.options} />
)} )}
</FormField> </FormField>
); );
@@ -242,58 +209,23 @@ function AccountCreation() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.visible && ( {notification.message !== '' && <Notification {...notification} />}
<motion.div </AnimatePresence>
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }} <AnimatePresence>
exit={{ y: 15, opacity: 0 }} {showProductModal && (
className={clsx( <ProductModal productInfo={productInfo} handleProductSelect={handleProductSelect} />
"p-4 mb-8 font-body text-center text-xl rounded-2xl flex items-center justify-center gap-2",
notification.type === "error"
? "bg-error-surface text-error"
: "bg-success-surface text-success"
)}
>
{notification.message.split(":").map((msg, index) => {
return index === 1 ? (
<span key={index} className="border-b border-dashed">
{msg}
</span>
) : (
<span key={index}>{msg}</span>
);
})}
<Copy
cursor={"pointer"}
size={15}
onClick={navigator.clipboard.writeText(
notification.message.split(":")[1].trim()
)}
/>
</motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
{renderProductModal()}
<FormBox title="Account Creation" disabled={submitting}> <FormBox title="Account Creation">
<div className="p-2 pt-7 "> <FieldsWrapper>
<div className="flex flex-col gap-4"> <FormHeader text={'Account Details'} />
<h1 className="text-2xl font-medium text-primary py-2"> {accountDetailsFields.map(renderField)}
Account Details <FormHeader text={'Additional Details'} />
</h1> {additionalDetailsFields.map(renderField)}
{accountDetailsFields.map(renerField)} </FieldsWrapper>
</div> <Button text={t('submit')} onClick={handleSubmit} />
<div className="flex flex-col gap-4">
<h1 className="text-2xl font-medium text-primary py-2 pt-6">
Additional Details
</h1>
{additionalDetailsFields.map(renerField)}
</div>
</div>
<Button
text={t("submit")}
onClick={handleSubmit}
disabled={submitting}
/>
</FormBox> </FormBox>
</div> </div>
); );

View File

@@ -1,133 +1,105 @@
import { useState } from "react"; import { useState } from 'react';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import Button from "../components/Button"; import Button from '../components/Button';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import { AnimatePresence, motion } from "motion/react"; import FieldsWrapper from '../components/FieldsWrapper';
import clsx from "clsx"; import FormField from '../components/FormField';
import FormInput from '../components/FormInput';
import FormSelect from '../components/FormSelect';
function CabinetCreation() { function CabinetCreation() {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const [cabinetId, setCabinetId] = useState({ id: "", valid: true }); const [cabinetDetails, setCabinetDetails] = useState({
const [cabinetKeyId, setCabinetKeyId] = useState({ id: "", valid: true }); cabinetId: '',
const [noOfLockers, setNoOfLockers] = useState({ number: 0, valid: true }); cabinetIdValid: true,
cabinetKeyId: '',
cabinetKeyIdValid: true,
noOfLockers: 0,
noOfLockersValid: true,
});
const handleNext = (e) => { const handleNext = (e) => {
e.preventDefault(); e.preventDefault();
const idRegex = /^[A-Z]{2}[0-9]{4}$/; let isFormValid = true;
if (!idRegex.test(cabinetId.id)) { const newValues = { ...cabinetDetails };
setCabinetId({ id: cabinetId.id, valid: false }); formFields.forEach((field) => {
return; if (field.validate) {
} else if (!idRegex.test(cabinetKeyId.id)) { const isFieldValid = field.validate(cabinetDetails[field.name]);
setCabinetKeyId({ id: cabinetKeyId.id, valid: false }); newValues[`${field.name}Valid`] = isFieldValid;
return; if (!isFieldValid) isFormValid = false;
} else if (noOfLockers.number === 0) { }
setNoOfLockers({ number: noOfLockers.number, valid: false }); });
if (!isFormValid) {
setCabinetDetails(newValues);
return; return;
} }
navigate("register-lockers", {
navigate('register-lockers', {
state: { state: {
cabinetId: cabinetId.id, cabinetId: cabinetDetails.cabinetId,
cabinetKeyId: cabinetKeyId.id, cabinetKeyId: cabinetDetails.cabinetKeyId,
noOfLockers: noOfLockers.number, noOfLockers: parseInt(cabinetDetails.noOfLockers),
}, },
}); });
}; };
const formFields = [
{
name: 'cabinetId',
label: t('cabinetId'),
type: 'input',
maxLength: 6,
validate: (v) => /^\w{2}\d{4}$/.test(v),
},
{
name: 'cabinetKeyId',
label: t('cabinetKeyId'),
type: 'input',
maxLength: 6,
validate: (v) => /^\w{2}\d{4}$/.test(v),
},
{
name: 'noOfLockers',
label: t('noOfLockers'),
type: 'input',
subType: 'number',
validate: (v) => parseInt(v) > 0,
},
];
const renderField = (field) => {
const commonProps = {
value: cabinetDetails[field.name],
onChange: (e) => {
const newCabinetDetails = { ...cabinetDetails };
newCabinetDetails[field.name] = e.target.value.toUpperCase();
newCabinetDetails[`${field.name}Valid`] = true;
setCabinetDetails(newCabinetDetails);
},
maxLength: field.maxLength,
type: field.subType,
};
const valid = cabinetDetails[`${field.name}Valid`];
return (
<FormField key={field.name} label={field.label}>
{field.type === 'input' ? (
<FormInput props={commonProps} valid={valid} />
) : (
<FormSelect props={commonProps} valid={valid} options={field.options} />
)}
</FormField>
);
};
return ( return (
<FormBox title={t("cabinetCreation")}> <FormBox title={t('cabinetCreation')}>
<div className="p-2 pt-7 flex flex-col gap-4"> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
<div className="flex"> <Button text={t('next')} onClick={handleNext} />
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
{t("cabinetId")}
</label>
<div className="w-full">
<input
value={cabinetId.id}
className={clsx(
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
!cabinetId.valid && "border-error"
)}
onChange={(e) =>
setCabinetId({
id: e.target.value.toUpperCase(),
valid: true,
})
}
type="text"
maxLength={6}
/>
<AnimatePresence initial={false}>
{!cabinetId.valid && (
<motion.div
className="w-1/5 text-sm text-error ml-3 pt-1"
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0 }}
key="cabinetIdError"
>
Invalid Cabinet Id
</motion.div>
)}
</AnimatePresence>
</div>
</div>
<div className="flex">
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
{t("cabinetKeyId")}
</label>
<div className="w-full">
<input
value={cabinetKeyId.id}
className={clsx(
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
!cabinetKeyId.valid && "border-error"
)}
onChange={(e) =>
setCabinetKeyId({
id: e.target.value.toUpperCase(),
valid: true,
})
}
type="text"
maxLength={6}
/>
{!cabinetKeyId.valid && (
<div className="text-sm text-error ml-3 pt-1">Invalid Key Id</div>
)}
</div>
</div>
<div className="flex">
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
{t("noOfLockers")}
</label>
<div className="w-full">
<input
value={noOfLockers.number}
className={clsx(
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
!noOfLockers.valid && "border-error"
)}
onChange={(e) =>
setNoOfLockers({ number: e.target.value, valid: true })
}
type="number"
/>
{!noOfLockers.valid && (
<div className="text-sm text-error ml-3 pt-1">
Invalid Number of Lockers
</div>
)}
</div>
</div>
</div>
<Button
text={t("next")}
onClick={(e) => {
handleNext(e);
}}
/>
</FormBox> </FormBox>
); );
} }

View File

@@ -1,67 +1,62 @@
import { useState } from "react"; import { useState } from 'react';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import Button from "../components/Button"; import Button from '../components/Button';
import { motion } from "motion/react"; import FormInput from '../components/FormInput';
import clsx from "clsx"; import FormSelect from '../components/FormSelect';
import { AnimatePresence } from "motion/react"; import FieldsWrapper from '../components/FieldsWrapper';
import FormField from '../components/FormField';
function CabinetMaintenace() { function CabinetMaintenace() {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const [operation, setOperation] = useState({ value: "", valid: true }); const [operation, setOperation] = useState({ value: '', valid: true });
const handleNext = (e) => { const handleNext = (e) => {
e.preventDefault(); e.preventDefault();
if (operation.value === "") { if (operation.value === '') {
setOperation({ value: operation.value, valid: false }); setOperation({ value: operation.value, valid: false });
} }
navigate(operation.value); navigate(operation.value);
}; };
const formFields = [
{
name: 'value',
label: t('operation'),
options: [{ label: t('create'), value: 'create' }],
type: 'select',
},
];
const renderField = (field) => {
const commonProps = {
value: operation[field.name],
onChange: (e) => {
const newValues = { ...operation };
newValues[field.name] = e.target.value;
newValues[`${field.name}Valid`] = true;
setOperation(newValues);
},
};
const options = field.options;
return (
<FormField label={field.label}>
{field.type === 'input' ? (
<FormInput props={commonProps} />
) : (
<FormSelect props={commonProps} options={options} />
)}
</FormField>
);
};
return ( return (
<div> <div>
<FormBox title={t("cabinetMaintenance")}> <FormBox title={t('cabinetMaintenance')}>
<div className="p-2 pt-7"> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
<div className="flex"> <Button text={t('next')} onClick={(e) => handleNext(e)} />
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
{t("operation")}
</label>
<div className="w-full">
<select
className={clsx(
"w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey",
!operation.valid && "border-error"
)}
onChange={(e) =>
setOperation({ value: e.target.value, valid: true })
}
defaultValue={operation.value}
value={operation.value}
>
<option value="" disabled>
{t("select")}
</option>
<option value="create">{t("create")}</option>
</select>
<AnimatePresence>
{!operation.valid && (
<motion.div
className="w-1/5 text-sm text-error ml-3 pt-1"
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
key="cabinetIdError"
>
Invalid Operation
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
<Button text={t("next")} onClick={(e) => handleNext(e)} />
</FormBox> </FormBox>
</div> </div>
); );

View File

@@ -1,26 +1,27 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from 'react';
import { useLoading } from "../hooks/useLoading"; import { useLoading } from '../hooks/useLoading';
import FormField from "../components/FormField"; import FormField from '../components/FormField';
import FormInput from "../components/FormInput"; import FormInput from '../components/FormInput';
import FormSelect from "../components/FormSelect"; import FormSelect from '../components/FormSelect';
import Button from "../components/Button"; import Button from '../components/Button';
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import { useLocation } from "react-router-dom"; import { useLocation } from 'react-router-dom';
import { lockerService } from "../services/locker.service"; import { lockerService } from '../services/locker.service';
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from 'motion/react';
import { Pencil } from "lucide-react"; import { Pencil } from 'lucide-react';
import Notification from "../components/Notification"; import Notification from '../components/Notification';
import FieldsWrapper from '../components/FieldsWrapper';
function ChargeEdit() { function ChargeEdit() {
const [chargeDetails, setChargeDetails] = useState({ const [chargeDetails, setChargeDetails] = useState({
rentAmount: "", rentAmount: '',
rentAmountEdit: false, rentAmountEdit: false,
penaltyAmount: "", penaltyAmount: '',
penaltyAmountEdit: false, penaltyAmountEdit: false,
}); });
const [notification, setNotification] = useState({ message: "", type: "" }); const [notification, setNotification] = useState({ message: '', type: '' });
const { setIsLoading } = useLoading(); const { setIsLoading } = useLoading();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -32,10 +33,7 @@ function ChargeEdit() {
const fetchCharges = async () => { const fetchCharges = async () => {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await lockerService.getCharges( const response = await lockerService.getCharges(productCode, interestCategory);
productCode,
interestCategory
);
if (response.status === 200) { if (response.status === 200) {
const { rent, penalty } = response.data; const { rent, penalty } = response.data;
setChargeDetails({ setChargeDetails({
@@ -47,14 +45,14 @@ function ChargeEdit() {
} else { } else {
setNotification({ setNotification({
message: response.data.message, message: response.data.message,
type: "error", type: 'error',
}); });
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setNotification({ setNotification({
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -69,28 +67,28 @@ function ChargeEdit() {
const formFields = [ const formFields = [
{ {
name: "rentAmount", name: 'rentAmount',
label: "Rent Amount", label: 'Rent Amount',
type: "input", type: 'input',
subType: "number", subType: 'number',
readOnly: !chargeDetails.rentAmountEdit, readOnly: !chargeDetails.rentAmountEdit,
icon: { icon: {
icon: <Pencil size={22} />, icon: <Pencil size={22} />,
mode: "plain", mode: 'plain',
onClick: () => { onClick: () => {
setChargeDetails({ ...chargeDetails, rentAmountEdit: true }); setChargeDetails({ ...chargeDetails, rentAmountEdit: true });
}, },
}, },
}, },
{ {
name: "penaltyAmount", name: 'penaltyAmount',
label: "Penalty Amount", label: 'Penalty Amount',
type: "input", type: 'input',
subType: "number", subType: 'number',
readOnly: !chargeDetails.penaltyAmountEdit, readOnly: !chargeDetails.penaltyAmountEdit,
icon: { icon: {
icon: <Pencil size={22} />, icon: <Pencil size={22} />,
mode: "plain", mode: 'plain',
onClick: () => { onClick: () => {
setChargeDetails({ ...chargeDetails, penaltyAmountEdit: true }); setChargeDetails({ ...chargeDetails, penaltyAmountEdit: true });
}, },
@@ -112,19 +110,19 @@ function ChargeEdit() {
if (response.status === 200) { if (response.status === 200) {
setNotification({ setNotification({
message: response.data.message, message: response.data.message,
type: "success", type: 'success',
}); });
} else { } else {
setNotification({ setNotification({
message: response.data.message, message: response.data.message,
type: "error", type: 'error',
}); });
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setNotification({ setNotification({
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -141,18 +139,16 @@ function ChargeEdit() {
}); });
}, },
readOnly: field.readOnly, readOnly: field.readOnly,
className: field.readOnly ? "bg-grey/[0.3]" : "",
type: field.subType,
}; };
const className = field.readOnly ? 'bg-grey/[0.3]' : '';
return ( return (
<FormField key={field.name} label={field.label} icon={field.icon}> <FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? ( {field.type === 'input' ? (
<FormInput <FormInput props={commonProps} className={className} />
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} options={field.options} className={className} />
)} )}
</FormField> </FormField>
); );
@@ -161,25 +157,17 @@ function ChargeEdit() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.message !== "" && <Notification {...notification} />} {notification.message !== '' && <Notification {...notification} />}
</AnimatePresence> </AnimatePresence>
<div className="relative">
{notification.type === "success" && ( <FormBox title={t('chargeEdit')}>
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" /> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
)} <Button
<FormBox title={t("chargeEdit")}> text={t('submit')}
<div className="p-2 pt-7 flex flex-col gap-4"> onClick={handleSubmit}
{formFields.map(renderField)} disabled={!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit}
</div> />
<Button </FormBox>
text={t("submit")}
onClick={handleSubmit}
disabled={
!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit
}
/>
</FormBox>
</div>
</div> </div>
); );
} }

View File

@@ -1,13 +1,13 @@
import clsx from "clsx"; import { useTranslation } from 'react-i18next';
import { useTranslation } from "react-i18next"; import FormField from '../components/FormField';
import FormField from "../components/FormField"; import FormInput from '../components/FormInput';
import FormInput from "../components/FormInput"; import FormSelect from '../components/FormSelect';
import FormSelect from "../components/FormSelect"; import Button from '../components/Button';
import Button from "../components/Button"; import FormBox from '../components/FormBox';
import FormBox from "../components/FormBox"; import { useNavigate } from 'react-router-dom';
import { useNavigate } from "react-router-dom"; import { useState } from 'react';
import { useState } from "react"; import { useToast } from '../hooks/useToast';
import { useToast } from "../hooks/useToast"; import FieldsWrapper from '../components/FieldsWrapper';
function ChargeManagement() { function ChargeManagement() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -15,26 +15,26 @@ function ChargeManagement() {
const showToast = useToast(); const showToast = useToast();
const [productDetails, setProductDetails] = useState({ const [productDetails, setProductDetails] = useState({
productCode: "", productCode: '',
interestCategory: "", interestCategory: '',
productCodeValid: true, productCodeValid: true,
interestCategoryValid: true, interestCategoryValid: true,
}); });
const formFields = [ const formFields = [
{ {
name: "productCode", name: 'productCode',
label: t("productCode"), label: t('productCode'),
type: "input", type: 'input',
subType: "number", subType: 'number',
maxLength: 4, maxLength: 4,
validate: (value) => /^[0-9]{4}/.test(value), validate: (value) => /^[0-9]{4}/.test(value),
}, },
{ {
name: "interestCategory", name: 'interestCategory',
label: t("interestCategory"), label: t('interestCategory'),
type: "input", type: 'input',
subType: "number", subType: 'number',
maxLength: 4, maxLength: 4,
validate: (value) => /^[0-9]{4}/.test(value), validate: (value) => /^[0-9]{4}/.test(value),
}, },
@@ -54,11 +54,11 @@ function ChargeManagement() {
if (!isValid) { if (!isValid) {
setProductDetails(newProductDetails); setProductDetails(newProductDetails);
showToast(t("highlightedFieldsInvalid"), "error"); showToast(t('highlightedFieldsInvalid'), 'error');
return; return;
} }
navigate("change", { navigate('change', {
state: { state: {
productCode: productDetails.productCode, productCode: productDetails.productCode,
interestCategory: productDetails.interestCategory, interestCategory: productDetails.interestCategory,
@@ -71,8 +71,8 @@ function ChargeManagement() {
value: productDetails[field.name], value: productDetails[field.name],
onChange: (e) => { onChange: (e) => {
const newLockerDetails = { ...productDetails }; const newLockerDetails = { ...productDetails };
if (field.subType === "number") { if (field.subType === 'number') {
e.target.value = e.target.value.replace(/\D/g, ""); e.target.value = e.target.value.replace(/\D/g, '');
if (e.target.value.length > field.maxLength) { if (e.target.value.length > field.maxLength) {
return; return;
} }
@@ -83,30 +83,25 @@ function ChargeManagement() {
setProductDetails(newLockerDetails); setProductDetails(newLockerDetails);
}, },
maxLength: field.maxLength, maxLength: field.maxLength,
className: clsx(!productDetails[`${field.name}Valid`] && "border-error"), type: field.subType,
readOnly: field.readOnly,
}; };
const valid = productDetails[`${field.name}Valid`];
return ( return (
<FormField key={field.name} label={field.label} icon={field.icon}> <FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? ( {field.type === 'input' ? (
<FormInput <FormInput props={commonProps} valid={valid} />
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} valid={valid} options={field.options} />
)} )}
</FormField> </FormField>
); );
}; };
return ( return (
<FormBox title={t("chargeManagement")}> <FormBox title={t('chargeManagement')}>
<div className="p-2 pt-7 flex flex-col gap-4"> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
{formFields.map(renderField)} <Button text={t('submit')} onClick={handleSubmit} />
</div>
<Button text={t("submit")} onClick={handleSubmit} />
</FormBox> </FormBox>
); );
} }

View File

@@ -1,16 +1,16 @@
import { useState } from "react"; import { useState } from 'react';
import { useLocation } from "react-router-dom"; import { useLocation } from 'react-router-dom';
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import Button from "../components/Button"; import Button from '../components/Button';
import Notification from "../components/Notification"; import Notification from '../components/Notification';
import { lockerService } from "../services/locker.service"; import { lockerService } from '../services/locker.service';
import { useToast } from "../hooks/useToast"; import { useToast } from '../hooks/useToast';
import { useLoading } from "../hooks/useLoading"; import { useLoading } from '../hooks/useLoading';
function CheckInOutLog() { function CheckInOutLog() {
const [time, setTime] = useState(null); const [time, setTime] = useState(null);
const [checkType, setCheckType] = useState(""); const [checkType, setCheckType] = useState('');
const [notification, setNotification] = useState({ message: "", type: "" }); const [notification, setNotification] = useState({ message: '', type: '' });
const location = useLocation(); const location = useLocation();
const showToast = useToast(); const showToast = useToast();
@@ -19,27 +19,23 @@ function CheckInOutLog() {
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
if (time === null || checkType === "") { if (time === null || checkType === '') {
showToast("Please fill in all fields", "error"); showToast('Please fill in all fields', 'error');
return; return;
} }
// Add your logic here // Add your logic here
try { try {
setIsLoading(true); setIsLoading(true);
const response = await lockerService.checkInOut( const response = await lockerService.checkInOut(accountNumber, time, checkType);
accountNumber,
time,
checkType
);
if (response.status === 200) { if (response.status === 200) {
setNotification({ message: response.data.message, type: "success" }); setNotification({ message: response.data.message, type: 'success' });
} else { } else {
console.log(response); console.log(response);
setNotification({ message: response.data.message, type: "error" }); setNotification({ message: response.data.message, type: 'error' });
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
setNotification({ message: error.message, type: "error" }); setNotification({ message: error.message, type: 'error' });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -47,16 +43,14 @@ function CheckInOutLog() {
return ( return (
<div> <div>
{notification.message !== "" && <Notification {...notification} />} {notification.message !== '' && <Notification {...notification} />}
<FormBox title="Check In/Out Log"> <FormBox title="Check In/Out Log">
<div className="px-4 pt-7 text-2xl font-display font-bold text-primary dark:text-primary-dark"> <div className="px-4 pt-7 text-2xl font-display font-bold text-primary dark:text-primary-dark">
{accountNumber} {accountNumber}
</div> </div>
<div className="p-2 pt-7 flex flex-col gap-4"> <div className="p-2 pt-7 flex flex-col gap-4">
<div className="flex"> <div className="flex">
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]"> <label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">Time</label>
Time
</label>
<input <input
type="time" type="time"
className="w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey text-black" className="w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey text-black"

View File

@@ -1,23 +1,20 @@
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import { useState } from "react"; import { useState } from 'react';
import FormField from "../components/FormField"; import FormField from '../components/FormField';
import FormInput from "../components/FormInput"; import FormInput from '../components/FormInput';
import { Search } from "lucide-react"; import { Search } from 'lucide-react';
import Button from "../components/Button"; import Button from '../components/Button';
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from 'motion/react';
import Notification from "../components/Notification"; import Notification from '../components/Notification';
import { useToast } from "../hooks/useToast"; import { useToast } from '../hooks/useToast';
import { lockerService } from "../services/locker.service"; import { lockerService } from '../services/locker.service';
import { useLoading } from "../hooks/useLoading"; import { useLoading } from '../hooks/useLoading';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import FieldsWrapper from '../components/FieldsWrapper';
function CheckInOutManagement() { function CheckInOutManagement() {
const [accountNumber, setAccountNumber] = useState(""); const [accountNumber, setAccountNumber] = useState('');
const [notification, setNotification] = useState({ const [notification, setNotification] = useState({ message: '', type: '' });
visible: false,
message: "",
type: "",
});
const showToast = useToast(); const showToast = useToast();
const { setIsLoading } = useLoading(); const { setIsLoading } = useLoading();
@@ -25,8 +22,8 @@ function CheckInOutManagement() {
const handleNext = async (e) => { const handleNext = async (e) => {
e.preventDefault(); e.preventDefault();
if (accountNumber === "") { if (accountNumber === '') {
showToast("Account Number is required", "error"); showToast('Account Number is required', 'error');
return; return;
} }
try { try {
@@ -36,26 +33,26 @@ function CheckInOutManagement() {
if (response.status === 200) { if (response.status === 200) {
const data = response.data; const data = response.data;
if (data.code === 1) { if (data.code === 1) {
navigate("log", { state: { accountNumber } }); navigate('log', { state: { accountNumber } });
} else if (data.code === 2) { } else if (data.code === 2) {
setNotification({ setNotification({
visible: true, visible: true,
message: message:
"Monthly access limit exceeded. A fine will be charged for each additional access.", 'Monthly access limit exceeded. A fine will be charged for each additional access.',
type: "warning", type: 'warning',
}); });
} else if (data.code === 3) { } else if (data.code === 3) {
setNotification({ setNotification({
visible: true, visible: true,
message: message:
"Rent for this account is due. Please pay the rent amount in full to access the locker.", 'Rent for this account is due. Please pay the rent amount in full to access the locker.',
type: "error", type: 'error',
}); });
} }
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
setNotification(error.message, "error"); setNotification(error.message, 'error');
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -63,21 +60,23 @@ function CheckInOutManagement() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.visible && <Notification notification={notification} />} {notification.message !== '' && <Notification {...notification} />}
</AnimatePresence> </AnimatePresence>
<FormBox title="Check In/Out"> <FormBox title="Check In/Out">
<div className="p-2 pt-7"> <FieldsWrapper>
<FormField <FormField
label="Account Number" label="Account Number"
icon={{ icon: <Search size={17} />, onClick: () => {} }} icon={{ icon: <Search size={17} />, onClick: () => {} }}
> >
<FormInput <FormInput
type="text" props={{
value={accountNumber} type: 'text',
onChange={(e) => setAccountNumber(e.target.value)} value: accountNumber,
onChange: (e) => setAccountNumber(e.target.value),
}}
/> />
</FormField> </FormField>
</div> </FieldsWrapper>
<Button text="Next" onClick={handleNext} /> <Button text="Next" onClick={handleNext} />
</FormBox> </FormBox>
</div> </div>

View File

@@ -1,11 +1,20 @@
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import { getUserInfoFromSession } from "../util/util"; import { getUserInfoFromSession } from '../util/util';
function Home() { function Home() {
const { t } = useTranslation(); const { t } = useTranslation();
const holidayList = [{ date: '23 May, 2024', name: 'Buddha Purnima' }, { date: '15 June, 2024', name: 'Raja Sankranti' }]; const holidayList = [
const homePageNotifications = ['hpn_complete_before_31', 'hpn_npa', 'hpn_helpdesk_contact', 'hpn_rupay_kcc_time', 'hpn_rupay_kcc_atm']; { date: '23 May, 2024', name: 'Buddha Purnima' },
{ date: '15 June, 2024', name: 'Raja Sankranti' },
];
const homePageNotifications = [
'hpn_complete_before_31',
'hpn_npa',
'hpn_helpdesk_contact',
'hpn_rupay_kcc_time',
'hpn_rupay_kcc_atm',
];
const userInformation = getUserInfoFromSession(); const userInformation = getUserInfoFromSession();
return ( return (
@@ -15,7 +24,12 @@ function Home() {
<FormBox title={t('holidayList')} alt={true}> <FormBox title={t('holidayList')} alt={true}>
<ul className="px-4 list-disc list-inside"> <ul className="px-4 list-disc list-inside">
{holidayList.map((holiday, index) => ( {holidayList.map((holiday, index) => (
<li key={index} className="py-2 font-body text-surface dark:text-surface-dark text-lg/loose">{t(holiday.date)} - {t(holiday.name)}</li> <li
key={index}
className="py-2 font-body text-surface dark:text-surface-dark text-lg/loose"
>
{t(holiday.date)} - {t(holiday.name)}
</li>
))} ))}
</ul> </ul>
</FormBox> </FormBox>
@@ -23,11 +37,14 @@ function Home() {
<div className="flex-grow"> <div className="flex-grow">
<FormBox title={t('information')}> <FormBox title={t('information')}>
<ul className="px-4 list-disc list-inside"> <ul className="px-4 list-disc list-inside">
{ {Object.keys(userInformation).map((key, index) => (
Object.keys(userInformation).map((key, index) => ( <li
<li key={index} className="py-2 font-body text-surface-dark dark:text-surface text-lg/loose">{t(key)}: {t(userInformation[key])}</li> key={index}
)) className="py-2 font-body text-surface-dark dark:text-surface text-lg/loose"
} >
{t(key)}: {t(userInformation[key])}
</li>
))}
</ul> </ul>
</FormBox> </FormBox>
</div> </div>
@@ -37,7 +54,12 @@ function Home() {
<FormBox title={t('notifications')}> <FormBox title={t('notifications')}>
<ul className="px-4 list-disc list-inside"> <ul className="px-4 list-disc list-inside">
{homePageNotifications.map((notification, index) => ( {homePageNotifications.map((notification, index) => (
<li key={index} className="py-2 font-body text-surface-dark dark:text-surface text-lg/relaxed">{t(notification)}</li> <li
key={index}
className="py-2 font-body text-surface-dark dark:text-surface text-lg/relaxed"
>
{t(notification)}
</li>
))} ))}
</ul> </ul>
</FormBox> </FormBox>
@@ -47,4 +69,4 @@ function Home() {
); );
} }
export default Home; export default Home;

View File

@@ -1,29 +1,29 @@
import { useState } from "react"; import { useState } from 'react';
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from 'motion/react';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import { useToast } from "../hooks/useToast"; import { useToast } from '../hooks/useToast';
import { useLoading } from "../hooks/useLoading"; import { useLoading } from '../hooks/useLoading';
import FormField from "../components/FormField"; import FormField from '../components/FormField';
import FormInput from "../components/FormInput"; import FormInput from '../components/FormInput';
import FormSelect from "../components/FormSelect"; import FormSelect from '../components/FormSelect';
import Button from "../components/Button"; import Button from '../components/Button';
import { lockerService } from "../services/locker.service"; import { lockerService } from '../services/locker.service';
import clsx from "clsx"; import FormBox from '../components/FormBox';
import FormBox from "../components/FormBox"; import Notification from '../components/Notification';
import Notification from "../components/Notification"; import FieldsWrapper from '../components/FieldsWrapper';
function KeySwap() { function KeySwap() {
const { t } = useTranslation(); const { t } = useTranslation();
const showToast = useToast(); const showToast = useToast();
const { isLoading, setIsLoading } = useLoading(); const { isLoading, setIsLoading } = useLoading();
const [notification, setNotification] = useState({ message: "", type: "" }); const [notification, setNotification] = useState({ message: '', type: '' });
const [keySwapDetails, setKeySwapDetails] = useState({ const [keySwapDetails, setKeySwapDetails] = useState({
cabinetId: "", cabinetId: '',
lockerId: "", lockerId: '',
reason: "", reason: '',
oldKey: "", oldKey: '',
newKey: "", newKey: '',
newKeyConfirm: "", newKeyConfirm: '',
cabinetIdValid: true, cabinetIdValid: true,
lockerIdValid: true, lockerIdValid: true,
reasonValid: true, reasonValid: true,
@@ -34,52 +34,52 @@ function KeySwap() {
const formFields = [ const formFields = [
{ {
name: "cabinetId", name: 'cabinetId',
label: t("cabinetId"), label: t('cabinetId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value), validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
}, },
{ {
name: "lockerId", name: 'lockerId',
label: t("lockerId"), label: t('lockerId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value), validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
}, },
{ {
name: "reason", name: 'reason',
label: t("reasonForChange"), label: t('reasonForChange'),
type: "input", type: 'input',
maxLength: 50, maxLength: 50,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => value !== "", validate: (value) => value !== '',
}, },
{ {
name: "oldKey", name: 'oldKey',
label: t("oldKeyId"), label: t('oldKeyId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value), validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
}, },
{ {
name: "newKey", name: 'newKey',
label: t("newKeyId"), label: t('newKeyId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value), validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
}, },
{ {
name: "newKeyConfirm", name: 'newKeyConfirm',
label: t("confirmNewKeyId"), label: t('confirmNewKeyId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => value !== "" && value === keySwapDetails.newKey, validate: (value) => value !== '' && value === keySwapDetails.newKey,
}, },
]; ];
@@ -98,7 +98,7 @@ function KeySwap() {
} }
if (!valid) { if (!valid) {
showToast(t("highlightedFieldsInvalid"), "error"); showToast(t('highlightedFieldsInvalid'), 'error');
return; return;
} }
@@ -114,18 +114,18 @@ function KeySwap() {
if (response.status === 200) { if (response.status === 200) {
setNotification({ setNotification({
message: response.data.message, message: response.data.message,
type: "success", type: 'success',
}); });
} else { } else {
setNotification({ setNotification({
message: response.data.message, message: response.data.message,
type: "error", type: 'error',
}); });
} }
} catch (error) { } catch (error) {
setNotification({ setNotification({
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -142,19 +142,16 @@ function KeySwap() {
setKeySwapDetails(newLockerDetails); setKeySwapDetails(newLockerDetails);
}, },
maxLength: field.maxLength, maxLength: field.maxLength,
className: clsx(!keySwapDetails[`${field.name}Valid`] && "border-error"), type: field.subType,
readOnly: field.readOnly,
}; };
const valid = keySwapDetails[`${field.name}Valid`];
return ( return (
<FormField key={field.name} label={field.label} icon={field.icon}> <FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? ( {field.type === 'input' ? (
<FormInput <FormInput props={commonProps} valid={valid} />
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} valid={valid} options={field.options} />
)} )}
</FormField> </FormField>
); );
@@ -163,19 +160,12 @@ function KeySwap() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.message !== "" && <Notification {...notification} />} {notification.message !== '' && <Notification {...notification} />}
</AnimatePresence> </AnimatePresence>
<div className="relative"> <FormBox title={t('lockerStatus')}>
{notification.type === "success" && ( <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" /> <Button text={t('submit')} onClick={handleKeySwap} />
)} </FormBox>
<FormBox title={t("lockerStatus")}>
<div className="p-2 pt-7 flex flex-col gap-4">
{formFields.map(renderField)}
</div>
<Button text={t("submit")} onClick={handleKeySwap} />
</FormBox>
</div>
</div> </div>
); );
} }

View File

@@ -1,67 +1,65 @@
import { useState } from "react"; import { useState } from 'react';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import Button from "../components/Button"; import Button from '../components/Button';
import { motion, AnimatePresence } from "motion/react"; import FormField from '../components/FormField';
import clsx from "clsx"; import FormInput from '../components/FormInput';
import FormSelect from '../components/FormSelect';
import FieldsWrapper from '../components/FieldsWrapper';
function LockerMaintenance() { function LockerMaintenance() {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
const [operation, setOperation] = useState({ value: "", valid: true }); const [operation, setOperation] = useState({ value: '', valid: true });
const handleNext = (e) => { const handleNext = (e) => {
e.preventDefault(); e.preventDefault();
if (operation.value === "") { if (operation.value === '') {
setOperation({ value: operation.value, valid: false }); setOperation({ value: operation.value, valid: false });
} }
navigate(operation.value); navigate(operation.value);
}; };
const formFields = [
{
name: 'value',
label: t('operation'),
options: [
{ label: t('status'), value: 'status' },
{ label: t('keySwap'), value: 'key-swap' },
],
type: 'select',
},
];
const renderField = (field) => {
const commonProps = {
value: operation[field.name],
onChange: (e) => {
const newValues = { ...operation };
newValues[field.name] = e.target.value;
newValues[`${field.name}Valid`] = true;
setOperation(newValues);
},
};
const options = field.options;
return (
<FormField label={field.label}>
{field.type === 'input' ? (
<FormInput props={commonProps} />
) : (
<FormSelect props={commonProps} options={options} />
)}
</FormField>
);
};
return ( return (
<div> <div>
<FormBox title={t("cabinetMaintenance")}> <FormBox title={t('lockerMaintenance')}>
<div className="p-2 pt-7"> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
<div className="flex"> <Button text={t('next')} onClick={(e) => handleNext(e)} />
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
{t("operation")}
</label>
<div className="w-full">
<select
className={clsx(
"w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey",
!operation.valid && "border-error"
)}
onChange={(e) =>
setOperation({ value: e.target.value, valid: true })
}
defaultValue={operation.value}
value={operation.value}
>
<option value="" disabled>
{t("select")}
</option>
<option value="status">{t("changeStatus")}</option>
<option value="key-swap">{t("keySwap")}</option>
</select>
<AnimatePresence initial={false}>
{!operation.valid && (
<motion.div
className="w-1/5 text-sm text-error ml-3 pt-1"
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0 }}
key="cabinetIdError"
>
Invalid Operation
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
<Button text={t("next")} onClick={(e) => handleNext(e)} />
</FormBox> </FormBox>
</div> </div>
); );

View File

@@ -1,56 +1,56 @@
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import FormField from "../components/FormField"; import FormField from '../components/FormField';
import FormInput from "../components/FormInput"; import FormInput from '../components/FormInput';
import FormSelect from "../components/FormSelect"; import FormSelect from '../components/FormSelect';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import { useState } from "react"; import { useState } from 'react';
import clsx from "clsx"; import Button from '../components/Button';
import Button from "../components/Button"; import { lockerService } from '../services/locker.service';
import { lockerService } from "../services/locker.service"; import { useLoading } from '../hooks/useLoading';
import { useLoading } from "../hooks/useLoading"; import { AnimatePresence } from 'motion/react';
import { AnimatePresence } from "motion/react"; import Notification from '../components/Notification';
import Notification from "../components/Notification"; import FieldsWrapper from '../components/FieldsWrapper';
function LockerStatus() { function LockerStatus() {
const { t } = useTranslation(); const { t } = useTranslation();
const [lockerDetails, setLockerDetails] = useState({ const [lockerDetails, setLockerDetails] = useState({
cabinetId: "", cabinetId: '',
lockerId: "", lockerId: '',
status: "", status: '',
cabinetIdValid: true, cabinetIdValid: true,
lockerIdValid: true, lockerIdValid: true,
statusValid: true, statusValid: true,
}); });
const { isLoading, setIsLoading } = useLoading(); const { isLoading, setIsLoading } = useLoading();
const [notification, setNotification] = useState({ message: "", type: "" }); const [notification, setNotification] = useState({ message: '', type: '' });
const formFields = [ const formFields = [
{ {
name: "cabinetId", name: 'cabinetId',
label: t("cabinetId"), label: t('cabinetId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value), validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
}, },
{ {
name: "lockerId", name: 'lockerId',
label: t("lockerId"), label: t('lockerId'),
type: "input", type: 'input',
maxLength: 6, maxLength: 6,
readOnly: isLoading, readOnly: isLoading,
validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value), validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
}, },
{ {
name: "status", name: 'status',
label: t("status"), label: t('status'),
type: "select", type: 'select',
readOnly: isLoading, readOnly: isLoading,
options: [ options: [
{ value: "open", label: t("open") }, { value: 'open', label: t('open') },
{ value: "close", label: t("close") }, { value: 'close', label: t('close') },
], ],
validate: (value) => value !== "", validate: (value) => value !== '',
}, },
]; ];
@@ -83,12 +83,12 @@ function LockerStatus() {
); );
setNotification({ setNotification({
message: response.data.message, message: response.data.message,
type: "success", type: 'success',
}); });
} catch (error) { } catch (error) {
setNotification({ setNotification({
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -98,7 +98,6 @@ function LockerStatus() {
const renderField = (field) => { const renderField = (field) => {
const commonProps = { const commonProps = {
value: lockerDetails[field.name], value: lockerDetails[field.name],
valid: lockerDetails[`${field.name}Valid`],
onChange: (e) => { onChange: (e) => {
const newLockerDetails = { ...lockerDetails }; const newLockerDetails = { ...lockerDetails };
newLockerDetails[field.name] = e.target.value.toUpperCase(); newLockerDetails[field.name] = e.target.value.toUpperCase();
@@ -106,19 +105,16 @@ function LockerStatus() {
setLockerDetails(newLockerDetails); setLockerDetails(newLockerDetails);
}, },
maxLength: field.maxLength, maxLength: field.maxLength,
className: clsx(!lockerDetails[`${field.name}Valid`] && "border-error"), type: field.subType,
readOnly: field.readOnly,
}; };
const valid = lockerDetails[`${field.name}Valid`];
return ( return (
<FormField key={field.name} label={field.label} icon={field.icon}> <FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? ( {field.type === 'input' ? (
<FormInput <FormInput props={commonProps} valid={valid} />
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} options={field.options} />
)} )}
</FormField> </FormField>
); );
@@ -127,19 +123,13 @@ function LockerStatus() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.message !== "" && <Notification {...notification} />} {notification.message !== '' && <Notification {...notification} />}
</AnimatePresence> </AnimatePresence>
<div className="relative">
{notification.type === "success" && ( <FormBox title={t('lockerStatus')}>
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" /> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
)} <Button text={t('submit')} onClick={handleSubmit} />
<FormBox title={t("lockerStatus")}> </FormBox>
<div className="p-2 pt-7 flex flex-col gap-4">
{formFields.map(renderField)}
</div>
<Button text={t("submit")} onClick={handleSubmit} />
</FormBox>
</div>
</div> </div>
); );
} }

View File

@@ -1,42 +1,43 @@
import { useLocation } from "react-router-dom"; import { useLocation } from 'react-router-dom';
import { useState } from "react"; import { useState } from 'react';
import clsx from "clsx"; import { useLoading } from '../hooks/useLoading';
import FormBox from "../components/FormBox"; import FormBox from '../components/FormBox';
import Button from "../components/Button"; import Button from '../components/Button';
import { useToast } from "../hooks/useToast"; import { lockerService } from '../services/locker.service';
import { lockerService } from "../services/locker.service"; import { AnimatePresence } from 'motion/react';
import { AnimatePresence } from "motion/react"; import Notification from '../components/Notification';
import { useLoading } from "../hooks/useLoading"; import FormField from '../components/FormField';
import Notification from "../components/Notification"; import FormInput from '../components/FormInput';
import FormSelect from '../components/FormSelect';
import FormHeader from '../components/FormHeader';
import FieldsWrapper from '../components/FieldsWrapper';
function LockersRegistration() { function LockersRegistration() {
const location = useLocation(); const location = useLocation();
const showToast = useToast();
const { setIsLoading } = useLoading(); const { setIsLoading } = useLoading();
const noOfLockers = location.state?.noOfLockers; const [notification, setNotification] = useState({ message: '', type: '' });
const cabinetId = location.state?.cabinetId;
const [submitting, setSubmitting] = useState(false);
const [notification, setNotification] = useState({
message: "",
type: "",
});
const initLockers = Array(noOfLockers ? parseInt(noOfLockers) : 0) const noOfLockers = location.state?.noOfLockers;
const cabinetId = location.state?.cabinetId;
const initLockers = Array(noOfLockers)
.fill() .fill()
.map(() => ({ .map(() => ({
id: "", id: '',
size: "", size: '',
keyId: "", keyId: '',
idValid: true, idValid: true,
sizeValid: true, sizeValid: true,
keyIdValid: true, keyIdValid: true,
})); }));
const [lockerValues, setLockerValues] = useState(initLockers); const [lockerValues, setLockerValues] = useState(initLockers);
if(!location.state) {
return <></> if (!location.state) {
return <></>;
} }
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
console.log("submitting");
e.preventDefault(); e.preventDefault();
const idRegex = /^[A-Z]{2}[0-9]{4}$/; const idRegex = /^[A-Z]{2}[0-9]{4}$/;
let valid = true; let valid = true;
@@ -48,18 +49,14 @@ function LockersRegistration() {
}; };
// Find duplicates // Find duplicates
const duplicateLockerIds = findDuplicates(lockerValues, "id"); const duplicateLockerIds = findDuplicates(lockerValues, 'id');
const duplicateKeyIds = findDuplicates(lockerValues, "keyId"); const duplicateKeyIds = findDuplicates(lockerValues, 'keyId');
const newValues = lockerValues.map((locker) => { const newValues = lockerValues.map((locker) => {
const newLocker = { ...locker }; const newLocker = { ...locker };
// Check ID // Check ID
if ( if (locker.id === '' || !idRegex.test(locker.id) || duplicateLockerIds.includes(locker.id)) {
locker.id === "" ||
!idRegex.test(locker.id) ||
duplicateLockerIds.includes(locker.id)
) {
newLocker.idValid = false; newLocker.idValid = false;
valid = false; valid = false;
} else { } else {
@@ -67,7 +64,7 @@ function LockersRegistration() {
} }
// Check size // Check size
if (locker.size === "") { if (locker.size === '') {
newLocker.sizeValid = false; newLocker.sizeValid = false;
valid = false; valid = false;
} else { } else {
@@ -76,7 +73,7 @@ function LockersRegistration() {
// Check keyId // Check keyId
if ( if (
locker.keyId === "" || locker.keyId === '' ||
!idRegex.test(locker.keyId) || !idRegex.test(locker.keyId) ||
duplicateKeyIds.includes(locker.keyId) duplicateKeyIds.includes(locker.keyId)
) { ) {
@@ -92,136 +89,101 @@ function LockersRegistration() {
setLockerValues(newValues); setLockerValues(newValues);
if (!valid) { if (!valid) {
const errorMessage =
duplicateLockerIds.length || duplicateKeyIds.length
? "Please ensure all IDs are unique."
: "Inavlid Ids";
showToast(errorMessage, "error");
return; return;
} }
try { try {
setSubmitting(true);
setIsLoading(true); setIsLoading(true);
const response = await lockerService.registerLockers( const response = await lockerService.registerLockers(cabinetId, lockerValues);
cabinetId,
lockerValues
);
setNotification({ setNotification({
message: `Cabinet creation successful. Cabinet ID: ${response.data.cabinetId}`, message: `Cabinet creation successful. Cabinet ID: ${response.data.cabinetId}`,
type: "success", type: 'success',
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setNotification({ setNotification({
message: `Error registering lockers. ${error.message}`, message: `Error registering lockers. ${error.message}`,
type: "error", type: 'error',
}); });
return; return;
} finally { } finally {
setIsLoading(false); setIsLoading(false);
setSubmitting(false);
} }
}; };
const lockerDetails = lockerValues.map((locker, index) => { const formRow = [
return ( {
<div key={index} className="flex gap-12 items-center"> label: 'Locker ID',
<label className="text-lg text-bold dark:text-primary-dark">{`Locker ${ type: 'input',
index + 1 subType: 'text',
}`}</label> name: 'id',
maxLength: 6,
validate: (value) => /^[A-Z]{2}[0-9]{4}$/.test(value),
},
{
label: 'Size',
type: 'select',
subType: 'text',
name: 'size',
options: [
{ label: 'Small', value: '1' },
{ label: 'Medium', value: '2' },
{ label: 'Large', value: '3' },
],
},
{
label: 'Key ID',
type: 'input',
subType: 'text',
name: 'keyId',
maxLength: 6,
validate: (value) => /^[A-Z]{2}[0-9]{4}$/.test(value),
},
];
const renderFormRow = (field) => {
const commonProps = {
value: lockerValues[field.name],
onChange: (e) => {
const newLockerValues = [...lockerValues];
newLockerValues[field.name] = e.target.value;
newLockerValues[`${field.name}Valid`] = true;
setLockerValues(newLockerValues);
},
maxLength: field.maxLength,
type: field.subType,
};
<input const valid = lockerValues[`${field.name}Valid`];
value={locker.id}
disabled={submitting}
className={clsx(
"w-64 h-9 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey",
!locker.idValid && "border-error text-error"
)}
onChange={(e) => {
const newValues = [...lockerValues];
newValues[index] = {
...newValues[index],
id: e.target.value.toUpperCase(),
idValid: true,
};
setLockerValues(newValues);
}}
placeholder="Locker ID"
type="text"
maxLength={6}
/>
<select return field.type === 'input' ? (
value={locker.size} <FormInput key={field.name} props={commonProps} valid={valid} />
disabled={submitting} ) : (
className={clsx( <FormSelect key={field.name} props={commonProps} valid={valid} options={field.options} />
"w-64 h-9 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey",
!locker.sizeValid && "border-error"
)}
onChange={(e) => {
const newValues = [...lockerValues];
newValues[index] = {
...newValues[index],
size: e.target.value,
sizeValid: true,
};
setLockerValues(newValues);
}}
>
<option value="" disabled>
Size
</option>
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
<input
value={locker.keyId}
disabled={submitting}
className={clsx(
"w-64 h-9 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey",
!locker.keyIdValid && "border-error"
)}
onChange={(e) => {
const newValues = [...lockerValues];
newValues[index] = {
...newValues[index],
keyId: e.target.value.toUpperCase(),
keyIdValid: true,
};
setLockerValues(newValues);
}}
placeholder="Key ID"
type="text"
maxLength={6}
/>
</div>
); );
}); };
const formFields = Array(noOfLockers).fill(formRow);
const renderFormFields = (row, index) => {
return (
<FormField key={index} label={`Locker ${index + 1}`} variant="long">
{row.map(renderFormRow)}
</FormField>
);
};
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.message !== "" && <Notification {...notification} />} {notification.message !== '' && <Notification {...notification} />}
</AnimatePresence> </AnimatePresence>
<div className="relative"> <div className="relative">
{notification.type === "success" && ( {notification.type === 'success' && (
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" /> <div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
)} )}
<FormBox title="Locker Registration" disabled={submitting}> <FormBox title="Locker Registration">
<div className="px-4 pt-7 text-2xl font-bold text-primary dark:text-primary-dark"> <FormHeader text={cabinetId} />
{cabinetId} <FieldsWrapper className="">{formFields.map(renderFormFields)}</FieldsWrapper>
</div> <Button text="Register" onClick={handleSubmit} />
<div className="flex flex-col gap-4 p-4">{lockerDetails}</div>
<Button
disabled={submitting}
text="Register"
onClick={(e) => {
handleSubmit(e);
}}
/>
</FormBox> </FormBox>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,10 @@
import axios from 'axios'; import axios from 'axios';
const api = axios.create({ const api = axios.create({
baseURL: "http://localhost:8081/api/v1", baseURL: 'http://localhost:8081/api/v1',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
}); });
export default api; export default api;

View File

@@ -1,8 +1,8 @@
import api from "./api"; import api from './api';
export const lockerService = { export const lockerService = {
registerLockers: async (cabinetId, lockers) => { registerLockers: async (cabinetId, lockers) => {
return api.post("/cabinet", { return api.post('/cabinet', {
cabinetId, cabinetId,
lockers: lockers.map(({ id, size, keyId }) => ({ lockers: lockers.map(({ id, size, keyId }) => ({
id, id,

View File

@@ -1,8 +1,43 @@
const productInfo = [ const productInfo = [
{ productCode: '3001', productCodeDescription: 'RECURRING DEPOSIT', interestCategory: '1004', interestCategoryDescription: 'NON MEMBER - SENIOR CITIZEN', payableGl: '16010', paidGl: '62110' }, {
{ productCode: '1101', productCodeDescription: 'CURRENT ACCOUNT', interestCategory: '1009', interestCategoryDescription: 'GENERAL', payableGl: '16018', paidGl: '62115' }, productCode: '3001',
{ productCode: '1101', productCodeDescription: 'SAVINGS DEPOSIT', interestCategory: '1007', interestCategoryDescription: 'NON MEMBER', payableGl: '16301', paidGl: '62117' }, productCodeDescription: 'RECURRING DEPOSIT',
{ productCode: '2002', productCodeDescription: 'CASH CERTIFICATE -GENERAL', interestCategory: '1047', interestCategoryDescription: 'COMPOUNDING', payableGl: '16011', paidGl: '62111' }, interestCategory: '1004',
{ productCode: '2002', productCodeDescription: 'CASH CERTIFICATE', interestCategory: '1005', interestCategoryDescription: 'NON MEMBER - SENIOR CITIZEN', payableGl: '16011', paidGl: '62111' }, interestCategoryDescription: 'NON MEMBER - SENIOR CITIZEN',
] payableGl: '16010',
export default productInfo; paidGl: '62110',
},
{
productCode: '1101',
productCodeDescription: 'CURRENT ACCOUNT',
interestCategory: '1009',
interestCategoryDescription: 'GENERAL',
payableGl: '16018',
paidGl: '62115',
},
{
productCode: '1101',
productCodeDescription: 'SAVINGS DEPOSIT',
interestCategory: '1007',
interestCategoryDescription: 'NON MEMBER',
payableGl: '16301',
paidGl: '62117',
},
{
productCode: '2002',
productCodeDescription: 'CASH CERTIFICATE -GENERAL',
interestCategory: '1047',
interestCategoryDescription: 'COMPOUNDING',
payableGl: '16011',
paidGl: '62111',
},
{
productCode: '2002',
productCodeDescription: 'CASH CERTIFICATE',
interestCategory: '1005',
interestCategoryDescription: 'NON MEMBER - SENIOR CITIZEN',
payableGl: '16011',
paidGl: '62111',
},
];
export default productInfo;

View File

@@ -1,14 +1,16 @@
const getUserInfoFromSession = () => { const getUserInfoFromSession = () => {
return { return {
username: "Rajesh Kumar", username: 'Rajesh Kumar',
pacsName: "Demo SKUS Ltd.", pacsName: 'Demo SKUS Ltd.',
userType: "pacsTeller", userType: 'pacsTeller',
moduleName: "locker", moduleName: 'locker',
} };
} };
const toTitleCase = (str) => { const toTitleCase = (str) => {
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); return str.replace(/\w\S*/g, function (txt) {
} return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
export { getUserInfoFromSession, toTitleCase }; export { getUserInfoFromSession, toTitleCase };

View File

@@ -1,9 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: 'selector', darkMode: 'selector',
theme: { theme: {
colors: { colors: {
@@ -13,7 +10,7 @@ export default {
error: { error: {
DEFAULT: '#A14444', DEFAULT: '#A14444',
dark: '#E5254B', dark: '#E5254B',
surface: {DEFAULT: '#D26464', dark: '#FCE9ED'} surface: { DEFAULT: '#D26464', dark: '#FCE9ED' },
}, },
onToast: { onToast: {
DEFAULT: '#646564', DEFAULT: '#646564',
@@ -22,22 +19,22 @@ export default {
warning: { warning: {
DEFAULT: '#A5A513', DEFAULT: '#A5A513',
dark: '#EA7000', dark: '#EA7000',
surface: {DEFAULT: '#C8C820', dark: '#FDF1E5'} surface: { DEFAULT: '#C8C820', dark: '#FDF1E5' },
}, },
success: { success: {
DEFAULT: '#038100', DEFAULT: '#038100',
dark: '#038100', dark: '#038100',
surface: {DEFAULT: '#E6F2E5', dark: '#E6F2E5'} surface: { DEFAULT: '#E6F2E5', dark: '#E6F2E5' },
}, },
transparent: 'transparent', transparent: 'transparent',
primary: { primary: {
DEFAULT: '#008C46', DEFAULT: '#008C46',
dark: '#E6F4E1' dark: '#E6F4E1',
}, },
secondary: { secondary: {
DEFAULT: '#B1E1B3', DEFAULT: '#B1E1B3',
dark: '#7DBD80', dark: '#7DBD80',
variant: {DEFAULT: '#80AE82', dark: '#5B875D'} variant: { DEFAULT: '#80AE82', dark: '#5B875D' },
}, },
tertiary: { tertiary: {
DEFAULT: '#f2f2df', DEFAULT: '#f2f2df',
@@ -46,17 +43,16 @@ export default {
surface: { surface: {
DEFAULT: '#F6F6F6', DEFAULT: '#F6F6F6',
dark: '#2d332d', dark: '#2d332d',
variant: {DEFAULT: '#F4FFF4', dark: '#2b372c'} variant: { DEFAULT: '#F4FFF4', dark: '#2b372c' },
}, },
}, },
fontFamily: { fontFamily: {
display: ['Montserrat', 'sans-serif'], display: ['Montserrat', 'sans-serif'],
body: ['Rubik', 'Noto Sans','Noto Sans Bengali', 'sans-serif'], body: ['Rubik', 'Noto Sans', 'Noto Sans Bengali', 'sans-serif'],
}, },
extend: { extend: {
borderWidth: { borderWidth: {
'3': '3px', 3: '3px',
}, },
fontSize: { fontSize: {
title: '40px', title: '40px',
@@ -75,7 +71,7 @@ export default {
'30%': { width: '30%' }, '30%': { width: '30%' },
'80%': { width: '10%' }, '80%': { width: '10%' },
'100%': { left: '100%' }, '100%': { left: '100%' },
} },
}, },
}, },
}, },
@@ -84,5 +80,4 @@ export default {
addVariant('second-last', '&:nth-last-child(2)'); addVariant('second-last', '&:nth-last-child(2)');
}, },
], ],
} };

View File

@@ -1,7 +1,7 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
}) });