refactor: update FormInput and FormSelect components to accept props directly; add FieldsWrapper for improved layout

This commit is contained in:
Md Asif 2024-12-25 21:13:00 +05:30
parent abad63787b
commit bb108f809f
11 changed files with 147 additions and 146 deletions

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,7 +1,7 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
function FormHeader({ text }) { function FormHeader({ text }) {
return <h1 className="text-2xl font-medium text-primary py-2">{text}</h1>; return <h1 className="text-2xl font-medium text-primary mt-5">{text}</h1>;
} }
FormHeader.propTypes = { FormHeader.propTypes = {

View File

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

View File

@ -3,21 +3,20 @@ import { AnimatePresence } from "motion/react";
import clsx from "clsx"; import clsx from "clsx";
import FieldError from "./FieldError"; import FieldError from "./FieldError";
function FormSelect({ value, onChange, options, className, valid = true }) { function FormSelect({ props, valid = true, className = "", options }) {
return ( return (
<div> <div>
<select <select
value={value} {...props}
className={clsx( className={clsx(
`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`, `w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`,
!valid && "border-error" !valid && "border-error"
)} )}
onChange={onChange}
> >
<option disabled value=""> <option disabled value="">
Select 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>
@ -31,8 +30,7 @@ function FormSelect({ value, onChange, options, className, valid = true }) {
} }
FormSelect.propTypes = { FormSelect.propTypes = {
value: PropTypes.string.isRequired, props: PropTypes.object,
onChange: PropTypes.func.isRequired,
className: PropTypes.string, className: PropTypes.string,
valid: PropTypes.bool, valid: PropTypes.bool,
options: PropTypes.arrayOf( options: PropTypes.arrayOf(

View File

@ -11,6 +11,7 @@ import productInfo from "../util/productList";
import Notification from "../components/Notification"; import Notification from "../components/Notification";
import ProductModal from "../components/ProductModal"; import ProductModal from "../components/ProductModal";
import FormHeader from "../components/FormHeader"; import FormHeader from "../components/FormHeader";
import FieldsWrapper from "../components/FieldsWrapper";
function AccountCreation() { function AccountCreation() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -182,7 +183,6 @@ function AccountCreation() {
const renderField = (field) => { const renderField = (field) => {
const commonProps = { const commonProps = {
value: accountDetails[field.name], value: accountDetails[field.name],
valid: accountDetails[`${field.name}Valid`],
onChange: (e) => { onChange: (e) => {
const newAccountDetails = { ...accountDetails }; const newAccountDetails = { ...accountDetails };
newAccountDetails[field.name] = e.target.value; newAccountDetails[field.name] = e.target.value;
@ -190,18 +190,21 @@ function AccountCreation() {
setAccountDetails(newAccountDetails); setAccountDetails(newAccountDetails);
}, },
maxLength: field.maxLength, maxLength: field.maxLength,
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>
); );
@ -222,20 +225,15 @@ function AccountCreation() {
)} )}
</AnimatePresence> </AnimatePresence>
<FormBox title="Account Creation" > <FormBox title="Account Creation">
<div className="p-2 pt-7 "> <FieldsWrapper>
<div className="flex flex-col gap-4"> <FormHeader text={"Account Details"} />
<FormHeader text={"Account Details"}/> {accountDetailsFields.map(renderField)}
{accountDetailsFields.map(renderField)} <FormHeader text={"Additional Details"} />
</div> {additionalDetailsFields.map(renderField)}
<div className="flex flex-col gap-4"> </FieldsWrapper>
<FormHeader text={"Additional Details"}/>
{additionalDetailsFields.map(renderField)}
</div>
</div>
<Button text={t("submit")} onClick={handleSubmit} /> <Button text={t("submit")} onClick={handleSubmit} />
</FormBox> </FormBox>
</div> </div>
); );
} }

View File

@ -11,6 +11,7 @@ import { lockerService } from "../services/locker.service";
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from "motion/react";
import { Pencil } from "lucide-react"; import { Pencil } from "lucide-react";
import Notification from "../components/Notification"; import Notification from "../components/Notification";
import FieldsWrapper from "../components/FieldsWrapper";
function ChargeEdit() { function ChargeEdit() {
const [chargeDetails, setChargeDetails] = useState({ const [chargeDetails, setChargeDetails] = useState({
@ -141,18 +142,20 @@ 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>
); );
@ -168,9 +171,7 @@ function ChargeEdit() {
<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={t("chargeEdit")}> <FormBox title={t("chargeEdit")}>
<div className="p-2 pt-7 flex flex-col gap-4"> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
{formFields.map(renderField)}
</div>
<Button <Button
text={t("submit")} text={t("submit")}
onClick={handleSubmit} onClick={handleSubmit}

View File

@ -1,4 +1,3 @@
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";
@ -8,6 +7,7 @@ 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();
@ -83,19 +83,19 @@ 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
{...commonProps} props={commonProps}
type={field.subType} valid={valid}
readOnly={field.readOnly}
/> />
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} valid={valid} options={field.options} />
)} )}
</FormField> </FormField>
); );
@ -103,9 +103,9 @@ function ChargeManagement() {
return ( return (
<FormBox title={t("chargeManagement")}> <FormBox title={t("chargeManagement")}>
<div className="p-2 pt-7 flex flex-col gap-4"> <FieldsWrapper>
{formFields.map(renderField)} {formFields.map(renderField)}
</div> </FieldsWrapper>
<Button text={t("submit")} onClick={handleSubmit} /> <Button text={t("submit")} onClick={handleSubmit} />
</FormBox> </FormBox>
); );

View File

@ -10,6 +10,7 @@ import { useToast } from "../hooks/useToast";
import { lockerService } from "../services/locker.service"; import { lockerService } from "../services/locker.service";
import { useLoading } from "../hooks/useLoading"; import { useLoading } from "../hooks/useLoading";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import FieldsWrapper from "../components/FieldsWrapper";
function CheckInOutManagement() { function CheckInOutManagement() {
const [accountNumber, setAccountNumber] = useState(""); const [accountNumber, setAccountNumber] = useState("");
@ -66,18 +67,20 @@ function CheckInOutManagement() {
{notification.visible && <Notification notification={notification} />} {notification.visible && <Notification notification={notification} />}
</AnimatePresence> </AnimatePresence>
<FormBox title="Check In/Out"> <FormBox title="Check In/Out">
<div className="p-2 pt-7"> <FieldsWrapper>
<FormField <FormField
label="Account Number" label="Account Number"
icon={{ icon: <Search size={17} />, onClick: () => {} }} icon={{ icon: <Search size={17} />, onClick: () => {} }}
> >
<FormInput <FormInput
type="text" props={{
value={accountNumber} type: "text",
onChange={(e) => setAccountNumber(e.target.value)} value: accountNumber,
onChange: (e) => setAccountNumber(e.target.value),
}}
/> />
</FormField> </FormField>
</div> </FieldsWrapper>
<Button text="Next" onClick={handleNext} /> <Button text="Next" onClick={handleNext} />
</FormBox> </FormBox>
</div> </div>

View File

@ -8,9 +8,9 @@ import FormInput from "../components/FormInput";
import FormSelect from "../components/FormSelect"; import FormSelect from "../components/FormSelect";
import Button from "../components/Button"; import Button from "../components/Button";
import { lockerService } from "../services/locker.service"; import { lockerService } from "../services/locker.service";
import clsx from "clsx";
import FormBox from "../components/FormBox"; import FormBox from "../components/FormBox";
import Notification from "../components/Notification"; import Notification from "../components/Notification";
import FieldsWrapper from "../components/FieldsWrapper";
function KeySwap() { function KeySwap() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -142,19 +142,20 @@ 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,9 +171,7 @@ function KeySwap() {
<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={t("lockerStatus")}> <FormBox title={t("lockerStatus")}>
<div className="p-2 pt-7 flex flex-col gap-4"> <FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
{formFields.map(renderField)}
</div>
<Button text={t("submit")} onClick={handleKeySwap} /> <Button text={t("submit")} onClick={handleKeySwap} />
</FormBox> </FormBox>
</div> </div>

View File

@ -4,12 +4,12 @@ import FormInput from "../components/FormInput";
import FormSelect from "../components/FormSelect"; import FormSelect from "../components/FormSelect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState } from "react"; import { useState } from "react";
import clsx from "clsx";
import Button from "../components/Button"; import Button from "../components/Button";
import { lockerService } from "../services/locker.service"; import { lockerService } from "../services/locker.service";
import { useLoading } from "../hooks/useLoading"; import { useLoading } from "../hooks/useLoading";
import { AnimatePresence } from "motion/react"; import { AnimatePresence } from "motion/react";
import Notification from "../components/Notification"; import Notification from "../components/Notification";
import FieldsWrapper from "../components/FieldsWrapper";
function LockerStatus() { function LockerStatus() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -98,7 +98,6 @@ function LockerStatus() {
const renderField = (field) => { const renderField = (field) => {
const commonProps = { const commonProps = {
value: lockerDetails[field.name], value: lockerDetails[field.name],
valid: lockerDetails[`${field.name}Valid`],
onChange: (e) => { onChange: (e) => {
const newLockerDetails = { ...lockerDetails }; const newLockerDetails = { ...lockerDetails };
newLockerDetails[field.name] = e.target.value.toUpperCase(); newLockerDetails[field.name] = e.target.value.toUpperCase();
@ -106,19 +105,16 @@ function LockerStatus() {
setLockerDetails(newLockerDetails); setLockerDetails(newLockerDetails);
}, },
maxLength: field.maxLength, maxLength: field.maxLength,
className: clsx(!lockerDetails[`${field.name}Valid`] && "border-error"), type: field.subType,
readOnly: field.readOnly,
}; };
const valid = lockerDetails[`${field.name}Valid`];
return ( return (
<FormField key={field.name} label={field.label} icon={field.icon}> <FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? ( {field.type === "input" ? (
<FormInput <FormInput props={commonProps} valid={valid} />
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
) : ( ) : (
<FormSelect {...commonProps} options={field.options} /> <FormSelect props={commonProps} options={field.options} />
)} )}
</FormField> </FormField>
); );
@ -134,9 +130,9 @@ function LockerStatus() {
<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={t("lockerStatus")}> <FormBox title={t("lockerStatus")}>
<div className="p-2 pt-7 flex flex-col gap-4"> <FieldsWrapper>
{formFields.map(renderField)} {formFields.map(renderField)}
</div> </FieldsWrapper>
<Button text={t("submit")} onClick={handleSubmit} /> <Button text={t("submit")} onClick={handleSubmit} />
</FormBox> </FormBox>
</div> </div>

View File

@ -9,6 +9,8 @@ import Notification from "../components/Notification";
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 FormHeader from "../components/FormHeader";
import FieldsWrapper from "../components/FieldsWrapper";
function LockersRegistration() { function LockersRegistration() {
const location = useLocation(); const location = useLocation();
@ -116,49 +118,65 @@ function LockersRegistration() {
} }
}; };
const lockerDetails = lockerValues.map((locker, index) => { const formRow = [
{
label: "Locker ID",
type: "input",
subType: "text",
name: "id",
maxLength: 6,
validate: (value) => /^[A-Z]{2}[0-9]{4}$/.test(value),
},
{
label: "Size",
type: "select",
subType: "text",
name: "size",
options: [
{ label: "Small", value: "1" },
{ label: "Medium", value: "2" },
{ label: "Large", value: "3" },
],
},
{
label: "Key ID",
type: "input",
subType: "text",
name: "keyId",
maxLength: 6,
validate: (value) => /^[A-Z]{2}[0-9]{4}$/.test(value),
},
];
const renderFormRow = (field) => {
const commonProps = {
value: lockerValues[field.name],
onChange: (e) => {
const newLockerValues = [...lockerValues];
newLockerValues[field.name] = e.target.value;
newLockerValues[`${field.name}Valid`] = true;
setLockerValues(newLockerValues);
},
maxLength: field.maxLength,
type: field.subType,
};
const valid = lockerValues[`${field.name}Valid`];
return field.type === "input" ? (
<FormInput props={commonProps} valid={valid} />
) : (
<FormSelect props={commonProps} valid={valid} options={field.options} />
);
};
const formFields = Array(noOfLockers).fill(formRow);
const renderFormFields = (row, index) => {
return ( return (
<FormField key={index} label={`Locker ${index + 1}`} variant="long"> <FormField key={index} label={`Locker ${index + 1}`} variant="long">
<FormInput {row.map(renderFormRow)}
value={locker.id}
onChange={ e => {
const newValues = [...lockerValues];
newValues[index].id = e.target.value;
newValues[index].idValid = true;
setLockerValues(newValues);
}}
valid={locker.idValid}
placeholder={"Locker Id"}
/>
<FormSelect
value={locker.size}
onChange={e => {
const newValues = [...lockerValues];
newValues[index].size = e.target.value;
newValues[index].sizeValid = true;
setLockerValues(newValues);
}}
valid={locker.sizeValid}
options={[
{label: 'Small', value: '1'},
{label: 'Medium', value: '2'},
{label: 'Large', value: '3'}
]}
/>
<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> </FormField>
); );
}); };
return ( return (
<div> <div>
@ -170,16 +188,11 @@ function LockersRegistration() {
<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"> <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="">
</div> {formFields.map(renderFormFields)}
<div className="flex flex-col gap-4 p-4">{lockerDetails}</div> </FieldsWrapper>
<Button <Button text="Register" onClick={handleSubmit} />
text="Register"
onClick={(e) => {
handleSubmit(e);
}}
/>
</FormBox> </FormBox>
</div> </div>
</div> </div>