reactored code to use formfields and formselects

This commit is contained in:
Md Asif 2024-12-24 19:10:20 +05:30
parent 962102d44c
commit 9f4059e2c6
8 changed files with 175 additions and 204 deletions

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

@ -2,13 +2,16 @@ import PropTypes from "prop-types";
import { motion } from "motion/react";
import clsx from "clsx";
function FormField({ label, children, icon }) {
function FormField({ label, children, icon, variant }) {
return (
<div className="flex">
<label className="mr-4 text-lg text-black dark:text-primary-dark md:w-[30%] xl:w-[20%] 2xl:w-[17%]">
<div className="flex items-center">
<label className={clsx(
"mr-20 text-lg text-black dark:text-primary-dark whitespace-nowrap",
variant === 'long' && "sm:w-[5%]"
)}>
{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}
{icon && (
<motion.div
@ -29,7 +32,8 @@ function FormField({ label, children, icon }) {
FormField.propTypes = {
label: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
icon: PropTypes.object.isRequired,
icon: PropTypes.object,
variant: PropTypes.string
};
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 py-2">{text}</h1>;
}
FormHeader.propTypes = {
text: PropTypes.string.isRequired
}
export default FormHeader;

View File

@ -1,8 +1,11 @@
import PropTypes from "prop-types";
import { motion, AnimatePresence } from "motion/react";
import clsx from "clsx";
function FormInput({
value,
onChange,
placeholder,
valid = true,
maxLength = 17,
readOnly = false,
@ -14,10 +17,14 @@ function FormInput({
<input
readOnly={readOnly}
value={value}
className={`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`}
className={clsx(
`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`,
!valid && "border-error"
)}
onChange={onChange}
type={type}
maxLength={maxLength}
placeholder={placeholder}
/>
<AnimatePresence>
{!valid && (
@ -44,6 +51,7 @@ FormInput.propTypes = {
type: PropTypes.string,
maxLength: PropTypes.number,
valid: PropTypes.bool,
placeholder: PropTypes.string,
};
export default FormInput;

View File

@ -1,40 +1,32 @@
import PropTypes from "prop-types";
import { motion, AnimatePresence } from "motion/react";
import { AnimatePresence } from "motion/react";
import clsx from "clsx";
import FieldError from "./FieldError";
function FormSelect({ value, onChange, options, className, valid = true }) {
return (
<div>
<select
value={value}
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}
>
<option disabled value="">
Select
</option>
{options.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
<AnimatePresence>
{!valid && (
<motion.div
className="text-sm text-error ml-3 pt-1"
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
key="cabinetIdError"
>
Invalid Value
</motion.div>
<select
value={value}
className={clsx(
`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`,
!valid && "border-error"
)}
onChange={onChange}
>
<option disabled value="">
Select
</option>
{options.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
<AnimatePresence>
{!valid && <FieldError text={"Invalid value"} />}
</AnimatePresence>
</div>
</div>
);
}

View File

@ -0,0 +1,35 @@
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

@ -1,8 +1,6 @@
import { AnimatePresence } from "motion/react";
import clsx from "clsx";
import { PackageSearch, Copy, UserSearch } from "lucide-react";
import { PackageSearch, UserSearch } from "lucide-react";
import { useState } from "react";
import { motion } from "motion/react";
import FormBox from "../components/FormBox";
import { useTranslation } from "react-i18next";
import FormField from "../components/FormField";
@ -10,17 +8,14 @@ import FormInput from "../components/FormInput";
import FormSelect from "../components/FormSelect";
import Button from "../components/Button";
import productInfo from "../util/productList";
import ProductListTable from "../components/ProductListTable";
import Notification from "../components/Notification";
import ProductModal from "../components/ProductModal";
import FormHeader from "../components/FormHeader";
function AccountCreation() {
const { t } = useTranslation();
const [notification] = useState({
visible: false,
message: "",
type: "",
});
const [notification] = useState({ message: "", type: "" });
const [showProductModal, setShowProductModal] = useState(false);
const [submitting] = useState(false);
const [accountDetails, setAccountDetails] = useState({
productCode: "",
interestCategory: "",
@ -48,6 +43,8 @@ function AccountCreation() {
const newAccountDetails = { ...accountDetails };
newAccountDetails.productCode = product.productCode;
newAccountDetails.interestCategory = product.interestCategory;
newAccountDetails.productCodeValid = true;
newAccountDetails.interestCategoryValid = true;
setAccountDetails(newAccountDetails);
setShowProductModal(false);
};
@ -181,36 +178,8 @@ function AccountCreation() {
}
console.log("Form is valid", accountDetails);
};
const renderProductModal = () => {
return (
<AnimatePresence mode="popLayout">
{showProductModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
key="productList"
className="fixed z-50 inset-0 flex items-center justify-center bg-black/50"
>
<motion.div
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, opacity: 0 }}
className="flex flex-col items-center bg-white p-4 py-8 rounded-3xl w-[60%] max-h-[80%] overflow-auto font-body"
>
<h2 className="text-xl mb-4">Select Product</h2>
<ProductListTable
productInfo={productInfo}
onSelectProduct={handleProductSelect}
/>
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
};
const renerField = (field) => {
const renderField = (field) => {
const commonProps = {
value: accountDetails[field.name],
valid: accountDetails[`${field.name}Valid`],
@ -221,7 +190,6 @@ function AccountCreation() {
setAccountDetails(newAccountDetails);
},
maxLength: field.maxLength,
className: clsx(!accountDetails[`${field.name}Valid`] && "border-error"),
};
return (
@ -242,59 +210,32 @@ function AccountCreation() {
return (
<div>
<AnimatePresence>
{notification.visible && (
<motion.div
initial={{ y: 15, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 15, 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.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>
{notification.message !== "" && <Notification {...notification} />}
</AnimatePresence>
<AnimatePresence>
{showProductModal && (
<ProductModal
productInfo={productInfo}
handleProductSelect={handleProductSelect}
/>
)}
</AnimatePresence>
{renderProductModal()}
<FormBox title="Account Creation" disabled={submitting}>
<FormBox title="Account Creation" >
<div className="p-2 pt-7 ">
<div className="flex flex-col gap-4">
<h1 className="text-2xl font-medium text-primary py-2">
Account Details
</h1>
{accountDetailsFields.map(renerField)}
<FormHeader text={"Account Details"}/>
{accountDetailsFields.map(renderField)}
</div>
<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)}
<FormHeader text={"Additional Details"}/>
{additionalDetailsFields.map(renderField)}
</div>
</div>
<Button
text={t("submit")}
onClick={handleSubmit}
disabled={submitting}
/>
<Button text={t("submit")} onClick={handleSubmit} />
</FormBox>
</div>
);
}

View File

@ -1,25 +1,23 @@
import { useLocation } from "react-router-dom";
import { useState } from "react";
import clsx from "clsx";
import { useToast } from "../hooks/useToast";
import { useLoading } from "../hooks/useLoading";
import FormBox from "../components/FormBox";
import Button from "../components/Button";
import { useToast } from "../hooks/useToast";
import { lockerService } from "../services/locker.service";
import { AnimatePresence } from "motion/react";
import { useLoading } from "../hooks/useLoading";
import Notification from "../components/Notification";
import FormField from "../components/FormField";
import FormInput from "../components/FormInput";
import FormSelect from "../components/FormSelect";
function LockersRegistration() {
const location = useLocation();
const showToast = useToast();
const { setIsLoading } = useLoading();
const noOfLockers = location.state?.noOfLockers;
const [notification, setNotification] = useState({ message: "", type: "" });
const noOfLockers = location.state?.noOfLockers;
const cabinetId = location.state?.cabinetId;
const [submitting, setSubmitting] = useState(false);
const [notification, setNotification] = useState({
message: "",
type: "",
});
const initLockers = Array(noOfLockers ? parseInt(noOfLockers) : 0)
.fill()
@ -31,12 +29,14 @@ function LockersRegistration() {
sizeValid: true,
keyIdValid: true,
}));
const [lockerValues, setLockerValues] = useState(initLockers);
if(!location.state) {
return <></>
if (!location.state) {
return <></>;
}
const handleSubmit = async (e) => {
console.log("submitting");
e.preventDefault();
const idRegex = /^[A-Z]{2}[0-9]{4}$/;
let valid = true;
@ -92,16 +92,10 @@ function LockersRegistration() {
setLockerValues(newValues);
if (!valid) {
const errorMessage =
duplicateLockerIds.length || duplicateKeyIds.length
? "Please ensure all IDs are unique."
: "Inavlid Ids";
showToast(errorMessage, "error");
return;
}
try {
setSubmitting(true);
setIsLoading(true);
const response = await lockerService.registerLockers(
cabinetId,
@ -120,84 +114,50 @@ function LockersRegistration() {
return;
} finally {
setIsLoading(false);
setSubmitting(false);
}
};
const lockerDetails = lockerValues.map((locker, index) => {
return (
<div key={index} className="flex gap-12 items-center">
<label className="text-lg text-bold dark:text-primary-dark">{`Locker ${
index + 1
}`}</label>
<input
<FormField key={index} label={`Locker ${index + 1}`} variant="long">
<FormInput
value={locker.id}
disabled={submitting}
className={clsx(
"w-64 h-9 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey",
!locker.idValid && "border-error text-error"
)}
onChange={(e) => {
onChange={ e => {
const newValues = [...lockerValues];
newValues[index] = {
...newValues[index],
id: e.target.value.toUpperCase(),
idValid: true,
};
newValues[index].id = e.target.value;
newValues[index].idValid = true;
setLockerValues(newValues);
}}
placeholder="Locker ID"
type="text"
maxLength={6}
valid={locker.idValid}
placeholder={"Locker Id"}
/>
<select
<FormSelect
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) => {
onChange={e => {
const newValues = [...lockerValues];
newValues[index] = {
...newValues[index],
size: e.target.value,
sizeValid: true,
};
newValues[index].size = e.target.value;
newValues[index].sizeValid = true;
setLockerValues(newValues);
}}
>
<option value="" disabled>
Size
</option>
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
<input
value={locker.keyId}
disabled={submitting}
className={clsx(
"w-64 h-9 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey",
!locker.keyIdValid && "border-error"
)}
onChange={(e) => {
const newValues = [...lockerValues];
newValues[index] = {
...newValues[index],
keyId: e.target.value.toUpperCase(),
keyIdValid: true,
};
setLockerValues(newValues);
}}
placeholder="Key ID"
type="text"
maxLength={6}
valid={locker.sizeValid}
options={[
{label: 'Small', value: '1'},
{label: 'Medium', value: '2'},
{label: 'Large', value: '3'}
]}
/>
</div>
<FormInput
value={locker.keyId}
onChange={ e => {
const newValues = [...lockerValues];
newValues[index].keyId = e.target.value;
newValues[index].keyIdValid = true;
setLockerValues(newValues);
}}
valid={locker.keyIdValid}
placeholder={"Locker Key Id"}
/>
</FormField>
);
});
@ -210,13 +170,12 @@ function LockersRegistration() {
{notification.type === "success" && (
<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">
{cabinetId}
</div>
<div className="flex flex-col gap-4 p-4">{lockerDetails}</div>
<Button
disabled={submitting}
text="Register"
onClick={(e) => {
handleSubmit(e);