mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Apply react hook form for user page
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"@emotion/is-prop-valid": "^1.2.1",
|
"@emotion/is-prop-valid": "^1.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.47.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-router-dom": "^6.17.0",
|
"react-router-dom": "^6.17.0",
|
||||||
|
|||||||
Generated
+12
@@ -14,6 +14,9 @@ dependencies:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
react-hook-form:
|
||||||
|
specifier: ^7.47.0
|
||||||
|
version: 7.47.0(react@18.2.0)
|
||||||
react-hot-toast:
|
react-hot-toast:
|
||||||
specifier: ^2.4.1
|
specifier: ^2.4.1
|
||||||
version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0)
|
version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -1815,6 +1818,15 @@ packages:
|
|||||||
scheduler: 0.23.0
|
scheduler: 0.23.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-hook-form@7.47.0(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-F/TroLjTICipmHeFlMrLtNLceO2xr1jU3CyiNla5zdwsGUGu2UOxxR4UyJgLlhMwLW/Wzp4cpJ7CPfgJIeKdSg==}
|
||||||
|
engines: {node: '>=12.22.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17 || ^18
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0):
|
/react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==}
|
resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { FieldValues, RegisterOptions, useFormContext } from 'react-hook-form';
|
||||||
import { StyledSelect } from './styled';
|
import { StyledSelect } from './styled';
|
||||||
|
|
||||||
export interface ISelectOptions {
|
export interface ISelectOptions {
|
||||||
@@ -6,20 +7,25 @@ export interface ISelectOptions {
|
|||||||
}
|
}
|
||||||
interface ISelect {
|
interface ISelect {
|
||||||
options: ISelectOptions[];
|
options: ISelectOptions[];
|
||||||
value: string;
|
optionsConfigForm?: RegisterOptions<FieldValues, string> | undefined;
|
||||||
onChange: React.ChangeEventHandler<HTMLSelectElement>;
|
value?: number;
|
||||||
name?: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Select = ({ options, value, onChange, name }: ISelect) => {
|
const Select = ({ options, id, optionsConfigForm }: ISelect) => {
|
||||||
|
const { register } = useFormContext() ?? {};
|
||||||
|
|
||||||
|
if(!register) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!options) return;
|
if (!options) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSelect
|
<StyledSelect
|
||||||
value={value}
|
|
||||||
onChange={onChange}
|
|
||||||
aria-label="Sort"
|
aria-label="Sort"
|
||||||
name={name}
|
id={id}
|
||||||
|
{...register(id, optionsConfigForm)}
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<option value={option.value} key={option.value}>
|
<option value={option.value} key={option.value}>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const INVALID_FIELD = 'This field is invalid format!';
|
||||||
|
const INVALID_PHONE = 'Phone number is invalid!';
|
||||||
|
const REQUIRED_FIELD_ERROR = 'This field is required!';
|
||||||
|
const INVALID_DISCOUNT = 'Discount should be greater than 0 and less than 100!';
|
||||||
|
|
||||||
|
export { REQUIRED_FIELD_ERROR, INVALID_DISCOUNT, INVALID_FIELD, INVALID_PHONE };
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
const invalidFormatMsg = (field: string) => {
|
|
||||||
return `Invalid ${field} format`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorMsg = (errorCode: number, msg: string) => {
|
const errorMsg = (errorCode: number, msg: string) => {
|
||||||
return `Error code: ${errorCode}. Message: ${msg}`;
|
return `Error code: ${errorCode}. Message: ${msg}`;
|
||||||
};
|
};
|
||||||
@@ -10,17 +6,11 @@ const ADD_SUCCESS = 'Add success';
|
|||||||
const EDIT_SUCCESS = 'Edit success';
|
const EDIT_SUCCESS = 'Edit success';
|
||||||
const CONFIRM_DELETE = 'Are you sure to delete it?';
|
const CONFIRM_DELETE = 'Are you sure to delete it?';
|
||||||
const DELETE_SUCCESS = 'Delete success';
|
const DELETE_SUCCESS = 'Delete success';
|
||||||
const REQUIRED_FIELD_ERROR = 'This is required field';
|
|
||||||
const DISCOUNT_FIELD_ERROR =
|
|
||||||
'Discount should be greater than 0 and less than 100';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
invalidFormatMsg,
|
|
||||||
ADD_SUCCESS,
|
ADD_SUCCESS,
|
||||||
errorMsg,
|
errorMsg,
|
||||||
EDIT_SUCCESS,
|
EDIT_SUCCESS,
|
||||||
CONFIRM_DELETE,
|
CONFIRM_DELETE,
|
||||||
DELETE_SUCCESS,
|
DELETE_SUCCESS,
|
||||||
REQUIRED_FIELD_ERROR,
|
|
||||||
DISCOUNT_FIELD_ERROR,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ export const DASHBOARD = '/dashboard';
|
|||||||
export const USER = '/user';
|
export const USER = '/user';
|
||||||
export const ROOM = '/room';
|
export const ROOM = '/room';
|
||||||
export const OTHER_PATH = '*';
|
export const OTHER_PATH = '*';
|
||||||
export const BASE_URL = 'https://hotel-management-api.loiphan.com/';
|
export const BASE_URL = 'http://localhost:3000/';
|
||||||
export const USER_PATH = 'users';
|
export const USER_PATH = 'users';
|
||||||
export const ROOM_PATH = 'rooms';
|
export const ROOM_PATH = 'rooms';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Constants
|
// Constants
|
||||||
import { REQUIRED_FIELD_ERROR, invalidFormatMsg } from '../constants/messages';
|
import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
@@ -54,36 +54,6 @@ const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => {
|
|||||||
}, {} as TKeyValue);
|
}, {} as TKeyValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
type TValidator = {
|
|
||||||
validatorFunc: (value: string) => boolean;
|
|
||||||
prop?: string;
|
|
||||||
customErrorMsg?: string;
|
|
||||||
required?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create validator object
|
|
||||||
* @param param0 Pass TValidator object
|
|
||||||
* @returns An object contains condition validator
|
|
||||||
*/
|
|
||||||
const addValidator = ({
|
|
||||||
validatorFunc,
|
|
||||||
prop = '',
|
|
||||||
customErrorMsg = '',
|
|
||||||
required = true,
|
|
||||||
}: TValidator) => {
|
|
||||||
return {
|
|
||||||
required,
|
|
||||||
validator: {
|
|
||||||
func: validatorFunc,
|
|
||||||
// prettier-ignore
|
|
||||||
error: customErrorMsg
|
|
||||||
? customErrorMsg
|
|
||||||
: invalidFormatMsg(prop),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the object contains values of object pass
|
* Return the object contains values of object pass
|
||||||
* @param obj Object need to get value
|
* @param obj Object need to get value
|
||||||
@@ -163,7 +133,6 @@ export {
|
|||||||
isRequired,
|
isRequired,
|
||||||
getPropValues,
|
getPropValues,
|
||||||
getValueFromObj,
|
getValueFromObj,
|
||||||
addValidator,
|
|
||||||
searchQuery,
|
searchQuery,
|
||||||
REQUIRED_FIELD_ERROR,
|
REQUIRED_FIELD_ERROR,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,47 +1,12 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
// Styled
|
|
||||||
import Input from '../../commons/styles/Input.ts';
|
|
||||||
import TextArea from '../../commons/styles/TextArea.ts';
|
|
||||||
|
|
||||||
// Components
|
|
||||||
import Form from '../../components/Form/index.tsx';
|
|
||||||
import FormRow from '../../components/LabelControl/index.tsx';
|
|
||||||
import Button from '../../commons/styles/Button.ts';
|
import Button from '../../commons/styles/Button.ts';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {
|
import {
|
||||||
TKeyValue,
|
|
||||||
TRoom,
|
TRoom,
|
||||||
TStateSchema,
|
|
||||||
TValidator,
|
|
||||||
} from '../../globals/types.ts';
|
} from '../../globals/types.ts';
|
||||||
|
|
||||||
// Hooks
|
|
||||||
import useForm from '../../hooks/useForm.ts';
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
import {
|
|
||||||
isValidDiscount,
|
|
||||||
isValidNumber,
|
|
||||||
isValidString,
|
|
||||||
skipCheck,
|
|
||||||
} from '../../helpers/validators.ts';
|
|
||||||
import { addValidator, getValueFromObj } from '../../helpers/utils.ts';
|
|
||||||
import { sendRequest } from '../../helpers/sendRequest.ts';
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
import { STATUS_CODE } from '../../constants/statusCode.ts';
|
|
||||||
import {
|
|
||||||
ADD_SUCCESS,
|
|
||||||
DISCOUNT_FIELD_ERROR,
|
|
||||||
EDIT_SUCCESS,
|
|
||||||
errorMsg,
|
|
||||||
} from '../../constants/messages.ts';
|
|
||||||
import { ROOM_PATH } from '../../constants/path.ts';
|
|
||||||
|
|
||||||
const FormBtn = styled(Button)`
|
const FormBtn = styled(Button)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -67,271 +32,272 @@ const RoomForm = ({
|
|||||||
room,
|
room,
|
||||||
isAdd,
|
isAdd,
|
||||||
}: IRoomFormProp) => {
|
}: IRoomFormProp) => {
|
||||||
const [reset, setReset] = useState(true);
|
// const [reset, setReset] = useState(true);
|
||||||
|
|
||||||
if (isAdd) {
|
// if (isAdd) {
|
||||||
// If this is add form, reset value
|
// // If this is add form, reset value
|
||||||
room = null;
|
// room = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
const initialValue: string = isAdd
|
// const initialValue: string = isAdd
|
||||||
? ''
|
// ? ''
|
||||||
: room!
|
// : room!
|
||||||
&& room.id.toString();
|
// && room.id.toString();
|
||||||
|
|
||||||
const {
|
// const {
|
||||||
idValue,
|
// idValue,
|
||||||
nameValue,
|
// nameValue,
|
||||||
priceValue,
|
// priceValue,
|
||||||
discountValue,
|
// discountValue,
|
||||||
statusValue,
|
// statusValue,
|
||||||
descriptionValue,
|
// descriptionValue,
|
||||||
} = getValueFromObj<TRoom>(room);
|
// } = getValueFromObj<TRoom>(room);
|
||||||
|
|
||||||
// Define your state schema
|
// // Define your state schema
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
const stateSchema: TStateSchema = {
|
// const stateSchema: TStateSchema = {
|
||||||
id: {
|
// id: {
|
||||||
value: idValue || ''
|
// value: idValue || ''
|
||||||
},
|
// },
|
||||||
name: {
|
// name: {
|
||||||
value: nameValue || '',
|
// value: nameValue || '',
|
||||||
error: ''
|
// error: ''
|
||||||
},
|
// },
|
||||||
price: {
|
// price: {
|
||||||
value: priceValue || '',
|
// value: priceValue || '',
|
||||||
error: ''
|
// error: ''
|
||||||
},
|
// },
|
||||||
discount: {
|
// discount: {
|
||||||
value: discountValue || '',
|
// value: discountValue || '',
|
||||||
error: ''
|
// error: ''
|
||||||
},
|
// },
|
||||||
status: {
|
// status: {
|
||||||
value: statusValue || '',
|
// value: statusValue || '',
|
||||||
error: ''
|
// error: ''
|
||||||
},
|
// },
|
||||||
description: {
|
// description: {
|
||||||
value: descriptionValue || '',
|
// value: descriptionValue || '',
|
||||||
error: ''
|
// error: ''
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
|
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
const stateValidatorSchema: TValidator = {
|
// const stateValidatorSchema: TValidator = {
|
||||||
name: addValidator({
|
// name: addValidator({
|
||||||
validatorFunc: isValidString,
|
// validatorFunc: isValidString,
|
||||||
prop: 'name'
|
// prop: 'name'
|
||||||
}),
|
// }),
|
||||||
price: addValidator({
|
// price: addValidator({
|
||||||
validatorFunc: isValidNumber,
|
// validatorFunc: isValidNumber,
|
||||||
prop: 'price',
|
// prop: 'price',
|
||||||
}),
|
// }),
|
||||||
discount: addValidator({
|
// discount: addValidator({
|
||||||
validatorFunc: isValidDiscount,
|
// validatorFunc: isValidDiscount,
|
||||||
customErrorMsg: DISCOUNT_FIELD_ERROR
|
// customErrorMsg: DISCOUNT_FIELD_ERROR
|
||||||
}),
|
// }),
|
||||||
status: addValidator({
|
// status: addValidator({
|
||||||
validatorFunc: skipCheck,
|
// validatorFunc: skipCheck,
|
||||||
prop: 'status',
|
// prop: 'status',
|
||||||
required: false,
|
// required: false,
|
||||||
}),
|
// }),
|
||||||
description: addValidator({
|
// description: addValidator({
|
||||||
validatorFunc: isValidString,
|
// validatorFunc: isValidString,
|
||||||
prop: 'description'
|
// prop: 'description'
|
||||||
}),
|
// }),
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Submit form
|
// // Submit form
|
||||||
const onSubmitForm = async (state: TKeyValue) => {
|
// const onSubmitForm = async (state: TKeyValue) => {
|
||||||
// Convert to room type
|
// // Convert to room type
|
||||||
const data: TRoom = {
|
// const data: TRoom = {
|
||||||
id: +state.id!,
|
// id: +state.id!,
|
||||||
name: '' + state.name,
|
// name: '' + state.name,
|
||||||
discount: +state.discount!,
|
// discount: +state.discount!,
|
||||||
price: +state.price!,
|
// price: +state.price!,
|
||||||
status: !!state.status,
|
// status: !!state.status,
|
||||||
description: '' + state.description,
|
// description: '' + state.description,
|
||||||
};
|
// };
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
if (isAdd) {
|
// if (isAdd) {
|
||||||
// Add request
|
// // Add request
|
||||||
const response = await sendRequest(
|
// const response = await sendRequest(
|
||||||
ROOM_PATH,
|
// ROOM_PATH,
|
||||||
JSON.stringify(data),
|
// JSON.stringify(data),
|
||||||
'POST',
|
// 'POST',
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (response.statusCode === STATUS_CODE.CREATE) {
|
// if (response.statusCode === STATUS_CODE.CREATE) {
|
||||||
toast.success(ADD_SUCCESS);
|
// toast.success(ADD_SUCCESS);
|
||||||
|
|
||||||
onResetForm();
|
// onResetForm();
|
||||||
} else {
|
// } else {
|
||||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
// throw new Error(errorMsg(response.statusCode, response.msg));
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// Edit request
|
// // Edit request
|
||||||
const response = await sendRequest(
|
// const response = await sendRequest(
|
||||||
ROOM_PATH + `/${room!.id}`,
|
// ROOM_PATH + `/${room!.id}`,
|
||||||
JSON.stringify(data),
|
// JSON.stringify(data),
|
||||||
'PUT',
|
// 'PUT',
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (response.statusCode == STATUS_CODE.OK) {
|
// if (response.statusCode == STATUS_CODE.OK) {
|
||||||
toast.success(EDIT_SUCCESS);
|
// toast.success(EDIT_SUCCESS);
|
||||||
} else {
|
// } else {
|
||||||
throw new Error(errorMsg(response.statusCode, response.msg));
|
// throw new Error(errorMsg(response.statusCode, response.msg));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// Reload table data
|
// // Reload table data
|
||||||
setReload(!reload);
|
// setReload(!reload);
|
||||||
} catch (error: unknown) {
|
// } catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
// if (error instanceof Error) {
|
||||||
toast.error(error.message);
|
// toast.error(error.message);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
onClose();
|
// onClose();
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Close and reset form
|
// // Close and reset form
|
||||||
const closeAndReset = () => {
|
// const closeAndReset = () => {
|
||||||
onClose();
|
// onClose();
|
||||||
onResetForm();
|
// onResetForm();
|
||||||
};
|
// };
|
||||||
|
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
const {
|
// const {
|
||||||
values,
|
// values,
|
||||||
errors,
|
// errors,
|
||||||
valid,
|
// valid,
|
||||||
handleOnChange,
|
// handleOnChange,
|
||||||
handleOnSubmit,
|
// handleOnSubmit,
|
||||||
disable } =
|
// disable } =
|
||||||
useForm(
|
// useForm(
|
||||||
stateSchema,
|
// stateSchema,
|
||||||
stateValidatorSchema,
|
// stateValidatorSchema,
|
||||||
onSubmitForm,
|
// onSubmitForm,
|
||||||
initialValue
|
// initialValue
|
||||||
);
|
// );
|
||||||
|
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
const {
|
// const {
|
||||||
id,
|
// id,
|
||||||
name,
|
// name,
|
||||||
price,
|
// price,
|
||||||
discount,
|
// discount,
|
||||||
status,
|
// status,
|
||||||
description
|
// description
|
||||||
} = values;
|
// } = values;
|
||||||
|
|
||||||
// Clear form value when this is a add form
|
// // Clear form value when this is a add form
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (isAdd) {
|
// if (isAdd) {
|
||||||
Object.keys(values).forEach((key) => (values[key] = ''));
|
// Object.keys(values).forEach((key) => (values[key] = ''));
|
||||||
}
|
// }
|
||||||
}, [isAdd]); // eslint-disable-line
|
// }, [isAdd]); // eslint-disable-line
|
||||||
|
|
||||||
// Reset form
|
// // Reset form
|
||||||
const onResetForm = () => {
|
// const onResetForm = () => {
|
||||||
setReset(!reset);
|
// setReset(!reset);
|
||||||
Object.keys(values).forEach((key) => (values[key] = ''));
|
// Object.keys(values).forEach((key) => (values[key] = ''));
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleOnSubmit}>
|
// <Form onSubmit={handleOnSubmit}>
|
||||||
<Input type="hidden" name="id" value={id as string} />
|
// <Input type="hidden" name="id" value={id as string} />
|
||||||
<FormRow
|
// <FormRow
|
||||||
label="Name"
|
// label="Name"
|
||||||
error={
|
// error={
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
errors.name && valid.name
|
// errors.name && valid.name
|
||||||
? (errors.name as string)
|
// ? (errors.name as string)
|
||||||
: ''
|
// : ''
|
||||||
}
|
// }
|
||||||
>
|
// >
|
||||||
<Input
|
// <Input
|
||||||
type="text"
|
// type="text"
|
||||||
name="name"
|
// name="name"
|
||||||
value={name as string}
|
// value={name as string}
|
||||||
onChange={handleOnChange}
|
// onChange={handleOnChange}
|
||||||
/>
|
// />
|
||||||
</FormRow>
|
// </FormRow>
|
||||||
<FormRow
|
// <FormRow
|
||||||
label="Price"
|
// label="Price"
|
||||||
error={
|
// error={
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
errors.price && valid.price
|
// errors.price && valid.price
|
||||||
? (errors.price as string)
|
// ? (errors.price as string)
|
||||||
: ''
|
// : ''
|
||||||
}
|
// }
|
||||||
>
|
// >
|
||||||
<Input
|
// <Input
|
||||||
type="text"
|
// type="text"
|
||||||
name="price"
|
// name="price"
|
||||||
value={price as string}
|
// value={price as string}
|
||||||
onChange={handleOnChange}
|
// onChange={handleOnChange}
|
||||||
/>
|
// />
|
||||||
</FormRow>
|
// </FormRow>
|
||||||
|
|
||||||
<FormRow
|
// <FormRow
|
||||||
label="Discount"
|
// label="Discount"
|
||||||
error={
|
// error={
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
errors.discount && valid.discount
|
// errors.discount && valid.discount
|
||||||
? (errors.discount as string)
|
// ? (errors.discount as string)
|
||||||
: ''
|
// : ''
|
||||||
}
|
// }
|
||||||
>
|
// >
|
||||||
<Input
|
// <Input
|
||||||
type="text"
|
// type="text"
|
||||||
name="discount"
|
// name="discount"
|
||||||
value={discount as string}
|
// value={discount as string}
|
||||||
onChange={handleOnChange}
|
// onChange={handleOnChange}
|
||||||
/>
|
// />
|
||||||
</FormRow>
|
// </FormRow>
|
||||||
|
|
||||||
<FormRow label="Status">
|
// <FormRow label="Status">
|
||||||
<Input
|
// <Input
|
||||||
type="checkbox"
|
// type="checkbox"
|
||||||
name="status"
|
// name="status"
|
||||||
checked={status as boolean}
|
// checked={status as boolean}
|
||||||
onChange={handleOnChange}
|
// onChange={handleOnChange}
|
||||||
/>
|
// />
|
||||||
</FormRow>
|
// </FormRow>
|
||||||
|
|
||||||
<FormRow
|
// <FormRow
|
||||||
label="Description"
|
// label="Description"
|
||||||
error={
|
// error={
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
errors.description && valid.description
|
// errors.description && valid.description
|
||||||
? (errors.description as string)
|
// ? (errors.description as string)
|
||||||
: ''
|
// : ''
|
||||||
}
|
// }
|
||||||
>
|
// >
|
||||||
<TextArea
|
// <TextArea
|
||||||
name="description"
|
// name="description"
|
||||||
rows={3}
|
// rows={3}
|
||||||
value={description as string}
|
// value={description as string}
|
||||||
onChange={handleOnChange}
|
// onChange={handleOnChange}
|
||||||
/>
|
// />
|
||||||
</FormRow>
|
// </FormRow>
|
||||||
|
|
||||||
<Form.Action>
|
// <Form.Action>
|
||||||
<FormBtn type="submit" name="submit" disabled={disable}>
|
// <FormBtn type="submit" name="submit" disabled={disable}>
|
||||||
{
|
// {
|
||||||
// prettier-ignore
|
// // prettier-ignore
|
||||||
isAdd
|
// isAdd
|
||||||
? 'Add'
|
// ? 'Add'
|
||||||
: 'Save'
|
// : 'Save'
|
||||||
}
|
// }
|
||||||
</FormBtn>
|
// </FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
// <FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
||||||
Close
|
// Close
|
||||||
</FormBtn>
|
// </FormBtn>
|
||||||
</Form.Action>
|
// </Form.Action>
|
||||||
</Form>
|
// </Form>
|
||||||
|
<p></p>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import Input from '../../commons/styles/Input';
|
import Input from '../../commons/styles/Input';
|
||||||
@@ -9,27 +9,17 @@ import TextArea from '../../commons/styles/TextArea';
|
|||||||
// Components
|
// Components
|
||||||
import Form from '../../components/Form';
|
import Form from '../../components/Form';
|
||||||
import FormRow from '../../components/LabelControl/index.tsx';
|
import FormRow from '../../components/LabelControl/index.tsx';
|
||||||
import Button from '../../commons/styles/Button.ts';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {
|
import { TKeyValue, TUser } from '../../globals/types';
|
||||||
TKeyValue,
|
|
||||||
TStateSchema,
|
|
||||||
TUser,
|
|
||||||
TValidator,
|
|
||||||
} from '../../globals/types';
|
|
||||||
|
|
||||||
// Hooks
|
|
||||||
import useForm from '../../hooks/useForm';
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
import { sendRequest } from '../../helpers/sendRequest.ts';
|
||||||
import {
|
import {
|
||||||
isValidString,
|
|
||||||
isValidNumber,
|
isValidNumber,
|
||||||
isValidPhoneNumber,
|
isValidPhoneNumber,
|
||||||
} from '../../helpers/validators';
|
isValidString,
|
||||||
import { addValidator, getValueFromObj } from '../../helpers/utils.ts';
|
} from '../../helpers/validators.ts';
|
||||||
import { sendRequest } from '../../helpers/sendRequest.ts';
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { STATUS_CODE } from '../../constants/statusCode.ts';
|
import { STATUS_CODE } from '../../constants/statusCode.ts';
|
||||||
@@ -40,17 +30,13 @@ import {
|
|||||||
} from '../../constants/messages.ts';
|
} from '../../constants/messages.ts';
|
||||||
import { USER_PATH } from '../../constants/path.ts';
|
import { USER_PATH } from '../../constants/path.ts';
|
||||||
import Select, { ISelectOptions } from '../../components/Select';
|
import Select, { ISelectOptions } from '../../components/Select';
|
||||||
|
|
||||||
|
// Hooks
|
||||||
import { useFetch } from '../../hooks/useFetch.ts';
|
import { useFetch } from '../../hooks/useFetch.ts';
|
||||||
|
|
||||||
const FormBtn = styled(Button)`
|
// Styled
|
||||||
width: 100%;
|
import { FormBtn } from './styled.ts';
|
||||||
|
import { INVALID_FIELD, INVALID_PHONE, REQUIRED_FIELD_ERROR } from '../../constants/formValidateMessage.ts';
|
||||||
&:disabled,
|
|
||||||
&[disabled] {
|
|
||||||
background-color: var(--disabled-btn-color);
|
|
||||||
cursor: no-drop;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface IUserFormProp {
|
interface IUserFormProp {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -67,10 +53,27 @@ const UserForm = ({
|
|||||||
user,
|
user,
|
||||||
isAdd,
|
isAdd,
|
||||||
}: IUserFormProp) => {
|
}: IUserFormProp) => {
|
||||||
const [reset, setReset] = useState(true);
|
const formMethods = useForm<TUser>({
|
||||||
|
defaultValues: {
|
||||||
|
roomId: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { errors, isDirty, isValid },
|
||||||
|
trigger,
|
||||||
|
} = formMethods;
|
||||||
const [options, setOptions] = useState<ISelectOptions[]>();
|
const [options, setOptions] = useState<ISelectOptions[]>();
|
||||||
const { data, errorFetchMsg } = useFetch('rooms');
|
const { data, errorFetchMsg } = useFetch('rooms');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
reset(user);
|
||||||
|
}
|
||||||
|
}, [reset, user]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
const tempData = data as TKeyValue[];
|
const tempData = data as TKeyValue[];
|
||||||
@@ -90,95 +93,15 @@ const UserForm = ({
|
|||||||
}
|
}
|
||||||
}, [data, errorFetchMsg]);
|
}, [data, errorFetchMsg]);
|
||||||
|
|
||||||
if (isAdd) {
|
|
||||||
// If this is add form, reset value
|
|
||||||
user = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const initialValue: string = isAdd
|
|
||||||
? ''
|
|
||||||
: user!
|
|
||||||
&& user.id.toLocaleString();
|
|
||||||
|
|
||||||
const {
|
|
||||||
idValue,
|
|
||||||
nameValue,
|
|
||||||
identifiedCodeValue,
|
|
||||||
phoneValue,
|
|
||||||
roomIdValue,
|
|
||||||
addressValue,
|
|
||||||
} = getValueFromObj<TUser>(user);
|
|
||||||
|
|
||||||
// Define your state schema
|
|
||||||
// prettier-ignore
|
|
||||||
const stateSchema: TStateSchema = {
|
|
||||||
id: { value: idValue || '' },
|
|
||||||
name: {
|
|
||||||
value: nameValue || '',
|
|
||||||
error: '' ,
|
|
||||||
},
|
|
||||||
identifiedCode: {
|
|
||||||
value: identifiedCodeValue || '',
|
|
||||||
error: '',
|
|
||||||
},
|
|
||||||
phone: {
|
|
||||||
value: phoneValue || '',
|
|
||||||
error: '',
|
|
||||||
},
|
|
||||||
roomId: {
|
|
||||||
value: roomIdValue || '' + (options && options[0].value),
|
|
||||||
error: '' ,
|
|
||||||
},
|
|
||||||
address: {
|
|
||||||
value: addressValue || '',
|
|
||||||
error: ''
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const stateValidatorSchema: TValidator = {
|
|
||||||
name: addValidator({
|
|
||||||
validatorFunc: isValidString,
|
|
||||||
prop: 'full name'
|
|
||||||
}),
|
|
||||||
identifiedCode: addValidator({
|
|
||||||
validatorFunc: isValidNumber,
|
|
||||||
prop: 'identified code',
|
|
||||||
}),
|
|
||||||
phone: addValidator({
|
|
||||||
validatorFunc: isValidPhoneNumber,
|
|
||||||
prop: 'phone number',
|
|
||||||
}),
|
|
||||||
roomId: addValidator({
|
|
||||||
validatorFunc: isValidNumber,
|
|
||||||
prop: 'room number'
|
|
||||||
}),
|
|
||||||
address: addValidator({
|
|
||||||
validatorFunc: isValidString,
|
|
||||||
prop: 'address'
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Submit form
|
// Submit form
|
||||||
const onSubmitForm = async (state: TKeyValue) => {
|
const onSubmit = async (user: TUser) => {
|
||||||
// Convert to user type
|
|
||||||
const user: TUser = {
|
|
||||||
id: +state.id!,
|
|
||||||
name: '' + state.name,
|
|
||||||
identifiedCode: '' + state.identifiedCode,
|
|
||||||
phone: '' + state.phone,
|
|
||||||
roomId: +state.roomId!,
|
|
||||||
address: '' + state.address,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
// Add request
|
// Add request
|
||||||
const response = await sendRequest(
|
const response = await sendRequest(
|
||||||
USER_PATH,
|
USER_PATH,
|
||||||
JSON.stringify(user),
|
JSON.stringify(user),
|
||||||
'POST',
|
'POST'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode === STATUS_CODE.CREATE) {
|
if (response.statusCode === STATUS_CODE.CREATE) {
|
||||||
@@ -188,14 +111,12 @@ const UserForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Update status when user create
|
// TODO Update status when user create
|
||||||
|
|
||||||
onResetForm();
|
|
||||||
} else {
|
} else {
|
||||||
// Edit request
|
// Edit request
|
||||||
const response = await sendRequest(
|
const response = await sendRequest(
|
||||||
USER_PATH + `/${user!.id}`,
|
USER_PATH + `/${user!.id}`,
|
||||||
JSON.stringify(data),
|
JSON.stringify(user),
|
||||||
'PUT',
|
'PUT'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == STATUS_CODE.OK) {
|
if (response.statusCode == STATUS_CODE.OK) {
|
||||||
@@ -212,153 +133,96 @@ const UserForm = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close and reset form
|
|
||||||
const closeAndReset = () => {
|
|
||||||
onClose();
|
|
||||||
onResetForm();
|
|
||||||
};
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const {
|
|
||||||
values,
|
|
||||||
errors,
|
|
||||||
valid,
|
|
||||||
handleOnChange,
|
|
||||||
handleOnSubmit,
|
|
||||||
disable } =
|
|
||||||
useForm(
|
|
||||||
stateSchema,
|
|
||||||
stateValidatorSchema,
|
|
||||||
onSubmitForm,
|
|
||||||
initialValue
|
|
||||||
);
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
identifiedCode,
|
|
||||||
phone,
|
|
||||||
roomId,
|
|
||||||
address
|
|
||||||
} = values;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAdd) {
|
|
||||||
Object.keys(values).forEach((key) => (values[key] = ''));
|
|
||||||
}
|
|
||||||
}, [isAdd]); // eslint-disable-line
|
|
||||||
|
|
||||||
// Reset form
|
|
||||||
const onResetForm = () => {
|
|
||||||
setReset(!reset);
|
|
||||||
Object.keys(values).forEach((key) => (values[key] = ''));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleOnSubmit}>
|
<FormProvider {...formMethods}>
|
||||||
<Input type="hidden" name="id" value={id as string} />
|
<Form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<FormRow
|
<Input type="hidden" id="id" {...register('id')} />
|
||||||
label="Full Name"
|
<FormRow label="Full Name" error={errors?.name?.message}>
|
||||||
error={
|
<Input
|
||||||
// prettier-ignore
|
type="text"
|
||||||
errors.name && valid.name
|
id="name"
|
||||||
? (errors.name as string)
|
{...register('name', {
|
||||||
: ''
|
required: REQUIRED_FIELD_ERROR,
|
||||||
}
|
validate: {
|
||||||
>
|
checkValidName: (value) =>
|
||||||
<Input
|
isValidString(value) || INVALID_FIELD,
|
||||||
type="text"
|
},
|
||||||
name="name"
|
onChange: () => trigger('name'),
|
||||||
value={name as string}
|
})}
|
||||||
onChange={handleOnChange}
|
/>
|
||||||
/>
|
</FormRow>
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<FormRow
|
<FormRow
|
||||||
label="Identified Code"
|
label="Identified Code"
|
||||||
error={
|
error={errors?.identifiedCode?.message}
|
||||||
errors.identifiedCode && valid.identifiedCode
|
>
|
||||||
? (errors.identifiedCode as string)
|
<Input
|
||||||
: ''
|
type="text"
|
||||||
}
|
id="identifiedCode"
|
||||||
>
|
{...register('identifiedCode', {
|
||||||
<Input
|
required: REQUIRED_FIELD_ERROR,
|
||||||
type="text"
|
validate: {
|
||||||
name="identifiedCode"
|
checkIdentifiedCode: (v) =>
|
||||||
value={identifiedCode as string}
|
isValidNumber(v) || INVALID_FIELD,
|
||||||
onChange={handleOnChange}
|
},
|
||||||
/>
|
onChange: () => trigger('identifiedCode'),
|
||||||
</FormRow>
|
})}
|
||||||
|
/>
|
||||||
|
</FormRow>
|
||||||
|
|
||||||
<FormRow
|
<FormRow label="Phone" error={errors?.phone?.message}>
|
||||||
label="Phone"
|
<Input
|
||||||
error={
|
type="text"
|
||||||
// prettier-ignore
|
id="phone"
|
||||||
errors.phone && valid.phone
|
{...register('phone', {
|
||||||
? (errors.phone as string)
|
required: REQUIRED_FIELD_ERROR,
|
||||||
: ''
|
validate: {
|
||||||
}
|
checkPhoneNum: (v) =>
|
||||||
>
|
isValidPhoneNumber(v) || INVALID_PHONE,
|
||||||
<Input
|
},
|
||||||
type="text"
|
onChange: () => trigger('phone'),
|
||||||
name="phone"
|
})}
|
||||||
value={phone as string}
|
/>
|
||||||
onChange={handleOnChange}
|
</FormRow>
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<FormRow
|
<FormRow label="Room">
|
||||||
label="Room"
|
<Select id="roomId" options={options!} />
|
||||||
error={
|
</FormRow>
|
||||||
// prettier-ignore
|
|
||||||
errors.roomId && valid.roomId
|
|
||||||
? (errors.roomId as string)
|
|
||||||
: ''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
name="roomId"
|
|
||||||
value={roomId as string}
|
|
||||||
onChange={handleOnChange}
|
|
||||||
options={options!}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<FormRow
|
<FormRow label="Address" error={errors.address?.message}>
|
||||||
label="Address"
|
<TextArea
|
||||||
error={
|
id="address"
|
||||||
// prettier-ignore
|
rows={3}
|
||||||
errors.address && valid.address
|
{...register('address', {
|
||||||
? (errors.address as string)
|
required: REQUIRED_FIELD_ERROR,
|
||||||
: ''
|
validate: {
|
||||||
}
|
checkValidString: (v) =>
|
||||||
>
|
isValidString(v) || INVALID_FIELD,
|
||||||
<TextArea
|
},
|
||||||
name="address"
|
onChange: () => trigger('address'),
|
||||||
rows={3}
|
})}
|
||||||
value={address as string}
|
/>
|
||||||
onChange={handleOnChange}
|
</FormRow>
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
|
|
||||||
<Form.Action>
|
<Form.Action>
|
||||||
<FormBtn type="submit" name="submit" disabled={disable}>
|
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
|
||||||
{
|
{
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
isAdd
|
isAdd
|
||||||
? 'Add'
|
? 'Add'
|
||||||
: 'Save'
|
: 'Save'
|
||||||
}
|
}
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
<FormBtn type="button" styled="secondary" onClick={closeAndReset}>
|
<FormBtn type="button" styled="secondary">
|
||||||
Close
|
Close
|
||||||
</FormBtn>
|
</FormBtn>
|
||||||
</Form.Action>
|
</Form.Action>
|
||||||
</Form>
|
</Form>
|
||||||
|
</FormProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import Button from '../../commons/styles/Button';
|
||||||
|
|
||||||
const StyledUser = styled.main`
|
const StyledUser = styled.main`
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-bottom: 100px;
|
padding-bottom: 100px;
|
||||||
@@ -21,4 +24,14 @@ const StyledOperationTable = styled.div`
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export { StyledUser, Title, StyledOperationTable };
|
const FormBtn = styled(Button)`
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:disabled,
|
||||||
|
&[disabled] {
|
||||||
|
background-color: var(--disabled-btn-color);
|
||||||
|
cursor: no-drop;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledUser, Title, StyledOperationTable, FormBtn };
|
||||||
|
|||||||
Reference in New Issue
Block a user