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";
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 = {

View File

@ -2,29 +2,15 @@ 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,
className = "",
type = "text",
}) {
function FormInput({ props, valid = true, className = "" }) {
return (
<div>
<input
readOnly={readOnly}
value={value}
{...props}
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,14 +30,9 @@ function FormInput({
}
FormInput.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
className: PropTypes.string,
type: PropTypes.string,
maxLength: PropTypes.number,
props: PropTypes.object,
valid: PropTypes.bool,
placeholder: PropTypes.string,
className: PropTypes.string,
};
export default FormInput;

View File

@ -3,21 +3,20 @@ import { AnimatePresence } from "motion/react";
import clsx from "clsx";
import FieldError from "./FieldError";
function FormSelect({ value, onChange, options, className, valid = true }) {
function FormSelect({ props, valid = true, className = "", options }) {
return (
<div>
<select
value={value}
{...props}
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 }) => (
{options?.map(({ value, label }) => (
<option key={value} value={value}>
{label}
</option>
@ -31,8 +30,7 @@ function FormSelect({ value, onChange, options, className, valid = true }) {
}
FormSelect.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
props: PropTypes.object,
className: PropTypes.string,
valid: PropTypes.bool,
options: PropTypes.arrayOf(

View File

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

View File

@ -11,6 +11,7 @@ import { lockerService } from "../services/locker.service";
import { AnimatePresence } from "motion/react";
import { Pencil } from "lucide-react";
import Notification from "../components/Notification";
import FieldsWrapper from "../components/FieldsWrapper";
function ChargeEdit() {
const [chargeDetails, setChargeDetails] = useState({
@ -141,18 +142,20 @@ function ChargeEdit() {
});
},
readOnly: field.readOnly,
className: field.readOnly ? "bg-grey/[0.3]" : "",
type: field.subType,
};
const className = field.readOnly ? "bg-grey/[0.3]" : "";
return (
<FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? (
<FormInput
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
<FormInput props={commonProps} className={className} />
) : (
<FormSelect {...commonProps} options={field.options} />
<FormSelect
props={commonProps}
options={field.options}
className={className}
/>
)}
</FormField>
);
@ -168,9 +171,7 @@ function ChargeEdit() {
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
)}
<FormBox title={t("chargeEdit")}>
<div className="p-2 pt-7 flex flex-col gap-4">
{formFields.map(renderField)}
</div>
<FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
<Button
text={t("submit")}
onClick={handleSubmit}

View File

@ -1,4 +1,3 @@
import clsx from "clsx";
import { useTranslation } from "react-i18next";
import FormField from "../components/FormField";
import FormInput from "../components/FormInput";
@ -8,6 +7,7 @@ import FormBox from "../components/FormBox";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useToast } from "../hooks/useToast";
import FieldsWrapper from "../components/FieldsWrapper";
function ChargeManagement() {
const { t } = useTranslation();
@ -83,19 +83,19 @@ function ChargeManagement() {
setProductDetails(newLockerDetails);
},
maxLength: field.maxLength,
className: clsx(!productDetails[`${field.name}Valid`] && "border-error"),
type: field.subType,
readOnly: field.readOnly,
};
const valid = productDetails[`${field.name}Valid`];
return (
<FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? (
<FormInput
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
props={commonProps}
valid={valid}
/>
) : (
<FormSelect {...commonProps} options={field.options} />
<FormSelect props={commonProps} valid={valid} options={field.options} />
)}
</FormField>
);
@ -103,9 +103,9 @@ function ChargeManagement() {
return (
<FormBox title={t("chargeManagement")}>
<div className="p-2 pt-7 flex flex-col gap-4">
<FieldsWrapper>
{formFields.map(renderField)}
</div>
</FieldsWrapper>
<Button text={t("submit")} onClick={handleSubmit} />
</FormBox>
);

View File

@ -10,6 +10,7 @@ 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("");
@ -66,18 +67,20 @@ function CheckInOutManagement() {
{notification.visible && <Notification notification={notification} />}
</AnimatePresence>
<FormBox title="Check In/Out">
<div className="p-2 pt-7">
<FieldsWrapper>
<FormField
label="Account Number"
icon={{ icon: <Search size={17} />, onClick: () => {} }}
>
<FormInput
type="text"
value={accountNumber}
onChange={(e) => setAccountNumber(e.target.value)}
props={{
type: "text",
value: accountNumber,
onChange: (e) => setAccountNumber(e.target.value),
}}
/>
</FormField>
</div>
</FieldsWrapper>
<Button text="Next" onClick={handleNext} />
</FormBox>
</div>

View File

@ -8,9 +8,9 @@ import FormInput from "../components/FormInput";
import FormSelect from "../components/FormSelect";
import Button from "../components/Button";
import { lockerService } from "../services/locker.service";
import clsx from "clsx";
import FormBox from "../components/FormBox";
import Notification from "../components/Notification";
import FieldsWrapper from "../components/FieldsWrapper";
function KeySwap() {
const { t } = useTranslation();
@ -142,19 +142,20 @@ function KeySwap() {
setKeySwapDetails(newLockerDetails);
},
maxLength: field.maxLength,
className: clsx(!keySwapDetails[`${field.name}Valid`] && "border-error"),
type: field.subType,
readOnly: field.readOnly,
};
const valid = keySwapDetails[`${field.name}Valid`];
return (
<FormField key={field.name} label={field.label} icon={field.icon}>
{field.type === "input" ? (
<FormInput
{...commonProps}
type={field.subType}
readOnly={field.readOnly}
/>
<FormInput props={commonProps} valid={valid} />
) : (
<FormSelect {...commonProps} options={field.options} />
<FormSelect
props={commonProps}
valid={valid}
options={field.options}
/>
)}
</FormField>
);
@ -170,9 +171,7 @@ function KeySwap() {
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
)}
<FormBox title={t("lockerStatus")}>
<div className="p-2 pt-7 flex flex-col gap-4">
{formFields.map(renderField)}
</div>
<FieldsWrapper>{formFields.map(renderField)}</FieldsWrapper>
<Button text={t("submit")} onClick={handleKeySwap} />
</FormBox>
</div>

View File

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

View File

@ -9,6 +9,8 @@ import Notification from "../components/Notification";
import FormField from "../components/FormField";
import FormInput from "../components/FormInput";
import FormSelect from "../components/FormSelect";
import FormHeader from "../components/FormHeader";
import FieldsWrapper from "../components/FieldsWrapper";
function LockersRegistration() {
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 (
<FormField key={index} label={`Locker ${index + 1}`} variant="long">
<FormInput
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"}
/>
{row.map(renderFormRow)}
</FormField>
);
});
};
return (
<div>
@ -170,16 +188,11 @@ function LockersRegistration() {
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
)}
<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
text="Register"
onClick={(e) => {
handleSubmit(e);
}}
/>
<FormHeader text={cabinetId} />
<FieldsWrapper className="">
{formFields.map(renderFormFields)}
</FieldsWrapper>
<Button text="Register" onClick={handleSubmit} />
</FormBox>
</div>
</div>