Compare commits

...

27 Commits

Author SHA1 Message Date
asif
f400490b65 added eslint and prettier and formatted the whole codebase 2025-08-25 02:00:54 +05:30
700a9261a8 fixed form title in LockerMaintenance.jsx 2024-12-28 19:55:30 +05:30
ffae2f728b updated translations 2024-12-27 17:24:56 +05:30
307d2621ce remove form diabling div from LockerStatus.jsx 2024-12-27 16:34:18 +05:30
6b19adf03e Refactored LockerMaintenance.jsx to use Form Components 2024-12-27 16:32:57 +05:30
dcfa7a46d8 removed form disabling mechanism and fixed notification bug in CheckInOutManagement.jsx 2024-12-27 16:20:36 +05:30
2afefc7442 Refactored CabinetMaintenance.jsx to use FormField and FormInput 2024-12-26 20:34:53 +05:30
d4fd7601f1 Refactored CabinetCreation.jsx to use FormInput and FromField 2024-12-26 16:29:12 +05:30
bd32eb02d6 added keys in form rows and made noOfLockers integer 2024-12-26 16:28:12 +05:30
bb108f809f refactor: update FormInput and FormSelect components to accept props directly; add FieldsWrapper for improved layout 2024-12-25 21:13:00 +05:30
abad63787b removed unused imports 2024-12-24 19:10:51 +05:30
9f4059e2c6 reactored code to use formfields and formselects 2024-12-24 19:10:20 +05:30
962102d44c refactor: enhance FormInput and FormSelect components with validation feedback and layout adjustments 2024-12-24 01:31:07 +05:30
03c4988ff1 refactor: streamline layout and error handling in CabinetCreation component 2024-12-24 01:06:10 +05:30
27f4597348 refactor: simplify notification state management across components 2024-12-24 01:03:46 +05:30
265d0b2209 refactor: update FormBox title in ChargeManagement component 2024-12-24 00:51:13 +05:30
a56f643301 feat: add CheckInOutLog page and integrate with locker service for check-in/out functionality 2024-12-24 00:50:06 +05:30
8b34a69dca refactor: update Notification component to use message and type props 2024-12-24 00:40:12 +05:30
f7fea99f30 feat: integrate navigation to log page upon successful account validation in CheckInOutManagement 2024-12-24 00:18:32 +05:30
bd461995c7 refactor: improve ChargeEdit component structure 2024-12-24 00:15:51 +05:30
17681e64ad renamed CheckInOut to CheckInOutManagement 2024-12-24 00:15:08 +05:30
154eba0474 fix: update button disabled state color for better accessibility 2024-12-24 00:05:09 +05:30
dea0047007 refactor: replace inline notification handling with Notification component in LockerStatus 2024-12-24 00:01:29 +05:30
f4b7027708 refactor: replace inline notification rendering with Notification component in LockersRegistration 2024-12-23 23:57:52 +05:30
b4b193a9fe feat: add Check In/Out management with notification system and update locker service 2024-12-23 23:49:35 +05:30
9d33cb5372 fix: handle optional state properties and improve animation transitions in LockersRegistration component 2024-12-23 20:47:19 +05:30
48b9b70c4a refactor animations for improved transitions in App and AccountCreation components 2024-12-23 20:47:13 +05:30
54 changed files with 1632 additions and 1379 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

@@ -36,7 +36,7 @@
"myIntimation": "আমার বিজ্ঞপ্তি", "myIntimation": "আমার বিজ্ঞপ্তি",
"changePassword": "পাসওয়ার্ড পরিবর্তন", "changePassword": "পাসওয়ার্ড পরিবর্তন",
"resetLogin": "লগইন স্ট্যাটাস রিসেট করুন", "resetLogin": "লগইন স্ট্যাটাস রিসেট করুন",
"currentDate" : "{{val, datetime}}", "currentDate": "{{val, datetime}}",
"notifications": "বিজ্ঞপ্তি", "notifications": "বিজ্ঞপ্তি",
"holidayList": "ছুটির তালিকা", "holidayList": "ছুটির তালিকা",
"information": "তথ্য", "information": "তথ্য",

View File

@@ -37,7 +37,7 @@
"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",

View File

