refactor: update FormInput and FormSelect components to accept props directly; add FieldsWrapper for improved layout
This commit is contained in:
parent
abad63787b
commit
bb108f809f
12
src/components/FieldsWrapper.jsx
Normal file
12
src/components/FieldsWrapper.jsx
Normal 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;
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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"}/>
|
||||
<FormBox title="Account Creation">
|
||||
<FieldsWrapper>
|
||||
<FormHeader text={"Account Details"} />
|
||||
{accountDetailsFields.map(renderField)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormHeader text={"Additional Details"}/>
|
||||
<FormHeader text={"Additional Details"} />
|
||||
{additionalDetailsFields.map(renderField)}
|
||||
</div>
|
||||
</div>
|
||||
</FieldsWrapper>
|
||||
<Button text={t("submit")} onClick={handleSubmit} />
|
||||
</FormBox>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user