diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 49dc105..f9cb827 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -55,5 +55,16 @@ "cabinetCreation": "Cabinet Creation", "cabinetId": "Cabinet ID", "cabinetKeyId": "Cabinet Key ID", - "noOfLockers": "No of Lockers" + "noOfLockers": "No of Lockers", + "productCode": "Product Code", + "interestCategory": "Interest Category", + "segmentCode": "Segment Code", + "accountHolderType": "Account Holder Type", + "primaryCifNumber": "Primary CIF Number", + "nomineeCifNumber": "Nominee CIF Number", + "activityCode": "Activity Code", + "customerType": "Customer Type", + "collateralFDAccount": "Collateral FD Account", + "rentPayAccount": "Rent Pay Account", + "submit": "Submit" } \ No newline at end of file diff --git a/src/components/BannerInfo.jsx b/src/components/BannerInfo.jsx index 64c2772..b1ef811 100644 --- a/src/components/BannerInfo.jsx +++ b/src/components/BannerInfo.jsx @@ -6,7 +6,7 @@ function BannerInfo({info}) { const {t} = useTranslation(); const infoElements = Object.keys(info).map((key) => ( - + )) infoElements.push( + + {label} + + + {children} + {icon && ( + + {icon} + + )} + + + + ); +} + +FormField.propTypes = { + label: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + icon: PropTypes.node, +}; + +export default FormField; diff --git a/src/components/FormInput.jsx b/src/components/FormInput.jsx new file mode 100644 index 0000000..73cee2a --- /dev/null +++ b/src/components/FormInput.jsx @@ -0,0 +1,32 @@ +import PropTypes from "prop-types"; + +function FormInput({ + value, + onChange, + maxLength=17, + readOnly = false, + className = "", + type = "text", +}) { + return ( + + ); +} + +FormInput.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + readOnly: PropTypes.bool, + className: PropTypes.string, + type: PropTypes.string, + maxLength: PropTypes.number, +}; + +export default FormInput; diff --git a/src/components/FormSelect.jsx b/src/components/FormSelect.jsx new file mode 100644 index 0000000..319e252 --- /dev/null +++ b/src/components/FormSelect.jsx @@ -0,0 +1,37 @@ +import PropTypes from "prop-types"; + +function FormSelect({ value, onChange, options, className }) { + return ( + + + Select + + {options.map(({ value, label }) => ( + + {label} + + ))} + + ); +} + +FormSelect.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + className: PropTypes.string, + options: PropTypes.arrayOf( + PropTypes.shape({ + value: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + }) + ).isRequired, +}; + +export default FormSelect; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index d2eccfc..8d954cd 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -24,7 +24,7 @@ function Header() { { name: "lockerOperation", submenu: [ - { name: "accountCreation", path: "account-creation" }, + { name: "accountCreation", path: "operation/account" }, { name: "cabinetMaintenance", path: "operation/cabinet" }, { name: "lockerMaintenance", path: "locker-maintenance" }, { name: "rentPenaltyCollection", path: "rent-collection" }, diff --git a/src/components/ProductListTable.jsx b/src/components/ProductListTable.jsx new file mode 100644 index 0000000..08b85c5 --- /dev/null +++ b/src/components/ProductListTable.jsx @@ -0,0 +1,74 @@ +import clsx from "clsx"; +import PropTypes from "prop-types"; + +function ProductListTable({ productInfo, onSelectProduct }) { + return ( + + + + + Product Code + + + Description + + + Interest Category + + + Description + + + + + {productInfo.map((prod, idx) => ( + onSelectProduct(prod)} + > + + {prod.productCode} + + + {prod.productCodeDescription} + + + {prod.interestCategory} + + + {prod.interestCategoryDescription} + + + ))} + + + ); +} + +ProductListTable.propTypes = { + productInfo: PropTypes.array.isRequired, + onSelectProduct: PropTypes.func.isRequired, +}; + +export default ProductListTable; diff --git a/src/main.jsx b/src/main.jsx index 28a628a..3590604 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -8,6 +8,7 @@ import Home from "./pages/Home.jsx"; import CabinetMaintenace from "./pages/CabinetMaintenance.jsx"; import CabinetCreation from "./pages/CabinetCreation.jsx"; import LockersRegistration from "./pages/LockersRegistration.jsx"; +import AccountCreation from "./pages/AccountCreation.jsx"; const router = createBrowserRouter([ { @@ -30,6 +31,10 @@ const router = createBrowserRouter([ path: "operation/cabinet/create/register-lockers", element: , }, + { + path: "operation/account", + element: , + } ], }, ]); diff --git a/src/pages/AccountCreation.jsx b/src/pages/AccountCreation.jsx new file mode 100644 index 0000000..46112d7 --- /dev/null +++ b/src/pages/AccountCreation.jsx @@ -0,0 +1,310 @@ +import { AnimatePresence } from "motion/react"; +import clsx from "clsx"; +import { PackageSearch, Copy, UserSearch } from "lucide-react"; +import { useState } from "react"; +import { motion } from "motion/react"; +import FormBox from "../components/FormBox"; +import { useTranslation } from "react-i18next"; +import FormField from "../components/FormField"; +import FormInput from "../components/FormInput"; +import FormSelect from "../components/FormSelect"; +import Button from "../components/Button"; +import { useToast } from "../hooks/useToast"; +import productInfo from "../util/productList"; +import ProductListTable from "../components/ProductListTable"; + +function AccountCreation() { + const { t } = useTranslation(); + const showToast = useToast(); + const [notification] = useState({ + visible: false, + message: "", + type: "", + }); + const [showProductModal, setShowProductModal] = useState(false); + const [submitting] = useState(false); + const [accountDetails, setAccountDetails] = useState({ + productCode: "", + interestCategory: "", + segmentCode: "", + accountHolderType: "", + primaryCifNumber: "", + nomineeCifNumber: "", + activityCode: "", + customerType: "", + collateralFDAccount: "", + rentPayAccount: "", + productCodeValid: true, + interestCategoryValid: true, + segmentCodeValid: true, + accountHolderTypeValid: true, + primaryCifNumberValid: true, + nomineeCifNumberValid: true, + activityCodeValid: true, + customerTypeValid: true, + collateralFDAccountValid: true, + rentPayAccountValid: true, + }); + + const handleProductSelect = (product) => { + const newAccountDetails = { ...accountDetails }; + newAccountDetails.productCode = product.productCode; + newAccountDetails.interestCategory = product.interestCategory; + setAccountDetails(newAccountDetails); + setShowProductModal(false); + }; + + const accountDetailsFields = [ + { + label: t("productCode"), + name: "productCode", + type: "input", + subType: "number", + readOnly: true, + validate: (value) => value !== "", + icon: ( + { + console.log("Product search clicked"); + console.log(showProductModal); + setShowProductModal(true); + }} + /> + ), + }, + { + label: t("interestCategory"), + name: "interestCategory", + type: "input", + subType: "number", + readOnly: true, + validate: (value) => value !== "", + }, + { + label: t("segmentCode"), + name: "segmentCode", + type: "select", + options: [ + { value: "0706", label: "0706: Individual" }, + { value: "0306", label: "0306: Staff" }, + { value: "5003", label: "5003: Senior Citizen" }, + { value: "5010", label: "5010: SHG" }, + { value: "5000", label: "5000: Bank" }, + { value: "5009", label: "5009: Institutions" }, + { value: "5050", label: "5050: Others" }, + { value: "5007", label: "5007: Society" }, + ], + validate: (value) => value !== "", + }, + { + label: t("accountHolderType"), + name: "accountHolderType", + type: "select", + options: [ + { value: "1", label: "Single" }, + { value: "2", label: "Joint" }, + ], + validate: (value) => value === "1" || value === "2", + }, + { + label: t("primaryCifNumber"), + name: "primaryCifNumber", + type: "input", + subType: "number", + maxLength: 17, + validate: (value) => /^[0-9]{17}$/.test(value), + icon: , + }, + { + label: t("nomineeCifNumber"), + name: "nomineeCifNumber", + type: "input", + subType: "number", + maxLength: 17, + validate: (value) => /^[0-9]{17}$/.test(value), + }, + ]; + const additionalDetailsFields = [ + { + label: t("activityCode"), + name: "activityCode", + type: "select", + options: [ + { value: "0701", label: "Direct Agriculture" }, + { value: "0702", label: "Indirect Agriculture" }, + { value: "0703", label: "Agricultural Services Unit" }, + { value: "0704", label: "Farm Irrigation" }, + { value: "0705", label: "Fruits & Vegetables" }, + { value: "0706", label: "Non-Agriculture" }, + ], + validate: (value) => value !== "", + }, + { + label: t("customerType"), + name: "customerType", + type: "select", + options: [ + { value: "0709", label: "Individual" }, + { value: "0701", label: "Corporate" }, + ], + validate: (value) => value === "0709" || value === "0701", + }, + { + label: t("collateralFDAccount"), + name: "collateralFDAccount", + type: "input", + subType: "number", + maxLength: 17, + validate: (value) => /^[0-9]{17}$/.test(value), + }, + { + label: t("rentPayAccount"), + name: "rentPayAccount", + type: "input", + subType: "number", + maxLength: 17, + validate: (value) => /^[0-9]{17}$/.test(value), + }, + ]; + + const handleSubmit = (e) => { + e.preventDefault(); + let isValid = true; + const newValidationState = { ...accountDetails }; + + // Validate account details fields + [...accountDetailsFields, ...additionalDetailsFields].forEach((field) => { + if (field.validate) { + const fieldIsValid = field.validate(accountDetails[field.name]); + newValidationState[`${field.name}Valid`] = fieldIsValid; + if (!fieldIsValid) isValid = false; + } + }); + + setAccountDetails(newValidationState); + + if (!isValid) { + showToast("Highlighted fields are invalid", "error"); + return; + } + console.log("Form is valid", accountDetails); + }; + const renderProductModal = () => { + return ( + + {showProductModal && ( + + + Select Product + + + + )} + + ); + }; + + const renerField = (field) => { + const commonProps = { + value: accountDetails[field.name], + onChange: (e) => { + const newAccountDetails = { ...accountDetails }; + newAccountDetails[field.name] = e.target.value; + newAccountDetails[`${field.name}Valid`] = true; + setAccountDetails(newAccountDetails); + }, + maxLength: field.maxLength, + className: clsx(!accountDetails[`${field.name}Valid`] && "border-error"), + }; + + return ( + + {field.type === "input" ? ( + + ) : ( + + )} + + ); + }; + + return ( + + + {notification.visible && ( + + {notification.message.split(":").map((msg, index) => { + return index === 1 ? ( + + {msg} + + ) : ( + {msg} + ); + })} + + + )} + + {renderProductModal()} + + + + + Account Details + + {accountDetailsFields.map(renerField)} + + + + Additional Details + + {additionalDetailsFields.map(renerField)} + + + + + + ); +} + +export default AccountCreation; diff --git a/src/util/productList.js b/src/util/productList.js new file mode 100644 index 0000000..0c1122a --- /dev/null +++ b/src/util/productList.js @@ -0,0 +1,12 @@ +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: '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' }, + { productCode: '2001', productCodeDescription: 'MIS', interestCategory: '1003', interestCategoryDescription: 'NON MEMBER - SENIOR CITIZEN', payableGl: '16137', paidGl: '62125' }, + { productCode: '2002', productCodeDescription: 'FIXED DEPOSIT', interestCategory: '1006', interestCategoryDescription: 'NONMEMBER - SENIOR CITIZEN', payableGl: '16009', paidGl: '62109' }, + { productCode: '2002', productCodeDescription: 'CASH CERTIFICATE', interestCategory: '1001', interestCategoryDescription: 'MEMBER', payableGl: '16011', paidGl: '62111' }, + { productCode: '1101', productCodeDescription: 'SAVINGS DEPOSIT- MEMBER', interestCategory: '1347', interestCategoryDescription: 'COMPOUNDING', payableGl: '16301', paidGl: '62117' } +] + export default productInfo; \ No newline at end of file