Fix bug and optimzed code

This commit is contained in:
2023-11-06 11:27:29 +07:00
parent 96d1e6038c
commit 7bfc2bcfa4
32 changed files with 285 additions and 224 deletions
@@ -1,7 +1,9 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
type TButtonStyle = 'primary' | 'secondary';
interface IButtonStyle { interface IButtonStyle {
styled?: 'primary' | 'secondary'; styled?: TButtonStyle;
} }
const Button = styled.button<IButtonStyle>` const Button = styled.button<IButtonStyle>`
@@ -11,7 +13,7 @@ const Button = styled.button<IButtonStyle>`
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
border-radius: var(--radius-md); border-radius: var(--radius-md);
${(props) => ${(props) =>
@@ -5,7 +5,7 @@ const ButtonIcon = styled.button`
padding: 8px; padding: 8px;
transition: all 0.2s; transition: all 0.2s;
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--radius-md);
@@ -5,9 +5,9 @@ const CommonInput = css`
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
padding: 10px 20px; padding: 10px 20px;
font-size: var(--fs-sm-x); font-size: var(--fs-sm-x);
width: 200px; width: 200px;
`; `;
@@ -1,7 +1,9 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
type TDirection = 'vertical' | 'horizontal';
interface IDirection { interface IDirection {
type?: 'vertical' | 'horizontal'; type?: TDirection;
} }
const Direction = styled.div<IDirection>` const Direction = styled.div<IDirection>`
+3 -1
View File
@@ -3,8 +3,10 @@ import styled, { css } from 'styled-components';
// Styled // Styled
import CommonInput from './CommonInput'; import CommonInput from './CommonInput';
type TInput = 'text' | 'checkbox' | 'hidden';
interface IInputTyped { interface IInputTyped {
type?: 'text' | 'checkbox' | 'hidden'; type?: TInput;
} }
const Input = styled.input<IInputTyped>` const Input = styled.input<IInputTyped>`
@@ -16,7 +16,7 @@ export interface IDialogProps {
const Dialog = forwardRef<HTMLDialogElement, IDialogProps>( const Dialog = forwardRef<HTMLDialogElement, IDialogProps>(
(props: IDialogProps, ref) => { (props: IDialogProps, ref) => {
const { title, children, onClose } = props; const { title, children, onClose } = props;
return ( return (
<StyledDialog ref={ref} onClose={onClose}> <StyledDialog ref={ref} onClose={onClose}>
<StyledTitle>{title}</StyledTitle> <StyledTitle>{title}</StyledTitle>
@@ -1,4 +1,4 @@
import { memo, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
// Styled // Styled
import { StyledSearch } from './styled'; import { StyledSearch } from './styled';
@@ -11,7 +11,7 @@ interface ISearch {
setValueSearch: (phone: string) => void; setValueSearch: (phone: string) => void;
} }
const Search = memo(({ setValueSearch, setPlaceHolder }: ISearch) => { const Search = ({ setValueSearch, setPlaceHolder }: ISearch) => {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const debounceValue = useDebounce<string>(query, 700); const debounceValue = useDebounce<string>(query, 700);
@@ -25,6 +25,6 @@ const Search = memo(({ setValueSearch, setPlaceHolder }: ISearch) => {
placeholder={setPlaceHolder} placeholder={setPlaceHolder}
/> />
); );
}); };
export default Search; export default Search;
@@ -68,7 +68,7 @@ const Select = ({
optionsConfigForm, optionsConfigForm,
value, value,
onChange, onChange,
ariaLabel ariaLabel,
}: ISelect) => { }: ISelect) => {
const { register } = useFormContext() ?? {}; const { register } = useFormContext() ?? {};
@@ -12,7 +12,7 @@ const Heading = styled.h1`
font-size: var(--fs-md); font-size: var(--fs-md);
text-transform: uppercase; text-transform: uppercase;
text-align: center; text-align: center;
color: var(--primary-color); color: var(--primary-color);
`; `;
@@ -20,11 +20,11 @@ const SortBy = memo(({ options }: ISortByProps) => {
}; };
return ( return (
<Select <Select
options={options} options={options}
value={sortBy} value={sortBy}
onChange={handleChange} onChange={handleChange}
ariaLabel='Sort' ariaLabel="Sort"
/> />
); );
}); });
@@ -42,7 +42,7 @@ const Body = <T,>({ data, render }: ITableBody<T>) => {
const Row = ({ children }: ITable) => { const Row = ({ children }: ITable) => {
const { columns } = useContext(TableContext); const { columns } = useContext(TableContext);
return <StyledRow columns={columns}>{children}</StyledRow>; return <StyledRow columns={columns}>{children}</StyledRow>;
}; };
+2 -5
View File
@@ -1,20 +1,17 @@
const errorMsg = (errorCode: number, msg: string) => {
return `Error code: ${errorCode}. Message: ${msg}`;
};
const ADD_SUCCESS = 'Add success'; const ADD_SUCCESS = 'Add success';
const EDIT_SUCCESS = 'Edit success'; const EDIT_SUCCESS = 'Edit success';
const CONFIRM_MESSAGE = 'Do you want to checkout this user?'; const CONFIRM_MESSAGE = 'Do you want to checkout this user?';
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 CHECKOUT_SUCCESS = 'Check out success'; const CHECKOUT_SUCCESS = 'Check out success';
const DENIED_ACTION = "Can't checkout user not in room!";
export { export {
ADD_SUCCESS, ADD_SUCCESS,
errorMsg,
EDIT_SUCCESS, EDIT_SUCCESS,
CONFIRM_MESSAGE, CONFIRM_MESSAGE,
CONFIRM_DELETE, CONFIRM_DELETE,
DELETE_SUCCESS, DELETE_SUCCESS,
CHECKOUT_SUCCESS, CHECKOUT_SUCCESS,
DENIED_ACTION
}; };
@@ -2,7 +2,6 @@ const STATUS_CODE = {
OK: 200, OK: 200,
CREATE: 201, CREATE: 201,
NOT_FOUND: 404, NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500,
}; };
const RESPONSE_MESSAGE = { const RESPONSE_MESSAGE = {
@@ -61,6 +61,13 @@ const INIT_VALUE_USER_FORM = {
phone: '', phone: '',
}; };
const INIT_VALUE_ROOM_FORM = {
name: '',
id: 0,
price: 0,
discount: 0,
};
export { export {
USER_PAGE, USER_PAGE,
ROOM_PAGE, ROOM_PAGE,
@@ -68,4 +75,5 @@ export {
ERROR, ERROR,
ORDERBY_OPTIONS, ORDERBY_OPTIONS,
INIT_VALUE_USER_FORM, INIT_VALUE_USER_FORM,
INIT_VALUE_ROOM_FORM,
}; };
@@ -1,17 +1,6 @@
// Constants // Constants
import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage'; import { REQUIRED_FIELD_ERROR } from '../constants/formValidateMessage';
/**
* Set required error for value
* @param value The value set required or not
* @param isRequired Set required for value
* @returns Return error text if value has required
*/
const isRequired = (value: string | number, isRequired: unknown) => {
if (!value && isRequired) return REQUIRED_FIELD_ERROR;
return '';
};
/** /**
* Create query url for search * Create query url for search
* @param columnSearch Column want to search * @param columnSearch Column want to search
@@ -53,6 +42,11 @@ const searchQuery = (
return query; return query;
}; };
/**
* Convert value to currency format with value
* @param value Value need to be converted
* @returns Return the string value with currency
*/
const formatCurrency = (value: number): string => { const formatCurrency = (value: number): string => {
return Intl.NumberFormat('en-US', { return Intl.NumberFormat('en-US', {
style: 'currency', style: 'currency',
@@ -60,4 +54,14 @@ const formatCurrency = (value: number): string => {
}).format(value); }).format(value);
}; };
export { isRequired, searchQuery, formatCurrency, REQUIRED_FIELD_ERROR }; /**
* Create error string
* @param errorCode Error code
* @param msg Message error
* @returns Return the full error string
*/
const errorMsg = (errorCode: number, msg: string) => {
return `Error code: ${errorCode}. Message: ${msg}`;
};
export { errorMsg, searchQuery, formatCurrency, REQUIRED_FIELD_ERROR };
+2 -2
View File
@@ -2,7 +2,7 @@
import { BASE_URL } from '../constants/path'; import { BASE_URL } from '../constants/path';
// Types // Types
import { TResponse } from '../types/response'; import { IResponse } from '../types/responses';
type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
@@ -17,7 +17,7 @@ export const sendRequest = async <T>(
path: string, path: string,
method: TMethodRequest = 'GET', method: TMethodRequest = 'GET',
body?: BodyInit body?: BodyInit
): Promise<TResponse<T>> => { ): Promise<IResponse<T>> => {
const response = await fetch(BASE_URL + path, { const response = await fetch(BASE_URL + path, {
method, method,
body, body,
+5 -5
View File
@@ -5,7 +5,7 @@ import { BASE_URL } from '../constants/path';
import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config'; import { DEFAULT_ORDER_BY, DEFAULT_SORT_BY } from '../constants/config';
// Helpers // Helpers
import { searchQuery } from '../helpers/utils'; import { searchQuery } from '../helpers/helper';
// Types // Types
import { Nullable } from '../types/common'; import { Nullable } from '../types/common';
@@ -40,11 +40,11 @@ const useFetch = (
setIsPending(true); setIsPending(true);
// Set default value // Set default value
const sortBy = tempSortBy const sortBy = tempSortBy
? tempSortBy ? tempSortBy
: DEFAULT_SORT_BY; : DEFAULT_SORT_BY;
const orderBy = tempOrderBy const orderBy = tempOrderBy
? tempOrderBy ? tempOrderBy
: DEFAULT_ORDER_BY; : DEFAULT_ORDER_BY;
// Query search // Query search
@@ -11,7 +11,7 @@ import { Nullable } from '../types/common';
*/ */
export const useOutsideClick = ( export const useOutsideClick = (
handler: () => void, handler: () => void,
listeningCapturing = true, listeningCapturing = true
): MutableRefObject<Nullable<HTMLUListElement>> => { ): MutableRefObject<Nullable<HTMLUListElement>> => {
const ref = useRef<HTMLUListElement>(null); const ref = useRef<HTMLUListElement>(null);
+2 -2
View File
@@ -12,7 +12,7 @@ import RoomForm from './Form';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TRoom } from '../../types/rooms'; import { IRoom } from '../../types/rooms';
// Hooks // Hooks
import { useForwardRef } from '../../hooks/useForwardRef'; import { useForwardRef } from '../../hooks/useForwardRef';
@@ -22,7 +22,7 @@ interface IRoomDialog {
reload?: boolean; reload?: boolean;
setReload?: Dispatch<SetStateAction<boolean>>; setReload?: Dispatch<SetStateAction<boolean>>;
ref?: MutableRefObject<Nullable<HTMLDialogElement>>; ref?: MutableRefObject<Nullable<HTMLDialogElement>>;
room?: Nullable<TRoom>; room?: Nullable<IRoom>;
isAdd?: boolean; isAdd?: boolean;
} }
+15 -28
View File
@@ -11,15 +11,12 @@ import Input from '../../commons/styles/Input.ts';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TRoom } from '../../types/rooms.ts'; import { IRoom } from '../../types/rooms';
// Constants // Constants
import { ROOM_PATH } from '../../constants/path.ts';
import { STATUS_CODE } from '../../constants/responseStatus.ts';
import { import {
ADD_SUCCESS, ADD_SUCCESS,
EDIT_SUCCESS, EDIT_SUCCESS,
errorMsg,
} from '../../constants/messages.ts'; } from '../../constants/messages.ts';
import { import {
INVALID_DISCOUNT, INVALID_DISCOUNT,
@@ -28,7 +25,6 @@ import {
} from '../../constants/formValidateMessage.ts'; } from '../../constants/formValidateMessage.ts';
// Helpers // Helpers
import { sendRequest } from '../../helpers/sendRequest.ts';
import { import {
isValidDiscount, isValidDiscount,
isValidNumber, isValidNumber,
@@ -38,6 +34,8 @@ import {
// Components // Components
import FormRow from '../../components/LabelControl/index.tsx'; import FormRow from '../../components/LabelControl/index.tsx';
import Form from '../../components/Form/index.tsx'; import Form from '../../components/Form/index.tsx';
import { addRoom, updateRoom } from '../../services/roomServices.ts';
import { INIT_VALUE_ROOM_FORM } from '../../constants/variables.ts';
const FormBtn = styled(Button)` const FormBtn = styled(Button)`
width: 100%; width: 100%;
@@ -53,7 +51,7 @@ interface IRoomFormProp {
onClose: () => void; onClose: () => void;
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
room?: Nullable<TRoom>; room?: Nullable<IRoom>;
isAdd: boolean; isAdd: boolean;
} }
@@ -64,7 +62,7 @@ const RoomForm = ({
room, room,
isAdd, isAdd,
}: IRoomFormProp) => { }: IRoomFormProp) => {
const formMethods = useForm<TRoom>(); const formMethods = useForm<IRoom>();
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -74,43 +72,32 @@ const RoomForm = ({
} = formMethods; } = formMethods;
useEffect(() => { useEffect(() => {
if (room) { if (room && !isAdd) {
reset(room); reset(room);
} else {
reset(INIT_VALUE_ROOM_FORM);
} }
}, [room, reset]); }, [room, reset, isAdd]);
// Submit form // Submit form
const onSubmit = async (room: TRoom) => { const onSubmit = async (room: IRoom) => {
// Calculate final price // Calculate final price
room.finalPrice = room.price - (room.price * room.discount) / 100; room.finalPrice = room.price - (room.price * room.discount) / 100;
try { try {
if (isAdd) { if (isAdd) {
// Add request // Add request
const response = await addRoom(room);
const response = await sendRequest(
ROOM_PATH, if(response) {
'POST',
JSON.stringify(room)
);
if (response.statusCode === STATUS_CODE.CREATE) {
toast.success(ADD_SUCCESS); toast.success(ADD_SUCCESS);
} else {
throw new Error(errorMsg(response.statusCode, response.msg));
} }
} else { } else {
// Edit request // Edit request
const response = await sendRequest( const response = await updateRoom(room);
ROOM_PATH + `/${room!.id}`,
'PUT',
JSON.stringify(room)
);
if (response.statusCode == STATUS_CODE.OK) { if (response) {
toast.success(EDIT_SUCCESS); toast.success(EDIT_SUCCESS);
} else {
throw new Error(errorMsg(response.statusCode, response.msg));
} }
} }
// Reload table data // Reload table data
+10 -10
View File
@@ -16,7 +16,7 @@ import OrderBy from '../../components/OrderBy';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TRoom } from '../../types/rooms'; import { IRoom } from '../../types/rooms';
// Constants // Constants
import { useFetch } from '../../hooks/useFetch'; import { useFetch } from '../../hooks/useFetch';
@@ -30,12 +30,12 @@ import Spinner from '../../commons/styles/Spinner';
// Helpers // Helpers
import { sendRequest } from '../../helpers/sendRequest'; import { sendRequest } from '../../helpers/sendRequest';
import { formatCurrency } from '../../helpers/utils'; import { formatCurrency } from '../../helpers/helper';
interface IRoomRow { interface IRoomRow {
room: TRoom; room: IRoom;
openFormDialog: () => void; openFormDialog: () => void;
setRoom: Dispatch<SetStateAction<Nullable<TRoom>>>; setRoom: Dispatch<SetStateAction<Nullable<IRoom>>>;
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
} }
@@ -47,12 +47,12 @@ const RoomRow = ({
reload, reload,
setReload, setReload,
}: IRoomRow) => { }: IRoomRow) => {
const handleEdit = (room: TRoom) => { const handleEdit = (room: IRoom) => {
setRoom(room); setRoom(room);
openFormDialog(); openFormDialog();
}; };
const handleDelete = async (room: TRoom) => { const handleDelete = async (room: IRoom) => {
if (confirm(CONFIRM_DELETE)) { if (confirm(CONFIRM_DELETE)) {
const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE'); const response = await sendRequest(ROOM_PATH + `/${room.id}`, 'DELETE');
@@ -101,7 +101,7 @@ interface IRoomTable {
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
openFormDialog: () => void; openFormDialog: () => void;
setRoom?: Dispatch<SetStateAction<Nullable<TRoom>>>; setRoom?: Dispatch<SetStateAction<Nullable<IRoom>>>;
} }
const RoomTable = ({ const RoomTable = ({
@@ -112,7 +112,7 @@ const RoomTable = ({
}: IRoomTable) => { }: IRoomTable) => {
const [nameSearch, setNameSearch] = useState(''); const [nameSearch, setNameSearch] = useState('');
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const [rooms, setRooms] = useState<TRoom[]>([]); const [rooms, setRooms] = useState<IRoom[]>([]);
const sortByValue = searchParams.get('sortBy') const sortByValue = searchParams.get('sortBy')
? searchParams.get('sortBy')! ? searchParams.get('sortBy')!
: ''; : '';
@@ -165,9 +165,9 @@ const RoomTable = ({
<div>Price</div> <div>Price</div>
<div>Status</div> <div>Status</div>
</Table.Header> </Table.Header>
<Table.Body<TRoom> <Table.Body<IRoom>
data={rooms} data={rooms}
render={(room: TRoom) => ( render={(room: IRoom) => (
<RoomRow <RoomRow
room={room} room={room}
key={room.id} key={room.id}
+2 -2
View File
@@ -11,12 +11,12 @@ import RoomDialog from './Dialog';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TRoom } from '../../types/rooms'; import { IRoom } from '../../types/rooms';
const Room = () => { const Room = () => {
const dialogRef = useRef<HTMLDialogElement>(null); const dialogRef = useRef<HTMLDialogElement>(null);
const [reload, setReload] = useState(true); const [reload, setReload] = useState(true);
const [room, setRoom] = useState<Nullable<TRoom>>(null); const [room, setRoom] = useState<Nullable<IRoom>>(null);
const [isAdd, setIsAdd] = useState(false); const [isAdd, setIsAdd] = useState(false);
const openFormDialog = (isAddForm: boolean = false) => { const openFormDialog = (isAddForm: boolean = false) => {
+2 -2
View File
@@ -12,7 +12,7 @@ import UserForm from './Form';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TUser } from '../../types/user'; import { IUser } from '../../types/users';
// Hooks // Hooks
import { useForwardRef } from '../../hooks/useForwardRef'; import { useForwardRef } from '../../hooks/useForwardRef';
@@ -22,7 +22,7 @@ interface IUserDialog {
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
ref: MutableRefObject<Nullable<HTMLDialogElement>>; ref: MutableRefObject<Nullable<HTMLDialogElement>>;
user: Nullable<TUser>; user: Nullable<IUser>;
isAdd: boolean; isAdd: boolean;
} }
+29 -40
View File
@@ -19,7 +19,6 @@ import FormRow from '../../components/LabelControl/index.tsx';
import Select, { ISelectOptions } from '../../components/Select'; import Select, { ISelectOptions } from '../../components/Select';
// Helpers // Helpers
import { sendRequest } from '../../helpers/sendRequest.ts';
import { import {
isEmptyObj, isEmptyObj,
isValidName, isValidName,
@@ -28,13 +27,7 @@ import {
} from '../../helpers/validators.ts'; } from '../../helpers/validators.ts';
// Constants // Constants
import { STATUS_CODE } from '../../constants/responseStatus.ts'; import { ADD_SUCCESS, EDIT_SUCCESS } from '../../constants/messages.ts';
import {
ADD_SUCCESS,
EDIT_SUCCESS,
errorMsg,
} from '../../constants/messages.ts';
import { USER_PATH } from '../../constants/path.ts';
import { import {
INVALID_FIELD, INVALID_FIELD,
INVALID_PHONE, INVALID_PHONE,
@@ -49,15 +42,16 @@ import { FormBtn } from './styled.ts';
import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts'; import { getAllRoom, updateRoomStatus } from '../../services/roomServices.ts';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common.ts';
import { TUser } from '../../types/user.ts'; import { IUser } from '../../types/users.ts';
import { TRoom } from '../../types/rooms.ts'; import { IRoom } from '../../types/rooms.ts';
import { createUser, updateUser } from '../../services/userServices.ts';
interface IUserFormProp { interface IUserFormProp {
onClose: () => void; onClose: () => void;
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
user: Nullable<TUser>; user: Nullable<IUser>;
isAdd: boolean; isAdd: boolean;
} }
@@ -68,7 +62,7 @@ const UserForm = ({
user, user,
isAdd, isAdd,
}: IUserFormProp) => { }: IUserFormProp) => {
const formMethods = useForm<TUser>(); const formMethods = useForm<IUser>();
const { const {
register, register,
handleSubmit, handleSubmit,
@@ -76,17 +70,25 @@ const UserForm = ({
formState: { errors, isDirty, isValid }, formState: { errors, isDirty, isValid },
trigger, trigger,
} = formMethods; } = formMethods;
const [rooms, setRooms] = useState<TRoom[]>([]); const [rooms, setRooms] = useState<IRoom[]>([]);
const [options, setOptions] = useState<ISelectOptions[]>(); const [options, setOptions] = useState<ISelectOptions[]>();
// Init value when edit form and load options // Init value when edit form and load options
useEffect(() => { useEffect(() => {
const load = async () => { const load = async () => {
const options: ISelectOptions[] = []; const options: ISelectOptions[] = [];
const tempUser = isAdd ? {} : { ...user }; const tempUser = isAdd
? {}
: { ...user };
// Load and set default options room // Load and set default options room
if (rooms.length > 0) { if (rooms.length > 0) {
// Init first options
options.push({
label: '---Select---',
value: '0',
});
rooms.forEach((item) => { rooms.forEach((item) => {
if (!item.status || tempUser?.roomId === item.id) if (!item.status || tempUser?.roomId === item.id)
options.push({ options.push({
@@ -98,16 +100,10 @@ const UserForm = ({
if (options.length > 0) { if (options.length > 0) {
setOptions(options); setOptions(options);
reset({ roomId: +options[0].value });
} }
if (!isEmptyObj(tempUser)) { if (!isEmptyObj(tempUser)) {
// Init value // Init value
// Set default value when user not have room yet.
if (!tempUser.roomId) {
tempUser.roomId = +options[0].value;
}
reset(tempUser); reset(tempUser);
} else { } else {
reset(INIT_VALUE_USER_FORM); reset(INIT_VALUE_USER_FORM);
@@ -119,36 +115,24 @@ const UserForm = ({
// Submit form // Submit form
const onSubmit = useCallback( const onSubmit = useCallback(
async (newUser: TUser) => { async (newUser: IUser) => {
try { try {
if (isAdd) { if (isAdd) {
// Add request // Add request
const response = await sendRequest( const response = await createUser(newUser);
USER_PATH,
'POST',
JSON.stringify(newUser)
);
if (response.statusCode === STATUS_CODE.CREATE) { if (response) {
toast.success(ADD_SUCCESS); toast.success(ADD_SUCCESS);
} else {
throw new Error(errorMsg(response.statusCode, response.msg));
} }
// Update room status // Update room status
updateRoomStatus(newUser.roomId, true); updateRoomStatus(newUser.roomId, true);
} else { } else {
// Edit request // Edit request
const response = await sendRequest( const response = await updateUser(newUser);
USER_PATH + `/${newUser.id}`,
'PUT',
JSON.stringify(newUser)
);
if (response.statusCode == STATUS_CODE.OK) { if (response) {
toast.success(EDIT_SUCCESS); toast.success(EDIT_SUCCESS);
} else {
throw new Error(errorMsg(response.statusCode, response.msg));
} }
// Update room status // Update room status
@@ -232,7 +216,7 @@ const UserForm = ({
</FormRow> </FormRow>
<FormRow label="Room"> <FormRow label="Room">
{options && options.length > 0 ? ( {options && options.length > 1 ? (
<Select <Select
id="roomId" id="roomId"
options={options!} options={options!}
@@ -240,6 +224,7 @@ const UserForm = ({
optionsConfigForm={{ optionsConfigForm={{
valueAsNumber: true, valueAsNumber: true,
onChange: () => trigger('roomId'), onChange: () => trigger('roomId'),
validate: (v) => v !== 0,
}} }}
/> />
) : ( ) : (
@@ -249,7 +234,11 @@ const UserForm = ({
<Form.Action> <Form.Action>
<FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}> <FormBtn type="submit" name="submit" disabled={!isDirty || !isValid}>
{isAdd ? 'Add' : 'Save'} {
isAdd
? 'Add'
: 'Save'
}
</FormBtn> </FormBtn>
<FormBtn type="button" styled="secondary" onClick={onClose}> <FormBtn type="button" styled="secondary" onClick={onClose}>
Close Close
+27 -22
View File
@@ -15,7 +15,7 @@ import OrderBy from '../../components/OrderBy';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TUser } from '../../types/user'; import { IUser } from '../../types/users';
// Hooks // Hooks
import { useFetch } from '../../hooks/useFetch'; import { useFetch } from '../../hooks/useFetch';
@@ -23,7 +23,7 @@ import { useFetch } from '../../hooks/useFetch';
// Constants // Constants
import { STATUS_CODE } from '../../constants/responseStatus'; import { STATUS_CODE } from '../../constants/responseStatus';
import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables'; import { ORDERBY_OPTIONS, USER_PAGE } from '../../constants/variables';
import { CONFIRM_MESSAGE } from '../../constants/messages'; import { CONFIRM_MESSAGE, DENIED_ACTION } from '../../constants/messages';
// Styled // Styled
import { StyledOperationTable } from './styled'; import { StyledOperationTable } from './styled';
@@ -34,9 +34,9 @@ import { updateRoomStatus } from '../../services/roomServices';
import { checkOutUser } from '../../services/userServices'; import { checkOutUser } from '../../services/userServices';
interface IUserRow { interface IUserRow {
user: TUser; user: IUser;
openFormDialog: () => void; openFormDialog: () => void;
setUser: Dispatch<SetStateAction<Nullable<TUser>>>; setUser: Dispatch<SetStateAction<Nullable<IUser>>>;
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
} }
@@ -48,26 +48,31 @@ const UserRow = ({
reload, reload,
setReload, setReload,
}: IUserRow) => { }: IUserRow) => {
const handleEdit = (user: TUser) => { const handleEdit = (user: IUser) => {
setUser(user); setUser(user);
openFormDialog(); openFormDialog();
}; };
const handleCheckOut = async (user: TUser) => { const handleCheckOut = async (user: IUser) => {
if (confirm(CONFIRM_MESSAGE)) { if(user.roomId !== 0) {
const resUpdateStatus = await updateRoomStatus(user.roomId, false); if (confirm(CONFIRM_MESSAGE)) {
const resCheckoutUser = await checkOutUser(user); const resUpdateStatus = await updateRoomStatus(user.roomId, false);
const resCheckoutUser = await checkOutUser(user);
if (
resCheckoutUser?.statusCode === STATUS_CODE.OK && if (
resUpdateStatus?.statusCode === STATUS_CODE.OK resCheckoutUser?.statusCode === STATUS_CODE.OK &&
) { resUpdateStatus?.statusCode === STATUS_CODE.OK
toast.success('Check out complete!'); ) {
toast.success('Check out complete!');
// Reload table
setReload(!reload); // Reload table
setReload(!reload);
}
} }
} else {
toast.error(DENIED_ACTION);
} }
}; };
const { id, name, identifiedCode, phone, roomId } = user; const { id, name, identifiedCode, phone, roomId } = user;
@@ -107,7 +112,7 @@ interface IUserTable {
reload: boolean; reload: boolean;
setReload: Dispatch<SetStateAction<boolean>>; setReload: Dispatch<SetStateAction<boolean>>;
openFormDialog: () => void; openFormDialog: () => void;
setUser?: Dispatch<SetStateAction<Nullable<TUser>>>; setUser?: Dispatch<SetStateAction<Nullable<IUser>>>;
} }
const UserTable = ({ const UserTable = ({
@@ -116,7 +121,7 @@ const UserTable = ({
openFormDialog, openFormDialog,
setUser, setUser,
}: IUserTable) => { }: IUserTable) => {
const [users, setUsers] = useState<TUser[]>([]); const [users, setUsers] = useState<IUser[]>([]);
const [phoneSearch, setPhoneSearch] = useState(''); const [phoneSearch, setPhoneSearch] = useState('');
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const sortByValue = searchParams.get('sortBy') const sortByValue = searchParams.get('sortBy')
@@ -172,9 +177,9 @@ const UserTable = ({
<div>Phone</div> <div>Phone</div>
<div>Room Id</div> <div>Room Id</div>
</Table.Header> </Table.Header>
<Table.Body<TUser> <Table.Body<IUser>
data={users} data={users}
render={(user: TUser) => ( render={(user: IUser) => (
<UserRow <UserRow
user={user} user={user}
key={user.id} key={user.id}
+2 -2
View File
@@ -11,12 +11,12 @@ import UserDialog from './Dialog';
// Types // Types
import { Nullable } from '../../types/common'; import { Nullable } from '../../types/common';
import { TUser } from '../../types/user'; import { IUser } from '../../types/users';
const User = () => { const User = () => {
const dialogRef = useRef<HTMLDialogElement>(null); const dialogRef = useRef<HTMLDialogElement>(null);
const [reload, setReload] = useState(true); const [reload, setReload] = useState(true);
const [user, setUser] = useState<Nullable<TUser>>(null); const [user, setUser] = useState<Nullable<IUser>>(null);
const [isAdd, setIsAdd] = useState(false); const [isAdd, setIsAdd] = useState(false);
const openFormDialog = (isAddForm: boolean = false) => { const openFormDialog = (isAddForm: boolean = false) => {
+80 -51
View File
@@ -2,24 +2,24 @@ import toast from 'react-hot-toast';
// Types // Types
import { Nullable } from '../types/common'; import { Nullable } from '../types/common';
import { TRoom } from '../types/rooms'; import { IResponse } from '../types/responses';
import { TResponse } from '../types/response'; import { IRoom } from '../types/rooms';
// Helpers // Helpers
import { sendRequest } from '../helpers/sendRequest'; import { sendRequest } from '../helpers/sendRequest';
import { errorMsg } from '../helpers/helper';
// Constants // Constants
import { STATUS_CODE, RESPONSE_MESSAGE } from '../constants/responseStatus'; import { STATUS_CODE, RESPONSE_MESSAGE } from '../constants/responseStatus';
import { errorMsg } from '../constants/messages';
import { ROOM_PATH } from '../constants/path'; import { ROOM_PATH } from '../constants/path';
/** /**
* Get all rooms from server * Get all rooms from server
* @returns Return all rooms in server * @returns Return all rooms in server
*/ */
const getAllRoom = async (): Promise<Nullable<TRoom[]>> => { const getAllRoom = async (): Promise<Nullable<IRoom[]>> => {
try { try {
const response = await sendRequest<TRoom[]>(ROOM_PATH); const response = await sendRequest<IRoom[]>(ROOM_PATH);
if (response.statusCode === STATUS_CODE.OK) { if (response.statusCode === STATUS_CODE.OK) {
const rooms = response.data!; const rooms = response.data!;
@@ -42,17 +42,17 @@ const getAllRoom = async (): Promise<Nullable<TRoom[]>> => {
* @param roomId The id room need to be get * @param roomId The id room need to be get
* @returns Return the room object depend on room id * @returns Return the room object depend on room id
*/ */
const getRoom = async (roomId: number): Promise<Nullable<TRoom>> => { const getRoom = async (roomId: number): Promise<Nullable<IRoom>> => {
try { try {
const response = await sendRequest<TRoom>(ROOM_PATH + '/' + roomId); const response = await sendRequest<IRoom>(ROOM_PATH + '/' + roomId);
if (response.statusCode === STATUS_CODE.OK) { if (response.statusCode !== STATUS_CODE.OK) {
const rooms = response.data!;
return rooms;
} else {
throw new Error(errorMsg(response.statusCode, response.msg)); throw new Error(errorMsg(response.statusCode, response.msg));
} }
const rooms = response.data!;
return rooms;
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
toast.error(error.message); toast.error(error.message);
@@ -67,18 +67,14 @@ const getRoom = async (roomId: number): Promise<Nullable<TRoom>> => {
* @param room Room object need to be updated * @param room Room object need to be updated
* @returns The response object * @returns The response object
*/ */
const updateRoom = async (room: TRoom): Promise<Nullable<TResponse<TRoom>>> => { const updateRoom = async (room: IRoom): Promise<Nullable<IResponse<IRoom>>> => {
try { try {
const response = await sendRequest<TRoom>( const response = await sendRequest<IRoom>(
ROOM_PATH + '/' + room.id, ROOM_PATH + '/' + room.id,
'PUT', 'PUT',
JSON.stringify(room) JSON.stringify(room)
); );
if (response.statusCode !== STATUS_CODE.OK) {
throw new Error(errorMsg(response.statusCode, response.msg));
}
return response; return response;
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof Error) { if (error instanceof Error) {
@@ -100,45 +96,78 @@ const updateRoomStatus = async (
roomId: number, roomId: number,
status: boolean, status: boolean,
roomIdNew?: number roomIdNew?: number
): Promise<Nullable<TResponse<TRoom>>> => { ): Promise<Nullable<IResponse<IRoom>>> => {
if (!roomIdNew) { try {
const response = await sendRequest<TRoom>( if (!roomIdNew) {
ROOM_PATH + '/' + roomId, const response = await sendRequest<IRoom>(
ROOM_PATH + '/' + roomId,
'PATCH',
JSON.stringify({ status: status })
);
return response;
}
// Update new room status
const resNewRoom = await sendRequest<IRoom>(
ROOM_PATH + '/' + roomIdNew,
'PATCH', 'PATCH',
JSON.stringify({ status: status }) JSON.stringify({ status: status })
); );
return response; // Update old room status;
} const resOldRoom = await sendRequest<IRoom>(
ROOM_PATH + '/' + roomId,
// Update new room status 'PATCH',
const resNewRoom = await sendRequest<TRoom>( JSON.stringify({ status: !status })
ROOM_PATH + '/' + roomIdNew, );
'PATCH',
JSON.stringify({ status: status })
);
// Update old room status; if (
const resOldRoom = await sendRequest<TRoom>( resNewRoom.statusCode === STATUS_CODE.OK &&
ROOM_PATH + '/' + roomId, resOldRoom.statusCode === STATUS_CODE.OK
'PATCH', ) {
JSON.stringify({ status: !status }) return {
); statusCode: STATUS_CODE.OK,
msg: RESPONSE_MESSAGE.UPDATE_SUCCESS,
if ( };
resNewRoom.statusCode === STATUS_CODE.OK && }
resOldRoom.statusCode === STATUS_CODE.OK } catch (error) {
) { if (error instanceof Error) {
return { toast.error(error.message);
statusCode: STATUS_CODE.OK, }
msg: RESPONSE_MESSAGE.UPDATE_SUCCESS,
};
} }
return { return null;
statusCode: STATUS_CODE.INTERNAL_SERVER_ERROR,
msg: 'Something went wrong!',
};
}; };
export { getRoom, updateRoom, updateRoomStatus, getAllRoom }; /**
* Add room to server
* @param room The room object need to be add
* @returns The response object if complete or null
*/
const addRoom = async (room: IRoom): Promise<Nullable<IResponse<IRoom>>> => {
try {
// Set default status room
room.status = false;
const response = await sendRequest<IRoom>(
ROOM_PATH,
'POST',
JSON.stringify(room)
);
if (response.statusCode !== STATUS_CODE.CREATE) {
throw new Error(errorMsg(response.statusCode, response.msg));
}
return response;
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(error.message);
}
}
return null;
};
export { getRoom, updateRoom, updateRoomStatus, getAllRoom, addRoom };
+44 -7
View File
@@ -1,21 +1,53 @@
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
// Constants // Constants
import { errorMsg } from '../constants/messages';
import { USER_PATH } from '../constants/path'; import { USER_PATH } from '../constants/path';
import { STATUS_CODE } from '../constants/responseStatus'; import { STATUS_CODE } from '../constants/responseStatus';
// Types // Types
import { Nullable } from '../types/common'; import { Nullable } from '../types/common';
import { TUser } from '../types/user'; import { IResponse } from '../types/responses';
import { TResponse } from '../types/response'; import { IUser } from '../types/users';
// Helpers // Helpers
import { sendRequest } from '../helpers/sendRequest'; import { sendRequest } from '../helpers/sendRequest';
import { errorMsg } from '../helpers/helper';
const updateUser = async (user: TUser): Promise<Nullable<TResponse<TUser>>> => { /**
* Create user to the server
* @param user The user object need to be created
* @returns The IResponse object if success or null
*/
const createUser = async (user: IUser): Promise<Nullable<IResponse<IUser>>> => {
try { try {
const response = await sendRequest<TUser>( const response = await sendRequest<IUser>(
USER_PATH,
'POST',
JSON.stringify(user)
);
if (response.statusCode !== STATUS_CODE.CREATE) {
throw new Error(errorMsg(response.statusCode, response.msg));
}
return response;
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(error.message);
}
}
return null;
}
/**
* Update the user to the server
* @param user The user object need to be updated
* @returns The IResponse object if update success or null
*/
const updateUser = async (user: IUser): Promise<Nullable<IResponse<IUser>>> => {
try {
const response = await sendRequest<IUser>(
USER_PATH + '/' + user.id, USER_PATH + '/' + user.id,
'PUT', 'PUT',
JSON.stringify(user) JSON.stringify(user)
@@ -35,7 +67,12 @@ const updateUser = async (user: TUser): Promise<Nullable<TResponse<TUser>>> => {
return null; return null;
}; };
const checkOutUser = async (user: TUser): Promise<Nullable<TResponse<TUser>>> => { /**
* Checkout user
* @param user The user need to be checkout
* @returns The IResponse object if checkout success or not
*/
const checkOutUser = async (user: IUser): Promise<Nullable<IResponse<IUser>>> => {
const tempUser = user; const tempUser = user;
if (tempUser) { if (tempUser) {
@@ -48,4 +85,4 @@ const checkOutUser = async (user: TUser): Promise<Nullable<TResponse<TUser>>> =>
return null; return null;
}; };
export { updateUser, checkOutUser }; export { updateUser, checkOutUser, createUser };
-7
View File
@@ -1,7 +0,0 @@
type TResponse<T> = {
statusCode: number;
msg: string;
data?: T;
};
export type { TResponse };
+7
View File
@@ -0,0 +1,7 @@
interface IResponse<T> {
statusCode: number;
msg: string;
data?: T;
}
export type { IResponse };
+3 -3
View File
@@ -1,10 +1,10 @@
type TRoom = { interface IRoom {
id: number; id: number;
name: string; name: string;
price: number; price: number;
discount: number; discount: number;
finalPrice: number; finalPrice: number;
status: boolean; status: boolean;
}; }
export type { TRoom }; export type { IRoom };
@@ -1,9 +1,9 @@
type TUser = { interface IUser {
id: number; id: number;
name: string; name: string;
identifiedCode: string; identifiedCode: string;
phone: string; phone: string;
roomId: number; roomId: number;
}; }
export type { TUser }; export type { IUser };