mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 20:01:59 +00:00
Merge pull request #19 from Nez27/feat/update-component-and-optimzed-code
Update component and optimzed code
This commit is contained in:
@@ -1,10 +1,25 @@
|
|||||||
import styled from 'styled-components';
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import CommonInput from './CommonInput';
|
import CommonInput from './CommonInput';
|
||||||
|
|
||||||
const Input = styled.input`
|
interface IInputTyped {
|
||||||
|
type?: 'text' | 'checkbox' | 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = styled.input<IInputTyped>`
|
||||||
${CommonInput}
|
${CommonInput}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.type === 'checkbox' &&
|
||||||
|
css`
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Input.defaultProps = {
|
||||||
|
type: 'text',
|
||||||
|
};
|
||||||
|
|
||||||
export default Input;
|
export default Input;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { IDialogProps } from '../../globals/interfaces';
|
|||||||
import { StyledBody, StyledDialog, StyledTitle } from './styled';
|
import { StyledBody, StyledDialog, StyledTitle } from './styled';
|
||||||
|
|
||||||
const Dialog = forwardRef((props, ref) => {
|
const Dialog = forwardRef((props, ref) => {
|
||||||
const { title, children, onClose } = props as IDialogProps;
|
const { title, children, onClose } = props as IDialogProps<unknown>;
|
||||||
return (
|
return (
|
||||||
<StyledDialog
|
<StyledDialog
|
||||||
ref={ref as React.LegacyRef<HTMLDialogElement> | undefined}
|
ref={ref as React.LegacyRef<HTMLDialogElement> | undefined}
|
||||||
@@ -17,6 +17,6 @@ const Dialog = forwardRef((props, ref) => {
|
|||||||
<StyledBody>{children}</StyledBody>
|
<StyledBody>{children}</StyledBody>
|
||||||
</StyledDialog>
|
</StyledDialog>
|
||||||
);
|
);
|
||||||
}) as React.FC<IDialogProps>;
|
}) as React.FC<IDialogProps<unknown>>;
|
||||||
|
|
||||||
export default Dialog;
|
export default Dialog;
|
||||||
|
|||||||
@@ -9,24 +9,25 @@ const StyledSearch = styled.input`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface ISearch {
|
interface ISearch {
|
||||||
setPhoneSearch: React.Dispatch<React.SetStateAction<string>>;
|
setPlaceHolder: string;
|
||||||
|
setValueSearch: React.Dispatch<React.SetStateAction<string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Search = ({ setPhoneSearch }: ISearch) => {
|
const Search = ({ setValueSearch, setPlaceHolder }: ISearch) => {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeOut = setTimeout(() => {
|
const timeOut = setTimeout(() => {
|
||||||
setPhoneSearch(query);
|
setValueSearch(query);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return () => clearTimeout(timeOut);
|
return () => clearTimeout(timeOut);
|
||||||
}, [setPhoneSearch, query]);
|
}, [setValueSearch, query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSearch
|
<StyledSearch
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder="Search by phone..."
|
placeholder={setPlaceHolder}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
// Components
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
|
||||||
interface ISortByProps {
|
interface ISortByProps {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const CommonRow = styled.div<ITable>`
|
|||||||
grid-template-columns: ${(props) => props.columns};
|
grid-template-columns: ${(props) => props.columns};
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledBody = styled.div`
|
const StyledBody = styled.div`
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
invalidFormatMsg,
|
invalidFormatMsg,
|
||||||
@@ -18,4 +19,5 @@ export {
|
|||||||
EDIT_SUCCESS,
|
EDIT_SUCCESS,
|
||||||
CONFIRM_DELETE,
|
CONFIRM_DELETE,
|
||||||
DELETE_SUCCESS,
|
DELETE_SUCCESS,
|
||||||
|
REQUIRED_FIELD_ERROR,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export const ROOM: string = '/room';
|
|||||||
export const OTHER_PATH: string = '*';
|
export const OTHER_PATH: string = '*';
|
||||||
export const BASE_URL: string = 'https://hotel-management-api.loiphan.com/';
|
export const BASE_URL: string = 'https://hotel-management-api.loiphan.com/';
|
||||||
export const USER_PATH = 'users';
|
export const USER_PATH = 'users';
|
||||||
|
export const ROOM_PATH = 'rooms';
|
||||||
|
|||||||
@@ -34,4 +34,47 @@ const USER_PAGE = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export { USER_PAGE };
|
// prettier-ignore
|
||||||
|
const ROOM_PAGE = {
|
||||||
|
ORDERBY_OPTIONS: [
|
||||||
|
{
|
||||||
|
value: 'asc',
|
||||||
|
label: 'Ascending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'desc',
|
||||||
|
label: 'Descending',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
SORTBY_OPTIONS: [
|
||||||
|
{
|
||||||
|
value: 'id',
|
||||||
|
label: 'Sort by id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'name',
|
||||||
|
label: 'Sort by name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'amount',
|
||||||
|
label: 'Sort by amount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'price',
|
||||||
|
label: 'Sort by price',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'discount',
|
||||||
|
label: 'Sort by discount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'status',
|
||||||
|
label: 'Sort by status',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const VALUE = 'value';
|
||||||
|
const ERROR = 'error';
|
||||||
|
|
||||||
|
export { USER_PAGE, ROOM_PAGE, VALUE, ERROR };
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// Types
|
|
||||||
import { TUser } from './types';
|
|
||||||
|
|
||||||
interface IMenusContext {
|
interface IMenusContext {
|
||||||
openId?: string;
|
openId?: string;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
@@ -23,14 +20,14 @@ interface ITableBody<T> {
|
|||||||
render?: (value: T) => JSX.Element;
|
render?: (value: T) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IDialogProps {
|
interface IDialogProps<T> {
|
||||||
title?: string;
|
title?: string;
|
||||||
children?: JSX.Element[] | JSX.Element;
|
children?: JSX.Element[] | JSX.Element;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
reload?: boolean;
|
reload?: boolean;
|
||||||
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
|
setReload?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
ref?: React.MutableRefObject<HTMLDialogElement | undefined>;
|
ref?: React.MutableRefObject<HTMLDialogElement | undefined>;
|
||||||
user?: TUser | null;
|
data?: T | null;
|
||||||
isAdd?: boolean;
|
isAdd?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ type TUser = {
|
|||||||
roomId: string;
|
roomId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TRoom = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
amount: string;
|
||||||
|
price: string;
|
||||||
|
discount: string;
|
||||||
|
description: string;
|
||||||
|
status: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type TStateSchema = {
|
type TStateSchema = {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
value?: string;
|
value?: string;
|
||||||
@@ -15,7 +25,11 @@ type TStateSchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TKeyValue = {
|
type TKeyValue = {
|
||||||
[key: string]: string | undefined | boolean;
|
[key: string]: boolean | undefined | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TKeyString = {
|
||||||
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TPropValues = 'value' | 'error' | boolean;
|
type TPropValues = 'value' | 'error' | boolean;
|
||||||
@@ -37,8 +51,10 @@ type TResponse = {
|
|||||||
|
|
||||||
export type {
|
export type {
|
||||||
TUser,
|
TUser,
|
||||||
|
TRoom,
|
||||||
TStateSchema,
|
TStateSchema,
|
||||||
TKeyValue,
|
TKeyValue,
|
||||||
|
TKeyString,
|
||||||
TPropValues,
|
TPropValues,
|
||||||
TValidator,
|
TValidator,
|
||||||
TResponse,
|
TResponse,
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
// Constants
|
// Constants
|
||||||
import { BASE_URL } from '../constants/path';
|
import { BASE_URL } from '../constants/path';
|
||||||
|
|
||||||
|
// Types
|
||||||
import { TResponse } from '../globals/types';
|
import { TResponse } from '../globals/types';
|
||||||
|
|
||||||
type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
type TMethodRequest = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The send request method to the server
|
||||||
|
* @param path The path of URL
|
||||||
|
* @param body The content will push on
|
||||||
|
* @param method HTTP method
|
||||||
|
* @returns The status code and message from server
|
||||||
|
*/
|
||||||
export const sendRequest = async (
|
export const sendRequest = async (
|
||||||
path: string,
|
path: string,
|
||||||
body: BodyInit | null | undefined,
|
body: BodyInit | null | undefined,
|
||||||
|
|||||||
@@ -1,26 +1,49 @@
|
|||||||
// Constants
|
// Constants
|
||||||
import { invalidFormatMsg } from '../constants/messages';
|
import { REQUIRED_FIELD_ERROR, invalidFormatMsg } from '../constants/messages';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TKeyValue, TPropValues, TStateSchema, TUser } from '../globals/types';
|
import {
|
||||||
|
TKeyString,
|
||||||
const VALUE = 'value';
|
TKeyValue,
|
||||||
const ERROR = 'error';
|
TPropValues,
|
||||||
const REQUIRED_FIELD_ERROR = 'This is required field';
|
TStateSchema,
|
||||||
|
} from '../globals/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function check value has type boolean or not
|
||||||
|
* @param value The value need to checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has type boolean
|
||||||
|
*/
|
||||||
const isBool = (value: unknown) => {
|
const isBool = (value: unknown) => {
|
||||||
return typeof value === 'boolean';
|
return typeof value === 'boolean';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function check value has type object or not
|
||||||
|
* @param value The value need to checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has type object.
|
||||||
|
*/
|
||||||
const isObject = (value: unknown) => {
|
const isObject = (value: unknown) => {
|
||||||
return typeof value === 'object' && value !== null;
|
return typeof value === 'object' && value !== null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) => {
|
const isRequired = (value: string | number, isRequired: unknown) => {
|
||||||
if (!value && isRequired) return REQUIRED_FIELD_ERROR;
|
if (!value && isRequired) return REQUIRED_FIELD_ERROR;
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get values from props
|
||||||
|
* @param stateSchema StateSchema value
|
||||||
|
* @param prop Prop value (Has 3 type: boolean | "value" | "error")
|
||||||
|
* @returns Return value object depend on props
|
||||||
|
*/
|
||||||
const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => {
|
const getPropValues = (stateSchema: TStateSchema, prop?: TPropValues) => {
|
||||||
return Object.keys(stateSchema).reduce((field, key) => {
|
return Object.keys(stateSchema).reduce((field, key) => {
|
||||||
field[key] = isBool(prop)
|
field[key] = isBool(prop)
|
||||||
@@ -37,6 +60,11 @@ type TValidator = {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create validator object
|
||||||
|
* @param param0 Pass TValidator object
|
||||||
|
* @returns An object contains condition validator
|
||||||
|
*/
|
||||||
const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => {
|
const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => {
|
||||||
return {
|
return {
|
||||||
required,
|
required,
|
||||||
@@ -47,18 +75,46 @@ const addValidator = ({ validatorFunc, prop, required = true }: TValidator) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getValueUser = (user: TUser | null = null, prop: string): string => {
|
/**
|
||||||
if (user) {
|
* Return the object contains values of object pass
|
||||||
return user[prop as keyof TUser];
|
* @param obj Object need to get value
|
||||||
|
* @returns The object contains value of object
|
||||||
|
*/
|
||||||
|
const getValueFromObj = <T>(obj: T | null = null): TKeyString => {
|
||||||
|
let result = {};
|
||||||
|
|
||||||
|
if (obj) {
|
||||||
|
for (const key of Object.keys(obj)) {
|
||||||
|
const tempValue = obj[key as keyof typeof obj];
|
||||||
|
const value: string | boolean | number =
|
||||||
|
typeof tempValue === 'boolean' || typeof tempValue === 'string'
|
||||||
|
? tempValue
|
||||||
|
: '';
|
||||||
|
|
||||||
|
result = { ...result, [`${key}Value`]: value };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchQuery = (phone: string, sort: string, order: string) => {
|
/**
|
||||||
|
* Create query url for search
|
||||||
|
* @param columnSearch Column want to search
|
||||||
|
* @param keySearch Keyword search
|
||||||
|
* @param sort Sort by
|
||||||
|
* @param order Order by
|
||||||
|
* @returns Return query url
|
||||||
|
*/
|
||||||
|
const searchQuery = (
|
||||||
|
columnSearch: string,
|
||||||
|
keySearch: string,
|
||||||
|
sort: string,
|
||||||
|
order: string,
|
||||||
|
) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const phoneParams = phone
|
const phoneParams = keySearch
|
||||||
? 'phone_like=' + phone
|
? `${columnSearch}_like=` + keySearch
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -92,10 +148,8 @@ export {
|
|||||||
isObject,
|
isObject,
|
||||||
isRequired,
|
isRequired,
|
||||||
getPropValues,
|
getPropValues,
|
||||||
getValueUser,
|
getValueFromObj,
|
||||||
addValidator,
|
addValidator,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
VALUE,
|
|
||||||
ERROR,
|
|
||||||
REQUIRED_FIELD_ERROR,
|
REQUIRED_FIELD_ERROR,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,49 @@
|
|||||||
const isValidString = (value: string): boolean => {
|
/**
|
||||||
return /^[a-zA-Z]{4,}(?: [a-zA-Z]+){0,2}$/gm.test(value);
|
* Check value is valid name or not
|
||||||
|
* @param value Values need to be checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
|
*/
|
||||||
|
const isValidName = (value: string): boolean => {
|
||||||
|
return /^[a-zA-Z]{2,}(?: [a-zA-Z]+){0,2}$/gm.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check value is valid number or not
|
||||||
|
* @param value Value need to be checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
|
*/
|
||||||
const isValidNumber = (value: string): boolean => {
|
const isValidNumber = (value: string): boolean => {
|
||||||
return /^[0-9]*$/.test(value);
|
return /^[0-9]*$/.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check value is valid phone number or not
|
||||||
|
* @param value Value need to be checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
|
*/
|
||||||
const isValidPhoneNumber = (value: string): boolean => {
|
const isValidPhoneNumber = (value: string): boolean => {
|
||||||
return /(84|0[3|5|7|8|9])+([0-9]{8})\b/g.test(value);
|
return /(84|0[3|5|7|8|9])+([0-9]{8})\b/g.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidAddress = (value: string): boolean => {
|
/**
|
||||||
return value.trim().length >= 10;
|
* Check value is valid string or not
|
||||||
|
* @param value Value need to be checked
|
||||||
|
* @returns A boolean indicating whether or not the argument has valid
|
||||||
|
*/
|
||||||
|
const isValidString = (value: string): boolean => {
|
||||||
|
return value.trim().length >= 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { isValidString, isValidNumber, isValidPhoneNumber, isValidAddress };
|
/**
|
||||||
|
* A method skip check value
|
||||||
|
* @returns Return true
|
||||||
|
*/
|
||||||
|
const skipCheck = () => true;
|
||||||
|
|
||||||
|
export {
|
||||||
|
isValidName,
|
||||||
|
isValidNumber,
|
||||||
|
isValidPhoneNumber,
|
||||||
|
isValidString,
|
||||||
|
skipCheck,
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user