@@ -37,7 +37,7 @@
"myIntimation": "मेरा सूचितकरण", "myIntimation": "मेरा सूचितकरण",
"changePassword": "पासवर्ड बदलें", "changePassword": "पासवर्ड बदलें",
"resetLogin": "लॉगिन स्थिति रीसेट करें", "resetLogin": "लॉगिन स्थिति रीसेट करें",
"currentDate" : "{{val, datetime}}", "currentDate": "{{val, datetime}}",
"notifications": "सूचना", "notifications": "सूचना",
"holidayList": "छुट्टी की सूची", "holidayList": "छुट्टी की सूची",
"information": "जानकारी", "information": "जानकारी",
@@ -51,5 +51,6 @@
"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();
@@ -25,7 +25,7 @@ function App() {
const location = useLocation(); const location = useLocation();
return ( return (
<LoadingProvider> <LoadingProvider>
<div className="flex flex-col min-h-screen"> <div className="flex flex-col min-h-screen scrollbar">
<Header /> <Header />
<LoadingBarWrapper /> <LoadingBarWrapper />
<main className="overflow-hidden flex flex-grow transition-color-mode md:p-10 2xl:px-70 bg-surface dark:bg-surface-dark"> <main className="overflow-hidden flex flex-grow transition-color-mode md:p-10 2xl:px-70 bg-surface dark:bg-surface-dark">
@@ -34,9 +34,9 @@ function App() {
<motion.div <motion.div
className="w-full ovwerflow-hidden" className="w-full ovwerflow-hidden"
key={location.pathname} key={location.pathname}
initial={{ opacity: 0, x: 50 }} initial={{ y: 15, opacity: 0 }}
animate={{ opacity: 1, x: 0 }} animate={{ y: 0, opacity: 1 }}
exit={{ opacity: 0, x: 50 }} exit={{ y: 15, opacity: 0 }}
> >
<AnimatedOutlet /> <AnimatedOutlet />
</motion.div> </motion.div>

View File

@@ -3,16 +3,15 @@ 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>

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 ( return <div className="flex justify-between pb-1">{infoElements}</div>;
<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,18 +2,21 @@ 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-[#ccc] dark:bg-[#ccc]")} 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]'
)}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
> >
{text} {text}
</motion.button> </motion.button>
) );
} }
Button.propTypes = { Button.propTypes = {

View File

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

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

@@ -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(
icon.mode === 'plain'
? 'text-[#444]'
: 'bg-primary rounded-full p-2 text-white cursor-pointer'
)}
onClick={icon.onClick} onClick={icon.onClick}
> >
{icon.icon} {icon.icon}
</motion.div> </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,32 +1,38 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { motion, AnimatePresence } from 'motion/react';
import clsx from 'clsx';
function FormInput({ function FormInput({ props, valid = true, className = '' }) {
value,
onChange,
maxLength=17,
readOnly = false,
className = "",
type = "text",
}) {
return ( return (
<div>
<input <input
readOnly={readOnly} {...props}
value={value} className={clsx(
className={`w-1/2 md:w-1/3 lg:w-1/4 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>
{!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>
); );
} }
FormInput.propTypes = { FormInput.propTypes = {
value: PropTypes.string.isRequired, props: PropTypes.object,
onChange: PropTypes.func.isRequired, valid: PropTypes.bool,
readOnly: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
type: PropTypes.string,
maxLength: PropTypes.number,
}; };
export default FormInput; export default FormInput;

View File

@@ -1,31 +1,38 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { AnimatePresence } from 'motion/react';
import clsx from 'clsx';
import FieldError from './FieldError';
import { useTranslation } from 'react-i18next';
function FormSelect({ value, onChange, options, className }) { function FormSelect({ props, valid = true, className = '', options }) {
const { t } = useTranslation();
return ( return (
<div>
<select <select
value={value} {...props}
className={ className={clsx(
"w-1/2 md:w-1/3 lg:w-1/4 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=""> <option disabled value="">
Select {t('select')}
</option> </option>
{options.map(({ value, label }) => ( {options?.map(({ value, label }) => (
<option key={value} value={value}> <option key={value} value={value}>
{label} {label}
</option> </option>
))} ))}
</select> </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,
options: PropTypes.arrayOf( options: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,

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>

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

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

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,28 +24,28 @@ 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
@@ -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

@@ -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 Placeholder from "./pages/Placeholder.jsx"; import CheckInOutManagement from './pages/CheckInOutManagement.jsx';
import CheckInOutLog from './pages/CheckInOutLog.jsx';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
path: "/", path: '/',
element: <App />, element: <App />,
children: [ children: [
{ {
@@ -27,50 +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: <Placeholder /> element: <CheckInOutManagement />,
} },
{
path: 'operation/check-in-out/log',
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,39 +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 { useToast } from "../hooks/useToast"; import ProductModal from '../components/ProductModal';
import productInfo from "../util/productList"; import FormHeader from '../components/FormHeader';
import ProductListTable from "../components/ProductListTable"; import FieldsWrapper from '../components/FieldsWrapper';
function AccountCreation() { function AccountCreation() {
const { t } = useTranslation(); const { t } = useTranslation();
const showToast = useToast(); const [notification] = useState({ message: '', type: '' });
const [notification] = useState({
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,
@@ -50,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 +175,12 @@ function AccountCreation() {
setAccountDetails(newValidationState); setAccountDetails(newValidationState);
if (!isValid) { if (!isValid) {
showToast("Highlighted fields are invalid", "error");
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={{ scale: 0.8 }}
animate={{ scale: 1 }}
exit={{ scale: 0.8 }}
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],
onChange: (e) => { onChange: (e) => {
@@ -223,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>
); );
@@ -244,58 +209,23 @@ function AccountCreation() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.visible && ( {notification.message !== '' && <Notification {...notification} />}
<motion.div </AnimatePresence>
initial={{ opacity: 0 }}
animate={{ opacity: 1 }} <AnimatePresence>
exit={{ 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,138 +1,106 @@
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 ( return (
<motion.div className="w-full h-fit" initial={{ scale: 0.9 }} animate={{ scale: 1 }} exit={{ scale: 0 }}> <FormField key={field.name} label={field.label}>
<FormBox title={t("cabinetCreation")}> {field.type === 'input' ? (
<div className="p-2 pt-7 flex flex-col gap-4"> <FormInput props={commonProps} valid={valid} />
<div className="flex"> ) : (
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]"> <FormSelect props={commonProps} valid={valid} options={field.options} />
{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) => </FormField>
setCabinetId({ );
id: e.target.value.toUpperCase(), };
valid: true,
}) return (
} <FormBox title={t('cabinetCreation')}>
type="text" <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
maxLength={6} <Button text={t('next')} onClick={handleNext} />
/>
<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>
</motion.div>
); );
} }

View File

@@ -1,68 +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 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,37 +1,33 @@
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 { useToast } from "../hooks/useToast"; 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 clsx from "clsx"; import { Pencil } from 'lucide-react';
import { AnimatePresence, motion } from "motion/react"; import Notification from '../components/Notification';
import { Pencil } from "lucide-react"; 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({ const [notification, setNotification] = useState({ message: '', type: '' });
visible: false,
message: "",
type: "",
});
const { setIsLoading } = useLoading(); const { setIsLoading } = useLoading();
const { t } = useTranslation(); const { t } = useTranslation();
const showToast = useToast();
const location = useLocation(); const location = useLocation();
const { productCode, interestCategory } = location.state; const productCode = location.state?.productCode;
const interestCategory = location.state?.interestCategory;
useEffect(() => { useEffect(() => {
const fetchCharges = async () => { const fetchCharges = async () => {
@@ -48,17 +44,15 @@ function ChargeEdit() {
}); });
} else { } else {
setNotification({ setNotification({
visible: true,
message: response.data.message, message: response.data.message,
type: "error", type: 'error',
}); });
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setNotification({ setNotification({
visible: true,
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -67,40 +61,44 @@ function ChargeEdit() {
fetchCharges(); fetchCharges();
}, [productCode, interestCategory, setIsLoading]); }, [productCode, interestCategory, setIsLoading]);
if (!location.state) {
return <></>;
}
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: () => {setChargeDetails({...chargeDetails, rentAmountEdit: true})}, onClick: () => {
} 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: () => {setChargeDetails({...chargeDetails, penaltyAmountEdit: true})}, onClick: () => {
} setChargeDetails({ ...chargeDetails, penaltyAmountEdit: true });
},
},
}, },
]; ];
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
if(!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit) {
showToast("No changes made", "warning");
return;
}
try { try {
setIsLoading(true); setIsLoading(true);
const response = await lockerService.updateCharges( const response = await lockerService.updateCharges(
@@ -111,23 +109,20 @@ function ChargeEdit() {
); );
if (response.status === 200) { if (response.status === 200) {
setNotification({ setNotification({
visible: true,
message: response.data.message, message: response.data.message,
type: "success", type: 'success',
}); });
} else { } else {
setNotification({ setNotification({
visible: true,
message: response.data.message, message: response.data.message,
type: "error", type: 'error',
}); });
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
setNotification({ setNotification({
visible: true,
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -144,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>
); );
@@ -164,34 +157,18 @@ function ChargeEdit() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.visible && ( {notification.message !== '' && <Notification {...notification} />}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={clsx(
"p-4 mb-8 font-body text-center text-lg 2xl: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}
</motion.div>
)}
</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 text={t("submit")} onClick={handleSubmit} disabled={!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit}/>
</FormBox> </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("lockerStatus")}> <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

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

View File

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

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>

View File

@@ -1,33 +1,29 @@
import { useState } from "react"; import { useState } from 'react';
import { motion, 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 { Copy } from "lucide-react"; 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({ const [notification, setNotification] = useState({ message: '', type: '' });
visible: false,
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,
@@ -38,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,
}, },
]; ];
@@ -102,7 +98,7 @@ function KeySwap() {
} }
if (!valid) { if (!valid) {
showToast(t("highlightedFieldsInvalid"), "error"); showToast(t('highlightedFieldsInvalid'), 'error');
return; return;
} }
@@ -117,22 +113,19 @@ function KeySwap() {
); );
if (response.status === 200) { if (response.status === 200) {
setNotification({ setNotification({
visible: true,
message: response.data.message, message: response.data.message,
type: "success", type: 'success',
}); });
} else { } else {
setNotification({ setNotification({
visible: true,
message: response.data.message, message: response.data.message,
type: "error", type: 'error',
}); });
} }
} catch (error) { } catch (error) {
setNotification({ setNotification({
visible: true,
message: error.message, message: error.message,
type: "error", type: 'error',
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -149,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>
); );
@@ -170,50 +160,14 @@ function KeySwap() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.visible && ( {notification.message !== '' && <Notification {...notification} />}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={clsx(
"p-4 mb-8 font-body text-center text-lg 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>
<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 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> </FormBox>
</div> </div>
</div> );
)
} }
export default KeySwap; export default KeySwap;

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,61 +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 { useToast } from "../hooks/useToast"; import Button from '../components/Button';
import clsx from "clsx"; import { lockerService } from '../services/locker.service';
import Button from "../components/Button"; import { useLoading } from '../hooks/useLoading';
import { lockerService } from "../services/locker.service"; import { AnimatePresence } from 'motion/react';
import { useLoading } from "../hooks/useLoading"; import Notification from '../components/Notification';
import { AnimatePresence, motion } from "motion/react"; import FieldsWrapper from '../components/FieldsWrapper';
function LockerStatus() { function LockerStatus() {
const { t } = useTranslation(); const { t } = useTranslation();
const showToast = useToast();
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({ const [notification, setNotification] = useState({ message: '', type: '' });
visible: false,
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 !== '',
}, },
]; ];
@@ -76,7 +71,6 @@ function LockerStatus() {
setLockerDetails(newValidationState); setLockerDetails(newValidationState);
if (!isValid) { if (!isValid) {
showToast("Highlighted fields are invalid", "error");
return; return;
} }
@@ -88,12 +82,14 @@ function LockerStatus() {
lockerDetails.status lockerDetails.status
); );
setNotification({ setNotification({
visible: true,
message: response.data.message, message: response.data.message,
type: "success", type: 'success',
}); });
} catch (error) { } catch (error) {
showToast(error.response.data.message, "error"); setNotification({
message: error.message,
type: 'error',
});
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@@ -109,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>
); );
@@ -130,34 +123,14 @@ function LockerStatus() {
return ( return (
<div> <div>
<AnimatePresence> <AnimatePresence>
{notification.visible && ( {notification.message !== '' && <Notification {...notification} />}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={clsx(
"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}
</motion.div>
)}
</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")}>
<div className="p-2 pt-7 flex flex-col gap-4">
{formFields.map(renderField)}
</div>
<Button text={t("submit")} onClick={handleSubmit} />
</FormBox> </FormBox>
</div> </div>
</div>
); );
} }

View File

@@ -1,43 +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 { Copy } from "lucide-react"; import Notification from '../components/Notification';
import { AnimatePresence } from "motion/react"; import FormField from '../components/FormField';
import { motion } from "motion/react"; import FormInput from '../components/FormInput';
import { useLoading } from "../hooks/useLoading"; 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, cabinetId } = location.state; const [notification, setNotification] = useState({ message: '', type: '' });
const [submitting, setSubmitting] = useState(false);
const [notification, setNotification] = useState({
visible: false,
message: "",
type: "",
});
const initLockers = Array(parseInt(noOfLockers)) 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;
@@ -49,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 {
@@ -68,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 {
@@ -77,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)
) { ) {
@@ -93,167 +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({
visible: true,
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({
visible: true,
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,
<input validate: (value) => /^[A-Z]{2}[0-9]{4}$/.test(value),
value={locker.id} },
disabled={submitting} {
className={clsx( label: 'Size',
"w-64 h-9 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey", type: 'select',
!locker.idValid && "border-error text-error" subType: 'text',
)} name: 'size',
onChange={(e) => { options: [
const newValues = [...lockerValues]; { label: 'Small', value: '1' },
newValues[index] = { { label: 'Medium', value: '2' },
...newValues[index], { label: 'Large', value: '3' },
id: e.target.value.toUpperCase(), ],
idValid: true, },
{
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,
}; };
setLockerValues(newValues);
}}
placeholder="Locker ID"
type="text"
maxLength={6}
/>
<select const valid = lockerValues[`${field.name}Valid`];
value={locker.size}
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.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 return field.type === 'input' ? (
value={locker.keyId} <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.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.visible && ( {notification.message !== '' && <Notification {...notification} />}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={clsx(
"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.type === "error" ? notification.message : 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>
<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

@@ -7,8 +7,8 @@ export const lockerService = {
lockers: lockers.map(({ id, size, keyId }) => ({ lockers: lockers.map(({ id, size, keyId }) => ({
id, id,
size, size,
keyId keyId,
})) })),
}); });
}, },
@@ -17,14 +17,31 @@ export const lockerService = {
}, },
keySwap: async (cabinetId, lockerId, reason, oldKey, newKey) => { keySwap: async (cabinetId, lockerId, reason, oldKey, newKey) => {
return api.patch(`/locker/key`, { cabinetId, lockerId, reason, oldKey, newKey }); return api.patch(`/locker/key`, {
cabinetId,
lockerId,
reason,
oldKey,
newKey,
});
}, },
updateCharges: async (productCode, interestCategory, rent, penalty) => { updateCharges: async (productCode, interestCategory, rent, penalty) => {
return api.patch(`/charge/${productCode}${interestCategory}`, { rent, penalty }); return api.patch(`/charge/${productCode}${interestCategory}`, {
rent,
penalty,
});
}, },
getCharges: async (productCode, interestCategory) => { getCharges: async (productCode, interestCategory) => {
return api.get(`/charge/${productCode}${interestCategory}`); return api.get(`/charge/${productCode}${interestCategory}`);
} },
preCheckIn: async (accountNumber) => {
return api.post(`/pre-checkin/${accountNumber}`);
},
checkInOut: async (accountNumber, time, checkType) => {
return api.post(`/check-in-out/${accountNumber}`, { time, checkType });
},
}; };

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: {
@@ -11,33 +8,33 @@ export default {
black: '#000000', black: '#000000',
grey: '#979797', grey: '#979797',
error: { error: {
DEFAULT: '#E5254B', DEFAULT: '#A14444',
dark: '#E5254B', dark: '#E5254B',
surface: {DEFAULT: '#FCE9ED', dark: '#FCE9ED'} surface: { DEFAULT: '#D26464', dark: '#FCE9ED' },
}, },
onToast: { onToast: {
DEFAULT: '#646564', DEFAULT: '#646564',
dark: '#646564', dark: '#646564',
}, },
warning: { warning: {
DEFAULT: '#EA7000', DEFAULT: '#A5A513',
dark: '#EA7000', dark: '#EA7000',
surface: {DEFAULT: '#FDF1E5', 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()],
}) });